Spaces:
Runtime error
Runtime error
luigi12345
commited on
Commit
•
43f4391
1
Parent(s):
3ea7037
Update app.py
Browse files
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 |
-
#
|
19 |
-
AWS_ACCESS_KEY_ID = "AKIASO2XOMEGIVD422N7"
|
20 |
-
AWS_SECRET_ACCESS_KEY = "Rl+rzgizFDZPnNgDUNk0N0gAkqlyaYqhx7O2ona9"
|
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="Rl+rzgizFDZPnNgDUNk0N0gAkqlyaYqhx7O2ona9",
|
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 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
)
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
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 |
-
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
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 |
-
|
221 |
-
|
222 |
-
|
223 |
-
|
224 |
-
campaign_id =
|
225 |
-
|
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 |
-
|
236 |
-
|
237 |
-
|
238 |
-
|
239 |
-
|
240 |
-
|
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 |
-
|
251 |
-
|
252 |
-
|
253 |
-
|
254 |
-
|
255 |
-
|
256 |
return term_id
|
257 |
|
258 |
# Function to fetch search terms
|
259 |
def fetch_search_terms(campaign_id=None):
|
260 |
-
|
261 |
-
|
|
|
262 |
if campaign_id:
|
263 |
campaign_id = validate_id(campaign_id, "campaign")
|
264 |
-
|
265 |
-
|
266 |
-
|
267 |
-
|
268 |
-
|
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 |
-
|
277 |
-
|
278 |
-
|
279 |
-
|
280 |
-
|
281 |
-
|
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 |
-
|
290 |
-
|
291 |
-
|
292 |
-
|
293 |
-
|
294 |
-
|
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 |
-
|
304 |
-
|
305 |
-
|
306 |
-
|
307 |
-
|
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 |
-
|
317 |
-
|
318 |
-
|
319 |
-
|
320 |
-
|
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 |
-
|
331 |
-
|
332 |
-
|
333 |
-
|
334 |
-
|
335 |
-
|
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 |
-
|
346 |
-
|
347 |
-
if
|
348 |
-
|
349 |
-
|
350 |
-
|
351 |
-
|
352 |
-
|
353 |
-
conn.commit()
|
354 |
-
conn.close()
|
355 |
|
356 |
# Function to fetch message templates
|
357 |
def fetch_message_templates(campaign_id=None):
|
358 |
-
|
359 |
-
|
360 |
if campaign_id:
|
361 |
campaign_id = validate_id(campaign_id, "campaign")
|
362 |
-
|
363 |
-
|
364 |
-
|
365 |
-
|
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 |
-
|
372 |
-
|
373 |
-
|
374 |
-
|
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 |
-
|
381 |
-
|
382 |
-
|
383 |
-
|
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 |
-
|
395 |
-
|
396 |
-
|
397 |
-
term
|
398 |
-
|
|
|
|
|
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 |
-
|
443 |
-
|
444 |
-
|
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 |
-
|
481 |
-
|
482 |
-
|
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("#
|
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("
|
567 |
with gr.Row():
|
568 |
-
|
569 |
-
|
570 |
-
|
571 |
-
|
572 |
-
|
573 |
-
|
574 |
-
|
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 |
-
|
581 |
-
|
582 |
-
|
583 |
-
|
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 |
-
|
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()
|