ak0601 commited on
Commit
bae75b8
·
verified ·
1 Parent(s): e035532

Update app_hug.py

Browse files
Files changed (1) hide show
  1. app_hug.py +669 -4
app_hug.py CHANGED
@@ -1,3 +1,658 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  from fastapi import FastAPI, HTTPException, BackgroundTasks, Depends, Header, status
2
  from fastapi.middleware.cors import CORSMiddleware
3
  from fastapi.security import APIKeyHeader
@@ -19,14 +674,24 @@ from googleapiclient.discovery import build
19
  from googleapiclient.errors import HttpError
20
  from datetime import datetime
21
  import json
 
 
 
22
 
23
- # Load environment variables (not needed on Hugging Face, but harmless)
24
  load_dotenv()
 
 
 
 
 
 
 
25
 
 
26
  # ------------------------------------------
27
  # Security Configuration
28
  # ------------------------------------------
29
- API_PASSWORD = os.getenv("API_PASSWORD", "Synapse@2025") # Can be overridden by env var
30
  api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)
31
 
32
  def verify_api_key(api_key: Optional[str] = Depends(api_key_header)) -> str:
@@ -259,7 +924,7 @@ def refine_message_with_feedback(
259
  SystemMessage(content="You are a professional recruitment message writer. Refine the given message based on feedback while maintaining professionalism and the original intent."),
260
  HumanMessage(content=prompt)
261
  ]
262
- response = llm.invoke(messages)
263
  content = response.content.strip()
264
  subject_line = ""
265
  body_content = ""
@@ -374,7 +1039,7 @@ def generate_recruitment_message_with_subject(
374
  SystemMessage(content="You are a professional recruitment message writer. Generate both an email subject line and body content. Follow the exact format requested."),
375
  HumanMessage(content=prompt)
376
  ]
377
- response = llm.invoke(messages)
378
  content = response.content.strip()
379
  subject_line = ""
380
  body_content = ""
 
1
+ # from fastapi import FastAPI, HTTPException, BackgroundTasks, Depends, Header, status
2
+ # from fastapi.middleware.cors import CORSMiddleware
3
+ # from fastapi.security import APIKeyHeader
4
+ # from pydantic import BaseModel, EmailStr, Field
5
+ # from typing import Optional, Tuple
6
+ # from enum import Enum
7
+ # import os
8
+ # import base64
9
+ # import pickle
10
+ # import pandas as pd
11
+ # from dotenv import load_dotenv
12
+ # from langchain_openai import ChatOpenAI
13
+ # from langchain.schema import HumanMessage, SystemMessage
14
+ # from email.mime.text import MIMEText
15
+ # from google.auth.transport.requests import Request
16
+ # from google.oauth2.credentials import Credentials
17
+ # from google_auth_oauthlib.flow import InstalledAppFlow
18
+ # from googleapiclient.discovery import build
19
+ # from googleapiclient.errors import HttpError
20
+ # from datetime import datetime
21
+ # import json
22
+
23
+ # # Load environment variables (not needed on Hugging Face, but harmless)
24
+ # load_dotenv()
25
+
26
+ # # ------------------------------------------
27
+ # # Security Configuration
28
+ # # ------------------------------------------
29
+ # API_PASSWORD = os.getenv("API_PASSWORD", "Synapse@2025") # Can be overridden by env var
30
+ # api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)
31
+
32
+ # def verify_api_key(api_key: Optional[str] = Depends(api_key_header)) -> str:
33
+ # """
34
+ # Verify the API key/password provided in the request header.
35
+ # """
36
+ # if api_key is None:
37
+ # raise HTTPException(
38
+ # status_code=status.HTTP_401_UNAUTHORIZED,
39
+ # detail="API Key required. Please provide X-API-Key header.",
40
+ # headers={"WWW-Authenticate": "ApiKey"},
41
+ # )
42
+
43
+ # if api_key != API_PASSWORD:
44
+ # raise HTTPException(
45
+ # status_code=status.HTTP_401_UNAUTHORIZED,
46
+ # detail="Invalid API Key",
47
+ # headers={"WWW-Authenticate": "ApiKey"},
48
+ # )
49
+
50
+ # return api_key
51
+
52
+ # # ------------------------------------------
53
+ # # Helper: Write GOOGLE_CREDENTIALS_JSON to file if needed
54
+ # # ------------------------------------------
55
+ # def ensure_credentials_file():
56
+ # credentials_env = os.getenv("GOOGLE_CREDENTIALS_JSON")
57
+ # credentials_path = "credentials_SYNAPSE.json"
58
+ # if not os.path.exists(credentials_path):
59
+ # if not credentials_env:
60
+ # raise Exception("GOOGLE_CREDENTIALS_JSON not found in environment variables.")
61
+ # try:
62
+ # parsed_json = json.loads(credentials_env)
63
+ # except json.JSONDecodeError:
64
+ # raise Exception("Invalid JSON in GOOGLE_CREDENTIALS_JSON")
65
+ # with open(credentials_path, "w") as f:
66
+ # json.dump(parsed_json, f, indent=2)
67
+ # return credentials_path
68
+
69
+ # # ------------------------------------------
70
+ # # FastAPI app
71
+ # # ------------------------------------------
72
+ # app = FastAPI(title="Recruitment Message Generator API", version="1.0.0")
73
+ # app.add_middleware(
74
+ # CORSMiddleware,
75
+ # allow_origins=["*"],
76
+ # allow_credentials=True,
77
+ # allow_methods=["*"],
78
+ # allow_headers=["*"],
79
+ # )
80
+
81
+ # SCOPES = ["https://www.googleapis.com/auth/gmail.send"]
82
+ # openai_api_key = os.getenv("OPENAI_API_KEY")
83
+
84
+ # # ------------------------------------------
85
+ # # Enums and Models
86
+ # # ------------------------------------------
87
+ # class MessageType(str, Enum):
88
+ # OUTREACH = "outreach"
89
+ # INTRODUCTORY = "introductory"
90
+ # FOLLOWUP = "followup"
91
+
92
+ # class GenerateMessageRequest(BaseModel):
93
+ # job_evaluation: Optional[str] = Field(None, description="(Optional) Recruiter's evaluation of candidate for the job")
94
+ # sender_email: EmailStr
95
+ # reply_to_email: Optional[EmailStr] = Field(None, description="Recruiter's email for reply-to header")
96
+ # recipient_email: EmailStr
97
+ # candidate_name: str
98
+ # current_role: str
99
+ # current_company: str
100
+ # company_name: str
101
+ # role: str
102
+ # recruiter_name: str
103
+ # organisation: str
104
+ # message_type: MessageType
105
+ # send_email: bool = False
106
+ # past_conversation: Optional[str] = Field(None, description="(Optional) Previous messages with candidate")
107
+
108
+ # class FeedbackRequest(BaseModel):
109
+ # message: str
110
+ # feedback: str
111
+
112
+ # class AuthenticateRequest(BaseModel):
113
+ # email: EmailStr
114
+
115
+ # class AuthenticateResponse(BaseModel):
116
+ # success: bool
117
+ # message: str
118
+ # error: Optional[str] = None
119
+
120
+ # class MessageResponse(BaseModel):
121
+ # success: bool
122
+ # message: str
123
+ # email_sent: bool = False
124
+ # email_subject: Optional[str] = None
125
+ # error: Optional[str] = None
126
+
127
+ # class SendMessageRequest(BaseModel):
128
+ # subject: str
129
+ # email_body: str
130
+ # sender_email: EmailStr
131
+ # recipient_email: EmailStr
132
+ # reply_to_email: Optional[EmailStr] = None
133
+
134
+ # # ------------------------------------------
135
+ # # Gmail Helper Functions
136
+ # # ------------------------------------------
137
+ # def get_token_file_path(email: str) -> str:
138
+ # tokens_dir = "gmail_tokens"
139
+ # if not os.path.exists(tokens_dir):
140
+ # os.makedirs(tokens_dir)
141
+ # safe_email = email.replace("@", "_at_").replace(".", "_dot_")
142
+ # return os.path.join(tokens_dir, f"token_{safe_email}.pickle")
143
+
144
+ # def check_user_token_exists(email: str) -> bool:
145
+ # token_file = get_token_file_path(email)
146
+ # return os.path.exists(token_file)
147
+
148
+ # def load_user_credentials(email: str):
149
+ # token_file = get_token_file_path(email)
150
+ # if os.path.exists(token_file):
151
+ # try:
152
+ # with open(token_file, 'rb') as token:
153
+ # creds = pickle.load(token)
154
+ # return creds
155
+ # except Exception:
156
+ # if os.path.exists(token_file):
157
+ # os.remove(token_file)
158
+ # return None
159
+
160
+ # def save_user_credentials(email: str, creds):
161
+ # token_file = get_token_file_path(email)
162
+ # with open(token_file, 'wb') as token:
163
+ # pickle.dump(creds, token)
164
+
165
+ # def create_new_credentials(email: str):
166
+ # credentials_path = ensure_credentials_file()
167
+ # flow = InstalledAppFlow.from_client_secrets_file(
168
+ # credentials_path, SCOPES
169
+ # )
170
+ # creds = flow.run_local_server(port=0)
171
+ # save_user_credentials(email, creds)
172
+ # return creds
173
+
174
+ # def authenticate_gmail(email: str, create_if_missing: bool = False):
175
+ # creds = load_user_credentials(email)
176
+ # if creds:
177
+ # if creds.expired and creds.refresh_token:
178
+ # try:
179
+ # creds.refresh(Request())
180
+ # save_user_credentials(email, creds)
181
+ # except Exception:
182
+ # if create_if_missing:
183
+ # try:
184
+ # creds = create_new_credentials(email)
185
+ # except:
186
+ # return None
187
+ # else:
188
+ # return None
189
+ # elif not creds.valid:
190
+ # creds = None
191
+ # if not creds:
192
+ # if create_if_missing:
193
+ # try:
194
+ # creds = create_new_credentials(email)
195
+ # except:
196
+ # return None
197
+ # else:
198
+ # return None
199
+ # try:
200
+ # service = build("gmail", "v1", credentials=creds)
201
+ # return service
202
+ # except Exception:
203
+ # return None
204
+
205
+ # from email.mime.text import MIMEText
206
+
207
+ # def create_email_message(sender: str, to: str, subject: str, message_text: str, reply_to: Optional[str] = None, is_html: bool = False):
208
+ # if is_html:
209
+ # message = MIMEText(message_text, "html")
210
+ # else:
211
+ # message = MIMEText(message_text, "plain")
212
+ # message["To"] = to
213
+ # message["From"] = sender
214
+ # message["Subject"] = subject
215
+ # if reply_to:
216
+ # message["Reply-To"] = reply_to
217
+ # raw_message = base64.urlsafe_b64encode(message.as_bytes()).decode()
218
+ # return {"raw": raw_message}
219
+
220
+ # def send_gmail_message(service, user_id: str, message: dict):
221
+ # try:
222
+ # result = service.users().messages().send(userId=user_id, body=message).execute()
223
+ # return result is not None
224
+ # except HttpError:
225
+ # return False
226
+
227
+ # # ------------------------------------------
228
+ # # LLM (OpenAI) Message Generation Helpers
229
+ # # ------------------------------------------
230
+ # def refine_message_with_feedback(
231
+ # original_message: str,
232
+ # feedback: str,
233
+ # ) -> Tuple[str, str]:
234
+ # api_key = os.getenv("OPENAI_API_KEY")
235
+ # llm = ChatOpenAI(
236
+ # model="gpt-4o-mini",
237
+ # temperature=0.7,
238
+ # max_tokens=600,
239
+ # openai_api_key=api_key
240
+ # )
241
+ # prompt = f"""
242
+ # Please refine the following recruitment message based on the provided feedback:
243
+
244
+ # ORIGINAL MESSAGE:
245
+ # {original_message}
246
+
247
+ # FEEDBACK:
248
+ # {feedback}
249
+
250
+ # Please provide your response in the following format:
251
+ # SUBJECT: [Your subject line here]
252
+
253
+ # BODY:
254
+ # [Your refined email body content here]
255
+ # Keep the same tone and intent as the original message, but incorporate the feedback to improve it.
256
+ # """
257
+ # try:
258
+ # messages = [
259
+ # SystemMessage(content="You are a professional recruitment message writer. Refine the given message based on feedback while maintaining professionalism and the original intent."),
260
+ # HumanMessage(content=prompt)
261
+ # ]
262
+ # response = llm.invoke(messages)
263
+ # content = response.content.strip()
264
+ # subject_line = ""
265
+ # body_content = ""
266
+ # lines = content.split('\n')
267
+ # body_found = False
268
+ # body_lines = []
269
+ # for line in lines:
270
+ # if line.strip().startswith('SUBJECT:'):
271
+ # subject_line = line.replace('SUBJECT:', '').strip()
272
+ # elif line.strip().startswith('BODY:'):
273
+ # body_found = True
274
+ # elif body_found and line.strip():
275
+ # body_lines.append(line)
276
+ # body_content = '\n'.join(body_lines).strip()
277
+ # if not subject_line:
278
+ # subject_line = "Recruitment Opportunity - Updated"
279
+ # if not body_content:
280
+ # body_content = content
281
+ # return subject_line, body_content
282
+ # except Exception as e:
283
+ # raise HTTPException(status_code=500, detail=f"Error refining message: {str(e)}")
284
+
285
+ # def generate_recruitment_message_with_subject(
286
+ # msg_type: str,
287
+ # company: str,
288
+ # role_title: str,
289
+ # recruiter: str,
290
+ # org: str,
291
+ # candidate: str,
292
+ # current_pos: str,
293
+ # evaluation: Optional[str] = None,
294
+ # feedback: Optional[str] = None,
295
+ # past_conversation: Optional[str] = None
296
+ # ) -> Tuple[str, str]:
297
+ # api_key = os.getenv("OPENAI_API_KEY")
298
+ # llm = ChatOpenAI(
299
+ # model="gpt-4o-mini",
300
+ # temperature=0.7,
301
+ # max_tokens=600,
302
+ # openai_api_key=api_key
303
+ # )
304
+ # # Outreach: Only request consent
305
+ # if msg_type == "outreach":
306
+ # prompt = f"""
307
+ # Generate a professional initial outreach message to a candidate.
308
+ # - Introduce yourself as {recruiter} from {org}
309
+ # - Clearly state you are reaching out about an open role ({role_title}) at {company}
310
+ # - Ask if they are open to learning more or interested in a quick chat.
311
+ # - Do NOT discuss any job evaluation or judgment.
312
+ # - Explicitly request their consent to share more details. E.g., 'Would you be open to hearing more about this opportunity?'
313
+ # - Keep it short and friendly.
314
+ # - No placeholders like [Candidate Name] or [Role Title] in the output.
315
+ # """
316
+ # else:
317
+ # base_prompt = f"""
318
+ # Generate a professional recruitment {msg_type} with the following details:
319
+ # - Company hiring: {company}
320
+ # - Role: {role_title}
321
+ # - Recruiter: {recruiter} from {org}
322
+ # - Candidate: {candidate}
323
+ # - Candidate's current position: {current_pos}
324
+ # """
325
+ # if evaluation:
326
+ # base_prompt += f"- Evaluation: {evaluation}\n"
327
+ # prompt = base_prompt
328
+ # if msg_type == "introductory":
329
+ # prompt += """
330
+ # Create an introductory message that:
331
+ # - Thanks the candidate for their initial response
332
+ # - Provides more details about the role and company
333
+ # - Explains why this opportunity aligns with their background
334
+ # - Suggests next steps (like a call or meeting)
335
+ # - Maintains a warm, professional tone
336
+ # - if in the Evaluation the verdict is rejected, Send a rejection message instead.
337
+ # """
338
+ # else: # followup
339
+ # prompt += """
340
+ # Create a follow-up message that:
341
+ # - References previous communication
342
+ # - Reiterates interest in the candidate
343
+ # - Addresses any potential concerns
344
+ # - Provides additional compelling reasons to consider the role
345
+ # - Includes a clear call to action
346
+ # """
347
+
348
+ # # Use feedback if provided
349
+ # if feedback:
350
+ # prompt += f"\n\nPlease modify the message based on this feedback: {feedback}"
351
+
352
+ # # Use past conversation if provided
353
+ # if past_conversation:
354
+ # prompt += f"""
355
+ # Use the following past conversation as context to inform your reply:
356
+
357
+ # PAST CONVERSATION:
358
+ # {past_conversation}
359
+
360
+ # Write a reply message to the candidate, maintaining professionalism and continuity.
361
+ # """
362
+
363
+ # prompt += """
364
+
365
+ # Please provide your response in the following format:
366
+ # SUBJECT: [Your subject line here]
367
+
368
+ # BODY:
369
+ # [Your email body content here]
370
+ # """
371
+
372
+ # try:
373
+ # messages = [
374
+ # SystemMessage(content="You are a professional recruitment message writer. Generate both an email subject line and body content. Follow the exact format requested."),
375
+ # HumanMessage(content=prompt)
376
+ # ]
377
+ # response = llm.invoke(messages)
378
+ # content = response.content.strip()
379
+ # subject_line = ""
380
+ # body_content = ""
381
+ # lines = content.split('\n')
382
+ # body_found = False
383
+ # body_lines = []
384
+ # for line in lines:
385
+ # if line.strip().startswith('SUBJECT:'):
386
+ # subject_line = line.replace('SUBJECT:', '').strip()
387
+ # elif line.strip().startswith('BODY:'):
388
+ # body_found = True
389
+ # elif body_found and line.strip():
390
+ # body_lines.append(line)
391
+ # body_content = '\n'.join(body_lines).strip()
392
+ # if not subject_line:
393
+ # subject_line = f"Opportunity at {company} - {role_title}"
394
+ # if not body_content:
395
+ # body_content = content
396
+ # return subject_line, body_content
397
+ # except Exception as e:
398
+ # raise HTTPException(status_code=500, detail=f"Error generating message: {str(e)}")
399
+
400
+
401
+ # def format_email_html(body: str, sender_name: Optional[str]=None, sender_org: Optional[str]=None):
402
+ # """Wrap plain text body in an HTML template for better appearance."""
403
+ # # Convert consecutive line breaks into HTML paragraphs
404
+ # import re
405
+ # # Smart paragraphing
406
+ # body = re.sub(r"\n\s*\n", "</p><p>", body.strip()) # Double newlines => new paragraph
407
+ # body = re.sub(r"\n", "<br>", body) # Single newlines => <br>
408
+ # # Optional signature
409
+ # signature = ""
410
+ # if sender_name or sender_org:
411
+ # signature = "<br><br>Best regards,<br>"
412
+ # if sender_name:
413
+ # signature += f"{sender_name}<br>"
414
+ # if sender_org:
415
+ # signature += f"{sender_org}"
416
+ # html = f"""
417
+ # <html>
418
+ # <body style="font-family: Arial, sans-serif; color: #222; line-height: 1.7; max-width: 540px;">
419
+ # <p>{body}</p>
420
+ # {signature}
421
+ # </body>
422
+ # </html>
423
+ # """
424
+ # return html
425
+
426
+
427
+ # # ------------------------------------------
428
+ # # FastAPI Endpoints
429
+ # # ------------------------------------------
430
+ # @app.get("/")
431
+ # async def root():
432
+ # """Root endpoint - public access"""
433
+ # return {
434
+ # "message": "Recruitment Message Generator API",
435
+ # "version": "1.0.0",
436
+ # "authentication": "Required for all endpoints except / and /health",
437
+ # "auth_header": "X-API-Key",
438
+ # "endpoints": [
439
+ # "/generate-message",
440
+ # "/refine-message",
441
+ # "/authenticate",
442
+ # "/send-message",
443
+ # "/docs"
444
+ # ]
445
+ # }
446
+
447
+ # @app.post("/send-message", response_model=MessageResponse, dependencies=[Depends(verify_api_key)])
448
+ # async def send_message(request: SendMessageRequest):
449
+ # try:
450
+ # # Authenticate sender
451
+ # service = authenticate_gmail(request.sender_email)
452
+ # if not service:
453
+ # return MessageResponse(
454
+ # success=False,
455
+ # message="",
456
+ # error="Gmail authentication failed"
457
+ # )
458
+
459
+
460
+ # formatted_html = format_email_html(
461
+ # request.email_body
462
+ # )
463
+ # # Create the email
464
+ # email_message = create_email_message(
465
+ # sender=request.sender_email,
466
+ # to=request.recipient_email,
467
+ # subject=request.subject,
468
+ # message_text=formatted_html,
469
+ # reply_to=request.reply_to_email,
470
+ # is_html=True
471
+ # )
472
+ # # Send the email
473
+ # email_sent = send_gmail_message(service, "me", email_message)
474
+ # if email_sent:
475
+ # return MessageResponse(
476
+ # success=True,
477
+ # message="Email sent successfully.",
478
+ # email_sent=True,
479
+ # email_subject=request.subject
480
+ # )
481
+ # else:
482
+ # return MessageResponse(
483
+ # success=False,
484
+ # message="",
485
+ # email_sent=False,
486
+ # email_subject=request.subject,
487
+ # error="Failed to send via Gmail API"
488
+ # )
489
+ # except Exception as e:
490
+ # return MessageResponse(
491
+ # success=False,
492
+ # message="",
493
+ # error=str(e)
494
+ # )
495
+
496
+ # @app.post("/generate-message", response_model=MessageResponse, dependencies=[Depends(verify_api_key)])
497
+ # async def generate_message(request: GenerateMessageRequest, background_tasks: BackgroundTasks):
498
+ # try:
499
+ # current_position = f"{request.current_role} at {request.current_company}"
500
+ # email_subject, generated_message = generate_recruitment_message_with_subject(
501
+ # msg_type=request.message_type.value.replace('followup', 'follow-up'),
502
+ # company=request.company_name,
503
+ # role_title=request.role,
504
+ # recruiter=request.recruiter_name,
505
+ # org=request.organisation,
506
+ # candidate=request.candidate_name,
507
+ # current_pos=current_position,
508
+ # evaluation=request.job_evaluation,
509
+ # past_conversation=request.past_conversation
510
+ # )
511
+ # email_sent = False
512
+ # if request.send_email:
513
+ # registered_users = []
514
+ # if os.path.exists("registered_users.csv"):
515
+ # df = pd.read_csv("registered_users.csv")
516
+ # registered_users = df['email'].tolist() if 'email' in df.columns else []
517
+ # if request.sender_email.lower() not in [user.lower() for user in registered_users]:
518
+ # return MessageResponse(
519
+ # success=True,
520
+ # message=generated_message,
521
+ # email_sent=False,
522
+ # email_subject=email_subject,
523
+ # error="Email not sent: Sender email is not registered"
524
+ # )
525
+ # service = authenticate_gmail(request.sender_email)
526
+ # if service:
527
+ # formatted_html = format_email_html(
528
+ # generated_message,
529
+ # sender_name=request.recruiter_name,
530
+ # sender_org=request.organisation
531
+ # )
532
+ # email_message = create_email_message(
533
+ # sender=request.sender_email,
534
+ # to=request.recipient_email,
535
+ # subject=email_subject,
536
+ # message_text=formatted_html,
537
+ # reply_to=request.reply_to_email,
538
+ # is_html=True
539
+ # )
540
+ # email_sent = send_gmail_message(service, "me", email_message)
541
+ # if not email_sent:
542
+ # return MessageResponse(
543
+ # success=True,
544
+ # message=generated_message,
545
+ # email_sent=False,
546
+ # email_subject=email_subject,
547
+ # error="Email not sent: Failed to send via Gmail API"
548
+ # )
549
+ # else:
550
+ # return MessageResponse(
551
+ # success=True,
552
+ # message=generated_message,
553
+ # email_sent=False,
554
+ # email_subject=email_subject,
555
+ # error="Email not sent: Gmail authentication failed"
556
+ # )
557
+ # return MessageResponse(
558
+ # success=True,
559
+ # message=generated_message,
560
+ # email_sent=email_sent,
561
+ # email_subject=email_subject
562
+ # )
563
+ # except Exception as e:
564
+ # return MessageResponse(
565
+ # success=False,
566
+ # message="",
567
+ # error=str(e)
568
+ # )
569
+
570
+ # @app.post("/refine-message", response_model=MessageResponse, dependencies=[Depends(verify_api_key)])
571
+ # async def refine_message(request: FeedbackRequest):
572
+ # try:
573
+ # email_subject, refined_message = refine_message_with_feedback(
574
+ # original_message=request.message,
575
+ # feedback=request.feedback
576
+ # )
577
+ # return MessageResponse(
578
+ # success=True,
579
+ # message=refined_message,
580
+ # email_sent=False,
581
+ # email_subject=email_subject
582
+ # )
583
+ # except Exception as e:
584
+ # return MessageResponse(
585
+ # success=False,
586
+ # message="",
587
+ # error=str(e)
588
+ # )
589
+
590
+ # @app.post("/authenticate", response_model=AuthenticateResponse, dependencies=[Depends(verify_api_key)])
591
+ # async def authenticate_user(request: AuthenticateRequest):
592
+ # try:
593
+ # if check_user_token_exists(request.email):
594
+ # service = authenticate_gmail(request.email, create_if_missing=False)
595
+ # if service:
596
+ # return AuthenticateResponse(
597
+ # success=True,
598
+ # message="User already authenticated and token is valid"
599
+ # )
600
+ # else:
601
+ # service = authenticate_gmail(request.email, create_if_missing=True)
602
+ # if service:
603
+ # return AuthenticateResponse(
604
+ # success=True,
605
+ # message="Token refreshed successfully"
606
+ # )
607
+ # else:
608
+ # return AuthenticateResponse(
609
+ # success=False,
610
+ # message="Failed to refresh token",
611
+ # error="Could not refresh existing token. Please check credentials.json"
612
+ # )
613
+ # else:
614
+ # try:
615
+ # creds = create_new_credentials(request.email)
616
+ # if creds:
617
+ # return AuthenticateResponse(
618
+ # success=True,
619
+ # message="Authentication successful. Token created and saved."
620
+ # )
621
+ # else:
622
+ # return AuthenticateResponse(
623
+ # success=False,
624
+ # message="Authentication failed",
625
+ # error="Failed to create credentials"
626
+ # )
627
+ # except Exception as e:
628
+ # return AuthenticateResponse(
629
+ # success=False,
630
+ # message="Authentication failed",
631
+ # error=str(e)
632
+ # )
633
+ # except Exception as e:
634
+ # return AuthenticateResponse(
635
+ # success=False,
636
+ # message="Authentication error",
637
+ # error=str(e)
638
+ # )
639
+
640
+ # @app.get("/health")
641
+ # async def health_check():
642
+ # """Health check endpoint - public access"""
643
+ # return {"status": "healthy", "timestamp": datetime.utcnow().isoformat()}
644
+
645
+ # # Protected documentation endpoint
646
+ # @app.get("/docs", dependencies=[Depends(verify_api_key)])
647
+ # async def get_documentation():
648
+ # """This will be handled by FastAPI's built-in docs"""
649
+ # pass
650
+
651
+ # if __name__ == "__main__":
652
+ # import uvicorn
653
+ # uvicorn.run(app, host="0.0.0.0", port=8000)
654
+
655
+
656
  from fastapi import FastAPI, HTTPException, BackgroundTasks, Depends, Header, status
657
  from fastapi.middleware.cors import CORSMiddleware
658
  from fastapi.security import APIKeyHeader
 
674
  from googleapiclient.errors import HttpError
675
  from datetime import datetime
676
  import json
677
+ from langfuse import Langfuse
678
+ from langfuse.langchain import CallbackHandler
679
+
680
 
 
681
  load_dotenv()
682
+ langfuse = Langfuse(
683
+ secret_key=os.getenv("SECRET_KEY"),
684
+ public_key=os.getenv("PUBLIC_KEY"),
685
+ host=os.getenv("APP_HOST")
686
+ )
687
+ langfuse_handler = CallbackHandler()
688
+
689
 
690
+ # Load environment variables (not needed on Hugging Face, but harmless)
691
  # ------------------------------------------
692
  # Security Configuration
693
  # ------------------------------------------
694
+ API_PASSWORD = os.getenv("API_PASSWORD") # Can be overridden by env var
695
  api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)
696
 
697
  def verify_api_key(api_key: Optional[str] = Depends(api_key_header)) -> str:
 
924
  SystemMessage(content="You are a professional recruitment message writer. Refine the given message based on feedback while maintaining professionalism and the original intent."),
925
  HumanMessage(content=prompt)
926
  ]
927
+ response = llm.invoke(messages, config={"callbacks": [langfuse_handler]})
928
  content = response.content.strip()
929
  subject_line = ""
930
  body_content = ""
 
1039
  SystemMessage(content="You are a professional recruitment message writer. Generate both an email subject line and body content. Follow the exact format requested."),
1040
  HumanMessage(content=prompt)
1041
  ]
1042
+ response = llm.invoke(messages, config={"callbacks": [langfuse_handler]})
1043
  content = response.content.strip()
1044
  subject_line = ""
1045
  body_content = ""