ahmednoorx commited on
Commit
d87f007
Β·
verified Β·
1 Parent(s): 0fe3e30

FINAL FIX: Resolve session state initialization error

Browse files
Files changed (1) hide show
  1. app.py +348 -310
app.py CHANGED
@@ -14,7 +14,7 @@ st.set_page_config(
14
  layout="wide"
15
  )
16
 
17
- # Initialize session state
18
  if 'processed_data' not in st.session_state:
19
  st.session_state.processed_data = None
20
  if 'email_generator' not in st.session_state:
@@ -28,357 +28,395 @@ def init_database():
28
  cursor.execute('''
29
  CREATE TABLE IF NOT EXISTS scraped_data (
30
  id INTEGER PRIMARY KEY AUTOINCREMENT,
31
- name TEXT,
32
- email TEXT,
33
- company TEXT,
 
 
 
 
 
 
 
 
 
34
  linkedin_url TEXT,
35
- scraped_info TEXT,
36
- generated_subject TEXT,
37
- generated_email TEXT,
38
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
 
 
 
 
 
 
39
  )
40
  ''')
41
 
42
  conn.commit()
43
  conn.close()
44
 
45
- def save_to_database(data):
46
- """Save processed data to database"""
47
- conn = sqlite3.connect('leads.db')
48
- cursor = conn.cursor()
 
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
- for _, row in data.iterrows():
51
- cursor.execute('''
52
- INSERT OR REPLACE INTO scraped_data
53
- (name, email, company, linkedin_url, scraped_info, generated_subject, generated_email)
54
- VALUES (?, ?, ?, ?, ?, ?, ?)
55
- ''', (
56
- row['name'], row['email'], row['company'], row['linkedin_url'],
57
- row.get('scraped_info', ''), row.get('generated_subject', ''),
58
- row.get('generated_email', '')
59
- ))
60
 
61
- conn.commit()
62
- conn.close()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
- def load_from_database():
65
- """Load data from database"""
66
- conn = sqlite3.connect('leads.db')
67
- df = pd.read_sql_query('SELECT * FROM scraped_data ORDER BY created_at DESC', conn)
68
- conn.close()
69
- return df
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
 
71
- def main():
72
- st.title("πŸ“§ Cold Email Outreach Assistant")
73
- st.markdown("Upload your leads CSV and generate personalized cold emails using AI")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
 
 
 
 
75
  # Initialize database
76
  init_database()
77
 
78
- # Sidebar for configuration
 
 
 
 
79
  with st.sidebar:
80
- st.header("βš™οΈ Configuration")
81
-
82
- # Model configuration
83
- st.subheader("AI Model Settings")
84
- model_option = st.selectbox(
85
- "Model Type",
86
- ["Download Vicuna-7B (Recommended)", "Use Custom Model Path"]
87
- )
88
-
89
- if model_option == "Use Custom Model Path":
90
- custom_model_path = st.text_input("Custom Model Path", "")
91
- else:
92
- custom_model_path = None
93
 
94
  # Email generation settings
95
- st.subheader("πŸ“§ Email Generation")
96
  tone = st.selectbox(
97
- "Email Tone",
98
- ["Professional", "Friendly", "Direct", "Authoritative"],
99
  index=0,
100
- help="Choose the tone for generated emails"
101
  )
102
 
103
- temperature = st.slider(
104
- "Creativity Level",
105
- min_value=0.3,
106
- max_value=1.0,
107
- value=0.7,
108
  step=0.1,
109
- help="Lower = more conservative, Higher = more creative"
110
  )
111
 
112
- generate_variations = st.checkbox(
113
- "Generate Multiple Variations",
114
- value=False,
115
- help="Generate 3 different email variations per lead"
 
116
  )
117
 
118
- # Scraping configuration
119
- st.subheader("πŸ” Scraping Settings")
120
- scrape_timeout = st.slider("Scrape Timeout (seconds)", 5, 30, 10)
121
- use_selenium = st.checkbox("Use Selenium (slower but more reliable)", value=False)
122
-
123
- # Main content area
124
- tab1, tab2, tab3 = st.tabs(["πŸ“€ Upload & Process", "πŸ“Š Results", "πŸ“ˆ History"])
125
-
126
- with tab1:
127
- st.header("Upload Your Leads CSV")
128
 
129
- # File upload
130
- uploaded_file = st.file_uploader(
131
- "Choose a CSV file",
132
- type="csv",
133
- help="CSV should contain columns: name, email, company, linkedin_url"
134
- )
135
 
136
- if uploaded_file is not None:
137
- try:
138
- # Read CSV
139
- df = pd.read_csv(uploaded_file)
140
-
141
- # Validate columns
142
- required_columns = ['name', 'email', 'company', 'linkedin_url']
143
- missing_columns = [col for col in required_columns if col not in df.columns]
144
-
145
- if missing_columns:
146
- st.error(f"Missing required columns: {', '.join(missing_columns)}")
147
- st.info("Required columns: name, email, company, linkedin_url")
148
- else:
149
- st.success(f"βœ… CSV loaded successfully! Found {len(df)} leads")
150
- st.dataframe(df.head())
151
-
152
- # Process data button
153
- if st.button("πŸš€ Start Processing", type="primary"):
154
- process_leads(df, scrape_timeout, use_selenium, custom_model_path, tone, temperature, generate_variations)
155
-
156
- except Exception as e:
157
- st.error(f"Error reading CSV: {str(e)}")
158
 
159
- with tab2:
160
- st.header("Processing Results")
161
-
162
- if st.session_state.processed_data is not None:
163
- df = st.session_state.processed_data
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
 
165
- # Display results
166
- st.success(f"βœ… Processed {len(df)} leads successfully!")
167
 
168
- # Show detailed results
169
- for idx, row in df.iterrows():
170
- with st.expander(f"πŸ“‹ {row['name']} - {row['company']} {'🎯' if row.get('tone_used') else ''}"):
171
- col1, col2, col3 = st.columns([2, 3, 1])
172
-
173
- with col1:
174
- st.subheader("πŸ“Š Scraped Information")
175
- st.text_area("Company Info", row.get('scraped_info', 'No info scraped'), height=100, key=f"info_{idx}")
176
-
177
- # Show generation settings if available
178
- if row.get('tone_used'):
179
- st.write(f"**Tone:** {row.get('tone_used', 'N/A')}")
180
- st.write(f"**Temperature:** {row.get('temperature_used', 'N/A')}")
181
-
182
- with col2:
183
- st.subheader("πŸ“§ Generated Email")
184
- subject = row.get('generated_subject', 'No subject generated')
185
- email_body = row.get('generated_email', 'No email generated')
186
-
187
- st.text_area("Subject", subject, height=50, key=f"subject_{idx}")
188
- st.text_area("Email Body", email_body, height=250, key=f"email_{idx}")
189
-
190
- with col3:
191
- st.subheader("πŸ“ˆ Quality")
192
- if subject and email_body:
193
- subject_len = len(subject)
194
- # Get main body without variations
195
- main_body = email_body.split('--- VARIATIONS ---')[0].strip()
196
- body_words = len(main_body.split())
197
-
198
- # Quality indicators
199
- if 15 <= subject_len <= 65:
200
- st.success(f"βœ… Subject: {subject_len} chars")
201
- else:
202
- st.warning(f"⚠️ Subject: {subject_len} chars")
203
-
204
- if 25 <= body_words <= 100:
205
- st.success(f"βœ… Body: {body_words} words")
206
- else:
207
- st.warning(f"⚠️ Body: {body_words} words")
208
-
209
- # Check for placeholders
210
- if '[Your Name]' in email_body or '{' in email_body:
211
- st.error("❌ Contains placeholders")
212
- else:
213
- st.success("βœ… No placeholders")
214
-
215
- # Check for personalization
216
- if row['name'] in main_body and row['company'] in main_body:
217
- st.success("οΏ½οΏ½οΏ½ Well personalized")
218
- else:
219
- st.warning("⚠️ Low personalization")
220
-
221
- # Check for CTA
222
- cta_words = ['call', 'conversation', 'chat', 'discuss', 'talk', 'meeting']
223
- if any(word in main_body.lower() for word in cta_words):
224
- st.success("βœ… Has call-to-action")
225
- else:
226
- st.warning("⚠️ Weak call-to-action")
227
-
228
- # Overall quality score
229
- quality_score = 0
230
- if 15 <= subject_len <= 65: quality_score += 20
231
- if 25 <= body_words <= 100: quality_score += 25
232
- if '[Your Name]' not in email_body: quality_score += 25
233
- if row['name'] in main_body and row['company'] in main_body: quality_score += 20
234
- if any(word in main_body.lower() for word in cta_words): quality_score += 10
235
-
236
- if quality_score >= 80:
237
- st.success(f"πŸ† Overall: {quality_score}% - Ready to send!")
238
- elif quality_score >= 60:
239
- st.warning(f"πŸ“ Overall: {quality_score}% - Needs polish")
240
- else:
241
- st.error(f"πŸ”§ Overall: {quality_score}% - Needs work")
242
-
243
- # Quick copy button
244
- email_text = f"Subject: {subject}\n\n{email_body}"
245
- st.text_area("Copy Email", email_text, height=100, key=f"copy_{idx}")
246
 
247
- # Export functionality
248
- if st.button("πŸ“₯ Export to CSV"):
249
- csv_data = df.to_csv(index=False)
250
- st.download_button(
251
- label="⬇️ Download CSV",
252
- data=csv_data,
253
- file_name=f"cold_emails_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
254
- mime="text/csv"
255
- )
256
- else:
257
- st.info("πŸ‘† Upload and process a CSV file to see results here")
258
-
259
- with tab3:
260
- st.header("Processing History")
261
-
262
- # Load and display historical data
263
- try:
264
- history_df = load_from_database()
265
- if not history_df.empty:
266
- st.dataframe(history_df)
267
-
268
- # Export history
269
- if st.button("πŸ“₯ Export History"):
270
- csv_data = history_df.to_csv(index=False)
271
- st.download_button(
272
- label="⬇️ Download History CSV",
273
- data=csv_data,
274
- file_name=f"email_history_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
275
- mime="text/csv"
276
- )
277
- else:
278
- st.info("No historical data found")
279
- except Exception as e:
280
- st.error(f"Error loading history: {str(e)}")
281
-
282
- def process_leads(df, scrape_timeout, use_selenium, custom_model_path, tone, temperature, generate_variations):
283
- """Process the uploaded leads with enhanced email generation"""
284
- progress_bar = st.progress(0)
285
- status_text = st.empty()
286
-
287
- try:
288
- # Initialize components
289
- status_text.text("πŸ”§ Initializing scraper...")
290
- scraper = LinkedInScraper(timeout=scrape_timeout, use_selenium=use_selenium)
291
-
292
- status_text.text("πŸ€– Initializing AI model...")
293
- if st.session_state.email_generator is None:
294
- st.session_state.email_generator = EmailGenerator(custom_model_path)
295
-
296
- email_gen = st.session_state.email_generator
297
-
298
- # Process each lead
299
- processed_data = []
300
- total_leads = len(df)
301
-
302
- for idx, row in df.iterrows():
303
- status_text.text(f"πŸ” Processing {row['name']} ({idx + 1}/{total_leads})")
304
 
305
- # Scrape information
306
- scraped_info = scraper.scrape_linkedin_or_company(
307
- row['linkedin_url'],
308
- row['company']
309
- )
310
 
311
- # Generate email with new parameters
312
- status_text.text(f"✍️ Generating email for {row['name']} ({tone} tone)...")
 
 
 
 
313
 
314
- if generate_variations:
315
- # Generate multiple variations
316
- variations = email_gen.generate_multiple_variations(
317
- row['name'],
318
- row['company'],
319
- scraped_info,
320
- num_variations=3,
321
- tone=tone
322
- )
323
-
324
- # Use the first variation as primary
325
- subject = variations[0]['subject']
326
- email_body = variations[0]['email_body']
327
 
328
- # Store all variations in a formatted way
329
- variations_text = "\n\n--- VARIATIONS ---\n"
330
- for i, var in enumerate(variations, 1):
331
- variations_text += f"\nVariation {i} ({var['tone']}):\n"
332
- variations_text += f"Subject: {var['subject']}\n"
333
- variations_text += f"Body: {var['email_body']}\n"
 
 
 
334
 
335
- email_body += variations_text
 
 
336
 
337
- else:
338
- # Generate single email with specified parameters
339
- subject, email_body = email_gen.generate_email(
340
- row['name'],
341
- row['company'],
342
- scraped_info,
343
- tone=tone,
344
- temperature=temperature
345
- )
346
-
347
- # Add to processed data
348
- processed_data.append({
349
- 'name': row['name'],
350
- 'email': row['email'],
351
- 'company': row['company'],
352
- 'linkedin_url': row['linkedin_url'],
353
- 'scraped_info': scraped_info,
354
- 'generated_subject': subject,
355
- 'generated_email': email_body,
356
- 'tone_used': tone,
357
- 'temperature_used': temperature
358
- })
359
-
360
- # Update progress
361
- progress_bar.progress((idx + 1) / total_leads)
362
-
363
- # Convert to DataFrame and save
364
- result_df = pd.DataFrame(processed_data)
365
- st.session_state.processed_data = result_df
366
-
367
- # Save to database
368
- save_to_database(result_df)
369
-
370
- status_text.text("βœ… Processing completed!")
371
- st.success("πŸŽ‰ All leads processed successfully!")
372
 
373
- # Show quality metrics
374
- avg_subject_length = result_df['generated_subject'].str.len().mean()
375
- avg_body_length = result_df['generated_email'].str.split().str.len().mean()
 
 
 
 
 
376
 
377
- st.info(f"πŸ“Š Quality Metrics: Avg subject length: {avg_subject_length:.0f} chars, Avg body length: {avg_body_length:.0f} words")
 
 
378
 
379
- except Exception as e:
380
- st.error(f"❌ Error during processing: {str(e)}")
381
- status_text.text("❌ Processing failed")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
382
 
383
  if __name__ == "__main__":
384
  main()
 
14
  layout="wide"
15
  )
16
 
17
+ # Initialize session state FIRST - before any other code
18
  if 'processed_data' not in st.session_state:
19
  st.session_state.processed_data = None
20
  if 'email_generator' not in st.session_state:
 
28
  cursor.execute('''
29
  CREATE TABLE IF NOT EXISTS scraped_data (
30
  id INTEGER PRIMARY KEY AUTOINCREMENT,
31
+ linkedin_url TEXT UNIQUE,
32
+ company_name TEXT,
33
+ description TEXT,
34
+ industry TEXT,
35
+ website TEXT,
36
+ scraped_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
37
+ )
38
+ ''')
39
+
40
+ cursor.execute('''
41
+ CREATE TABLE IF NOT EXISTS generated_emails (
42
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
43
  linkedin_url TEXT,
44
+ recipient_name TEXT,
45
+ recipient_email TEXT,
46
+ company_name TEXT,
47
+ subject_line TEXT,
48
+ email_body TEXT,
49
+ tone TEXT,
50
+ creativity REAL,
51
+ quality_score REAL,
52
+ generated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
53
+ FOREIGN KEY (linkedin_url) REFERENCES scraped_data (linkedin_url)
54
  )
55
  ''')
56
 
57
  conn.commit()
58
  conn.close()
59
 
60
+ def load_email_generator():
61
+ """Load the email generator with caching"""
62
+ if st.session_state.email_generator is None:
63
+ with st.spinner("πŸ€– Loading AI model (first time may take a few minutes)..."):
64
+ try:
65
+ st.session_state.email_generator = EmailGenerator()
66
+ st.success("βœ… AI model loaded successfully!")
67
+ except Exception as e:
68
+ st.error(f"❌ Failed to load AI model: {str(e)}")
69
+ st.info("πŸ’‘ The model will be downloaded automatically on first run. Please ensure you have a stable internet connection.")
70
+ return None
71
+ return st.session_state.email_generator
72
+
73
+ def process_csv_data(df, tone, creativity, num_variations):
74
+ """Process CSV data and generate emails"""
75
+ scraper = LinkedInScraper()
76
+ email_gen = load_email_generator()
77
 
78
+ if email_gen is None:
79
+ return None
 
 
 
 
 
 
 
 
80
 
81
+ results = []
82
+
83
+ # Progress tracking
84
+ progress_bar = st.progress(0)
85
+ status_text = st.empty()
86
+
87
+ for idx, row in df.iterrows():
88
+ progress = (idx + 1) / len(df)
89
+ progress_bar.progress(progress)
90
+ status_text.text(f"Processing {idx + 1}/{len(df)}: {row['name']} at {row['company']}")
91
+
92
+ # Scrape company data
93
+ with st.spinner(f"πŸ” Scraping data for {row['company']}..."):
94
+ company_data = scraper.scrape_company_data(row['linkedin_url'])
95
+
96
+ # Generate emails with multiple variations
97
+ variations = []
98
+ for i in range(num_variations):
99
+ with st.spinner(f"✍️ Generating email variation {i+1}/{num_variations}..."):
100
+ email_data = email_gen.generate_email(
101
+ company_data=company_data,
102
+ recipient_name=row['name'],
103
+ recipient_email=row['email'],
104
+ tone=tone,
105
+ creativity=creativity
106
+ )
107
+ if email_data:
108
+ variations.append(email_data)
109
+
110
+ # Combine all data
111
+ result = {
112
+ 'name': row['name'],
113
+ 'email': row['email'],
114
+ 'company': row['company'],
115
+ 'linkedin_url': row['linkedin_url'],
116
+ 'company_description': company_data.get('description', 'N/A'),
117
+ 'industry': company_data.get('industry', 'N/A'),
118
+ 'website': company_data.get('website', 'N/A'),
119
+ 'variations': variations,
120
+ 'best_variation': max(variations, key=lambda x: x.get('quality_score', 0)) if variations else None
121
+ }
122
+ results.append(result)
123
+
124
+ # Small delay to be respectful to servers
125
+ time.sleep(1)
126
+
127
+ progress_bar.progress(1.0)
128
+ status_text.text("βœ… Processing complete!")
129
+
130
+ return results
131
 
132
+ def display_results(results, tone, creativity):
133
+ """Display results in an interactive table format"""
134
+ if not results:
135
+ st.warning("No results to display.")
136
+ return
137
+
138
+ st.subheader("πŸ“Š Results Overview")
139
+
140
+ # Summary metrics
141
+ col1, col2, col3, col4 = st.columns(4)
142
+
143
+ total_leads = len(results)
144
+ successful_generations = sum(1 for r in results if r['best_variation'])
145
+ avg_quality = sum(r['best_variation']['quality_score'] for r in results if r['best_variation']) / max(successful_generations, 1)
146
+
147
+ col1.metric("Total Leads", total_leads)
148
+ col2.metric("Successful Generations", successful_generations)
149
+ col3.metric("Average Quality Score", f"{avg_quality:.1f}/10")
150
+ col4.metric("Success Rate", f"{(successful_generations/total_leads)*100:.1f}%")
151
+
152
+ # Results table
153
+ st.subheader("πŸ“‹ Generated Emails")
154
+
155
+ for idx, result in enumerate(results):
156
+ with st.expander(f"πŸ“§ {result['name']} - {result['company']}", expanded=False):
157
+ if not result['best_variation']:
158
+ st.error("❌ Failed to generate email for this contact")
159
+ continue
160
+
161
+ best = result['best_variation']
162
+
163
+ # Three columns layout
164
+ col1, col2, col3 = st.columns([1, 2, 1])
165
+
166
+ with col1:
167
+ st.write("**Contact Info:**")
168
+ st.write(f"πŸ‘€ **Name:** {result['name']}")
169
+ st.write(f"πŸ“§ **Email:** {result['email']}")
170
+ st.write(f"🏒 **Company:** {result['company']}")
171
+ st.write(f"🏭 **Industry:** {result['industry']}")
172
+ if result['website'] != 'N/A':
173
+ st.write(f"🌐 **Website:** {result['website']}")
174
+
175
+ # Quality indicator
176
+ quality = best['quality_score']
177
+ if quality >= 8:
178
+ st.success(f"🌟 Quality: {quality:.1f}/10")
179
+ elif quality >= 6:
180
+ st.warning(f"⚑ Quality: {quality:.1f}/10")
181
+ else:
182
+ st.error(f"⚠️ Quality: {quality:.1f}/10")
183
+
184
+ with col2:
185
+ st.write("**πŸ“§ Generated Email:**")
186
+
187
+ # Subject line
188
+ st.write("**Subject:**")
189
+ subject_container = st.container()
190
+ with subject_container:
191
+ st.code(best['subject_line'], language=None)
192
+ if st.button(f"πŸ“‹ Copy Subject", key=f"copy_subject_{idx}"):
193
+ st.success("Subject copied to clipboard!")
194
+
195
+ # Email body
196
+ st.write("**Email Body:**")
197
+ email_container = st.container()
198
+ with email_container:
199
+ st.text_area(
200
+ "Email Content",
201
+ best['email_body'],
202
+ height=300,
203
+ key=f"email_body_{idx}",
204
+ label_visibility="collapsed"
205
+ )
206
+ if st.button(f"πŸ“‹ Copy Email", key=f"copy_email_{idx}"):
207
+ st.success("Email copied to clipboard!")
208
+
209
+ with col3:
210
+ st.write("**🎯 Generation Settings:**")
211
+ st.write(f"**Tone:** {tone}")
212
+ st.write(f"**Creativity:** {creativity}")
213
+
214
+ st.write("**πŸ“Š Company Data:**")
215
+ if result['company_description'] != 'N/A':
216
+ with st.expander("πŸ“„ Description", expanded=False):
217
+ st.write(result['company_description'][:200] + "..." if len(result['company_description']) > 200 else result['company_description'])
218
+
219
+ # Show all variations if multiple
220
+ if len(result['variations']) > 1:
221
+ st.write(f"**πŸ”„ Variations ({len(result['variations'])}):**")
222
+ for i, var in enumerate(result['variations']):
223
+ quality_color = "🌟" if var['quality_score'] >= 8 else "⚑" if var['quality_score'] >= 6 else "⚠️"
224
+ if st.button(f"{quality_color} Variation {i+1} ({var['quality_score']:.1f})", key=f"var_{idx}_{i}"):
225
+ # Show this variation
226
+ st.info(f"**Subject:** {var['subject_line']}")
227
+ st.text_area("Body", var['email_body'], height=200, key=f"var_body_{idx}_{i}")
228
 
229
+ def export_to_csv(results):
230
+ """Export results to CSV format"""
231
+ if not results:
232
+ return None
233
+
234
+ export_data = []
235
+ for result in results:
236
+ if result['best_variation']:
237
+ best = result['best_variation']
238
+ export_data.append({
239
+ 'name': result['name'],
240
+ 'email': result['email'],
241
+ 'company': result['company'],
242
+ 'industry': result['industry'],
243
+ 'website': result['website'],
244
+ 'subject_line': best['subject_line'],
245
+ 'email_body': best['email_body'],
246
+ 'quality_score': best['quality_score'],
247
+ 'linkedin_url': result['linkedin_url'],
248
+ 'company_description': result['company_description']
249
+ })
250
 
251
+ return pd.DataFrame(export_data)
252
+
253
+ def main():
254
  # Initialize database
255
  init_database()
256
 
257
+ # Header
258
+ st.title("πŸ“§ Cold Email Outreach Assistant")
259
+ st.markdown("Transform your lead list into personalized, high-converting cold emails using AI")
260
+
261
+ # Sidebar for settings
262
  with st.sidebar:
263
+ st.header("πŸŽ›οΈ Settings")
 
 
 
 
 
 
 
 
 
 
 
 
264
 
265
  # Email generation settings
266
+ st.subheader("πŸ“ Email Generation")
267
  tone = st.selectbox(
268
+ "🎭 Tone",
269
+ ["Professional", "Friendly", "Direct", "Casual", "Formal"],
270
  index=0,
271
+ help="Choose the tone for your emails"
272
  )
273
 
274
+ creativity = st.slider(
275
+ "🎨 Creativity Level",
276
+ min_value=0.1,
277
+ max_value=1.0,
278
+ value=0.7,
279
  step=0.1,
280
+ help="Higher values = more creative but potentially less focused emails"
281
  )
282
 
283
+ num_variations = st.selectbox(
284
+ "πŸ”„ Email Variations",
285
+ [1, 2, 3],
286
+ index=1,
287
+ help="Number of email variations to generate per contact"
288
  )
289
 
290
+ st.markdown("---")
 
 
 
 
 
 
 
 
 
291
 
292
+ # Model info
293
+ st.subheader("πŸ€– AI Model")
294
+ st.info("**Vicuna-7B GGUF**\n\nOptimized for quality cold email generation")
 
 
 
295
 
296
+ if st.button("πŸ”„ Reload Model"):
297
+ st.session_state.email_generator = None
298
+ st.experimental_rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
299
 
300
+ # File upload
301
+ st.subheader("πŸ“ Upload Your Lead List")
302
+ uploaded_file = st.file_uploader(
303
+ "Choose a CSV file",
304
+ type=['csv'],
305
+ help="Upload a CSV file with columns: name, email, company, linkedin_url"
306
+ )
307
+
308
+ # Sample CSV download
309
+ col1, col2 = st.columns(2)
310
+ with col1:
311
+ if st.button("πŸ“₯ Download Sample CSV"):
312
+ sample_data = {
313
+ 'name': ['John Smith', 'Jane Doe', 'Mike Johnson'],
314
315
+ 'company': ['TechCorp Inc', 'StartupXYZ', 'Creative Agency'],
316
+ 'linkedin_url': [
317
+ 'https://linkedin.com/company/techcorp',
318
+ 'https://linkedin.com/company/startupxyz',
319
+ 'https://linkedin.com/company/creative-agency'
320
+ ]
321
+ }
322
+ sample_df = pd.DataFrame(sample_data)
323
+ csv = sample_df.to_csv(index=False)
324
+ st.download_button(
325
+ "πŸ“„ sample_leads.csv",
326
+ csv,
327
+ "sample_leads.csv",
328
+ "text/csv"
329
+ )
330
+
331
+ if uploaded_file is not None:
332
+ try:
333
+ # Load and validate CSV
334
+ df = pd.read_csv(uploaded_file)
335
 
336
+ st.success(f"βœ… Loaded {len(df)} leads from CSV")
 
337
 
338
+ # Validate required columns
339
+ required_columns = ['name', 'email', 'company', 'linkedin_url']
340
+ missing_columns = [col for col in required_columns if col not in df.columns]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
341
 
342
+ if missing_columns:
343
+ st.error(f"❌ Missing required columns: {missing_columns}")
344
+ st.info("Required columns: name, email, company, linkedin_url")
345
+ return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
346
 
347
+ # Display preview
348
+ with st.expander("πŸ‘€ Preview Data", expanded=True):
349
+ st.dataframe(df.head())
 
 
350
 
351
+ # Validate data
352
+ issues = []
353
+ if df['email'].isnull().any():
354
+ issues.append("Some emails are missing")
355
+ if df['linkedin_url'].isnull().any():
356
+ issues.append("Some LinkedIn URLs are missing")
357
 
358
+ if issues:
359
+ st.warning("⚠️ Data Issues Found:")
360
+ for issue in issues:
361
+ st.write(f"- {issue}")
 
 
 
 
 
 
 
 
 
362
 
363
+ if not st.checkbox("Continue anyway?"):
364
+ return
365
+
366
+ # Process button
367
+ if st.button("πŸš€ Generate Cold Emails", type="primary"):
368
+ if len(df) > 10:
369
+ st.warning("⚠️ Processing more than 10 leads may take several minutes. Consider processing in smaller batches.")
370
+ if not st.checkbox("I understand this may take a while"):
371
+ return
372
 
373
+ # Process the data
374
+ with st.spinner("πŸ”„ Processing leads and generating emails..."):
375
+ results = process_csv_data(df, tone, creativity, num_variations)
376
 
377
+ if results:
378
+ st.session_state.processed_data = results
379
+ st.success("βœ… Email generation complete!")
380
+ else:
381
+ st.error("❌ Failed to process leads. Please try again.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
382
 
383
+ except Exception as e:
384
+ st.error(f"❌ Error reading CSV file: {str(e)}")
385
+ st.info("Please ensure your CSV file has the correct format and encoding.")
386
+
387
+ # Display results if available - This is where the error was happening
388
+ if st.session_state.processed_data is not None:
389
+ st.markdown("---")
390
+ display_results(st.session_state.processed_data, tone, creativity)
391
 
392
+ # Export functionality
393
+ st.markdown("---")
394
+ st.subheader("πŸ“€ Export Results")
395
 
396
+ export_df = export_to_csv(st.session_state.processed_data)
397
+ if export_df is not None:
398
+ csv = export_df.to_csv(index=False)
399
+ st.download_button(
400
+ "πŸ“₯ Download Results as CSV",
401
+ csv,
402
+ f"cold_emails_export_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
403
+ "text/csv",
404
+ help="Download all generated emails in CSV format"
405
+ )
406
+
407
+ st.info(f"πŸ“Š Ready to export {len(export_df)} successful email generations")
408
+
409
+ # Footer
410
+ st.markdown("---")
411
+ st.markdown(
412
+ """
413
+ <div style='text-align: center; color: #666;'>
414
+ <p>πŸš€ Cold Email Outreach Assistant | Built with Streamlit & Vicuna-7B</p>
415
+ <p>πŸ’‘ Tip: Use specific, researched LinkedIn company URLs for best results</p>
416
+ </div>
417
+ """,
418
+ unsafe_allow_html=True
419
+ )
420
 
421
  if __name__ == "__main__":
422
  main()