AIEmailAutomationUtility 0.0.18__py3-none-any.whl → 0.0.20__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,36 +7,35 @@ import loggerutility as logger
7
7
  import ast, re
8
8
 
9
9
  class EmailReplyAssistant:
10
+ class EventHandler(AssistantEventHandler):
11
+ def __init__(self):
12
+ super().__init__()
13
+ self.delta_values = []
14
+
15
+ def on_text_created(self, text):
16
+ if isinstance(text, str):
17
+ logger.log(f"\nAssistant: {text}")
18
+
19
+ def on_text_delta(self, delta, snapshot):
20
+ self.delta_values.append(delta.value)
21
+
22
+ def on_tool_call_created(self, tool_call):
23
+ logger.log(f"\nAssistant: {tool_call.type}\n")
24
+
25
+ def on_tool_call_delta(self, delta, snapshot):
26
+ if delta.type == 'code_interpreter':
27
+ if delta.code_interpreter.input:
28
+ logger.log(delta.code_interpreter.input)
29
+ if delta.code_interpreter.outputs:
30
+ logger.log(f"\n\nOutput >", flush=True)
31
+ for output in delta.code_interpreter.outputs:
32
+ if output.type == "logs":
33
+ logger.log(output.logs, flush=True)
10
34
  def __init__(self):
11
35
  pass
12
36
 
13
37
  def Reply_Email_Ai_Assistant(self, openAI_key, assistant_ID, email_content, subject):
14
38
  try:
15
- class EventHandler(AssistantEventHandler):
16
- def __init__(self):
17
- super().__init__()
18
- self.delta_values = []
19
-
20
- def on_text_created(self, text):
21
- if isinstance(text, str):
22
- logger.log(f"\nAssistant: {text}")
23
-
24
- def on_text_delta(self, delta, snapshot):
25
- self.delta_values.append(delta.value)
26
-
27
- def on_tool_call_created(self, tool_call):
28
- logger.log(f"\nAssistant: {tool_call.type}\n")
29
-
30
- def on_tool_call_delta(self, delta, snapshot):
31
- if delta.type == 'code_interpreter':
32
- if delta.code_interpreter.input:
33
- logger.log(delta.code_interpreter.input)
34
- if delta.code_interpreter.outputs:
35
- logger.log(f"\n\nOutput >", flush=True)
36
- for output in delta.code_interpreter.outputs:
37
- if output.type == "logs":
38
- logger.log(output.logs, flush=True)
39
-
40
39
  openAI_response = ""
41
40
  client = OpenAI(api_key=openAI_key)
42
41
  thread = client.beta.threads.create()
@@ -47,7 +46,7 @@ class EmailReplyAssistant:
47
46
  content=f"subject:{subject}\nemail body:{email_content}",
48
47
  )
49
48
 
50
- event_handler = EventHandler()
49
+ event_handler = EmailReplyAssistant().EventHandler()
51
50
 
52
51
  with client.beta.threads.runs.stream(
53
52
  thread_id=thread.id,
@@ -92,4 +91,66 @@ class EmailReplyAssistant:
92
91
  return email_response
93
92
 
94
93
  except Exception as e:
95
- logger.log(f"Error in Read_Email: {str(e)}")
94
+ logger.log(f"Error in Read_Email: {str(e)}")
95
+
96
+ def identify_customer_product_reply_assitant(self, openAI_key, assistant_ID, email_content, subject, prompt):
97
+ try:
98
+ openAI_response = ""
99
+ client = OpenAI(api_key=openAI_key)
100
+ thread = client.beta.threads.create()
101
+
102
+ client.beta.threads.messages.create(
103
+ thread_id=thread.id,
104
+ role="user",
105
+ content=f"subject:{subject}\nemail body:{email_content}",
106
+ )
107
+
108
+ event_handler = EmailReplyAssistant().EventHandler()
109
+
110
+ with client.beta.threads.runs.stream(
111
+ thread_id=thread.id,
112
+ assistant_id=assistant_ID,
113
+ instructions=prompt,
114
+ event_handler=event_handler,
115
+ ) as stream:
116
+ stream.until_done()
117
+
118
+ delta_values = event_handler.delta_values
119
+ openAI_response = ''.join(delta_values)
120
+ logger.log(f"openAI_response ::: {openAI_response}")
121
+ formated_openAI_response = openAI_response.replace("\n```", "").replace("```", "").replace("json","").replace("JSON","").replace("csv","").replace("CSV","").replace("html","")
122
+ return {"status": "Success", "message": formated_openAI_response}
123
+
124
+ except Exception as error:
125
+ return {"status": "Failed", "message": error}
126
+
127
+ def create_quotation_draft(self, openAI_key, assistant_ID, email_content, subject, prompt):
128
+ try:
129
+ openAI_response = ""
130
+ client = OpenAI(api_key=openAI_key)
131
+ thread = client.beta.threads.create()
132
+
133
+ client.beta.threads.messages.create(
134
+ thread_id=thread.id,
135
+ role="user",
136
+ content=f"subject:{subject}\nemail body:{email_content}",
137
+ )
138
+
139
+ event_handler = EmailReplyAssistant().EventHandler()
140
+
141
+ with client.beta.threads.runs.stream(
142
+ thread_id=thread.id,
143
+ assistant_id=assistant_ID,
144
+ instructions=prompt,
145
+ event_handler=event_handler,
146
+ ) as stream:
147
+ stream.until_done()
148
+
149
+ delta_values = event_handler.delta_values
150
+ openAI_response = ''.join(delta_values)
151
+ logger.log(f"openAI_response ::: {openAI_response}")
152
+ formated_openAI_response = openAI_response.replace("\n```", "").replace("```", "").replace("json","").replace("JSON","").replace("csv","").replace("CSV","").replace("html","")
153
+ return formated_openAI_response
154
+
155
+ except Exception as error:
156
+ return None
@@ -59,11 +59,12 @@ class Email_Classification:
59
59
  }
60
60
 
61
61
  genai.configure(api_key=gemini_api_key)
62
- model = genai.GenerativeModel('gemini-1.0-pro')
62
+ # model = genai.GenerativeModel('gemini-1.0-pro')
63
+ model = genai.GenerativeModel('gemini-1.5-pro-latest')
63
64
  response = model.generate_content(message_list)
64
65
 
65
- logger.log(f"Input Question ::: {email_body}\ngemini-1.0-pro Response::: {response} {type(response)}")
66
- logger.log(f"\n\nResponse GeminiAI endpoint::::: {response} \n{type(response)}", "0")
66
+ # logger.log(f"Input Question ::: {email_body}\ngemini-1.0-pro Response::: {response} {type(response)}")
67
+ # logger.log(f"\n\nResponse GeminiAI endpoint::::: {response} \n{type(response)}", "0")
67
68
 
68
69
  final_result = ""
69
70
  for part in response:
@@ -4,15 +4,16 @@ import shutil
4
4
  import requests
5
5
  import loggerutility as logger
6
6
  from flask import Flask,request
7
+ from datetime import datetime
8
+
7
9
 
8
10
  class Email_DocumentUploader:
9
11
  def upload_document(self, upload_config, file_data):
10
12
  # try:
11
13
  logger.log("inside function" )
12
14
  # Create temp directory if needed
13
- temp_dir = "temp"
14
- if not os.path.exists(temp_dir):
15
- os.makedirs(temp_dir)
15
+ today_date = datetime.today().strftime('%Y-%m-%d')
16
+ temp_dir = os.path.join("ORDERS", today_date)
16
17
 
17
18
  # Save file temporarily
18
19
  file_path = os.path.join(temp_dir, file_data['filename'])
@@ -50,6 +51,7 @@ class Email_DocumentUploader:
50
51
 
51
52
  if response.status_code == 200:
52
53
  result = json.loads(response.text)
54
+ logger.log(f"file Upload Response ::: {result}")
53
55
  document_id = result["ID"]["Document_Id"]
54
56
  return str(response.status_code), document_id
55
57
  else:
@@ -6,6 +6,8 @@ import time
6
6
  import loggerutility as logger
7
7
  from flask import Flask,request
8
8
  import json
9
+ from email.mime.multipart import MIMEMultipart
10
+ from email.mime.text import MIMEText
9
11
 
10
12
  class Email_Draft:
11
13
  def draft_email(self, email_config, email_details, response_content):
@@ -33,7 +35,63 @@ class Email_Draft:
33
35
  return True, utf8_message.decode("utf-8")
34
36
 
35
37
  except Exception as e:
36
- logger.log(f"Error creating draft: {str(e)}")
38
+ return False, str(e)
39
+
40
+ def quotation_draft_email(self, email_config, email_details, response_content):
41
+ try:
42
+ with imaplib.IMAP4_SSL(host=email_config['host'], port=imaplib.IMAP4_SSL_PORT) as imap_ssl:
43
+ imap_ssl.login(email_config['email'], email_config['password'])
44
+
45
+ # Create the email object
46
+ message = MIMEMultipart("alternative")
47
+ message["From"] = email_config['email']
48
+ message["To"] = email_details['sender']
49
+ message["CC"] = email_details['cc']
50
+
51
+ # Set the subject with "Re:" prefix
52
+ subject = email_details['subject']
53
+ if not subject.startswith("Re:"):
54
+ subject = f"Re: {subject}"
55
+ message["Subject"] = subject
56
+
57
+ # Email details for original message
58
+ mail_details = f'{datetime.datetime.now().strftime("On %a, %b %d, %Y at %I:%M %p")} {email_details["sender"]} wrote:'
59
+
60
+ # Extract response content
61
+ body = response_content.get('body', '')
62
+ table_html = response_content.get('table_', '')
63
+ signature = response_content.get('signature', '')
64
+ email_body = email_details['body']
65
+
66
+ # Replace new lines with <br> for proper HTML formatting
67
+ body_html = body.replace("\n", "<br>").replace("\n\n", "<br>")
68
+ signature_html = signature.replace("\n", "<br>").replace("\n\n", "<br>")
69
+ email_body_html = email_body.replace("\n", "<br>").replace("\n\n", "<br>")
70
+
71
+ # Create HTML content
72
+ html_content = (
73
+ "<html><body>"
74
+ f"<p>{body_html}</p>"
75
+ f"{table_html}"
76
+ f"<p>{signature_html}</p>"
77
+ "<hr>"
78
+ f"<p>{mail_details}</p>"
79
+ f"<p>{email_body_html}</p>"
80
+ "</body></html>"
81
+ )
82
+
83
+ # Attach HTML content
84
+ message.attach(MIMEText(html_content, "html"))
85
+
86
+ utf8_message = message.as_string().encode("utf-8")
87
+
88
+ # Append the draft to Gmail
89
+ imap_ssl.append("[Gmail]/Drafts", '', imaplib.Time2Internaldate(time.time()), utf8_message)
90
+
91
+ return True, utf8_message.decode("utf-8")
92
+
93
+ except Exception as e:
94
+ return False, str(e)
37
95
 
38
96
  def draft_email_response(self, email_details):
39
97
  try:
@@ -7,6 +7,11 @@ import loggerutility as logger
7
7
  from flask import request
8
8
  import traceback
9
9
  from fpdf import FPDF
10
+ from openai import OpenAI
11
+ import requests
12
+ import openai
13
+ import google.generativeai as genai
14
+ from datetime import datetime
10
15
 
11
16
  from .Save_Transaction import Save_Transaction
12
17
  from .Email_Upload_Document import Email_Upload_Document
@@ -14,6 +19,7 @@ from .Email_Classification import Email_Classification
14
19
  from .EmailReplyAssistant import EmailReplyAssistant
15
20
  from .Email_Draft import Email_Draft
16
21
  from .Email_DocumentUploader import Email_DocumentUploader
22
+ import sqlite3
17
23
 
18
24
  class Email_Read:
19
25
  def read_email(self, email_config):
@@ -135,7 +141,6 @@ class Email_Read:
135
141
  content_type = part.get_content_type()
136
142
  if content_type == "text/plain":
137
143
  email_body += part.get_payload(decode=True).decode('utf-8', errors='replace')
138
- fileName = self.download_attachment(msg)
139
144
  else:
140
145
  email_body = msg.get_payload(decode=True).decode('utf-8', errors='replace')
141
146
 
@@ -219,23 +224,32 @@ class Email_Read:
219
224
  responseMethod, parameters = self.get_JsonArray_values(emailCategory, file_JsonArray)
220
225
  logger.log(f"responseMethod ::: {responseMethod}")
221
226
  logger.log(f"parameters ::: {parameters}")
222
- if responseMethod == "Upload_Document" :
223
-
224
- if len(fileName) != 0 :
225
227
 
226
- email_upload_document = Email_DocumentUploader()
227
- with open(f"temp/{fileName}", "rb") as file:
228
+ # Download the attachment
229
+ fileName = self.download_attachment(msg)
230
+
231
+ # Get today's date folder path
232
+ today_date = datetime.today().strftime('%Y-%m-%d')
233
+ order_folder = os.path.join("ORDERS", today_date)
234
+
235
+ if responseMethod == "Upload_Document":
236
+ if len(fileName) != 0:
237
+ email_upload_document = Email_DocumentUploader()
238
+ file_path = os.path.join(order_folder, fileName) # Correct file path
239
+
240
+ with open(file_path, "rb") as file:
228
241
  response_status, restAPI_Result = email_upload_document.email_document_upload(file, parameters)
229
242
  logger.log(f"email_upload_document_response ::: {restAPI_Result}")
230
243
  else:
231
-
232
244
  new_fileName = self.create_file_from_emailBody(email_body, sender_email_addr, parameters)
233
- with open(f"temp/{new_fileName}", "rb") as file:
245
+ new_file_path = os.path.join(order_folder, new_fileName)
246
+
247
+ with open(new_file_path, "rb") as file:
234
248
  response_status, restAPI_Result = email_upload_document.email_document_upload(file, parameters)
235
249
  logger.log(f"email_upload_document_response ::: {restAPI_Result}")
236
250
 
237
- if response_status == "200" :
238
- logger.log(f"Attachment uploaded sucessfully against Document id: '{restAPI_Result}'.")
251
+ if response_status == "200":
252
+ logger.log(f"Attachment uploaded successfully against Document ID: '{restAPI_Result}'.")
239
253
  else:
240
254
  logger.log(restAPI_Result)
241
255
 
@@ -257,6 +271,445 @@ class Email_Read:
257
271
  # logger.log(f"Error during mail close/logout: {str(close_error)}")
258
272
  # return {"status": "Failed", "message": f"Error reading emails: {str(close_error)}"}
259
273
 
274
+ def read_email_quotation(self, email_config):
275
+ # try:
276
+ logger.log(f"inside read_email_automation")
277
+ LABEL = "Unprocessed_Email"
278
+ file_JsonArray = []
279
+ templateName = "ai_email_automation.json"
280
+ fileName = ""
281
+
282
+ Model_Name = email_config.get('model_type', 'OpenAI')
283
+ reciever_email_addr = email_config.get('email', '').replace("\xa0", "").strip()
284
+ receiver_email_pwd = email_config.get('password', '').replace("\xa0", "").strip()
285
+ host = email_config.get('host', '')
286
+ port = email_config.get('port', '')
287
+
288
+ mail = imaplib.IMAP4_SSL(host, port)
289
+ mail.login(reciever_email_addr, receiver_email_pwd)
290
+ logger.log("login successfully")
291
+ mail.select('inbox')
292
+
293
+ file_JsonArray, categories = self.read_JSON_File(templateName)
294
+
295
+ while True:
296
+ status, email_ids = mail.search(None, 'UNSEEN')
297
+ emails = []
298
+
299
+ if status == 'OK':
300
+ email_ids = email_ids[0].split()
301
+
302
+ if not email_ids:
303
+ logger.log("Email not found, going to check new mail")
304
+ logger.log("Email not found,\ngoing to check new mail \n")
305
+ else:
306
+
307
+ for email_id in email_ids:
308
+ email_body = ""
309
+ attachments = []
310
+ status, data = mail.fetch(email_id, '(RFC822)')
311
+
312
+ if status == 'OK':
313
+ raw_email = data[0][1]
314
+ msg = email.message_from_bytes(raw_email)
315
+
316
+ subject = msg['Subject']
317
+ sender_email_addr = msg['From']
318
+ cc_email_addr = msg['CC']
319
+ subject = msg['Subject']
320
+
321
+ if msg.is_multipart():
322
+ for part in msg.walk():
323
+ content_type = part.get_content_type()
324
+ if content_type == "text/plain":
325
+ email_body += part.get_payload(decode=True).decode('utf-8', errors='replace')
326
+ else:
327
+ email_body = msg.get_payload(decode=True).decode('utf-8', errors='replace')
328
+
329
+ openai_Process_Input = email_body
330
+ logger.log(f"\nEmail Subject::: {subject}")
331
+ logger.log(f"\nEmail body::: {openai_Process_Input}")
332
+
333
+ openai_api_key = email_config.get('openai_api_key', '')
334
+ geminiAI_APIKey = email_config.get('gemini_api_key', '')
335
+ signature = email_config.get('signature', '')
336
+ localAIURL = email_config.get('local_ai_url', '')
337
+ logger.log(f"\ngeminiAI_APIKey::: {geminiAI_APIKey}")
338
+ logger.log(f"\nlocalAIURL::: {localAIURL}")
339
+ logger.log(f"\nsignature::: {signature}")
340
+
341
+ if len(str(openai_Process_Input)) > 0 :
342
+ email_cat_data = {
343
+ "model_type" : Model_Name,
344
+ "openai_api_key" : openai_api_key,
345
+ "categories" : categories,
346
+ "email_body" : email_body,
347
+ "gemini_api_key" : geminiAI_APIKey,
348
+ "signature" : signature,
349
+ "local_ai_url" : localAIURL,
350
+ }
351
+ # logger.log(f"\nemail_cat_data ::: {email_cat_data}")
352
+ email_classification = Email_Classification()
353
+ emailCategory = email_classification.detect_category(email_cat_data)
354
+ emailCategory = emailCategory['message']
355
+ logger.log(f"\nDetected Email category ::: {emailCategory}")
356
+
357
+ if emailCategory == "Quotation":
358
+ responseMethod, parameters = self.get_JsonArray_values(emailCategory, file_JsonArray)
359
+
360
+ logger.log(f"Inside Quotation")
361
+ # Step 4: Identify customer from email using AI
362
+ customer_data = self.identify_customer(email_body, subject, Model_Name, openai_api_key, geminiAI_APIKey, localAIURL, parameters["Customer_Assistant_Id"])
363
+ logger.log(f"Identified customer: {customer_data}")
364
+
365
+ # Step 5: Identify product from email using AI
366
+ products = self.identify_products(email_body, subject, Model_Name, openai_api_key, geminiAI_APIKey, localAIURL, parameters["Product_Assistant_Id"])
367
+
368
+ for product in products:
369
+ db_connection = sqlite3.connect('price_list.db')
370
+ cursor = db_connection.cursor()
371
+
372
+ # Get rate from SQLite database
373
+ query = f'SELECT Price FROM price_list_table WHERE "Item No." = "{product.get("item_no", "")}";'
374
+ cursor.execute(query)
375
+ result = cursor.fetchone()
376
+
377
+ if result:
378
+ rate = result[0]
379
+ product["rate"] = rate
380
+ else:
381
+ product["rate"] = None
382
+
383
+ logger.log(f"Identified products: {products}")
384
+ logger.log(f"Identified products length: {len(products)}")
385
+ quotation_draft = self.generate_quotation_draft(
386
+ customer_data,
387
+ products,
388
+ Model_Name,
389
+ openai_api_key,
390
+ geminiAI_APIKey,
391
+ localAIURL,
392
+ parameters["Customer_Assistant_Id"],
393
+ email_body,
394
+ subject,
395
+ signature
396
+ )
397
+ logger.log(f"quotation_draft ::: {quotation_draft}")
398
+
399
+ # Step 8: Send draft quotation email
400
+ email_details = {"sender":sender_email_addr, "cc":cc_email_addr, "subject":subject, "body": email_body}
401
+ email_draft = Email_Draft()
402
+ status, response = email_draft.quotation_draft_email(email_config, email_details, quotation_draft)
403
+ logger.log(f"status ::: {status}")
404
+
405
+ logger.log(f"Quotation email sent to {sender_email_addr}")
406
+
407
+ else:
408
+ logger.log(f"Marking email as UNREAD. ")
409
+ mail.store(email_id, '-FLAGS', '\\Seen')
410
+
411
+ mail.create(LABEL)
412
+ mail.copy(email_id, LABEL)
413
+ mail.store(email_id, '+FLAGS', '\\Deleted') # Mark for deletion
414
+ mail.expunge()
415
+ logger.log(f"Mail removed from inbox and added to '{LABEL}' label.")
416
+
417
+ time.sleep(10)
418
+
419
+ def identify_customer(self, email_body, subject, model_type, openai_api_key, gemini_api_key, local_ai_url, assistant_id):
420
+ logger.log("Inside identify_customer")
421
+
422
+ if model_type == "OpenAI":
423
+ prompt = f"""Identify the customer code, customer name in json format from the following email {email_body} and received from {subject}. Do not include any instruction as the output will be directly in a program."""
424
+ emailreplyassistant = EmailReplyAssistant()
425
+ ai_result = emailreplyassistant.identify_customer_product_reply_assitant(openai_api_key, assistant_id, email_body, subject, prompt)
426
+
427
+ elif model_type == "GeminiAI":
428
+ prompt = f"""Identify the customer code, customer name in json format from the following email {email_body} and received from {subject}. Do not include any instruction as the output will be directly in a program."""
429
+ ai_result = self.identify_customer_product_GeminiAI(gemini_api_key, email_body, prompt)
430
+
431
+ elif model_type == "LocalAI":
432
+ prompt = f"""Identify the customer code and customer name in JSON format from the following email: {email_body}, received from {subject}.
433
+ If no customer details are found, return:{{"customer_name": "","customer_code": ""}}Only return the JSON object. No explanations, no additional text."""
434
+ ai_result = self.identify_customer_product_LocalAI(openai_api_key, email_body, local_ai_url, prompt)
435
+
436
+ else:
437
+ ai_result = "{}"
438
+
439
+ customer_data = {}
440
+ if ai_result["status"] == "Success":
441
+ customer_data = json.loads(ai_result["message"])
442
+ else:
443
+ customer_data = {
444
+ "customer_name": "",
445
+ "customer_code": ""
446
+ }
447
+ return customer_data
448
+
449
+ def identify_products(self, email_body, subject, model_type, openai_api_key, gemini_api_key, local_ai_url, assistant_id):
450
+ logger.log("Inside identify_products")
451
+
452
+ if model_type == "OpenAI":
453
+ prompt = f"""
454
+ Can you give me price information of all products in following format requested_description, item_no, make, description, price, price unit, inventory unit for following items in strictly in JSON String format {email_body}.
455
+ If there is one product or multiple should return in list.
456
+ Do not include any instruction as the output will be directly in a program.
457
+ """
458
+ emailreplyassistant = EmailReplyAssistant()
459
+ ai_result = emailreplyassistant.identify_customer_product_reply_assitant(openai_api_key, assistant_id, email_body, subject, prompt)
460
+
461
+ elif model_type == "GeminiAI":
462
+ prompt = f"""
463
+ Can you give me price information of all products in following format requested_description, item_no, make, description, price, price unit, inventory unit for following items in strictly in JSON String format {email_body}.
464
+ If there is one product or multiple should return in list.
465
+ Do not include any instruction as the output will be directly in a program.
466
+ """
467
+ ai_result = self.identify_customer_product_GeminiAI(gemini_api_key, email_body, prompt)
468
+
469
+ elif model_type == "LocalAI":
470
+ prompt = f"""Can you give me price information in following format requested_description, item_no, make, description, price, price unit, inventory unit for following items it strictly in json format which loads directly in json {email_body}. If there is one product or multiple should return in list.
471
+ If no product details are found, return:[] Only return the JSON object. No explanations, no additional text."""
472
+ ai_result = self.identify_customer_product_LocalAI(openai_api_key, email_body, local_ai_url, prompt)
473
+
474
+ else:
475
+ ai_result = "{}"
476
+
477
+ product_data = {}
478
+ if ai_result["status"] == "Success":
479
+ logger.log(f"ai_result ::: {ai_result}")
480
+ product_data = json.loads(ai_result["message"])
481
+ else:
482
+ product_data = []
483
+ return product_data
484
+
485
+ def generate_quotation_draft(self, customer_data, products, model_type, openai_api_key, gemini_api_key, local_ai_url, assistant_id, email_body, subject, signature):
486
+ logger.log("Inside generate_quotation_draft")
487
+
488
+ customer = customer_data
489
+
490
+ product_table = "Products:\n"
491
+ for product in products:
492
+ product_table += f'- {product.get("requested_description")} (Code: {product.get("item_no")}) = ${product.get("rate")}\n'
493
+
494
+ if model_type == "OpenAI":
495
+ prompt = f"""
496
+ Generate product information in HTML tabular format with line separators for rows and columns in a draft reply based on the following information:
497
+
498
+ Customer: {customer.get('customer_name', '')}
499
+ Customer Code: {customer.get('customer_code', '')}
500
+
501
+ {product_table}
502
+ product_table must contain only price column even if it is none(set it as -).
503
+ Original Email Subject: {subject}
504
+
505
+ Return only the following JSON String format:
506
+ {{
507
+ "email_body": {{
508
+ "body": "Draft email body proper response, It should not be same like mail content and does not having any signature part like Best regards.",
509
+ "table_html": "Table Details with Sr. No. in HTML",
510
+ "signature": "{signature}"
511
+ }}
512
+ }}
513
+
514
+ Do not include signature in body and any instructions, explanations, or additional text—only the JSON object.
515
+ """
516
+ logger.log(f"Quotation draft ::: {prompt}")
517
+ emailreplyassistant = EmailReplyAssistant()
518
+ ai_result = emailreplyassistant.create_quotation_draft(openai_api_key, assistant_id, email_body, subject, prompt)
519
+
520
+ elif model_type == "GeminiAI":
521
+ prompt = f"""
522
+ Create an HTML product information email draft with the following details:
523
+
524
+ Customer Name: {customer.get('customer_name', '')}
525
+ Customer Code: {customer.get('customer_code', '')}
526
+
527
+ Product Information:
528
+ {product_table}
529
+ Note: Include price column with a value of "-" if price is not available.
530
+
531
+ Email Subject Reference: {subject}
532
+
533
+ Please format the response as a valid JSON string with these fields:
534
+ {{
535
+ "email_body": {{
536
+ "body": "Professional email content that summarizes the product information without being identical to the input data. Do not include signature here.",
537
+ "table_": "HTML table with SR. No. column and product details",
538
+ "signature": "{signature}"
539
+ }}
540
+ }}
541
+
542
+ Ensure the JSON is properly formatted with escaped newlines (\\n) and no trailing commas. Return only the valid JSON string without additional explanations or instructions.
543
+ """
544
+ logger.log(f"Quotation draft ::: {prompt}")
545
+ ai_result = self.create_quotation_draft_GeminiAI(gemini_api_key, email_body, prompt)
546
+
547
+ elif model_type == "LocalAI":
548
+ prompt = f"""
549
+ Generate product information in HTML tabular format with line separators for rows and columns in a draft reply based on the following information:
550
+
551
+ Customer: {customer.get('customer_name', '')}
552
+ Customer Code: {customer.get('customer_code', '')}
553
+
554
+ {product_table}
555
+ - The table must contain the **Price** column, even if it is empty (set it as `-` if None).
556
+ - The table should include **Sr. No.** as the first column.
557
+ - Format the table with `<table>`, `<tr>`, `<th>`, and `<td>` tags with some border to table.
558
+
559
+ Original Email Subject: {subject}
560
+
561
+ Return **strictly** in the following JSON String format:
562
+ - All keys must be: `body`, `table_`, and `signature` inside the `email_body` JSON.
563
+ - **Do not include** `\n`, `\`, `\\`, or any unnecessary escape characters.
564
+ - Do not include instructions, explanations, or additional text—only the JSON object.
565
+
566
+ Format:
567
+ {{
568
+ "email_body": {{
569
+ "body": "Draft email body proper response, It should not contain the table or signature.",
570
+ "table_": "Table Details with Sr. No. in HTML only",
571
+ "signature": "{signature}"
572
+ }}
573
+ }}
574
+ """
575
+ logger.log(f"Quotation draft ::: {prompt}")
576
+ ai_result = self.create_quotation_draft_LocalAI(openai_api_key, email_body, local_ai_url, prompt)
577
+
578
+ else:
579
+ ai_result = "Error: Unable to generate quotation draft. Please check the configuration."
580
+
581
+ logger.log(f"Quotation draft ai_result::: {ai_result}")
582
+ quotation_draft_data = None
583
+ if ai_result != None:
584
+ quotation_draft_data = json.loads(ai_result)["email_body"]
585
+ return quotation_draft_data
586
+
587
+ def identify_customer_product_LocalAI(self, openai_api_key, email_body, local_ai_url, prompt):
588
+ logger.log("Inside identify_customer_product_LocalAI")
589
+ try:
590
+ message = [{
591
+ "role": "user",
592
+ "content": f"{prompt}"
593
+ }]
594
+
595
+ logger.log(f"Final Local AI message for detecting category::: {message}")
596
+ openai.api_key = openai_api_key
597
+ client = OpenAI(base_url=local_ai_url, api_key="lm-studio")
598
+ completion = client.chat.completions.create(
599
+ model="mistral",
600
+ messages=message,
601
+ temperature=0,
602
+ stream=False,
603
+ max_tokens=4096
604
+ )
605
+
606
+ final_result = str(completion.choices[0].message.content)
607
+ final_result = final_result.replace("\n```", "").replace("```", "").replace("json","").replace("JSON","").replace("csv","").replace("CSV","").replace("html","")
608
+ logger.log(f"finalResult:520 {final_result}")
609
+ return {"status": "Success", "message": final_result}
610
+
611
+ except Exception as e:
612
+ logger.log(f"Error with LocalAI detection/generation: {str(e)}")
613
+ return {"success": "Failed", "message": f"Error with LocalAI detection/generation: {str(e)}"}
614
+
615
+ def create_quotation_draft_LocalAI(self, openai_api_key, email_body, local_ai_url, prompt):
616
+ logger.log("Inside create_quotation_draft_LocalAI")
617
+ try:
618
+ message = [{
619
+ "role": "user",
620
+ "content": f"{prompt}"
621
+ }]
622
+
623
+ logger.log(f"Final Local AI message for detecting category::: {message}")
624
+ openai.api_key = openai_api_key
625
+ client = OpenAI(base_url=local_ai_url, api_key="lm-studio")
626
+ completion = client.chat.completions.create(
627
+ model="mistral",
628
+ messages=message,
629
+ temperature=0,
630
+ stream=False,
631
+ max_tokens=4096
632
+ )
633
+
634
+ final_result = str(completion.choices[0].message.content)
635
+ final_result = final_result.replace("\n```", "").replace("```", "").replace("json","").replace("JSON","").replace("csv","").replace("CSV","").replace("html","")
636
+ logger.log(f"finalResult:520 {final_result}")
637
+ return final_result
638
+
639
+ except Exception as e:
640
+ logger.log(f"Error with LocalAI detection/generation: {str(e)}")
641
+ return str(e)
642
+
643
+ def identify_customer_product_GeminiAI(self, gemini_api_key, email_body, prompt):
644
+ logger.log("Inside identify_customer_product_GeminiAI")
645
+ try:
646
+ message = [{
647
+ "role": "user",
648
+ "content": f"{prompt}"
649
+ }]
650
+
651
+ logger.log(f"Final Gemini AI message for detecting category::: {message}")
652
+ message_list = str(message)
653
+
654
+ genai.configure(api_key=gemini_api_key)
655
+ # model = genai.GenerativeModel('gemini-1.0-pro')
656
+ model = genai.GenerativeModel('gemini-1.5-pro-latest')
657
+ response = model.generate_content(message_list)
658
+
659
+ final_result = ""
660
+ for part in response:
661
+ final_result = part.text
662
+ logger.log(f"response::: {final_result}")
663
+ if final_result:
664
+ try:
665
+ final_result = final_result.replace("\\", "").replace('```', '').replace('json', '')
666
+ if final_result.startswith("{{") and final_result.endswith("}}"):
667
+ final_result = final_result[1:-1]
668
+ except json.JSONDecodeError:
669
+ logger.log(f"Exception : Invalid JSON Response GEMINI 1.5: {final_result} {type(final_result)}")
670
+
671
+ logger.log(f"finalResult::: {final_result}")
672
+ return {"status": "Success", "message": final_result}
673
+
674
+ except Exception as e:
675
+ logger.log(f"Error with Gemini AI detection/generation: {str(e)}")
676
+ return {"success": "Failed", "message": f"Error with Gemini AI detection/generation: {str(e)}"}
677
+
678
+ def create_quotation_draft_GeminiAI(self, gemini_api_key, email_body, prompt):
679
+ logger.log("Inside identify_customer_product_GeminiAI")
680
+ try:
681
+ message = [{
682
+ "role": "user",
683
+ "content": f"{prompt}"
684
+ }]
685
+
686
+ logger.log(f"Final Gemini AI message for detecting category::: {message}")
687
+ message_list = str(message)
688
+
689
+ genai.configure(api_key=gemini_api_key)
690
+ # model = genai.GenerativeModel('gemini-1.0-pro')
691
+ model = genai.GenerativeModel('gemini-1.5-pro-latest')
692
+ response = model.generate_content(message_list)
693
+
694
+ final_result = ""
695
+ for part in response:
696
+ final_result = part.text
697
+ logger.log(f"response::: {final_result}")
698
+ if final_result:
699
+ try:
700
+ final_result = final_result.replace('```', '').replace('json', '')
701
+ if final_result.startswith("{{") and final_result.endswith("}}"):
702
+ final_result = final_result[1:-1]
703
+ except json.JSONDecodeError:
704
+ logger.log(f"Exception : Invalid JSON Response GEMINI 1.5: {final_result} {type(final_result)}")
705
+
706
+ logger.log(f"finalResult::: {final_result}")
707
+ return final_result
708
+
709
+ except Exception as e:
710
+ logger.log(f"Error with Gemini AI detection/generation: {str(e)}")
711
+ return {"success": "Failed", "message": f"Error with Gemini AI detection/generation: {str(e)}"}
712
+
260
713
  def save_attachment(self, part, download_dir):
261
714
  try:
262
715
  filename = part.get_filename()
@@ -310,9 +763,14 @@ class Email_Read:
310
763
  logger.log(f"Error in Read_Email: {str(e)}")
311
764
 
312
765
  def download_attachment(self, msg):
313
- filepath = ""
314
- filename = ""
315
- ATTACHMENT_SAVE_PATH = "temp"
766
+ base_folder = "ORDERS" # Main folder for storing orders
767
+ today_date = datetime.today().strftime('%Y-%m-%d') # Format: YYYY-MM-DD
768
+ date_folder = os.path.join(base_folder, today_date) # Path: ORDERS/YYYY-MM-DD
769
+
770
+ # Ensure folders exist
771
+ os.makedirs(date_folder, exist_ok=True)
772
+
773
+ filename = ""
316
774
 
317
775
  for part in msg.walk():
318
776
  if part.get_content_maintype() == 'multipart':
@@ -321,11 +779,8 @@ class Email_Read:
321
779
  continue
322
780
  filename = part.get_filename()
323
781
  if filename:
324
- if not os.path.exists(ATTACHMENT_SAVE_PATH):
325
- os.mkdir(ATTACHMENT_SAVE_PATH)
326
- filepath = os.path.join(ATTACHMENT_SAVE_PATH, filename)
327
- else:
328
- filepath = os.path.join(ATTACHMENT_SAVE_PATH, filename)
782
+ filepath = os.path.join(date_folder, filename) # Save inside date-wise folder
783
+
329
784
  with open(filepath, 'wb') as f:
330
785
  f.write(part.get_payload(decode=True))
331
786
  logger.log(f"\nAttachment saved: '{filepath}'")
@@ -371,15 +826,19 @@ class Email_Read:
371
826
  return responseMethod, parameters
372
827
 
373
828
  def create_file_from_emailBody(self, text, sender_email_addr, parameters):
374
-
375
- dir = "temp/"
376
- if not os.path.exists(dir):
377
- os.mkdir(dir)
829
+ base_folder = "ORDERS"
830
+ today_date = datetime.today().strftime('%Y-%m-%d') # Format: YYYY-MM-DD
831
+ order_folder = os.path.join(base_folder, today_date)
832
+
833
+ # Ensure the date-wise folder exists
834
+ os.makedirs(order_folder, exist_ok=True)
835
+
836
+ # Generate filename from sender's email
378
837
  fileName = sender_email_addr[sender_email_addr.find("<")+1:sender_email_addr.find("@")].strip().replace(".","_")
379
838
 
380
839
  if parameters["FILE_TYPE"] == "pdf":
381
840
  fileName = fileName + ".pdf"
382
- filePath = dir + fileName
841
+ filePath = os.path.join(order_folder, fileName)
383
842
 
384
843
  pdf = FPDF()
385
844
  pdf.add_page()
@@ -390,13 +849,13 @@ class Email_Read:
390
849
 
391
850
  elif parameters["FILE_TYPE"] == "txt":
392
851
  fileName = fileName + ".txt"
393
- filePath = dir + fileName
852
+ filePath = os.path.join(order_folder, fileName)
394
853
 
395
- with open(filePath, "w") as file:
854
+ with open(filePath, "w") as file:
396
855
  file.write(text)
397
856
  logger.log(f"New TXT file created from email body and stored in '{filePath}'")
398
857
  else:
399
- message = f"Invalid File Type received. "
858
+ message = f"Invalid File Type received."
400
859
  self.send_response(200)
401
860
  self.send_header('Content-type', 'text/html')
402
861
  self.end_headers()
@@ -0,0 +1,137 @@
1
+ import cx_Oracle
2
+ from DatabaseConnectionUtility import Oracle
3
+ from DatabaseConnectionUtility import Dremio
4
+ from DatabaseConnectionUtility import InMemory
5
+ from DatabaseConnectionUtility import Oracle
6
+ from DatabaseConnectionUtility import MySql
7
+ from DatabaseConnectionUtility import MSSQLServer
8
+ from DatabaseConnectionUtility import SAPHANA
9
+ from DatabaseConnectionUtility import Postgress
10
+ import loggerutility as logger
11
+ import commonutility as common
12
+ import traceback
13
+ import imaplib
14
+ from email.message import Message
15
+ import datetime
16
+ import time
17
+
18
+ class Save_Draft:
19
+
20
+ connection = None
21
+
22
+ def get_database_connection(self, dbDetails):
23
+ if dbDetails['DB_VENDORE'] != None:
24
+ klass = globals()[dbDetails['DB_VENDORE']]
25
+ dbObject = klass()
26
+ connection_obj = dbObject.getConnection(dbDetails)
27
+ return connection_obj
28
+
29
+ def commit(self):
30
+ if self.connection:
31
+ try:
32
+ self.connection.commit()
33
+ print("Transaction committed successfully.")
34
+ except cx_Oracle.Error as error:
35
+ print(f"Error during commit: {error}")
36
+ else:
37
+ print("No active connection to commit.")
38
+
39
+ def rollback(self):
40
+ if self.connection:
41
+ try:
42
+ self.connection.rollback()
43
+ print("Transaction rolled back successfully.")
44
+ except cx_Oracle.Error as error:
45
+ print(f"Error during rollback: {error}")
46
+ else:
47
+ print("No active connection to rollback.")
48
+
49
+ def close_connection(self):
50
+ if self.connection:
51
+ try:
52
+ self.connection.close()
53
+ print("Connection closed successfully.")
54
+ except cx_Oracle.Error as error:
55
+ print(f"Error during close: {error}")
56
+ else:
57
+ print("No active connection to close.")
58
+
59
+ def draft_Email(self, reciever_email_addr, receiver_email_pwd, host_name, sender_email_addr, cc_email_addr, subject, email_body):
60
+ try:
61
+ mail_details = ""
62
+ with imaplib.IMAP4_SSL(host=host_name, port=imaplib.IMAP4_SSL_PORT) as imap_ssl:
63
+ resp_code, response = imap_ssl.login(reciever_email_addr, receiver_email_pwd)
64
+ message = Message()
65
+ message["From"] = reciever_email_addr
66
+ message["To"] = sender_email_addr
67
+ message["CC"] = cc_email_addr
68
+
69
+ if subject.startswith("Re:"):
70
+ message["Subject"] = f"{subject} "
71
+ else:
72
+ message["Subject"] = f"Re: {subject} "
73
+
74
+ mail_details = f'{datetime.datetime.now().strftime("On %a, %b %d, %Y at %I:%M %p")} {sender_email_addr} wrote:'
75
+ message.set_payload(f"{email_body}\n\n{mail_details}")
76
+ utf8_message = str(message).encode("utf-8")
77
+
78
+ imap_ssl.append("[Gmail]/Drafts", '', imaplib.Time2Internaldate(time.time()), utf8_message)
79
+ print("Draft Mail saved successfully.")
80
+
81
+ return "Success"
82
+
83
+ except Exception as error:
84
+ print(f"Error ::: {error}")
85
+ return "Fail"
86
+
87
+ def check_drafts(self, dbDetails, email_info):
88
+
89
+ while True:
90
+
91
+ self.connection = self.get_database_connection(dbDetails)
92
+
93
+ if self.connection:
94
+ try:
95
+
96
+ cursor = self.connection.cursor()
97
+ queryy = f"SELECT * FROM DRAFT_EMAIL_INFO WHERE STATUS = 'Pending'"
98
+ cursor.execute(queryy)
99
+ pending_records = cursor.fetchall()
100
+ cursor.close()
101
+
102
+ for data in pending_records:
103
+ # print(f"data ::: {data}")
104
+ response = self.draft_Email(email_info['email'], email_info['password'], email_info['host'], data[0], data[1], data[2], data[3].read())
105
+ if response == 'Success':
106
+ cursor = self.connection.cursor()
107
+ update_query = """
108
+ UPDATE DRAFT_EMAIL_INFO SET
109
+ STATUS = :status
110
+ WHERE TRIM(TO_EMAIL) = TRIM(:to_email)
111
+ AND TRIM(SUBJECT) = TRIM(:subject)
112
+ """
113
+ values = {
114
+ 'status': 'Done',
115
+ 'to_email': data[0],
116
+ 'subject': data[2]
117
+ }
118
+ cursor.execute(update_query, values)
119
+ print(f"Successfully updated row.")
120
+ cursor.close()
121
+
122
+ self.commit()
123
+
124
+ except Exception as e:
125
+ print(f"Rollback due to error: {e}")
126
+
127
+ finally:
128
+ print('Closed connection successfully.')
129
+ self.close_connection()
130
+ else:
131
+ print(f'\n In getInvokeIntent exception stacktrace : ', "1")
132
+ descr = str("Connection fail")
133
+ print(f'\n Exception ::: {descr}', "0")
134
+
135
+ time.sleep(10)
136
+
137
+
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: AIEmailAutomationUtility
3
- Version: 0.0.18
4
- Summary: Solve the issue of ascii codec can not encode character
3
+ Version: 0.0.20
4
+ Summary: Change the Gemini AI prompt to generate quotation draft
5
5
  Author: Proteus Technology PVT. LTD.
6
6
  Author-email: <apps@baseinformation.com>
7
7
  Keywords: python,first package
@@ -13,4 +13,4 @@ Classifier: Operating System :: MacOS :: MacOS X
13
13
  Classifier: Operating System :: Microsoft :: Windows
14
14
  License-File: LICENCE.txt
15
15
 
16
- Solve the issue of ascii codec can not encode character
16
+ Change the Gemini AI prompt to generate quotation draft
@@ -0,0 +1,14 @@
1
+ AIEmailAutomationUtility/EmailReplyAssistant.py,sha256=oRobM6C1AwiRA3lQ2Sh51m-FaH2PpbUEidbkOG7O9Ww,6339
2
+ AIEmailAutomationUtility/Email_Classification.py,sha256=anqJ0_Iyr4xRSSJBI3KkavELSoz_EENdbofr07NjTB0,9416
3
+ AIEmailAutomationUtility/Email_DocumentUploader.py,sha256=P5P36rgDzALZEpO09aJWM0HEA-6vl48HNwz7GO6SbXc,3159
4
+ AIEmailAutomationUtility/Email_Draft.py,sha256=DcyBeDaE8CReKHnHxLiz-o2tDxuUgwy91c4k0qhQbVw,7749
5
+ AIEmailAutomationUtility/Email_Read.py,sha256=mVuSwmzPUjO4IRkSIOAPPDcNhKGvwkIwXJfBC8T9QQU,45571
6
+ AIEmailAutomationUtility/Email_Upload_Document.py,sha256=3bdkxfDlwoeRp-46KPw2Gs1dqBhEIoA1yE5GCudpdV8,1320
7
+ AIEmailAutomationUtility/Save_Draft.py,sha256=yzLgFN14I_lXE6qL0I3tKNduvcnWdbsY9i2mKdTtio4,5348
8
+ AIEmailAutomationUtility/Save_Transaction.py,sha256=Gg1w6hhzHmEFjsuzYvkq-3-EsWReetjLHsYSv5YIGgM,3816
9
+ AIEmailAutomationUtility/__init__.py,sha256=UzDkFSvLwwc0NLnvMiM0jNV5pIWUlM9p2zvpcrh9rkM,344
10
+ AIEmailAutomationUtility-0.0.20.dist-info/LICENCE.txt,sha256=2qX9IkEUBx0VJp1Vh9O2dsRwE-IpYId0lXDyn7OVsJ8,1073
11
+ AIEmailAutomationUtility-0.0.20.dist-info/METADATA,sha256=NbzpD-Fb235OBUjnM5uRYH5zWR3c5IzwqczD4Bj8mxM,611
12
+ AIEmailAutomationUtility-0.0.20.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
13
+ AIEmailAutomationUtility-0.0.20.dist-info/top_level.txt,sha256=3jTWrTUblVkaP7mpwY2UBSnrlfot5Ykpfsehyke-Uzw,25
14
+ AIEmailAutomationUtility-0.0.20.dist-info/RECORD,,
@@ -1,13 +0,0 @@
1
- AIEmailAutomationUtility/EmailReplyAssistant.py,sha256=AK2I1j2wa6NvR9ysKgtQsm-wkaeKQycQdzREXR0Ptb8,3886
2
- AIEmailAutomationUtility/Email_Classification.py,sha256=eL52Td50zo7V0QTIqjN4Khhg-5HKvf2RUITIcKz5yZ0,9343
3
- AIEmailAutomationUtility/Email_DocumentUploader.py,sha256=ImTmMz_JeU6Xynt9kyu7lFv7vqrxzqAtBF-A7014fYc,3055
4
- AIEmailAutomationUtility/Email_Draft.py,sha256=BfseewnnlwNl1moodq3kZiUPXDUE9a_nQjuFQsUp3fY,5244
5
- AIEmailAutomationUtility/Email_Read.py,sha256=syha5CaAHXPyxuiFTlUmo3zG1pby3m8Yqkt7c20AFqk,20844
6
- AIEmailAutomationUtility/Email_Upload_Document.py,sha256=3bdkxfDlwoeRp-46KPw2Gs1dqBhEIoA1yE5GCudpdV8,1320
7
- AIEmailAutomationUtility/Save_Transaction.py,sha256=Gg1w6hhzHmEFjsuzYvkq-3-EsWReetjLHsYSv5YIGgM,3816
8
- AIEmailAutomationUtility/__init__.py,sha256=UzDkFSvLwwc0NLnvMiM0jNV5pIWUlM9p2zvpcrh9rkM,344
9
- AIEmailAutomationUtility-0.0.18.dist-info/LICENCE.txt,sha256=2qX9IkEUBx0VJp1Vh9O2dsRwE-IpYId0lXDyn7OVsJ8,1073
10
- AIEmailAutomationUtility-0.0.18.dist-info/METADATA,sha256=MHLf-mZpAhkQcXWChPXo6dsern0qE2JD2GAmz1OdhAM,611
11
- AIEmailAutomationUtility-0.0.18.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
12
- AIEmailAutomationUtility-0.0.18.dist-info/top_level.txt,sha256=3jTWrTUblVkaP7mpwY2UBSnrlfot5Ykpfsehyke-Uzw,25
13
- AIEmailAutomationUtility-0.0.18.dist-info/RECORD,,