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.
- AIEmailAutomationUtility/EmailReplyAssistant.py +88 -27
- AIEmailAutomationUtility/Email_Classification.py +4 -3
- AIEmailAutomationUtility/Email_DocumentUploader.py +5 -3
- AIEmailAutomationUtility/Email_Draft.py +59 -1
- AIEmailAutomationUtility/Email_Read.py +485 -26
- AIEmailAutomationUtility/Save_Draft.py +137 -0
- {AIEmailAutomationUtility-0.0.18.dist-info → AIEmailAutomationUtility-0.0.20.dist-info}/METADATA +3 -3
- AIEmailAutomationUtility-0.0.20.dist-info/RECORD +14 -0
- AIEmailAutomationUtility-0.0.18.dist-info/RECORD +0 -13
- {AIEmailAutomationUtility-0.0.18.dist-info → AIEmailAutomationUtility-0.0.20.dist-info}/LICENCE.txt +0 -0
- {AIEmailAutomationUtility-0.0.18.dist-info → AIEmailAutomationUtility-0.0.20.dist-info}/WHEEL +0 -0
- {AIEmailAutomationUtility-0.0.18.dist-info → AIEmailAutomationUtility-0.0.20.dist-info}/top_level.txt +0 -0
@@ -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
|
-
|
14
|
-
|
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
|
-
|
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
|
-
|
227
|
-
|
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
|
-
|
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
|
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
|
-
|
314
|
-
|
315
|
-
|
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
|
-
|
325
|
-
|
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
|
-
|
376
|
-
|
377
|
-
|
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 =
|
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 =
|
852
|
+
filePath = os.path.join(order_folder, fileName)
|
394
853
|
|
395
|
-
with
|
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
|
+
|
{AIEmailAutomationUtility-0.0.18.dist-info → AIEmailAutomationUtility-0.0.20.dist-info}/METADATA
RENAMED
@@ -1,7 +1,7 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: AIEmailAutomationUtility
|
3
|
-
Version: 0.0.
|
4
|
-
Summary:
|
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
|
-
|
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,,
|
{AIEmailAutomationUtility-0.0.18.dist-info → AIEmailAutomationUtility-0.0.20.dist-info}/LICENCE.txt
RENAMED
File without changes
|
{AIEmailAutomationUtility-0.0.18.dist-info → AIEmailAutomationUtility-0.0.20.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|