File size: 21,252 Bytes
ce07192
 
c399b67
ce07192
7f372a3
 
 
ce07192
 
01e805b
3cc3c22
 
902384a
d48b720
902384a
 
 
 
939a757
902384a
 
 
 
070fca3
 
 
 
 
 
 
 
 
 
 
 
 
 
902384a
 
 
 
 
 
 
 
 
 
 
 
 
 
977c309
902384a
 
070fca3
902384a
 
070fca3
 
 
 
 
 
 
 
 
902384a
 
 
 
 
 
 
 
 
c399b67
 
902384a
 
070fca3
902384a
 
 
 
 
 
 
070fca3
da42754
070fca3
2e15d5a
902384a
c399b67
 
60839be
 
 
 
 
 
 
c399b67
 
2455f1a
070fca3
 
 
 
 
2455f1a
070fca3
 
902384a
070fca3
 
 
 
 
 
d48b720
902384a
d48b720
 
 
902384a
 
 
 
 
 
 
d48b720
 
 
 
 
902384a
 
 
 
 
 
 
 
 
 
 
 
977c309
 
ce07192
 
 
 
3cc3c22
 
cee38d2
8c7fd74
3cc3c22
cee38d2
 
 
 
 
3cc3c22
 
 
b9e57dd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51581e7
b9e57dd
 
 
 
 
 
56afcbc
b9e57dd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3cc3c22
 
56afcbc
 
3cc3c22
 
7f372a3
ce07192
7f372a3
 
 
ce07192
7f372a3
ce07192
 
 
 
 
 
 
 
7f372a3
 
 
 
 
68cd283
7f372a3
 
 
 
 
 
 
 
68cd283
7f372a3
 
 
 
 
 
 
 
 
 
ce07192
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cdf97c3
ce07192
 
7f372a3
 
 
ae85d1a
 
9484cb3
ae85d1a
 
 
 
ce07192
 
9ee3134
7f372a3
 
 
ae85d1a
 
9484cb3
ae85d1a
 
 
 
0f31443
 
56afcbc
 
 
0f31443
 
 
 
 
 
 
cdf97c3
 
0f31443
48674a5
56afcbc
0f31443
 
 
 
 
 
cdf97c3
 
0f31443
48674a5
56afcbc
 
ce07192
 
 
 
 
 
 
 
 
 
 
 
3cc3c22
9dfe978
c293e7a
7c39580
60839be
c293e7a
9dfe978
c293e7a
60839be
 
c399b67
 
60839be
 
 
 
 
 
 
 
 
 
 
070fca3
da42754
 
 
 
e908559
 
3cc3c22
b9e57dd
 
 
 
ae630f0
dff5169
 
 
ae630f0
 
 
 
 
 
 
 
 
 
 
b9e57dd
 
 
 
 
 
 
 
 
 
 
 
 
81d9b58
b9e57dd
2d86257
597074c
b9e57dd
 
30e686c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b9e57dd
81d9b58
ce07192
30e686c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81d8376
30e686c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81d8376
30e686c
 
 
9aef43e
 
 
 
 
 
 
 
 
 
 
 
 
 
209134a
9aef43e
 
 
 
 
 
 
 
 
 
 
 
 
209134a
6d10c70
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
import os
import requests
import logging
from bs4 import BeautifulSoup
import time
import xml.etree.ElementTree as ET
from urllib.parse import urljoin
import gradio as gr
from groq import Groq
import json
from googleapiclient.discovery import build
from google.oauth2.service_account import Credentials
from flask import Flask, redirect, request, session
from google.auth.transport.requests import Request
from google_auth_oauthlib.flow import Flow
import base64

# Flask app setup
app = Flask(__name__)
app.secret_key = os.environ.get("FLASK_SECRET_KEY")
REDIRECT_URI = "https://huggingface.co/spaces/curiousgeorge1292/Custom_Profile_Email_Generator/oauth2callback"
GMAIL_SCOPES = ["https://www.googleapis.com/auth/gmail.compose"]

# Create a simple credential storage mechanism
class CredentialStore:
    def __init__(self):
        self.credentials = {}

    def set_credentials(self, user_email, creds):
        self.credentials[user_email] = creds

    def get_credentials(self, user_email):
        return self.credentials.get(user_email)

# Create a global instance
credential_store = CredentialStore()

# Retrieve JSON string from the environment variable
client_secrets_json = os.environ.get("GMAIL_OAUTH_SECRET_JSON")

if not client_secrets_json:
    raise ValueError("Environment variable GMAIL_OAUTH_SECRET_JSON is not set or empty.")

# Parse JSON string into a dictionary
client_secrets = json.loads(client_secrets_json)

# Write it to a temporary file (Google API requires a file path)
with open("temp_client_secrets.json", "w") as temp_file:
    json.dump(client_secrets, temp_file)

# Initialize Flow using the temporary JSON file
flow = Flow.from_client_secrets_file(
        'temp_client_secrets.json',  # Path to the temporary JSON file
        scopes=['https://www.googleapis.com/auth/gmail.compose'],
        redirect_uri=REDIRECT_URI
)

def get_user_email_from_credentials(credentials):
    try:
        service = build('gmail', 'v1', credentials=credentials)
        user_info = service.users().getProfile(userId='me').execute()
        return user_info.get('emailAddress')
    except Exception as e:
        print(f"Error getting user email: {e}")
        return None

@app.route('/oauth2callback')
def oauth2callback():
    flow = Flow.from_client_secrets_file(
        'temp_client_secrets.json',
        scopes=GMAIL_SCOPES,
        redirect_uri=REDIRECT_URI
    )
    flow.fetch_token(authorization_response=request.url)

    logging.basicConfig(level=logging.DEBUG)

    # Save the credentials to the session
    credentials = flow.credentials
    creds_dict = {
        'token': credentials.token,
        'refresh_token': credentials.refresh_token,
        'token_uri': credentials.token_uri,
        'client_id': credentials.client_id,
        'client_secret': credentials.client_secret,
        'scopes': credentials.scopes
    }
    user_email = get_user_email_from_credentials(credentials)
    credential_store.set_credentials(user_email, creds_dict)
        
    return "Authorization successful. You can now save emails to Gmail drafts"

    logging.basicConfig(level=logging.DEBUG)

@app.route('/authenticate_gmail')
def authenticate_gmail():
    authorization_url, state = flow.authorization_url(
        access_type='offline',
        include_granted_scopes='true'
    )
    return redirect(authorization_url)

    logging.basicConfig(level=logging.DEBUG)
    # Gmail OAuth2 logic here
    #credentials = get_credentials_from_oauth()
    #if 'user_id' not in flask.session:
        #flask.session['user_id'] = str(uuid.uuid4())  # Generate a unique ID if not already present
    #user_id = flask.session.get('user_id')  # Get user-specific ID
    #redis_store.set(user_id, credentials)  # Save credentials to Redis (or a database)
    # After successful authentication:
    #session['credentials'] = credentials
    #return "Authentication successful!"

#@app.route('/check_credentials')
#def check_credentials():
    #if 'credentials' in session:
        #return "Credentials are stored in session."
    #else:
        #return "No credentials found. Please authorize first."


def save_to_gmail_drafts(credentials_info, subject, body, recipient):

    # Initialize the credentials
    credentials = Credentials(
        credentials_info['token'],
        refresh_token=credentials_info.get('refresh_token'),
        token_uri=credentials_info['token_uri'],
        client_id=credentials_info['client_id'],
        client_secret=credentials_info['client_secret']
    )

    # Refresh the token if expired
    if not credentials.valid and credentials.expired and credentials.refresh_token:
        credentials.refresh(Request())
        
    service = build('gmail', 'v1', credentials=credentials)

    # Create the email message
    raw_message = f"To: {recipient}\nSubject: {subject}\n\n{body}"
    raw_message_bytes = base64.urlsafe_b64encode(raw_message.encode("utf-8")).decode("utf-8")

    # Save the draft
    message = {'message': {'raw': raw_message_bytes}}
    draft = service.users().drafts().create(userId="me", body=message).execute()
    return f"Draft created with ID: {draft['id']}"

# Clean up the temporary file
if os.path.exists("temp_client_secrets.json"):
    os.remove("temp_client_secrets.json")

# Initialize Groq client
client = Groq(api_key=os.environ.get("GROQ_API_KEY"))

# Google Sheets setup
SCOPES = ['https://www.googleapis.com/auth/spreadsheets']
SERVICE_ACCOUNT_JSON = os.environ.get("MY_SERVICE_ACCOUNT_JSON")
SPREADSHEET_ID = os.environ.get("GOOGLE_SHEET_ID")

if not SERVICE_ACCOUNT_JSON:
    raise ValueError("MY_SERVICE_ACCOUNT_JSON environment variable is not set or empty.")

service_account_info = json.loads(SERVICE_ACCOUNT_JSON)
credentials = Credentials.from_service_account_info(service_account_info, scopes=SCOPES)
service = build('sheets', 'v4', credentials=credentials)
sheet = service.spreadsheets()

def fetch_user_details(email):
    """
    Fetch user details from Google Sheet based on email.
    """
    try:
        result = sheet.values().get(spreadsheetId=SPREADSHEET_ID, range="Sheet1!A:G").execute()
        rows = result.get('values', [])
        for row in rows:
            if row[0] == email:  # Assuming email is in the first column (A)
                return {
                    "email": row[0],
                    "name": row[1],
                    "professional_title": row[2],
                    "industry": row[3],
                    "target_audience": row[4],
                    "personal_background": row[5],
                    "company_url": row[6]
                }
        return None
    except Exception as e:
        return f"Error fetching details: {str(e)}"

def save_user_info(email, name, professional_title, industry, target_audience, personal_background, company_url):
    """
    Save or update user details in Google Sheet.
    """
    try:
        result = sheet.values().get(spreadsheetId=SPREADSHEET_ID, range="Sheet1!A:G").execute()
        rows = result.get('values', [])
        updated = False

        # Check if email exists and update the row
        for i, row in enumerate(rows):
            if row[0] == email:
                rows[i] = [email, name, professional_title, industry, target_audience, personal_background, company_url]
                updated = True
                break

        if not updated:  # Append a new row if email doesn't exist
            rows.append([email, name, professional_title, industry, target_audience, personal_background, company_url])

        # Save updated rows back to the sheet
        body = {"values": rows}
        sheet.values().update(
            spreadsheetId=SPREADSHEET_ID, range="Sheet1!A:G",
            valueInputOption="RAW", body=body
        ).execute()
        return "Details saved successfully."
    except Exception as e:
        return f"Error saving details: {str(e)}"



def handle_user_details(email, name=None, professional_title=None, industry=None, target_audience=None, personal_background=None, company_url=None, action="verify"):
    if action == "verify":
        # Fetch details based on email
        user_details = fetch_user_details(email)
        if user_details:
            return (
                f"Record found for {email}. You can update details if needed.",
                user_details  # Return existing details
            )
        else:
            return (
                f"No record found for {email}. Please enter details and save.",
                {}  # Return empty fields
            )

    elif action == "save":
        # Save or update details
        result = save_user_info(email, name, professional_title, industry, target_audience, personal_background, company_url)
        return result, {}


# Step 1: User profile management UI
def user_profile(email, name, professional_title, industry, target_audience, personal_background, company_url):
    save_user_info(email, name, professional_title, industry, target_audience, personal_background, company_url)
    return "Your information has been saved! Proceed to Step 2 for email generation."

# Helper function to extract content from a URL
def extract_content(url):
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36'
    }
    try:
        response = requests.get(url, headers=headers, timeout=10)
        response.raise_for_status()
        soup = BeautifulSoup(response.text, 'html.parser')
        paragraphs = soup.find_all('p')
        content = ' '.join([para.get_text() for para in paragraphs])
        return content[:2000]  # Limit content to 2000 characters to avoid overload
    except Exception as e:
        return f"Error extracting content from {url}: {str(e)}"

# Helper function to parse a sitemap and get valid URLs
def parse_sitemap(sitemap_url):
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36'
    }
    urls = []
    try:
        response = requests.get(sitemap_url, headers=headers, timeout=10)
        response.raise_for_status()
        root = ET.fromstring(response.content)
        for loc in root.findall(".//{http://www.sitemaps.org/schemas/sitemap/0.9}loc"):
            urls.append(loc.text)
    except Exception as e:
        return f"Error parsing sitemap from {sitemap_url}: {str(e)}"
    return urls

# Wrapper to handle retries and delay
def safe_extract_content(url, delay=2):
    content = extract_content(url)
    if "Error extracting content" in content:
        print(content)  # Log the error
        return None
    time.sleep(delay)  # Respect crawl-delay
    return content

# Function to fetch LinkedIn profile insights using Proxycurl API
def fetch_linkedin_insights(profile_url):
    api_key = os.environ.get("PROXYCURL_API_KEY")
    api_endpoint = "https://nubela.co/proxycurl/api/v2/linkedin"
    headers = {"Authorization": f"Bearer {api_key}"}
    params = {"url": profile_url, "fallback_to_cache": "on-error"}

    try:
        response = requests.get(api_endpoint, headers=headers, params=params, timeout=10)
        response.raise_for_status()
        profile_data = response.json()
        insights = f"{profile_data.get('headline', '')}. {profile_data.get('summary', '')}"
        return insights
    except Exception as e:
        return f"Error fetching LinkedIn insights: {str(e)}"

# Function to generate email using Llama
def generate_email(name, email, prospect_name, linkedin_url, website_url, context_url, word_count, email_purpose, interested_position, company_url, professional_title, personal_background, output_language):
    # Fetch insights from LinkedIn and reference URLs
    linkedin_insights = fetch_linkedin_insights(linkedin_url)
    website_sitemap_url = urljoin(website_url, "sitemap_index.xml")
    website_content = safe_extract_content(website_url)
    if not website_content:
        # If direct scraping fails, fall back to the sitemap
        website_urls = parse_sitemap(website_sitemap_url)
        if isinstance(website_urls, list):
            for url in urls:
                website_content = safe_extract_content(url)
                if website_content:
                    break
    context_content = extract_content(context_url) if context_url else ""

    # Fetch details from the company website
    company_sitemap_url = urljoin(company_url, "sitemap_index.xml")
    company_content = safe_extract_content(company_url)
    if not company_content:
        # If direct scraping fails, fall back to the sitemap
        company_urls = parse_sitemap(company_sitemap_url)
        if isinstance(company_urls, list):
            for url in urls:
                company_content = safe_extract_content(url)
                if company_content:
                    break
    job_application = os.environ.get("JOB_APPLICATION")
    sales_cold_email = os.environ.get("SALES_COLD_EMAIL")

    # Construct the purpose-specific prompt
    if email_purpose == "Job Application":
        prompt = job_application.format(
            name=name,
            professional_title=professional_title,
            interested_position=interested_position,
            website_content=website_content,
            personal_background=personal_background,
            word_count=word_count,
            prospect_name=prospect_name,
            output_language=output_language
        )
        
    elif email_purpose == "Sales Cold Email":
        prompt = sales_cold_email.format(
            company_content=company_content,
            prospect_name=prospect_name,
            linkedin_insights=linkedin_insights,
            website_content=website_content,
            context_content=context_content,
            word_count=word_count,
            output_language=output_language
        )
        
    else:
        return "Invalid email purpose selected. Please choose either 'Job Application' or 'Sales Cold Email'."

    # Generate email using Llama
    try:
        chat_response = client.chat.completions.create(
            messages=[{"role": "user", "content": prompt}],
            model="llama3-8b-8192",
        )
        email_content = chat_response.choices[0].message.content
        return email_content
    except Exception as e:
        return f"Error generating email: {str(e)}"

# Step 2: Email generation UI
def email_agent(credentials_info, name, email, prospect_name, linkedin_url, website_url, context_url, word_count, email_purpose, interested_position, company_url, professional_title, personal_background, output_language): 
    
    try:
            
        # Generate the email content
        email_content = generate_email(name, email, prospect_name, linkedin_url, website_url, context_url, word_count, email_purpose, interested_position, company_url, professional_title, personal_background, output_language)

        if not email_content:
            return ("<div style='color: red;'>Failed to generate email content.</div>", "")

        logging.basicConfig(level=logging.DEBUG)
            
        # Save to Gmail drafts
        try:            
            save_to_gmail_drafts(credentials_info,
                subject="Generated Email Subject", 
                body=email_content, 
                recipient=email  # Replace with the recipient's email
                )
            return ("<div style='color: green;'>Email saved to Gmail drafts successfully.</div>", email_content)
        except Exception as e:
            return (f"<div style='color: red;'>Error saving to Gmail drafts: {str(e)}</div>", email_content)

        credentials_info = credential_store.get_credentials(email)
        if not credentials_info:
            return ("<div style='color: red;'>Please click the 'Authenticate with Gmail' button above to connect your Gmail account.</div>", "")

    except Exception as e:
        return (f"<div style='color: red;'>Error: {str(e)}</div>", "")

# Gradio Interface
def verify_user(email):
    # Logic for verifying the email
    message, user_details = handle_user_details(email, action="verify")
    
    # Ensure user_details is a dictionary
    if not isinstance(user_details, dict):
        user_details = {}

    if user_details:  # Populate fields if user details are found
        return (
            message,
            gr.update(visible=True, value=user_details.get("name", "")),
            gr.update(visible=True, value=user_details.get("professional_title", "")),
            gr.update(visible=True, value=user_details.get("industry", "")),
            gr.update(visible=True, value=user_details.get("target_audience", "")),
            gr.update(visible=True, value=user_details.get("personal_background", "")),
            gr.update(visible=True, value=user_details.get("company_url", ""))
        )
    else:
        # Show empty fields for new user
        return (
            message,
            gr.update(visible=True, value=""),
            gr.update(visible=True, value=""),
            gr.update(visible=True, value=""),
            gr.update(visible=True, value=""),
            gr.update(visible=True, value=""),
            gr.update(visible=True, value="")
        )


def save_and_switch_tab(email, name, professional_title, industry, target_audience, personal_background, company_url):
    # Logic for saving the user details
    message, _ = handle_user_details(email, name, professional_title, industry, target_audience, personal_background, company_url, action="save")
    return message, gr.update(selected=1)

with gr.Blocks() as ui:
    with gr.Tabs() as tabs:
        with gr.Tab("Step 1: Profile"):
            email = gr.Textbox(label="Email", placeholder="Enter your email")
            verify_button = gr.Button("Verify")
            with gr.Row():
                status = gr.Textbox(label="Status", interactive=False)

        # Other fields are initially hidden
        #with gr.Row() as details_section:
            name = gr.Textbox(label="Name", visible=False)
            professional_title = gr.Textbox(label="Professional Title", visible=False)
            industry = gr.Textbox(label="Industry", visible=False)
            target_audience = gr.Textbox(label="Target Audience", visible=False)
            personal_background = gr.Textbox(label="Personal Background", visible=False)
            company_url = gr.Textbox(label="Company URL", visible=False)
            save_button = gr.Button("Save & Proceed")

        

        with gr.Tab("Step 2: Email Generation"):
            # Gmail Authentication Button (HTML + JavaScript)
            auth_button_html = """
            <div style="text-align: center; margin: 10px;">
                <button onclick="window.location.href='/authenticate_gmail'" 
                        style="padding: 10px 20px; font-size: 16px; cursor: pointer; background-color: #4285F4; color: white; border: none; border-radius: 5px;">
                    Authenticate with Gmail
                </button>
            </div>
            """
            gr.HTML(auth_button_html)
            
            prospect_name = gr.Textbox(label="Prospect's Name")
            linkedin_url = gr.Textbox(label="LinkedIn Profile URL")
            website_url = gr.Textbox(label="Website URL")
            context_url = gr.Textbox(label="Additional Context URL (optional)")
            word_count = gr.Slider(label="Email Length (words)", minimum=150, maximum=1000, step=10, value=500)
            email_purpose = gr.Dropdown(label="Email Purpose", choices=["Job Application", "Sales Cold Email", "Select"], value="Select")
            output_language = gr.Dropdown(label="Select Output Language", choices=["English", "Spanish", "Portuguese"], value="English")
            interested_position = gr.Textbox(label="Interested Position (for Job Applications)")
    
            email_button = gr.Button("Generate Email")
            email_status = gr.HTML(label="Status")
            generated_email = gr.HTML(label="Generated Email")
    
        # Function to fetch user_id dynamically
        def get_user_id():
            return session.get('user_id', "default_user")
    
        email_button.click(
            lambda *args: email_agent(None, *args),  # Pass None as credentials_info
            inputs=[
                name, email, prospect_name, linkedin_url, website_url, context_url, 
                word_count, email_purpose, interested_position, company_url, 
                professional_title, personal_background, output_language
            ],
            outputs=[email_status, generated_email]
        )
    # Events
    verify_button.click(
        verify_user,
        inputs=[email],
        outputs=[
            status,
            name,
            professional_title,
            industry,
            target_audience,
            personal_background,
            company_url,
        ]
    )

    save_button.click(
        save_and_switch_tab,
        inputs=[
            email,
            name,
            professional_title,
            industry,
            target_audience,
            personal_background,
            company_url,
        ],
        outputs=[status, tabs]
    )

ui.launch(share=True)