AIEmailAutomationUtility 0.0.18__py3-none-any.whl → 0.0.19__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>")
68
+ signature_html = signature.replace("\n", "<br>")
69
+ email_body_html = email_body.replace("\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,443 @@ 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
+ Generate product information in HTML tabular format with line separators for rows and columns in a draft reply based on the following information:
523
+
524
+ Customer: {customer.get('customer_name', '')}
525
+ Customer Code: {customer.get('customer_code', '')}
526
+
527
+ {product_table}
528
+ product_table must contain only price column even if it is none(set it as -).
529
+ Original Email Subject: {subject}
530
+
531
+ Return only the following JSON String format:
532
+ {{
533
+ "email_body": {{
534
+ "body": "Draft email body proper response, It should not be same like mail content and does not having any signature part like Best regards.",
535
+ "table_html": "Table Details with Sr. No. in HTML",
536
+ "signature": "{signature}"
537
+ }}
538
+ }}
539
+
540
+ Do not include signature in body and any instructions, explanations, or additional text—only the JSON object.
541
+ """
542
+ logger.log(f"Quotation draft ::: {prompt}")
543
+ ai_result = self.create_quotation_draft_GeminiAI(gemini_api_key, email_body, prompt)
544
+
545
+ elif model_type == "LocalAI":
546
+ prompt = f"""
547
+ Generate product information in HTML tabular format with line separators for rows and columns in a draft reply based on the following information:
548
+
549
+ Customer: {customer.get('customer_name', '')}
550
+ Customer Code: {customer.get('customer_code', '')}
551
+
552
+ {product_table}
553
+ - The table must contain the **Price** column, even if it is empty (set it as `-` if None).
554
+ - The table should include **Sr. No.** as the first column.
555
+ - Format the table with `<table>`, `<tr>`, `<th>`, and `<td>` tags with some border to table.
556
+
557
+ Original Email Subject: {subject}
558
+
559
+ Return **strictly** in the following JSON String format:
560
+ - All keys must be: `body`, `table_`, and `signature` inside the `email_body` JSON.
561
+ - **Do not include** `\n`, `\`, `\\`, or any unnecessary escape characters.
562
+ - Do not include instructions, explanations, or additional text—only the JSON object.
563
+
564
+ Format:
565
+ {{
566
+ "email_body": {{
567
+ "body": "Draft email body proper response, It should not contain the table or signature.",
568
+ "table_": "Table Details with Sr. No. in HTML only",
569
+ "signature": "{signature}"
570
+ }}
571
+ }}
572
+ """
573
+ logger.log(f"Quotation draft ::: {prompt}")
574
+ ai_result = self.create_quotation_draft_LocalAI(openai_api_key, email_body, local_ai_url, prompt)
575
+
576
+ else:
577
+ ai_result = "Error: Unable to generate quotation draft. Please check the configuration."
578
+
579
+ logger.log(f"Quotation draft ai_result::: {ai_result}")
580
+ quotation_draft_data = None
581
+ if ai_result != None:
582
+ quotation_draft_data = json.loads(ai_result)["email_body"]
583
+ return quotation_draft_data
584
+
585
+ def identify_customer_product_LocalAI(self, openai_api_key, email_body, local_ai_url, prompt):
586
+ logger.log("Inside identify_customer_product_LocalAI")
587
+ try:
588
+ message = [{
589
+ "role": "user",
590
+ "content": f"{prompt}"
591
+ }]
592
+
593
+ logger.log(f"Final Local AI message for detecting category::: {message}")
594
+ openai.api_key = openai_api_key
595
+ client = OpenAI(base_url=local_ai_url, api_key="lm-studio")
596
+ completion = client.chat.completions.create(
597
+ model="mistral",
598
+ messages=message,
599
+ temperature=0,
600
+ stream=False,
601
+ max_tokens=4096
602
+ )
603
+
604
+ final_result = str(completion.choices[0].message.content)
605
+ final_result = final_result.replace("\n```", "").replace("```", "").replace("json","").replace("JSON","").replace("csv","").replace("CSV","").replace("html","")
606
+ logger.log(f"finalResult:520 {final_result}")
607
+ return {"status": "Success", "message": final_result}
608
+
609
+ except Exception as e:
610
+ logger.log(f"Error with LocalAI detection/generation: {str(e)}")
611
+ return {"success": "Failed", "message": f"Error with LocalAI detection/generation: {str(e)}"}
612
+
613
+ def create_quotation_draft_LocalAI(self, openai_api_key, email_body, local_ai_url, prompt):
614
+ logger.log("Inside create_quotation_draft_LocalAI")
615
+ try:
616
+ message = [{
617
+ "role": "user",
618
+ "content": f"{prompt}"
619
+ }]
620
+
621
+ logger.log(f"Final Local AI message for detecting category::: {message}")
622
+ openai.api_key = openai_api_key
623
+ client = OpenAI(base_url=local_ai_url, api_key="lm-studio")
624
+ completion = client.chat.completions.create(
625
+ model="mistral",
626
+ messages=message,
627
+ temperature=0,
628
+ stream=False,
629
+ max_tokens=4096
630
+ )
631
+
632
+ final_result = str(completion.choices[0].message.content)
633
+ final_result = final_result.replace("\n```", "").replace("```", "").replace("json","").replace("JSON","").replace("csv","").replace("CSV","").replace("html","")
634
+ logger.log(f"finalResult:520 {final_result}")
635
+ return final_result
636
+
637
+ except Exception as e:
638
+ logger.log(f"Error with LocalAI detection/generation: {str(e)}")
639
+ return str(e)
640
+
641
+ def identify_customer_product_GeminiAI(self, gemini_api_key, email_body, prompt):
642
+ logger.log("Inside identify_customer_product_GeminiAI")
643
+ try:
644
+ message = [{
645
+ "role": "user",
646
+ "content": f"{prompt}"
647
+ }]
648
+
649
+ logger.log(f"Final Gemini AI message for detecting category::: {message}")
650
+ message_list = str(message)
651
+
652
+ genai.configure(api_key=gemini_api_key)
653
+ # model = genai.GenerativeModel('gemini-1.0-pro')
654
+ model = genai.GenerativeModel('gemini-1.5-pro-latest')
655
+ response = model.generate_content(message_list)
656
+
657
+ final_result = ""
658
+ for part in response:
659
+ final_result = part.text
660
+ logger.log(f"response::: {final_result}")
661
+ if final_result:
662
+ try:
663
+ final_result = final_result.replace("\\", "").replace('```', '').replace('json', '')
664
+ if final_result.startswith("{{") and final_result.endswith("}}"):
665
+ final_result = final_result[1:-1]
666
+ except json.JSONDecodeError:
667
+ logger.log(f"Exception : Invalid JSON Response GEMINI 1.5: {final_result} {type(final_result)}")
668
+
669
+ logger.log(f"finalResult::: {final_result}")
670
+ return {"status": "Success", "message": final_result}
671
+
672
+ except Exception as e:
673
+ logger.log(f"Error with Gemini AI detection/generation: {str(e)}")
674
+ return {"success": "Failed", "message": f"Error with Gemini AI detection/generation: {str(e)}"}
675
+
676
+ def create_quotation_draft_GeminiAI(self, gemini_api_key, email_body, prompt):
677
+ logger.log("Inside identify_customer_product_GeminiAI")
678
+ try:
679
+ message = [{
680
+ "role": "user",
681
+ "content": f"{prompt}"
682
+ }]
683
+
684
+ logger.log(f"Final Gemini AI message for detecting category::: {message}")
685
+ message_list = str(message)
686
+
687
+ genai.configure(api_key=gemini_api_key)
688
+ # model = genai.GenerativeModel('gemini-1.0-pro')
689
+ model = genai.GenerativeModel('gemini-1.5-pro-latest')
690
+ response = model.generate_content(message_list)
691
+
692
+ final_result = ""
693
+ for part in response:
694
+ final_result = part.text
695
+ logger.log(f"response::: {final_result}")
696
+ if final_result:
697
+ try:
698
+ final_result = final_result.replace("\\", "").replace('```', '').replace('json', '')
699
+ if final_result.startswith("{{") and final_result.endswith("}}"):
700
+ final_result = final_result[1:-1]
701
+ except json.JSONDecodeError:
702
+ logger.log(f"Exception : Invalid JSON Response GEMINI 1.5: {final_result} {type(final_result)}")
703
+
704
+ logger.log(f"finalResult::: {final_result}")
705
+ return final_result
706
+
707
+ except Exception as e:
708
+ logger.log(f"Error with Gemini AI detection/generation: {str(e)}")
709
+ return {"success": "Failed", "message": f"Error with Gemini AI detection/generation: {str(e)}"}
710
+
260
711
  def save_attachment(self, part, download_dir):
261
712
  try:
262
713
  filename = part.get_filename()
@@ -310,9 +761,14 @@ class Email_Read:
310
761
  logger.log(f"Error in Read_Email: {str(e)}")
311
762
 
312
763
  def download_attachment(self, msg):
313
- filepath = ""
314
- filename = ""
315
- ATTACHMENT_SAVE_PATH = "temp"
764
+ base_folder = "ORDERS" # Main folder for storing orders
765
+ today_date = datetime.today().strftime('%Y-%m-%d') # Format: YYYY-MM-DD
766
+ date_folder = os.path.join(base_folder, today_date) # Path: ORDERS/YYYY-MM-DD
767
+
768
+ # Ensure folders exist
769
+ os.makedirs(date_folder, exist_ok=True)
770
+
771
+ filename = ""
316
772
 
317
773
  for part in msg.walk():
318
774
  if part.get_content_maintype() == 'multipart':
@@ -321,11 +777,8 @@ class Email_Read:
321
777
  continue
322
778
  filename = part.get_filename()
323
779
  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)
780
+ filepath = os.path.join(date_folder, filename) # Save inside date-wise folder
781
+
329
782
  with open(filepath, 'wb') as f:
330
783
  f.write(part.get_payload(decode=True))
331
784
  logger.log(f"\nAttachment saved: '{filepath}'")
@@ -371,15 +824,19 @@ class Email_Read:
371
824
  return responseMethod, parameters
372
825
 
373
826
  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)
827
+ base_folder = "ORDERS"
828
+ today_date = datetime.today().strftime('%Y-%m-%d') # Format: YYYY-MM-DD
829
+ order_folder = os.path.join(base_folder, today_date)
830
+
831
+ # Ensure the date-wise folder exists
832
+ os.makedirs(order_folder, exist_ok=True)
833
+
834
+ # Generate filename from sender's email
378
835
  fileName = sender_email_addr[sender_email_addr.find("<")+1:sender_email_addr.find("@")].strip().replace(".","_")
379
836
 
380
837
  if parameters["FILE_TYPE"] == "pdf":
381
838
  fileName = fileName + ".pdf"
382
- filePath = dir + fileName
839
+ filePath = os.path.join(order_folder, fileName)
383
840
 
384
841
  pdf = FPDF()
385
842
  pdf.add_page()
@@ -390,13 +847,13 @@ class Email_Read:
390
847
 
391
848
  elif parameters["FILE_TYPE"] == "txt":
392
849
  fileName = fileName + ".txt"
393
- filePath = dir + fileName
850
+ filePath = os.path.join(order_folder, fileName)
394
851
 
395
- with open(filePath, "w") as file:
852
+ with open(filePath, "w") as file:
396
853
  file.write(text)
397
854
  logger.log(f"New TXT file created from email body and stored in '{filePath}'")
398
855
  else:
399
- message = f"Invalid File Type received. "
856
+ message = f"Invalid File Type received."
400
857
  self.send_response(200)
401
858
  self.send_header('Content-type', 'text/html')
402
859
  self.end_headers()
@@ -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.19
4
+ Summary: Add a code for creating a draft for the quotation category email.
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
+ Add a code for creating a draft for the quotation category email.
@@ -0,0 +1,13 @@
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=QUJUpNWO8wdLl2x3eoN92jXVii8mpK8G3uwJn8ycYSY,7677
5
+ AIEmailAutomationUtility/Email_Read.py,sha256=81RIJT8q-HEuKF5WCgMiGXgxHRcPTl-tI9GG0mlq91c,45524
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.19.dist-info/LICENCE.txt,sha256=2qX9IkEUBx0VJp1Vh9O2dsRwE-IpYId0lXDyn7OVsJ8,1073
10
+ AIEmailAutomationUtility-0.0.19.dist-info/METADATA,sha256=6Vg6SHzfV00Wx-pXhls1DZOMlqnu3r6h-U8DJmO8mAw,631
11
+ AIEmailAutomationUtility-0.0.19.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
12
+ AIEmailAutomationUtility-0.0.19.dist-info/top_level.txt,sha256=3jTWrTUblVkaP7mpwY2UBSnrlfot5Ykpfsehyke-Uzw,25
13
+ AIEmailAutomationUtility-0.0.19.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,,