luigi12345 commited on
Commit
43f4391
1 Parent(s): 3ea7037

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +280 -331
app.py CHANGED
@@ -1,6 +1,5 @@
1
  import os
2
  import re
3
- import sqlite3
4
  import requests
5
  import pandas as pd
6
  from datetime import datetime
@@ -14,60 +13,11 @@ from requests.adapters import HTTPAdapter
14
  from urllib3.util.retry import Retry
15
  import logging
16
  import json
 
 
 
17
 
18
- # Configuration
19
- AWS_ACCESS_KEY_ID = "AKIASO2XOMEGIVD422N7"
20
- AWS_SECRET_ACCESS_KEY = "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"
21
- REGION_NAME = "us-east-1"
22
-
23
- OPENAI_API_KEY ="sk-1234"
24
- OPENAI_API_BASE = "https://openai-proxy-kl3l.onrender.com"
25
- OPENAI_MODEL = "gpt-3.5-turbo"
26
-
27
- # SQLite configuration
28
- sqlite_db_path = "autoclient.db"
29
-
30
-
31
- # Ensure the database file exists
32
- try:
33
- if not os.path.exists(sqlite_db_path):
34
- open(sqlite_db_path, 'w').close()
35
- except IOError as e:
36
- logging.error(f"Failed to create database file: {e}")
37
- raise
38
-
39
- # Initialize AWS SES client
40
- try:
41
- ses_client = boto3.client('ses',
42
- aws_access_key_id="AKIASO2XOMEGIVD422N7",
43
- aws_secret_access_key="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",
44
- region_name="us-east-1")
45
- except (NoCredentialsError, PartialCredentialsError) as e:
46
- logging.error(f"AWS SES client initialization failed: {e}")
47
- raise
48
-
49
- # SQLite connection
50
- def get_db_connection():
51
- try:
52
- return sqlite3.connect(sqlite_db_path)
53
- except sqlite3.Error as e:
54
- logging.error(f"Database connection failed: {e}")
55
- raise
56
-
57
- # HTTP session with retry strategy
58
- session = requests.Session()
59
- retries = Retry(total=5, backoff_factor=1, status_forcelist=[502, 503, 504])
60
- session.mount('https://', HTTPAdapter(max_retries=retries))
61
-
62
- # Setup logging
63
- try:
64
- logging.basicConfig(level=logging.INFO, filename='app.log', filemode='a',
65
- format='%(asctime)s - %(levelname)s - %(message)s')
66
- except IOError as e:
67
- print(f"Error setting up logging: {e}")
68
- raise
69
-
70
- # Input validation functions
71
  def validate_name(name):
72
  if not name or not name.strip():
73
  raise ValueError("Name cannot be empty or just whitespace")
@@ -105,111 +55,84 @@ def validate_num_results(num_results):
105
  raise ValueError("Invalid number of results")
106
  return num_results
107
 
108
- # Initialize database
109
- def init_db():
110
- conn = get_db_connection()
111
- cursor = conn.cursor()
112
- cursor.executescript('''
113
- CREATE TABLE IF NOT EXISTS projects (
114
- id INTEGER PRIMARY KEY AUTOINCREMENT,
115
- project_name TEXT NOT NULL,
116
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
117
- );
118
-
119
- CREATE TABLE IF NOT EXISTS campaigns (
120
- id INTEGER PRIMARY KEY AUTOINCREMENT,
121
- campaign_name TEXT NOT NULL,
122
- project_id INTEGER,
123
- campaign_type TEXT NOT NULL,
124
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
125
- FOREIGN KEY (project_id) REFERENCES projects (id)
126
- );
127
-
128
- CREATE TABLE IF NOT EXISTS message_templates (
129
- id INTEGER PRIMARY KEY AUTOINCREMENT,
130
- template_name TEXT NOT NULL,
131
- subject TEXT,
132
- body_content TEXT NOT NULL,
133
- campaign_id INTEGER,
134
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
135
- FOREIGN KEY (campaign_id) REFERENCES campaigns (id)
136
- );
137
-
138
- CREATE TABLE IF NOT EXISTS leads (
139
- id INTEGER PRIMARY KEY AUTOINCREMENT,
140
- email TEXT,
141
- phone TEXT,
142
- first_name TEXT,
143
- last_name TEXT,
144
- company TEXT,
145
- job_title TEXT,
146
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
147
- );
148
-
149
- CREATE TABLE IF NOT EXISTS lead_sources (
150
- id INTEGER PRIMARY KEY AUTOINCREMENT,
151
- lead_id INTEGER,
152
- search_query TEXT,
153
- url TEXT,
154
- page_title TEXT,
155
- meta_description TEXT,
156
- http_status INTEGER,
157
- scrape_duration TEXT,
158
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
159
- FOREIGN KEY (lead_id) REFERENCES leads (id)
160
- );
161
-
162
- CREATE TABLE IF NOT EXISTS campaign_leads (
163
- id INTEGER PRIMARY KEY AUTOINCREMENT,
164
- campaign_id INTEGER,
165
- lead_id INTEGER,
166
- status TEXT DEFAULT 'active',
167
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
168
- FOREIGN KEY (campaign_id) REFERENCES campaigns (id),
169
- FOREIGN KEY (lead_id) REFERENCES leads (id)
170
- );
171
-
172
- CREATE TABLE IF NOT EXISTS messages (
173
- id INTEGER PRIMARY KEY AUTOINCREMENT,
174
- campaign_id INTEGER,
175
- lead_id INTEGER,
176
- template_id INTEGER,
177
- customized_subject TEXT,
178
- customized_content TEXT,
179
- sent_at TIMESTAMP,
180
- status TEXT DEFAULT 'pending',
181
- engagement_data TEXT,
182
- FOREIGN KEY (campaign_id) REFERENCES campaigns (id),
183
- FOREIGN KEY (lead_id) REFERENCES leads (id),
184
- FOREIGN KEY (template_id) REFERENCES message_templates (id)
185
- );
186
-
187
- CREATE TABLE IF NOT EXISTS search_terms (
188
- id INTEGER PRIMARY KEY AUTOINCREMENT,
189
- term TEXT NOT NULL,
190
- status TEXT DEFAULT 'pending',
191
- processed_leads INTEGER DEFAULT 0,
192
- last_processed_at TIMESTAMP,
193
- campaign_id INTEGER,
194
- FOREIGN KEY (campaign_id) REFERENCES campaigns (id)
195
- );
196
- ''')
197
- conn.commit()
198
- conn.close()
199
- logging.info("Database initialized successfully!")
200
-
201
- # Call this at the start of your script
202
- init_db()
203
 
204
  # Function to create a new project
205
  def create_project(project_name):
206
  project_name = validate_name(project_name)
207
- conn = get_db_connection()
208
- cursor = conn.cursor()
209
- cursor.execute("INSERT INTO projects (project_name) VALUES (?)", (project_name,))
210
- project_id = cursor.lastrowid
211
- conn.commit()
212
- conn.close()
213
  return project_id
214
 
215
  # Function to create a new campaign
@@ -217,13 +140,12 @@ def create_campaign(campaign_name, project_id, campaign_type):
217
  campaign_name = validate_name(campaign_name)
218
  project_id = validate_id(project_id, "project")
219
  campaign_type = validate_campaign_type(campaign_type)
220
- conn = get_db_connection()
221
- cursor = conn.cursor()
222
- cursor.execute("INSERT INTO campaigns (campaign_name, project_id, campaign_type) VALUES (?, ?, ?)",
223
- (campaign_name, project_id, campaign_type))
224
- campaign_id = cursor.lastrowid
225
- conn.commit()
226
- conn.close()
227
  return campaign_id
228
 
229
  # Function to create a new message template
@@ -232,93 +154,80 @@ def create_message_template(template_name, subject, body_content, campaign_id):
232
  subject = validate_name(subject)
233
  body_content = sanitize_html(body_content)
234
  campaign_id = validate_id(campaign_id, "campaign")
235
- conn = get_db_connection()
236
- cursor = conn.cursor()
237
- cursor.execute("""
238
- INSERT INTO message_templates (template_name, subject, body_content, campaign_id)
239
- VALUES (?, ?, ?, ?)
240
- """, (template_name, subject, body_content, campaign_id))
241
- template_id = cursor.lastrowid
242
- conn.commit()
243
- conn.close()
244
  return template_id
245
 
246
  # Function to add a new search term
247
  def add_search_term(term, campaign_id):
248
  term = validate_name(term)
249
  campaign_id = validate_id(campaign_id, "campaign")
250
- conn = get_db_connection()
251
- cursor = conn.cursor()
252
- cursor.execute("INSERT INTO search_terms (term, campaign_id) VALUES (?, ?)", (term, campaign_id))
253
- term_id = cursor.lastrowid
254
- conn.commit()
255
- conn.close()
256
  return term_id
257
 
258
  # Function to fetch search terms
259
  def fetch_search_terms(campaign_id=None):
260
- conn = get_db_connection()
261
- cursor = conn.cursor()
 
262
  if campaign_id:
263
  campaign_id = validate_id(campaign_id, "campaign")
264
- cursor.execute('SELECT id, term, processed_leads, status FROM search_terms WHERE campaign_id = ?', (campaign_id,))
265
- else:
266
- cursor.execute('SELECT id, term, processed_leads, status FROM search_terms')
267
- rows = cursor.fetchall()
268
- conn.close()
269
- return pd.DataFrame(rows, columns=["ID", "Search Term", "Leads Fetched", "Status"])
270
 
271
  # Function to update search term status
272
  def update_search_term_status(term_id, new_status, processed_leads):
273
  term_id = validate_id(term_id, "search term")
274
  new_status = validate_status(new_status, ["pending", "completed"])
275
  processed_leads = validate_num_results(processed_leads)
276
- conn = get_db_connection()
277
- cursor = conn.cursor()
278
- cursor.execute("""
279
- UPDATE search_terms
280
- SET status = ?, processed_leads = ?, last_processed_at = CURRENT_TIMESTAMP
281
- WHERE id = ?
282
- """, (new_status, processed_leads, term_id))
283
- conn.commit()
284
- conn.close()
285
 
286
  # Function to save a new lead
287
  def save_lead(email, phone, first_name, last_name, company, job_title):
288
  email = validate_email(email)
289
- conn = get_db_connection()
290
- cursor = conn.cursor()
291
- cursor.execute("""
292
- INSERT INTO leads (email, phone, first_name, last_name, company, job_title)
293
- VALUES (?, ?, ?, ?, ?, ?)
294
- """, (email, phone, first_name, last_name, company, job_title))
295
- lead_id = cursor.lastrowid
296
- conn.commit()
297
- conn.close()
298
  return lead_id
299
 
300
  # Function to save lead source
301
  def save_lead_source(lead_id, search_query, url, page_title, meta_description, http_status, scrape_duration):
302
  lead_id = validate_id(lead_id, "lead")
303
- conn = get_db_connection()
304
- cursor = conn.cursor()
305
- cursor.execute("""
306
- INSERT INTO lead_sources (lead_id, search_query, url, page_title, meta_description, http_status, scrape_duration)
307
- VALUES (?, ?, ?, ?, ?, ?, ?)
308
- """, (lead_id, search_query, url, page_title, meta_description, http_status, scrape_duration))
309
- conn.commit()
310
- conn.close()
311
 
312
  # Function to add a lead to a campaign
313
  def add_lead_to_campaign(campaign_id, lead_id):
314
  campaign_id = validate_id(campaign_id, "campaign")
315
  lead_id = validate_id(lead_id, "lead")
316
- conn = get_db_connection()
317
- cursor = conn.cursor()
318
- cursor.execute("INSERT OR IGNORE INTO campaign_leads (campaign_id, lead_id) VALUES (?, ?)",
319
- (campaign_id, lead_id))
320
- conn.commit()
321
- conn.close()
322
 
323
  # Function to create a new message
324
  def create_message(campaign_id, lead_id, template_id, customized_subject, customized_content):
@@ -327,62 +236,51 @@ def create_message(campaign_id, lead_id, template_id, customized_subject, custom
327
  template_id = validate_id(template_id, "template")
328
  customized_subject = validate_name(customized_subject)
329
  customized_content = sanitize_html(customized_content)
330
- conn = get_db_connection()
331
- cursor = conn.cursor()
332
- cursor.execute("""
333
- INSERT INTO messages (campaign_id, lead_id, template_id, customized_subject, customized_content)
334
- VALUES (?, ?, ?, ?, ?)
335
- """, (campaign_id, lead_id, template_id, customized_subject, customized_content))
336
- message_id = cursor.lastrowid
337
- conn.commit()
338
- conn.close()
339
  return message_id
340
 
341
  # Function to update message status
342
  def update_message_status(message_id, status, sent_at=None):
343
  message_id = validate_id(message_id, "message")
344
  status = validate_status(status, ["pending", "sent", "failed"])
345
- conn = get_db_connection()
346
- cursor = conn.cursor()
347
- if sent_at:
348
- cursor.execute("UPDATE messages SET status = ?, sent_at = ? WHERE id = ?",
349
- (status, sent_at, message_id))
350
- else:
351
- cursor.execute("UPDATE messages SET status = ? WHERE id = ?",
352
- (status, message_id))
353
- conn.commit()
354
- conn.close()
355
 
356
  # Function to fetch message templates
357
  def fetch_message_templates(campaign_id=None):
358
- conn = get_db_connection()
359
- cursor = conn.cursor()
360
  if campaign_id:
361
  campaign_id = validate_id(campaign_id, "campaign")
362
- cursor.execute('SELECT id, template_name FROM message_templates WHERE campaign_id = ?', (campaign_id,))
363
- else:
364
- cursor.execute('SELECT id, template_name FROM message_templates')
365
- rows = cursor.fetchall()
366
- conn.close()
367
- return [f"{row[0]}: {row[1]}" for row in rows]
368
 
369
  # Function to fetch projects
370
  def fetch_projects():
371
- conn = get_db_connection()
372
- cursor = conn.cursor()
373
- cursor.execute('SELECT id, project_name FROM projects')
374
- rows = cursor.fetchall()
375
- conn.close()
376
- return [f"{row[0]}: {row[1]}" for row in rows]
377
 
378
  # Function to fetch campaigns
379
  def fetch_campaigns():
380
- conn = get_db_connection()
381
- cursor = conn.cursor()
382
- cursor.execute('SELECT id, campaign_name FROM campaigns')
383
- campaigns = cursor.fetchall()
384
- conn.close()
385
- return [f"{campaign[0]}: {campaign[1]}" for campaign in campaigns]
386
 
387
  # Bulk search function
388
  async def bulk_search(selected_terms, num_results, progress=gr.Progress()):
@@ -391,11 +289,13 @@ async def bulk_search(selected_terms, num_results, progress=gr.Progress()):
391
  num_results = validate_num_results(num_results)
392
  total_leads = 0
393
  for term_id in selected_terms:
394
- conn = get_db_connection()
395
- cursor = conn.cursor()
396
- cursor.execute('SELECT term, processed_leads FROM search_terms WHERE id = ?', (term_id,))
397
- term, processed_leads = cursor.fetchone()
398
- conn.close()
 
 
399
 
400
  leads_found = 0
401
  try:
@@ -439,16 +339,9 @@ async def bulk_send(template_id, from_email, reply_to, progress=gr.Progress()):
439
  raise ValueError("Invalid from email address")
440
  if not re.match(r"[^@]+@[^@]+\.[^@]+", reply_to):
441
  raise ValueError("Invalid reply to email address")
442
- conn = get_db_connection()
443
- cursor = conn.cursor()
444
- cursor.execute('''
445
- SELECT m.id, l.email, m.customized_subject, m.customized_content
446
- FROM messages m
447
- JOIN leads l ON m.lead_id = l.id
448
- WHERE m.template_id = ? AND m.status = 'pending'
449
- ''', (template_id,))
450
- messages = cursor.fetchall()
451
- conn.close()
452
 
453
  total_sent = 0
454
  for message_id, email, subject, content in messages:
@@ -477,11 +370,9 @@ def get_email_preview(template_id, from_email, reply_to):
477
  template_id = validate_id(template_id, "template")
478
  from_email = validate_email(from_email)
479
  reply_to = validate_email(reply_to)
480
- conn = get_db_connection()
481
- cursor = conn.cursor()
482
- cursor.execute('SELECT subject, body_content FROM message_templates WHERE id = ?', (template_id,))
483
- template = cursor.fetchone()
484
- conn.close()
485
 
486
  if template:
487
  subject, body_content = template
@@ -490,41 +381,6 @@ def get_email_preview(template_id, from_email, reply_to):
490
  else:
491
  return "Template not found"
492
 
493
- # Function to sanitize HTML content
494
- def sanitize_html(content):
495
- return re.sub('<[^<]+?>', '', content)
496
-
497
- # Function to find valid emails in HTML text
498
- def find_emails(html_text):
499
- email_regex = re.compile(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,7}\b')
500
- all_emails = set(email_regex.findall(html_text))
501
- valid_emails = {email for email in all_emails if is_valid_email(email)}
502
-
503
- unique_emails = {}
504
- for email in valid_emails:
505
- domain = email.split('@')[1]
506
- if domain not in unique_emails:
507
- unique_emails[domain] = email
508
-
509
- return set(unique_emails.values())
510
-
511
- # Function to validate email address
512
- def is_valid_email(email):
513
- invalid_patterns = [
514
- r'\.png', r'\.jpg', r'\.jpeg', r'\.gif', r'\.bmp', r'^no-reply@',
515
- r'^prueba@', r'^\d+[a-z]*@'
516
- ]
517
- typo_domains = ["gmil.com", "gmal.com", "gmaill.com", "gnail.com"]
518
- if len(email) < 6 or len(email) > 254:
519
- return False
520
- for pattern in invalid_patterns:
521
- if re.search(pattern, email, re.IGNORECASE):
522
- return False
523
- domain = email.split('@')[1]
524
- if domain in typo_domains or not re.match(r"^[A-Za-z0-9.-]+\.[A-Za-z]{2,}$", domain):
525
- return False
526
- return True
527
-
528
  # Function to refresh search terms
529
  def refresh_search_terms(campaign_id):
530
  return df_to_list(fetch_search_terms(campaign_id))
@@ -546,9 +402,31 @@ def manual_search(term, num_results):
546
  logging.error(f"Error in manual search: {e}")
547
  return results[:num_results]
548
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
549
  # Gradio interface
550
  with gr.Blocks() as gradio_app:
551
- gr.Markdown("# Email Campaign Management System")
552
 
553
  with gr.Tab("Projects and Campaigns"):
554
  with gr.Row():
@@ -562,28 +440,30 @@ with gr.Blocks() as gradio_app:
562
  campaign_type = gr.Radio(["Email", "SMS"], label="Campaign Type")
563
  create_campaign_btn = gr.Button("Create Campaign")
564
  campaign_status = gr.Textbox(label="Campaign Status", interactive=False)
 
565
 
566
- with gr.Tab("Message Templates"):
567
  with gr.Row():
568
- with gr.Column():
569
- template_name = gr.Textbox(label="Template Name")
570
- subject = gr.Textbox(label="Subject")
571
- body_content = gr.Code(language="html", label="Body Content")
572
- campaign_id_for_template = gr.Dropdown(label="Campaign", choices=fetch_campaigns())
573
- create_template_btn = gr.Button("Create Template")
574
- with gr.Column():
575
- template_status = gr.Textbox(label="Template Status", interactive=False)
576
- template_preview = gr.HTML(label="Template Preview")
577
 
578
  with gr.Tab("Search Terms"):
579
  with gr.Row():
580
- with gr.Column():
581
- search_term = gr.Textbox(label="Search Term")
582
- campaign_id_for_search = gr.Dropdown(label="Campaign", choices=fetch_campaigns())
583
- add_term_btn = gr.Button("Add Search Term")
584
- with gr.Column():
585
- search_term_status = gr.Textbox(label="Search Term Status", interactive=False)
586
  search_term_list = gr.Dataframe(df_to_list(fetch_search_terms()), headers=["ID", "Search Term", "Leads Fetched", "Status"])
 
 
 
 
 
587
 
588
  with gr.Tab("Bulk Operations"):
589
  with gr.Row():
@@ -606,14 +486,82 @@ with gr.Blocks() as gradio_app:
606
  bulk_send_button = gr.Button("Bulk Send")
607
  bulk_search_send_button = gr.Button("Bulk Search & Send")
608
 
 
609
  log_output = gr.TextArea(label="Process Logs", interactive=False)
610
 
611
  with gr.Tab("Manual Search"):
612
  with gr.Row():
613
  manual_search_term = gr.Textbox(label="Manual Search Term")
614
- manual_num_results = gr.Slider(minimum=1, maximum=50, value=10, step=1)
615
- manual_search_btn = gr.Button("Search")
616
- manual_search_results = gr.Dataframe(headers=["Email", "Source"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
617
 
618
  # Move these lines inside the Blocks context
619
  gradio_app.load(lambda: gr.update(value=df_to_list(fetch_search_terms())), outputs=search_term_df)
@@ -646,6 +594,7 @@ with gr.Blocks() as gradio_app:
646
  )
647
  manual_search_btn.click(manual_search, inputs=[manual_search_term, manual_num_results], outputs=manual_search_results)
648
  refresh_btn.click(refresh_search_terms, inputs=[campaign_id_for_bulk], outputs=[search_term_df])
 
649
 
650
  # Launch the app outside the Blocks context
651
  gradio_app.launch()
 
1
  import os
2
  import re
 
3
  import requests
4
  import pandas as pd
5
  from datetime import datetime
 
13
  from urllib3.util.retry import Retry
14
  import logging
15
  import json
16
+ from sqlalchemy import create_engine, func
17
+ from sqlalchemy.orm import sessionmaker
18
+ from schema import Base, Project, Campaign, Lead, LeadSource, CampaignLead, EmailCampaign, SearchTerm, KnowledgeBase, EmailTemplate, OptimizedSearchTerm, SearchTermEffectiveness, SearchTermGroup, AIRequestLog, AutomationLog
19
 
20
+ # Validation functions
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  def validate_name(name):
22
  if not name or not name.strip():
23
  raise ValueError("Name cannot be empty or just whitespace")
 
55
  raise ValueError("Invalid number of results")
56
  return num_results
57
 
58
+ def sanitize_html(content):
59
+ return re.sub('<[^<]+?>', '', content)
60
+
61
+ def find_emails(html_text):
62
+ email_regex = re.compile(r'\b[A-Za-z0-9._%+-]+@[^@]+\.[A-Z|a-z]{2,7}\b')
63
+ all_emails = set(email_regex.findall(html_text))
64
+ valid_emails = {email for email in all_emails if is_valid_email(email)}
65
+
66
+ unique_emails = {}
67
+ for email in valid_emails:
68
+ domain = email.split('@')[1]
69
+ if domain not in unique_emails:
70
+ unique_emails[domain] = email
71
+
72
+ return set(unique_emails.values())
73
+
74
+ def is_valid_email(email):
75
+ invalid_patterns = [
76
+ r'\.png', r'\.jpg', r'\.jpeg', r'\.gif', r'\.bmp', r'^no-reply@',
77
+ r'^prueba@', r'^\d+[a-z]*@'
78
+ ]
79
+ typo_domains = ["gmil.com", "gmal.com", "gmaill.com", "gnail.com"]
80
+ if len(email) < 6 or len(email) > 254:
81
+ return False
82
+ for pattern in invalid_patterns:
83
+ if re.search(pattern, email, re.IGNORECASE):
84
+ return False
85
+ domain = email.split('@')[1]
86
+ if domain in typo_domains or not re.match(r"^[A-Za-z0-9.-]+\.[A-Za-z]{2,}$", domain):
87
+ return False
88
+ return True
89
+
90
+ # Configuration
91
+ aws_access_key_id = os.getenv("AWS_ACCESS_KEY_ID", "default_aws_access_key_id")
92
+ aws_secret_access_key = os.getenv("AWS_SECRET_ACCESS_KEY", "default_aws_secret_access_key")
93
+ region_name = "us-east-1"
94
+
95
+ openai.api_key = os.getenv("OPENAI_API_KEY", "default_openai_api_key")
96
+ openai.api_base = os.getenv("OPENAI_API_BASE", "http://127.0.0.1:11434/v1")
97
+ openai_model = "mistral"
98
+
99
+ # Database configuration
100
+ DATABASE_URL = f"postgresql://{os.getenv('SUPABASE_DB_USER')}:{os.getenv('SUPABASE_DB_PASSWORD')}@{os.getenv('SUPABASE_DB_HOST')}:{os.getenv('SUPABASE_DB_PORT')}/{os.getenv('SUPABASE_DB_NAME')}"
101
+ engine = create_engine(DATABASE_URL)
102
+ SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
103
+
104
+ # Initialize AWS SES client
105
+ try:
106
+ ses_client = boto3.client('ses',
107
+ aws_access_key_id=aws_access_key_id,
108
+ aws_secret_access_key=aws_secret_access_key,
109
+ region_name=region_name)
110
+ except (NoCredentialsError, PartialCredentialsError) as e:
111
+ logging.error(f"AWS SES client initialization failed: {e}")
112
+ raise
113
+
114
+ # HTTP session with retry strategy
115
+ session = requests.Session()
116
+ retries = Retry(total=5, backoff_factor=1, status_forcelist=[502, 503, 504])
117
+ session.mount('https://', HTTPAdapter(max_retries=retries))
118
+
119
+ # Setup logging
120
+ try:
121
+ logging.basicConfig(level=logging.INFO, filename='app.log', filemode='a',
122
+ format='%(asctime)s - %(levelname)s - %(message)s')
123
+ except IOError as e:
124
+ print(f"Error setting up logging: {e}")
125
+ raise
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
 
127
  # Function to create a new project
128
  def create_project(project_name):
129
  project_name = validate_name(project_name)
130
+ db = SessionLocal()
131
+ new_project = Project(project_name=project_name)
132
+ db.add(new_project)
133
+ db.commit()
134
+ project_id = new_project.id
135
+ db.close()
136
  return project_id
137
 
138
  # Function to create a new campaign
 
140
  campaign_name = validate_name(campaign_name)
141
  project_id = validate_id(project_id, "project")
142
  campaign_type = validate_campaign_type(campaign_type)
143
+ db = SessionLocal()
144
+ new_campaign = Campaign(campaign_name=campaign_name, project_id=project_id, campaign_type=campaign_type)
145
+ db.add(new_campaign)
146
+ db.commit()
147
+ campaign_id = new_campaign.id
148
+ db.close()
 
149
  return campaign_id
150
 
151
  # Function to create a new message template
 
154
  subject = validate_name(subject)
155
  body_content = sanitize_html(body_content)
156
  campaign_id = validate_id(campaign_id, "campaign")
157
+ db = SessionLocal()
158
+ new_template = EmailTemplate(template_name=template_name, subject=subject, body_content=body_content, campaign_id=campaign_id)
159
+ db.add(new_template)
160
+ db.commit()
161
+ template_id = new_template.id
162
+ db.close()
 
 
 
163
  return template_id
164
 
165
  # Function to add a new search term
166
  def add_search_term(term, campaign_id):
167
  term = validate_name(term)
168
  campaign_id = validate_id(campaign_id, "campaign")
169
+ db = SessionLocal()
170
+ new_term = SearchTerm(term=term, campaign_id=campaign_id)
171
+ db.add(new_term)
172
+ db.commit()
173
+ term_id = new_term.id
174
+ db.close()
175
  return term_id
176
 
177
  # Function to fetch search terms
178
  def fetch_search_terms(campaign_id=None):
179
+ db = SessionLocal()
180
+ query = db.query(SearchTerm.id, SearchTerm.term, func.count(LeadSource.id).label('processed_leads'), SearchTerm.category.label('status'))
181
+ query = query.outerjoin(LeadSource)
182
  if campaign_id:
183
  campaign_id = validate_id(campaign_id, "campaign")
184
+ query = query.filter(SearchTerm.campaign_id == campaign_id)
185
+ query = query.group_by(SearchTerm.id)
186
+ rows = query.all()
187
+ db.close()
188
+ return pd.DataFrame([(row.id, row.term, row.processed_leads, row.status) for row in rows], columns=["ID", "Search Term", "Leads Fetched", "Status"])
 
189
 
190
  # Function to update search term status
191
  def update_search_term_status(term_id, new_status, processed_leads):
192
  term_id = validate_id(term_id, "search term")
193
  new_status = validate_status(new_status, ["pending", "completed"])
194
  processed_leads = validate_num_results(processed_leads)
195
+ db = SessionLocal()
196
+ term = db.query(SearchTerm).filter(SearchTerm.id == term_id).first()
197
+ if term:
198
+ term.category = new_status
199
+ db.commit()
200
+ db.close()
 
 
 
201
 
202
  # Function to save a new lead
203
  def save_lead(email, phone, first_name, last_name, company, job_title):
204
  email = validate_email(email)
205
+ db = SessionLocal()
206
+ new_lead = Lead(email=email, phone=phone, first_name=first_name, last_name=last_name, company=company, job_title=job_title)
207
+ db.add(new_lead)
208
+ db.commit()
209
+ lead_id = new_lead.id
210
+ db.close()
 
 
 
211
  return lead_id
212
 
213
  # Function to save lead source
214
  def save_lead_source(lead_id, search_query, url, page_title, meta_description, http_status, scrape_duration):
215
  lead_id = validate_id(lead_id, "lead")
216
+ db = SessionLocal()
217
+ new_lead_source = LeadSource(lead_id=lead_id, url=url, page_title=page_title, meta_description=meta_description, http_status=http_status, scrape_duration=scrape_duration)
218
+ db.add(new_lead_source)
219
+ db.commit()
220
+ db.close()
 
 
 
221
 
222
  # Function to add a lead to a campaign
223
  def add_lead_to_campaign(campaign_id, lead_id):
224
  campaign_id = validate_id(campaign_id, "campaign")
225
  lead_id = validate_id(lead_id, "lead")
226
+ db = SessionLocal()
227
+ new_campaign_lead = CampaignLead(campaign_id=campaign_id, lead_id=lead_id, status='active')
228
+ db.add(new_campaign_lead)
229
+ db.commit()
230
+ db.close()
 
231
 
232
  # Function to create a new message
233
  def create_message(campaign_id, lead_id, template_id, customized_subject, customized_content):
 
236
  template_id = validate_id(template_id, "template")
237
  customized_subject = validate_name(customized_subject)
238
  customized_content = sanitize_html(customized_content)
239
+ db = SessionLocal()
240
+ new_message = EmailCampaign(campaign_id=campaign_id, lead_id=lead_id, template_id=template_id, customized_subject=customized_subject, customized_content=customized_content)
241
+ db.add(new_message)
242
+ db.commit()
243
+ message_id = new_message.id
244
+ db.close()
 
 
 
245
  return message_id
246
 
247
  # Function to update message status
248
  def update_message_status(message_id, status, sent_at=None):
249
  message_id = validate_id(message_id, "message")
250
  status = validate_status(status, ["pending", "sent", "failed"])
251
+ db = SessionLocal()
252
+ message = db.query(EmailCampaign).filter(EmailCampaign.id == message_id).first()
253
+ if message:
254
+ message.status = status
255
+ if sent_at:
256
+ message.sent_at = sent_at
257
+ db.commit()
258
+ db.close()
 
 
259
 
260
  # Function to fetch message templates
261
  def fetch_message_templates(campaign_id=None):
262
+ db = SessionLocal()
263
+ query = db.query(EmailTemplate.id, EmailTemplate.template_name)
264
  if campaign_id:
265
  campaign_id = validate_id(campaign_id, "campaign")
266
+ query = query.filter(EmailTemplate.campaign_id == campaign_id)
267
+ templates = query.all()
268
+ db.close()
269
+ return [f"{template.id}: {template.template_name}" for template in templates]
 
 
270
 
271
  # Function to fetch projects
272
  def fetch_projects():
273
+ db = SessionLocal()
274
+ projects = db.query(Project.id, Project.project_name).all()
275
+ db.close()
276
+ return [f"{project.id}: {project.project_name}" for project in projects]
 
 
277
 
278
  # Function to fetch campaigns
279
  def fetch_campaigns():
280
+ db = SessionLocal()
281
+ campaigns = db.query(Campaign.id, Campaign.campaign_name).all()
282
+ db.close()
283
+ return [f"{campaign.id}: {campaign.campaign_name}" for campaign in campaigns]
 
 
284
 
285
  # Bulk search function
286
  async def bulk_search(selected_terms, num_results, progress=gr.Progress()):
 
289
  num_results = validate_num_results(num_results)
290
  total_leads = 0
291
  for term_id in selected_terms:
292
+ db = SessionLocal()
293
+ term = db.query(SearchTerm.term, func.count(LeadSource.id).label('processed_leads')).outerjoin(LeadSource).filter(SearchTerm.id == term_id).first()
294
+ db.close()
295
+ if term:
296
+ term, processed_leads = term.term, term.processed_leads
297
+ else:
298
+ continue
299
 
300
  leads_found = 0
301
  try:
 
339
  raise ValueError("Invalid from email address")
340
  if not re.match(r"[^@]+@[^@]+\.[^@]+", reply_to):
341
  raise ValueError("Invalid reply to email address")
342
+ db = SessionLocal()
343
+ messages = db.query(EmailCampaign.id, Lead.email, EmailCampaign.customized_subject, EmailCampaign.customized_content).join(Lead).filter(EmailCampaign.template_id == template_id, EmailCampaign.status == 'pending').all()
344
+ db.close()
 
 
 
 
 
 
 
345
 
346
  total_sent = 0
347
  for message_id, email, subject, content in messages:
 
370
  template_id = validate_id(template_id, "template")
371
  from_email = validate_email(from_email)
372
  reply_to = validate_email(reply_to)
373
+ db = SessionLocal()
374
+ template = db.query(EmailTemplate.subject, EmailTemplate.body_content).filter(EmailTemplate.id == template_id).first()
375
+ db.close()
 
 
376
 
377
  if template:
378
  subject, body_content = template
 
381
  else:
382
  return "Template not found"
383
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
384
  # Function to refresh search terms
385
  def refresh_search_terms(campaign_id):
386
  return df_to_list(fetch_search_terms(campaign_id))
 
402
  logging.error(f"Error in manual search: {e}")
403
  return results[:num_results]
404
 
405
+ # Add a function to fetch leads
406
+ def fetch_leads():
407
+ db = SessionLocal()
408
+ leads = db.query(Lead.id, Lead.email, Lead.first_name, Lead.last_name, Lead.company, Lead.job_title).all()
409
+ db.close()
410
+ return pd.DataFrame([(lead.id, lead.email, lead.first_name, lead.last_name, lead.company, lead.job_title) for lead in leads], columns=["ID", "Email", "First Name", "Last Name", "Company", "Job Title"])
411
+
412
+ # Add a function to fetch campaigns with leads count
413
+ def fetch_campaigns_with_leads():
414
+ db = SessionLocal()
415
+ campaigns = db.query(Campaign.id, Campaign.campaign_name, func.count(CampaignLead.id).label('leads_count')).outerjoin(CampaignLead).group_by(Campaign.id).all()
416
+ db.close()
417
+ return pd.DataFrame([(campaign.id, campaign.campaign_name, campaign.leads_count) for campaign in campaigns], columns=["ID", "Campaign Name", "Leads Count"])
418
+
419
+ # Add a function to fetch leads for a specific campaign
420
+ def fetch_leads_for_campaign(campaign_id):
421
+ campaign_id = validate_id(campaign_id, "campaign")
422
+ db = SessionLocal()
423
+ leads = db.query(Lead.id, Lead.email, Lead.first_name, Lead.last_name, Lead.company, Lead.job_title).join(CampaignLead).filter(CampaignLead.campaign_id == campaign_id).all()
424
+ db.close()
425
+ return pd.DataFrame([(lead.id, lead.email, lead.first_name, lead.last_name, lead.company, lead.job_title) for lead in leads], columns=["ID", "Email", "First Name", "Last Name", "Company", "Job Title"])
426
+
427
  # Gradio interface
428
  with gr.Blocks() as gradio_app:
429
+ gr.Markdown("# AUTOCLIENT")
430
 
431
  with gr.Tab("Projects and Campaigns"):
432
  with gr.Row():
 
440
  campaign_type = gr.Radio(["Email", "SMS"], label="Campaign Type")
441
  create_campaign_btn = gr.Button("Create Campaign")
442
  campaign_status = gr.Textbox(label="Campaign Status", interactive=False)
443
+ campaign_list = gr.Dataframe(fetch_campaigns_with_leads(), headers=["ID", "Campaign Name", "Leads Count"])
444
 
445
+ with gr.Tab("Email Templates"):
446
  with gr.Row():
447
+ template_name = gr.Textbox(label="Template Name")
448
+ subject = gr.Textbox(label="Subject")
449
+ body_content = gr.Code(language="html", label="Body Content", lines=10)
450
+ campaign_id_for_template = gr.Dropdown(label="Campaign", choices=fetch_campaigns())
451
+ create_template_btn = gr.Button("Create Template")
452
+ template_status = gr.Textbox(label="Template Status", interactive=False)
453
+ template_preview = gr.HTML(label="Template Preview")
 
 
454
 
455
  with gr.Tab("Search Terms"):
456
  with gr.Row():
457
+ search_term = gr.Textbox(label="Search Term")
458
+ campaign_id_for_search = gr.Dropdown(label="Campaign", choices=fetch_campaigns())
459
+ add_term_btn = gr.Button("Add Search Term")
460
+ search_term_status = gr.Textbox(label="Search Term Status", interactive=False)
 
 
461
  search_term_list = gr.Dataframe(df_to_list(fetch_search_terms()), headers=["ID", "Search Term", "Leads Fetched", "Status"])
462
+ with gr.Row():
463
+ edit_term_id = gr.Number(label="Term ID to Edit")
464
+ new_term = gr.Textbox(label="New Term")
465
+ edit_term_btn = gr.Button("Edit Term")
466
+ delete_term_btn = gr.Button("Delete Term")
467
 
468
  with gr.Tab("Bulk Operations"):
469
  with gr.Row():
 
486
  bulk_send_button = gr.Button("Bulk Send")
487
  bulk_search_send_button = gr.Button("Bulk Search & Send")
488
 
489
+ send_progress = gr.Progress()
490
  log_output = gr.TextArea(label="Process Logs", interactive=False)
491
 
492
  with gr.Tab("Manual Search"):
493
  with gr.Row():
494
  manual_search_term = gr.Textbox(label="Manual Search Term")
495
+ manual_num_results = gr.Slider(minimum=1, maximum=50, value=10, step=1, label="Number of Results")
496
+ manual_search_btn = gr.Button("Search")
497
+ manual_search_results = gr.Dataframe(headers=["Email", "Source", "First Name", "Last Name", "Company", "Job Title"])
498
+ add_to_campaign_btn = gr.Button("Add Selected to Campaign")
499
+ add_status = gr.Textbox(label="Add Status", interactive=False)
500
+
501
+ with gr.Tab("Leads"):
502
+ leads_df = gr.Dataframe(fetch_leads(), headers=["ID", "Email", "First Name", "Last Name", "Company", "Job Title"])
503
+ campaign_id_for_leads = gr.Dropdown(label="Campaign", choices=fetch_campaigns())
504
+ fetch_leads_btn = gr.Button("Fetch Leads for Campaign")
505
+ leads_for_campaign_df = gr.Dataframe(headers=["ID", "Email", "First Name", "Last Name", "Company", "Job Title"])
506
+
507
+ with gr.Row():
508
+ lead_email = gr.Textbox(label="Email")
509
+ lead_first_name = gr.Textbox(label="First Name")
510
+ lead_last_name = gr.Textbox(label="Last Name")
511
+ lead_company = gr.Textbox(label="Company")
512
+ lead_job_title = gr.Textbox(label="Job Title")
513
+ add_lead_btn = gr.Button("Add Lead")
514
+ lead_status = gr.Textbox(label="Lead Status", interactive=False)
515
+
516
+ with gr.Tab("Campaign Analytics"):
517
+ campaign_select = gr.Dropdown(label="Select Campaign", choices=fetch_campaigns())
518
+ refresh_analytics_btn = gr.Button("Refresh Analytics")
519
+ with gr.Row():
520
+ total_leads = gr.Number(label="Total Leads")
521
+ emails_sent = gr.Number(label="Emails Sent")
522
+ open_rate = gr.Number(label="Open Rate")
523
+ click_rate = gr.Number(label="Click Rate")
524
+ performance_chart = gr.Plot(label="Campaign Performance")
525
+
526
+ with gr.Tab("Lead Import/Export"):
527
+ with gr.Row():
528
+ import_file = gr.File(label="Import CSV")
529
+ import_btn = gr.Button("Import Leads")
530
+ import_status = gr.Textbox(label="Import Status", interactive=False)
531
+ export_campaign = gr.Dropdown(label="Select Campaign for Export", choices=fetch_campaigns())
532
+ export_btn = gr.Button("Export Leads")
533
+ export_status = gr.Textbox(label="Export Status", interactive=False)
534
+
535
+ with gr.Tab("Email Scheduling"):
536
+ schedule_campaign = gr.Dropdown(label="Select Campaign", choices=fetch_campaigns())
537
+ schedule_template = gr.Dropdown(label="Select Template", choices=fetch_message_templates())
538
+ schedule_date = gr.Datetime(label="Schedule Date and Time")
539
+ schedule_btn = gr.Button("Schedule Campaign")
540
+ schedule_status = gr.Textbox(label="Schedule Status", interactive=False)
541
+ scheduled_campaigns = gr.Dataframe(headers=["ID", "Campaign", "Template", "Scheduled Time", "Status"])
542
+
543
+ with gr.Tab("AI Content Assistant"):
544
+ content_type = gr.Radio(["Email Subject", "Email Body", "Search Term"], label="Content Type")
545
+ campaign_context = gr.Dropdown(label="Select Campaign for Context", choices=fetch_campaigns())
546
+ prompt = gr.Textbox(label="Prompt for AI", lines=3)
547
+ generate_btn = gr.Button("Generate Content")
548
+ generated_content = gr.Textbox(label="Generated Content", lines=10)
549
+ use_content_btn = gr.Button("Use Generated Content")
550
+
551
+ with gr.Tab("User Management"):
552
+ with gr.Row():
553
+ username = gr.Textbox(label="Username")
554
+ email = gr.Textbox(label="Email")
555
+ password = gr.Textbox(label="Password", type="password")
556
+ role = gr.Dropdown(label="Role", choices=["Admin", "User", "Viewer"])
557
+ add_user_btn = gr.Button("Add User")
558
+ user_status = gr.Textbox(label="User Status", interactive=False)
559
+ user_list = gr.Dataframe(headers=["ID", "Username", "Email", "Role", "Last Login"])
560
+ with gr.Row():
561
+ edit_user_id = gr.Number(label="User ID to Edit")
562
+ new_role = gr.Dropdown(label="New Role", choices=["Admin", "User", "Viewer"])
563
+ edit_user_btn = gr.Button("Edit User Role")
564
+ delete_user_btn = gr.Button("Delete User")
565
 
566
  # Move these lines inside the Blocks context
567
  gradio_app.load(lambda: gr.update(value=df_to_list(fetch_search_terms())), outputs=search_term_df)
 
594
  )
595
  manual_search_btn.click(manual_search, inputs=[manual_search_term, manual_num_results], outputs=manual_search_results)
596
  refresh_btn.click(refresh_search_terms, inputs=[campaign_id_for_bulk], outputs=[search_term_df])
597
+ fetch_leads_btn.click(fetch_leads_for_campaign, inputs=[campaign_id_for_leads], outputs=[leads_for_campaign_df])
598
 
599
  # Launch the app outside the Blocks context
600
  gradio_app.launch()