AIEmailAutomationUtility 0.0.17__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.
- 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 -28
- {AIEmailAutomationUtility-0.0.17.dist-info → AIEmailAutomationUtility-0.0.19.dist-info}/METADATA +3 -3
- AIEmailAutomationUtility-0.0.19.dist-info/RECORD +13 -0
- AIEmailAutomationUtility-0.0.17.dist-info/RECORD +0 -13
- {AIEmailAutomationUtility-0.0.17.dist-info → AIEmailAutomationUtility-0.0.19.dist-info}/LICENCE.txt +0 -0
- {AIEmailAutomationUtility-0.0.17.dist-info → AIEmailAutomationUtility-0.0.19.dist-info}/WHEEL +0 -0
- {AIEmailAutomationUtility-0.0.17.dist-info → AIEmailAutomationUtility-0.0.19.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>")
|
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):
|
@@ -92,8 +98,8 @@ class Email_Read:
|
|
92
98
|
fileName = ""
|
93
99
|
|
94
100
|
Model_Name = email_config.get('model_type', 'OpenAI')
|
95
|
-
reciever_email_addr =
|
96
|
-
receiver_email_pwd =
|
101
|
+
reciever_email_addr = email_config.get('email', '').replace("\xa0", "").strip()
|
102
|
+
receiver_email_pwd = email_config.get('password', '').replace("\xa0", "").strip()
|
97
103
|
host = email_config.get('host', '')
|
98
104
|
port = email_config.get('port', '')
|
99
105
|
|
@@ -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,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
|
-
|
314
|
-
|
315
|
-
|
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
|
-
|
325
|
-
|
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
|
-
|
376
|
-
|
377
|
-
|
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 =
|
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 =
|
850
|
+
filePath = os.path.join(order_folder, fileName)
|
394
851
|
|
395
|
-
with
|
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()
|
{AIEmailAutomationUtility-0.0.17.dist-info → AIEmailAutomationUtility-0.0.19.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.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
|
-
|
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=Wc8HzZfMkDZ6XjjfNOibrI6r5XsytPEoA1ilw4DPRuA,20792
|
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.17.dist-info/LICENCE.txt,sha256=2qX9IkEUBx0VJp1Vh9O2dsRwE-IpYId0lXDyn7OVsJ8,1073
|
10
|
-
AIEmailAutomationUtility-0.0.17.dist-info/METADATA,sha256=CrzAQxzsUyms22Uaoe8QMRk4oM-MjwGVB-7UAFHH2xc,583
|
11
|
-
AIEmailAutomationUtility-0.0.17.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
12
|
-
AIEmailAutomationUtility-0.0.17.dist-info/top_level.txt,sha256=3jTWrTUblVkaP7mpwY2UBSnrlfot5Ykpfsehyke-Uzw,25
|
13
|
-
AIEmailAutomationUtility-0.0.17.dist-info/RECORD,,
|
{AIEmailAutomationUtility-0.0.17.dist-info → AIEmailAutomationUtility-0.0.19.dist-info}/LICENCE.txt
RENAMED
File without changes
|
{AIEmailAutomationUtility-0.0.17.dist-info → AIEmailAutomationUtility-0.0.19.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|