AIEmailAutomationUtility 0.0.21__py3-none-any.whl → 0.0.23__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/Email_Read.py +334 -625
- AIEmailAutomationUtility/Process_Category.py +824 -0
- AIEmailAutomationUtility/__init__.py +1 -0
- {AIEmailAutomationUtility-0.0.21.dist-info → AIEmailAutomationUtility-0.0.23.dist-info}/METADATA +3 -3
- {AIEmailAutomationUtility-0.0.21.dist-info → AIEmailAutomationUtility-0.0.23.dist-info}/RECORD +8 -7
- {AIEmailAutomationUtility-0.0.21.dist-info → AIEmailAutomationUtility-0.0.23.dist-info}/LICENCE.txt +0 -0
- {AIEmailAutomationUtility-0.0.21.dist-info → AIEmailAutomationUtility-0.0.23.dist-info}/WHEEL +0 -0
- {AIEmailAutomationUtility-0.0.21.dist-info → AIEmailAutomationUtility-0.0.23.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,824 @@
|
|
1
|
+
from .Email_DocumentUploader import Email_DocumentUploader
|
2
|
+
from .EmailReplyAssistant import EmailReplyAssistant
|
3
|
+
from http.cookies import SimpleCookie
|
4
|
+
from .Email_Draft import Email_Draft
|
5
|
+
import google.generativeai as genai
|
6
|
+
from email.utils import parseaddr
|
7
|
+
import loggerutility as logger
|
8
|
+
from datetime import datetime
|
9
|
+
from openai import OpenAI
|
10
|
+
from fpdf import FPDF
|
11
|
+
import sqlite3
|
12
|
+
import openai
|
13
|
+
import json
|
14
|
+
import csv
|
15
|
+
import os
|
16
|
+
|
17
|
+
class Process_Category:
|
18
|
+
|
19
|
+
def process_cat(self, category, dataValues):
|
20
|
+
|
21
|
+
Model_Name = dataValues.get('Model_Name')
|
22
|
+
file_JsonArray = dataValues.get('file_JsonArray')
|
23
|
+
openai_api_key = dataValues.get('openai_api_key')
|
24
|
+
openai_Process_Input = dataValues.get('openai_Process_Input')
|
25
|
+
subject = dataValues.get('subject')
|
26
|
+
sender_email_addr = dataValues.get('sender_email_addr')
|
27
|
+
cc_email_addr = dataValues.get('cc_email_addr')
|
28
|
+
email_body = dataValues.get('email_body')
|
29
|
+
email_config = dataValues.get('email_config')
|
30
|
+
msg = dataValues.get('msg')
|
31
|
+
geminiAI_APIKey = dataValues.get('geminiAI_APIKey')
|
32
|
+
localAIURL = dataValues.get('localAIURL')
|
33
|
+
signature = dataValues.get('signature')
|
34
|
+
LABEL = dataValues.get('LABEL')
|
35
|
+
mail = dataValues.get('mail')
|
36
|
+
email_id = dataValues.get('email_id')
|
37
|
+
uid = dataValues.get('uid')
|
38
|
+
to_email_addr =dataValues.get('to_email_addr')
|
39
|
+
user_id =dataValues.get('user_id')
|
40
|
+
date = msg.get('Date', '')
|
41
|
+
is_html = dataValues.get('is_html')
|
42
|
+
import_file = dataValues.get('import_file')
|
43
|
+
|
44
|
+
|
45
|
+
print(f"the json datavalues of mailare ------------------------ {mail}")
|
46
|
+
print(f"the json datavalues of email_id are ------------------------ {email_id}")
|
47
|
+
print(f"the category is ------------------------ {category}")
|
48
|
+
|
49
|
+
if category == "Product Enquiry":
|
50
|
+
if Model_Name == "OpenAI":
|
51
|
+
action_taken = "Reply email drafted using Open_AI Model"
|
52
|
+
responseMethod, parameters = self.get_JsonArray_values(category, file_JsonArray)
|
53
|
+
print(f"the repsonse method{responseMethod}")
|
54
|
+
if responseMethod == "Reply_Email_Ai_Assistant":
|
55
|
+
emailreplyassistant = EmailReplyAssistant()
|
56
|
+
openai_Response = emailreplyassistant.Reply_Email_Ai_Assistant(openai_api_key, parameters["Assistant_Id"], openai_Process_Input, subject)
|
57
|
+
logger.log(f"Process openai_Response ::: {openai_Response['message']}\n")
|
58
|
+
email_details = {"sender": sender_email_addr, "cc": cc_email_addr, "subject": subject, "body": email_body}
|
59
|
+
print(f"response generated")
|
60
|
+
email_draft = Email_Draft()
|
61
|
+
status, response = email_draft.draft_email(email_config, email_details, openai_Response['message'])
|
62
|
+
logger.log(f"status ::: {status}")
|
63
|
+
print(f"Email draft called")
|
64
|
+
csv_data_status = status
|
65
|
+
csv_data_response = response
|
66
|
+
else:
|
67
|
+
message = f"Invalid response method received '{responseMethod}' for category : '{category}'"
|
68
|
+
raise ValueError(message)
|
69
|
+
elif Model_Name == "LocalAI":
|
70
|
+
action_taken = "Reply email drafted using Local_AI Model"
|
71
|
+
logger.log("localAI")
|
72
|
+
Detect_Email_category = False
|
73
|
+
LocalAI_Response = category
|
74
|
+
logger.log(f"Process LocalAI_Response ::: {LocalAI_Response}\n")
|
75
|
+
email_details = {"sender": sender_email_addr, "cc": cc_email_addr, "subject": subject, "body": email_body}
|
76
|
+
|
77
|
+
email_draft = Email_Draft()
|
78
|
+
status, response = email_draft.draft_email(email_config, email_details, LocalAI_Response)
|
79
|
+
logger.log(f"status ::: {status}")
|
80
|
+
csv_data_status = status
|
81
|
+
csv_data_response = response
|
82
|
+
elif Model_Name == "GeminiAI":
|
83
|
+
action_taken = "Reply email drafted using Gemini Model"
|
84
|
+
logger.log("GeminiAI")
|
85
|
+
Detect_Email_category = False
|
86
|
+
GeminiAI_Response = category
|
87
|
+
logger.log(f"Process GeminiAI_Response ::: {GeminiAI_Response}\n")
|
88
|
+
email_details = {"sender": sender_email_addr, "cc": cc_email_addr, "subject": subject, "body": email_body}
|
89
|
+
|
90
|
+
email_draft = Email_Draft()
|
91
|
+
status, response = email_draft.draft_email(email_config, email_details, GeminiAI_Response)
|
92
|
+
logger.log(f"status ::: {status}")
|
93
|
+
csv_data_status = status
|
94
|
+
csv_data_response = response
|
95
|
+
else:
|
96
|
+
raise ValueError(f"Invalid Model Name provided : '{Model_Name}'")
|
97
|
+
|
98
|
+
elif category == "Purchase Order":
|
99
|
+
action_taken = "Document uploaded"
|
100
|
+
responseMethod, parameters = self.get_JsonArray_values(category, file_JsonArray)
|
101
|
+
logger.log(f"responseMethod ::: {responseMethod}")
|
102
|
+
logger.log(f"parameters ::: {parameters}")
|
103
|
+
|
104
|
+
# Download the attachment
|
105
|
+
fileName = self.download_attachment(msg)
|
106
|
+
|
107
|
+
# Get today's date folder path
|
108
|
+
today_date = datetime.today().strftime('%Y-%m-%d')
|
109
|
+
order_folder = os.path.join("ORDERS", today_date)
|
110
|
+
|
111
|
+
if responseMethod == "Upload_Document":
|
112
|
+
email_upload_document = Email_DocumentUploader()
|
113
|
+
if len(fileName) != 0:
|
114
|
+
file_path = os.path.join(order_folder, fileName) # Correct file path
|
115
|
+
|
116
|
+
with open(file_path, "rb") as file:
|
117
|
+
response_status, restAPI_Result = email_upload_document.email_document_upload(file, parameters)
|
118
|
+
logger.log(f"email_upload_document_response ::: {restAPI_Result}")
|
119
|
+
else:
|
120
|
+
new_fileName = self.create_file_from_emailBody(email_body, sender_email_addr, parameters)
|
121
|
+
new_file_path = os.path.join(order_folder, new_fileName)
|
122
|
+
|
123
|
+
with open(new_file_path, "rb") as file:
|
124
|
+
response_status, restAPI_Result = email_upload_document.email_document_upload(file, parameters)
|
125
|
+
logger.log(f"email_upload_document_response ::: {restAPI_Result}")
|
126
|
+
|
127
|
+
if response_status == "200":
|
128
|
+
logger.log(f"Attachment uploaded successfully against Document ID: '{restAPI_Result}'.")
|
129
|
+
csv_data_status="Success",
|
130
|
+
csv_data_response=f"Attachment uploaded successfully against Document ID: '{restAPI_Result}'"
|
131
|
+
else:
|
132
|
+
logger.log(restAPI_Result)
|
133
|
+
csv_data_status="Fail",
|
134
|
+
csv_data_response=f"Attachment uploaded Failed against Document ID: '{restAPI_Result}'"
|
135
|
+
else:
|
136
|
+
message = f"Invalid response method received '{responseMethod}' for category : '{category}'"
|
137
|
+
raise ValueError(message)
|
138
|
+
|
139
|
+
elif category == "Quotation":
|
140
|
+
action_taken = f"Mail drafted for products rate"
|
141
|
+
responseMethod, parameters = self.get_JsonArray_values(category, file_JsonArray)
|
142
|
+
logger.log(f"Inside Quotation")
|
143
|
+
|
144
|
+
# Step 4: Identify customer from email using AI
|
145
|
+
customer_data = self.identify_customer(email_body, subject, Model_Name, openai_api_key, geminiAI_APIKey, localAIURL, parameters["Customer_Assistant_Id"])
|
146
|
+
logger.log(f"Identified customer: {customer_data}")
|
147
|
+
print(f'this is the customer data we are getting',customer_data)
|
148
|
+
# Extract customer code once
|
149
|
+
customer_code = customer_data.get("customer_code", "")
|
150
|
+
|
151
|
+
# Step 5: Identify product from email using AI
|
152
|
+
products = self.identify_products(email_body, subject, Model_Name, openai_api_key, geminiAI_APIKey, localAIURL, parameters["Product_Assistant_Id"])
|
153
|
+
print(f'this is the products data we are getting',products)
|
154
|
+
|
155
|
+
db_connection = sqlite3.connect('/home/base/git/ai-email-automation/AI_Email/database/fetchprice.db')
|
156
|
+
for product in products:
|
157
|
+
cursor = db_connection.cursor()
|
158
|
+
|
159
|
+
item_no = product.get("item_no", "").strip()
|
160
|
+
make = product.get("make", "").strip()
|
161
|
+
rate = None
|
162
|
+
found_rate = False
|
163
|
+
|
164
|
+
# Step 1: Get base price from PRICE_LIST
|
165
|
+
query_price = '''
|
166
|
+
SELECT PRICE
|
167
|
+
FROM PRICE_LIST
|
168
|
+
WHERE ITEM_NO = ?;
|
169
|
+
'''
|
170
|
+
cursor.execute(query_price, (item_no,))
|
171
|
+
price_result = cursor.fetchone()
|
172
|
+
|
173
|
+
if price_result:
|
174
|
+
price_raw = price_result[0]
|
175
|
+
|
176
|
+
if isinstance(price_raw, str):
|
177
|
+
# Clean the string: remove INR and commas
|
178
|
+
price_cleaned = price_raw.replace('INR', '').replace(',', '').strip()
|
179
|
+
else:
|
180
|
+
price_cleaned = price_raw # already numeric
|
181
|
+
|
182
|
+
try:
|
183
|
+
raw_price = float(price_cleaned)
|
184
|
+
print(f"[0] Base price for item '{item_no}' is {raw_price}")
|
185
|
+
except (TypeError, ValueError):
|
186
|
+
print(f"[0] Invalid raw price for item '{item_no}': {price_result[0]}")
|
187
|
+
product["rate"] = None
|
188
|
+
continue
|
189
|
+
else:
|
190
|
+
print(f"[0] No base price found for item '{item_no}'. Skipping...")
|
191
|
+
product["rate"] = None
|
192
|
+
continue
|
193
|
+
|
194
|
+
|
195
|
+
# Condition 1: Exact match in special_rate_customer_wise
|
196
|
+
query1 = '''
|
197
|
+
SELECT RATE
|
198
|
+
FROM SPECIAL_RATE_CUSTOMER_WISE
|
199
|
+
WHERE ITEM_CODE = ?
|
200
|
+
AND CUSTOMER_CODE = ?;
|
201
|
+
'''
|
202
|
+
cursor.execute(query1, (item_no, customer_code))
|
203
|
+
result = cursor.fetchone()
|
204
|
+
|
205
|
+
if result:
|
206
|
+
rate = result[0]
|
207
|
+
found_rate = True
|
208
|
+
print(f"[1] Special Rate for item '{item_no}' and customer '{customer_code}' is {rate}")
|
209
|
+
|
210
|
+
# Condition 2: Customer + Manufacturer discount
|
211
|
+
if not found_rate:
|
212
|
+
query2 = '''
|
213
|
+
SELECT DISCOUNT
|
214
|
+
FROM CUSTOMER_WISE_DISCOUNT
|
215
|
+
WHERE CUSTOMER_CODE = ? AND MAKE = ?;
|
216
|
+
'''
|
217
|
+
cursor.execute(query2, (customer_code, make))
|
218
|
+
discount_result = cursor.fetchone()
|
219
|
+
|
220
|
+
if discount_result:
|
221
|
+
discount_percent = discount_result[0]
|
222
|
+
rate = raw_price * (1 - discount_percent / 100)
|
223
|
+
rate = round(rate, 2)
|
224
|
+
found_rate = True
|
225
|
+
print(f"[2] Discounted rate for '{make}' ({discount_percent}%) on price {raw_price}: {rate}")
|
226
|
+
|
227
|
+
# Condition 3: Past Sales most used make
|
228
|
+
if not found_rate:
|
229
|
+
query3 = '''
|
230
|
+
SELECT MAKE, MAX(DISCOUNT)
|
231
|
+
FROM PAST_SALES
|
232
|
+
WHERE CUSTOMER_CODE = ?
|
233
|
+
AND ITEM_CODE = ?
|
234
|
+
GROUP BY MAKE
|
235
|
+
ORDER BY COUNT(*) DESC
|
236
|
+
LIMIT 1;
|
237
|
+
'''
|
238
|
+
cursor.execute(query3, (customer_code, item_no))
|
239
|
+
past_sales_result = cursor.fetchone()
|
240
|
+
|
241
|
+
if past_sales_result:
|
242
|
+
most_common_make, past_discount = past_sales_result
|
243
|
+
if isinstance(raw_price, (int, float)):
|
244
|
+
rate = raw_price * (1 - past_discount / 100)
|
245
|
+
rate = round(rate, 2)
|
246
|
+
found_rate = True
|
247
|
+
print(f"[3] Fallback: Most used make '{most_common_make}' for customer '{customer_code}' got {past_discount}% discount. Rate: {rate}")
|
248
|
+
else:
|
249
|
+
print(f"[3] Fallback: Invalid price for item '{item_no}'")
|
250
|
+
else:
|
251
|
+
print(f"[3] No past sales data for item '{item_no}' and customer '{customer_code}'")
|
252
|
+
|
253
|
+
# Condition 4: Manufacturer General Discount
|
254
|
+
if not found_rate:
|
255
|
+
query4 = '''
|
256
|
+
SELECT GENERAL_DISCOUNT
|
257
|
+
FROM MANUFACTURE_WISE_GENERAL_DISCOUNT
|
258
|
+
WHERE MAKE = ?;
|
259
|
+
'''
|
260
|
+
cursor.execute(query4, (make,))
|
261
|
+
general_discount_result = cursor.fetchone()
|
262
|
+
|
263
|
+
if general_discount_result:
|
264
|
+
general_discount_percent = general_discount_result[0]
|
265
|
+
rate = raw_price * (1 - general_discount_percent / 100)
|
266
|
+
rate = round(rate, 2)
|
267
|
+
found_rate = True
|
268
|
+
print(f"[4] General Discount for '{make}' ({general_discount_percent}%) on price {raw_price}: {rate}")
|
269
|
+
else:
|
270
|
+
print(f"[4] No applicable discount found for item '{item_no}'")
|
271
|
+
|
272
|
+
product["rate"] = rate
|
273
|
+
cursor.close()
|
274
|
+
|
275
|
+
db_connection.close()
|
276
|
+
|
277
|
+
logger.log(f"Identified products: {products}")
|
278
|
+
logger.log(f"Identified products length: {len(products)}")
|
279
|
+
quotation_draft = self.generate_quotation_draft(
|
280
|
+
customer_data,
|
281
|
+
products,
|
282
|
+
Model_Name,
|
283
|
+
openai_api_key,
|
284
|
+
geminiAI_APIKey,
|
285
|
+
localAIURL,
|
286
|
+
parameters["Customer_Assistant_Id"],
|
287
|
+
email_body,
|
288
|
+
subject,
|
289
|
+
signature
|
290
|
+
)
|
291
|
+
logger.log(f"quotation_draft ::: {quotation_draft}")
|
292
|
+
|
293
|
+
# Step 8: Send draft quotation email
|
294
|
+
email_details = {"sender": sender_email_addr, "cc": cc_email_addr, "subject": subject, "body": email_body}
|
295
|
+
email_draft = Email_Draft()
|
296
|
+
status, response = email_draft.quotation_draft_email(email_config, email_details, quotation_draft)
|
297
|
+
logger.log(f"status ::: {status}")
|
298
|
+
|
299
|
+
csv_data_status = status
|
300
|
+
csv_data_response = response
|
301
|
+
# csv_data_status = "status"
|
302
|
+
# csv_data_response = "response"
|
303
|
+
logger.log(f"Quotation email sent to {sender_email_addr}")
|
304
|
+
|
305
|
+
elif category == "Others" and import_file == True:
|
306
|
+
action_taken = "-"
|
307
|
+
csv_data_status = "Fail"
|
308
|
+
csv_data_response = f"-"
|
309
|
+
else:
|
310
|
+
action_taken = f"Saved the mail to the label: {LABEL}"
|
311
|
+
csv_data_status = "Success"
|
312
|
+
csv_data_response = f""
|
313
|
+
|
314
|
+
logger.log(f"Marking email as UNREAD. ")
|
315
|
+
print(f"email_id: {email_id}, type: {type(email_id)}")
|
316
|
+
mail.store(email_id, '-FLAGS', '\\Seen')
|
317
|
+
mail.create(LABEL)
|
318
|
+
mail.copy(email_id, LABEL)
|
319
|
+
mail.store(email_id, '+FLAGS', '\\Deleted') # Mark for deletion
|
320
|
+
mail.expunge()
|
321
|
+
logger.log(f"Mail removed from inbox and added to '{LABEL}' label.")
|
322
|
+
|
323
|
+
filename = f"{uid}"
|
324
|
+
print(f"the file name is ---------------- {filename}")
|
325
|
+
self.store_email_details_to_csv(
|
326
|
+
email_id=uid,
|
327
|
+
to_email=parseaddr(to_email_addr)[1],
|
328
|
+
from_email=parseaddr(sender_email_addr)[1],
|
329
|
+
cc_email=parseaddr(cc_email_addr)[1] if cc_email_addr else '',
|
330
|
+
subject=subject,
|
331
|
+
body=email_body,
|
332
|
+
email_type=category,
|
333
|
+
action_performed=action_taken,
|
334
|
+
filename=filename,
|
335
|
+
user_id=user_id,
|
336
|
+
status=csv_data_status,
|
337
|
+
response=csv_data_response
|
338
|
+
|
339
|
+
)
|
340
|
+
self.store_email_as_eml(
|
341
|
+
uid=uid,
|
342
|
+
from_email=sender_email_addr,
|
343
|
+
to_email=to_email_addr,
|
344
|
+
cc_email=cc_email_addr,
|
345
|
+
subject=subject,
|
346
|
+
date=date,
|
347
|
+
body_content=email_body,
|
348
|
+
is_html=is_html,
|
349
|
+
filename=filename,
|
350
|
+
user_id=user_id
|
351
|
+
)
|
352
|
+
|
353
|
+
def get_JsonArray_values(self, category, jsonArray):
|
354
|
+
responseMethod = ""
|
355
|
+
parameters = ""
|
356
|
+
|
357
|
+
for eachJson in jsonArray :
|
358
|
+
for key, value in eachJson.items():
|
359
|
+
if value == category:
|
360
|
+
responseMethod = eachJson["Response_Method"]
|
361
|
+
parameters = eachJson["Parameters"]
|
362
|
+
|
363
|
+
return responseMethod, parameters
|
364
|
+
|
365
|
+
def download_attachment(self, msg):
|
366
|
+
base_folder = "ORDERS" # Main folder for storing orders
|
367
|
+
today_date = datetime.today().strftime('%Y-%m-%d') # Format: YYYY-MM-DD
|
368
|
+
date_folder = os.path.join(base_folder, today_date) # Path: ORDERS/YYYY-MM-DD
|
369
|
+
|
370
|
+
# Ensure folders exist
|
371
|
+
os.makedirs(date_folder, exist_ok=True)
|
372
|
+
|
373
|
+
filename = ""
|
374
|
+
|
375
|
+
for part in msg.walk():
|
376
|
+
if part.get_content_maintype() == 'multipart':
|
377
|
+
continue
|
378
|
+
if part.get('Content-Disposition') is None:
|
379
|
+
continue
|
380
|
+
filename = part.get_filename()
|
381
|
+
if filename:
|
382
|
+
filepath = os.path.join(date_folder, filename) # Save inside date-wise folder
|
383
|
+
|
384
|
+
with open(filepath, 'wb') as f:
|
385
|
+
f.write(part.get_payload(decode=True))
|
386
|
+
logger.log(f"\nAttachment saved: '{filepath}'")
|
387
|
+
else:
|
388
|
+
logger.log("\nNo Attachment found.")
|
389
|
+
return filename
|
390
|
+
|
391
|
+
def create_file_from_emailBody(self, text, sender_email_addr, parameters):
|
392
|
+
base_folder = "ORDERS"
|
393
|
+
today_date = datetime.today().strftime('%Y-%m-%d') # Format: YYYY-MM-DD
|
394
|
+
order_folder = os.path.join(base_folder, today_date)
|
395
|
+
|
396
|
+
# Ensure the date-wise folder exists
|
397
|
+
os.makedirs(order_folder, exist_ok=True)
|
398
|
+
|
399
|
+
# Generate filename from sender's email
|
400
|
+
fileName = sender_email_addr[sender_email_addr.find("<")+1:sender_email_addr.find("@")].strip().replace(".","_")
|
401
|
+
|
402
|
+
if parameters["FILE_TYPE"] == "pdf":
|
403
|
+
fileName = fileName + ".pdf"
|
404
|
+
filePath = os.path.join(order_folder, fileName)
|
405
|
+
|
406
|
+
pdf = FPDF()
|
407
|
+
pdf.add_page()
|
408
|
+
pdf.set_font("Arial", size=12)
|
409
|
+
pdf.multi_cell(0, 10, text)
|
410
|
+
pdf.output(filePath)
|
411
|
+
logger.log(f"New PDF file created from email body and stored in '{filePath}'")
|
412
|
+
|
413
|
+
elif parameters["FILE_TYPE"] == "txt":
|
414
|
+
fileName = fileName + ".txt"
|
415
|
+
filePath = os.path.join(order_folder, fileName)
|
416
|
+
|
417
|
+
with open(filePath, "w") as file:
|
418
|
+
file.write(text)
|
419
|
+
logger.log(f"New TXT file created from email body and stored in '{filePath}'")
|
420
|
+
else:
|
421
|
+
message = f"Invalid File Type received."
|
422
|
+
self.send_response(200)
|
423
|
+
self.send_header('Content-type', 'text/html')
|
424
|
+
self.end_headers()
|
425
|
+
self.wfile.write(message.encode('utf-8'))
|
426
|
+
|
427
|
+
return fileName
|
428
|
+
|
429
|
+
def identify_customer(self, email_body, subject, model_type, openai_api_key, gemini_api_key, local_ai_url, assistant_id):
|
430
|
+
logger.log("Inside identify_customer")
|
431
|
+
|
432
|
+
if model_type == "OpenAI":
|
433
|
+
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."""
|
434
|
+
emailreplyassistant = EmailReplyAssistant()
|
435
|
+
ai_result = emailreplyassistant.identify_customer_product_reply_assitant(openai_api_key, assistant_id, email_body, subject, prompt)
|
436
|
+
|
437
|
+
elif model_type == "GeminiAI":
|
438
|
+
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."""
|
439
|
+
ai_result = self.identify_customer_product_GeminiAI(gemini_api_key, email_body, prompt)
|
440
|
+
|
441
|
+
elif model_type == "LocalAI":
|
442
|
+
prompt = f"""Identify the customer code and customer name in JSON format from the following email: {email_body}, received from {subject}.
|
443
|
+
If no customer details are found, return:{{"customer_name": "","customer_code": ""}}Only return the JSON object. No explanations, no additional text."""
|
444
|
+
ai_result = self.identify_customer_product_LocalAI(openai_api_key, email_body, local_ai_url, prompt)
|
445
|
+
|
446
|
+
else:
|
447
|
+
ai_result = "{}"
|
448
|
+
|
449
|
+
customer_data = {}
|
450
|
+
if ai_result["status"] == "Success":
|
451
|
+
customer_data = json.loads(ai_result["message"])
|
452
|
+
else:
|
453
|
+
customer_data = {
|
454
|
+
"customer_name": "",
|
455
|
+
"customer_code": ""
|
456
|
+
}
|
457
|
+
return customer_data
|
458
|
+
|
459
|
+
def identify_products(self, email_body, subject, model_type, openai_api_key, gemini_api_key, local_ai_url, assistant_id):
|
460
|
+
logger.log("Inside identify_products")
|
461
|
+
|
462
|
+
if model_type == "OpenAI":
|
463
|
+
prompt = f"""
|
464
|
+
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}.
|
465
|
+
If there is one product or multiple should return in list.
|
466
|
+
Do not include any instruction as the output will be directly in a program.
|
467
|
+
"""
|
468
|
+
emailreplyassistant = EmailReplyAssistant()
|
469
|
+
ai_result = emailreplyassistant.identify_customer_product_reply_assitant(openai_api_key, assistant_id, email_body, subject, prompt)
|
470
|
+
|
471
|
+
elif model_type == "GeminiAI":
|
472
|
+
prompt = f"""
|
473
|
+
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}.
|
474
|
+
If there is one product or multiple should return in list.
|
475
|
+
Do not include any instruction as the output will be directly in a program.
|
476
|
+
"""
|
477
|
+
ai_result = self.identify_customer_product_GeminiAI(gemini_api_key, email_body, prompt)
|
478
|
+
|
479
|
+
elif model_type == "LocalAI":
|
480
|
+
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.
|
481
|
+
If no product details are found, return:[] Only return the JSON object. No explanations, no additional text."""
|
482
|
+
ai_result = self.identify_customer_product_LocalAI(openai_api_key, email_body, local_ai_url, prompt)
|
483
|
+
|
484
|
+
else:
|
485
|
+
ai_result = "{}"
|
486
|
+
|
487
|
+
product_data = {}
|
488
|
+
if ai_result["status"] == "Success":
|
489
|
+
logger.log(f"ai_result ::: {ai_result}")
|
490
|
+
product_data = json.loads(ai_result["message"])
|
491
|
+
else:
|
492
|
+
product_data = []
|
493
|
+
return product_data
|
494
|
+
|
495
|
+
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):
|
496
|
+
logger.log("Inside generate_quotation_draft")
|
497
|
+
|
498
|
+
customer = customer_data
|
499
|
+
|
500
|
+
product_table = "Products:\n"
|
501
|
+
for product in products:
|
502
|
+
product_table += f'- {product.get("requested_description")} (Code: {product.get("item_no")}) = ${product.get("rate")}\n'
|
503
|
+
|
504
|
+
if model_type == "OpenAI":
|
505
|
+
prompt = f"""
|
506
|
+
Generate product information in HTML tabular format with line separators for rows and columns in a draft reply based on the following information:
|
507
|
+
|
508
|
+
Customer: {customer.get('customer_name', '')}
|
509
|
+
Customer Code: {customer.get('customer_code', '')}
|
510
|
+
|
511
|
+
{product_table}
|
512
|
+
product_table must contain only price column even if it is none(set it as -).
|
513
|
+
Original Email Subject: {subject}
|
514
|
+
|
515
|
+
Return only the following JSON String format:
|
516
|
+
{{
|
517
|
+
"email_body": {{
|
518
|
+
"body": "Draft email body proper response, It should not be same like mail content and does not having any signature part like Best regards.",
|
519
|
+
"table_html": "Table Details with Sr. No. in HTML",
|
520
|
+
"signature": "{signature}"
|
521
|
+
}}
|
522
|
+
}}
|
523
|
+
|
524
|
+
Do not include signature in body and any instructions, explanations, or additional text—only the JSON object.
|
525
|
+
"""
|
526
|
+
logger.log(f"Quotation draft ::: {prompt}")
|
527
|
+
emailreplyassistant = EmailReplyAssistant()
|
528
|
+
ai_result = emailreplyassistant.create_quotation_draft(openai_api_key, assistant_id, email_body, subject, prompt)
|
529
|
+
|
530
|
+
elif model_type == "GeminiAI":
|
531
|
+
prompt = f"""
|
532
|
+
Create an HTML product information email draft with the following details:
|
533
|
+
|
534
|
+
Customer Name: {customer.get('customer_name', '')}
|
535
|
+
Customer Code: {customer.get('customer_code', '')}
|
536
|
+
|
537
|
+
Product Information:
|
538
|
+
{product_table}
|
539
|
+
Note: Include price column with a value of "-" if price is not available.
|
540
|
+
|
541
|
+
Email Subject Reference: {subject}
|
542
|
+
|
543
|
+
Please format the response as a valid JSON string with these fields:
|
544
|
+
{{
|
545
|
+
"email_body": {{
|
546
|
+
"body": "Professional email content that summarizes the product information without being identical to the input data. Do not include signature here.",
|
547
|
+
"table_": "HTML table with SR. No. column and product details",
|
548
|
+
"signature": "{signature}"
|
549
|
+
}}
|
550
|
+
}}
|
551
|
+
|
552
|
+
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.
|
553
|
+
"""
|
554
|
+
logger.log(f"Quotation draft ::: {prompt}")
|
555
|
+
ai_result = self.create_quotation_draft_GeminiAI(gemini_api_key, email_body, prompt)
|
556
|
+
|
557
|
+
elif model_type == "LocalAI":
|
558
|
+
prompt = f"""
|
559
|
+
Generate product information in HTML tabular format with line separators for rows and columns in a draft reply based on the following information:
|
560
|
+
|
561
|
+
Customer: {customer.get('customer_name', '')}
|
562
|
+
Customer Code: {customer.get('customer_code', '')}
|
563
|
+
|
564
|
+
{product_table}
|
565
|
+
- The table must contain the **Price** column, even if it is empty (set it as `-` if None).
|
566
|
+
- The table should include **Sr. No.** as the first column.
|
567
|
+
- Format the table with `<table>`, `<tr>`, `<th>`, and `<td>` tags with some border to table.
|
568
|
+
|
569
|
+
Original Email Subject: {subject}
|
570
|
+
|
571
|
+
Return **strictly** in the following JSON String format:
|
572
|
+
- All keys must be: `body`, `table_`, and `signature` inside the `email_body` JSON.
|
573
|
+
- **Do not include** `\n`, `\`, `\\`, or any unnecessary escape characters.
|
574
|
+
- Do not include instructions, explanations, or additional text—only the JSON object.
|
575
|
+
|
576
|
+
Format:
|
577
|
+
{{
|
578
|
+
"email_body": {{
|
579
|
+
"body": "Draft email body proper response, It should not contain the table or signature.",
|
580
|
+
"table_": "Table Details with Sr. No. in HTML only",
|
581
|
+
"signature": "{signature}"
|
582
|
+
}}
|
583
|
+
}}
|
584
|
+
"""
|
585
|
+
logger.log(f"Quotation draft ::: {prompt}")
|
586
|
+
ai_result = self.create_quotation_draft_LocalAI(openai_api_key, email_body, local_ai_url, prompt)
|
587
|
+
|
588
|
+
else:
|
589
|
+
ai_result = "Error: Unable to generate quotation draft. Please check the configuration."
|
590
|
+
|
591
|
+
logger.log(f"Quotation draft ai_result::: {ai_result}")
|
592
|
+
quotation_draft_data = None
|
593
|
+
if ai_result != None:
|
594
|
+
quotation_draft_data = json.loads(ai_result)["email_body"]
|
595
|
+
return quotation_draft_data
|
596
|
+
|
597
|
+
def identify_customer_product_LocalAI(self, openai_api_key, email_body, local_ai_url, prompt):
|
598
|
+
logger.log("Inside identify_customer_product_LocalAI")
|
599
|
+
try:
|
600
|
+
message = [{
|
601
|
+
"role": "user",
|
602
|
+
"content": f"{prompt}"
|
603
|
+
}]
|
604
|
+
|
605
|
+
logger.log(f"Final Local AI message for detecting category::: {message}")
|
606
|
+
openai.api_key = openai_api_key
|
607
|
+
client = OpenAI(base_url=local_ai_url, api_key="lm-studio")
|
608
|
+
completion = client.chat.completions.create(
|
609
|
+
model="mistral",
|
610
|
+
messages=message,
|
611
|
+
temperature=0,
|
612
|
+
stream=False,
|
613
|
+
max_tokens=4096
|
614
|
+
)
|
615
|
+
|
616
|
+
final_result = str(completion.choices[0].message.content)
|
617
|
+
final_result = final_result.replace("\n```", "").replace("```", "").replace("json","").replace("JSON","").replace("csv","").replace("CSV","").replace("html","")
|
618
|
+
logger.log(f"finalResult:520 {final_result}")
|
619
|
+
return {"status": "Success", "message": final_result}
|
620
|
+
|
621
|
+
except Exception as e:
|
622
|
+
logger.log(f"Error with LocalAI detection/generation: {str(e)}")
|
623
|
+
return {"success": "Failed", "message": f"Error with LocalAI detection/generation: {str(e)}"}
|
624
|
+
|
625
|
+
def create_quotation_draft_LocalAI(self, openai_api_key, email_body, local_ai_url, prompt):
|
626
|
+
logger.log("Inside create_quotation_draft_LocalAI")
|
627
|
+
try:
|
628
|
+
message = [{
|
629
|
+
"role": "user",
|
630
|
+
"content": f"{prompt}"
|
631
|
+
}]
|
632
|
+
|
633
|
+
logger.log(f"Final Local AI message for detecting category::: {message}")
|
634
|
+
openai.api_key = openai_api_key
|
635
|
+
client = OpenAI(base_url=local_ai_url, api_key="lm-studio")
|
636
|
+
completion = client.chat.completions.create(
|
637
|
+
model="mistral",
|
638
|
+
messages=message,
|
639
|
+
temperature=0,
|
640
|
+
stream=False,
|
641
|
+
max_tokens=4096
|
642
|
+
)
|
643
|
+
|
644
|
+
final_result = str(completion.choices[0].message.content)
|
645
|
+
final_result = final_result.replace("\n```", "").replace("```", "").replace("json","").replace("JSON","").replace("csv","").replace("CSV","").replace("html","")
|
646
|
+
logger.log(f"finalResult:520 {final_result}")
|
647
|
+
return final_result
|
648
|
+
|
649
|
+
except Exception as e:
|
650
|
+
logger.log(f"Error with LocalAI detection/generation: {str(e)}")
|
651
|
+
return str(e)
|
652
|
+
|
653
|
+
def identify_customer_product_GeminiAI(self, gemini_api_key, email_body, prompt):
|
654
|
+
logger.log("Inside identify_customer_product_GeminiAI")
|
655
|
+
try:
|
656
|
+
message = [{
|
657
|
+
"role": "user",
|
658
|
+
"content": f"{prompt}"
|
659
|
+
}]
|
660
|
+
|
661
|
+
logger.log(f"Final Gemini AI message for detecting category::: {message}")
|
662
|
+
message_list = str(message)
|
663
|
+
|
664
|
+
genai.configure(api_key=gemini_api_key)
|
665
|
+
# model = genai.GenerativeModel('gemini-1.0-pro')
|
666
|
+
model = genai.GenerativeModel('gemini-1.5-pro-latest')
|
667
|
+
response = model.generate_content(message_list)
|
668
|
+
|
669
|
+
final_result = ""
|
670
|
+
for part in response:
|
671
|
+
final_result = part.text
|
672
|
+
logger.log(f"response::: {final_result}")
|
673
|
+
if final_result:
|
674
|
+
try:
|
675
|
+
final_result = final_result.replace("\\", "").replace('```', '').replace('json', '')
|
676
|
+
if final_result.startswith("{{") and final_result.endswith("}}"):
|
677
|
+
final_result = final_result[1:-1]
|
678
|
+
except json.JSONDecodeError:
|
679
|
+
logger.log(f"Exception : Invalid JSON Response GEMINI 1.5: {final_result} {type(final_result)}")
|
680
|
+
|
681
|
+
logger.log(f"finalResult::: {final_result}")
|
682
|
+
return {"status": "Success", "message": final_result}
|
683
|
+
|
684
|
+
except Exception as e:
|
685
|
+
logger.log(f"Error with Gemini AI detection/generation: {str(e)}")
|
686
|
+
return {"success": "Failed", "message": f"Error with Gemini AI detection/generation: {str(e)}"}
|
687
|
+
|
688
|
+
def create_quotation_draft_GeminiAI(self, gemini_api_key, email_body, prompt):
|
689
|
+
logger.log("Inside identify_customer_product_GeminiAI")
|
690
|
+
try:
|
691
|
+
message = [{
|
692
|
+
"role": "user",
|
693
|
+
"content": f"{prompt}"
|
694
|
+
}]
|
695
|
+
|
696
|
+
logger.log(f"Final Gemini AI message for detecting category::: {message}")
|
697
|
+
message_list = str(message)
|
698
|
+
|
699
|
+
genai.configure(api_key=gemini_api_key)
|
700
|
+
# model = genai.GenerativeModel('gemini-1.0-pro')
|
701
|
+
model = genai.GenerativeModel('gemini-1.5-pro-latest')
|
702
|
+
response = model.generate_content(message_list)
|
703
|
+
|
704
|
+
final_result = ""
|
705
|
+
for part in response:
|
706
|
+
final_result = part.text
|
707
|
+
logger.log(f"response::: {final_result}")
|
708
|
+
if final_result:
|
709
|
+
try:
|
710
|
+
final_result = final_result.replace('```', '').replace('json', '')
|
711
|
+
if final_result.startswith("{{") and final_result.endswith("}}"):
|
712
|
+
final_result = final_result[1:-1]
|
713
|
+
except json.JSONDecodeError:
|
714
|
+
logger.log(f"Exception : Invalid JSON Response GEMINI 1.5: {final_result} {type(final_result)}")
|
715
|
+
|
716
|
+
logger.log(f"finalResult::: {final_result}")
|
717
|
+
return final_result
|
718
|
+
|
719
|
+
except Exception as e:
|
720
|
+
logger.log(f"Error with Gemini AI detection/generation: {str(e)}")
|
721
|
+
return {"success": "Failed", "message": f"Error with Gemini AI detection/generation: {str(e)}"}
|
722
|
+
|
723
|
+
def store_email_details_to_csv(self, email_id, to_email, from_email, cc_email, subject, body, email_type, action_performed, filename, user_id,status,response):
|
724
|
+
"""
|
725
|
+
Stores the extracted email details to a CSV file inside 'Mail_log/mail_log_user_id' folder
|
726
|
+
with the name user_id.csv.
|
727
|
+
"""
|
728
|
+
|
729
|
+
# Ensure the Mail_log folder exists
|
730
|
+
log_folder = "Mail_log"
|
731
|
+
os.makedirs(log_folder, exist_ok=True)
|
732
|
+
|
733
|
+
# Create the mail_log_user_id folder inside 'Mail_log' if it doesn't exist
|
734
|
+
user_folder = os.path.join(log_folder, f"{user_id}")
|
735
|
+
os.makedirs(user_folder, exist_ok=True)
|
736
|
+
|
737
|
+
full_csv_path = os.path.join(user_folder, f"{filename}.csv")
|
738
|
+
|
739
|
+
current_timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
740
|
+
|
741
|
+
# Prepare the data to write
|
742
|
+
csv_data = [{
|
743
|
+
'to': to_email,
|
744
|
+
'from': from_email,
|
745
|
+
'cc': cc_email,
|
746
|
+
'subject': subject,
|
747
|
+
'body': body.replace('\n', ' ').replace('\r', ''),
|
748
|
+
'Category': email_type,
|
749
|
+
'Action performed': action_performed,
|
750
|
+
'unique_id': email_id,
|
751
|
+
'timestamp': current_timestamp,
|
752
|
+
'status of mail draft': status,
|
753
|
+
'Response generated':response
|
754
|
+
}]
|
755
|
+
|
756
|
+
# Write to CSV file (user_id.csv)
|
757
|
+
with open(full_csv_path, 'a', newline='', encoding='utf-8') as csvfile:
|
758
|
+
fieldnames = ['to', 'from', 'cc', 'timestamp', 'subject', 'body', 'Category', 'Action performed', 'unique_id','status of mail draft','Response generated']
|
759
|
+
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
|
760
|
+
|
761
|
+
# If the file is empty, write the header
|
762
|
+
if os.stat(full_csv_path).st_size == 0:
|
763
|
+
writer.writeheader()
|
764
|
+
|
765
|
+
# Write the email data
|
766
|
+
writer.writerows(csv_data)
|
767
|
+
|
768
|
+
# Log the action
|
769
|
+
logger.log(f"Email with ID '{email_id}' details appended to {full_csv_path}")
|
770
|
+
|
771
|
+
def store_email_as_eml(self, uid, from_email, to_email, cc_email, subject, date, body_content,filename,user_id,is_html=False,):
|
772
|
+
"""
|
773
|
+
Stores a simplified EML-style email as a .eml file inside 'Mail_Formats' folder.
|
774
|
+
|
775
|
+
Args:
|
776
|
+
uid (str): Unique ID of the email.
|
777
|
+
from_email (str): Sender email.
|
778
|
+
to_email (str): Recipient email.
|
779
|
+
cc_email (str): CC email.
|
780
|
+
subject (str): Subject of the email.
|
781
|
+
date (str): Date of the email.
|
782
|
+
body_content (str): Email body content.
|
783
|
+
is_html (bool): Whether the body_content is in HTML format.
|
784
|
+
"""
|
785
|
+
archive_folder = "Mail_Formats"
|
786
|
+
user_folder = os.path.join(archive_folder, f"{user_id}")
|
787
|
+
os.makedirs(user_folder, exist_ok=True)
|
788
|
+
|
789
|
+
# Define the full path for the CSV file (csv_filename.csv inside the mail_log_user_id folder)
|
790
|
+
eml_file_path = os.path.join(user_folder, f"{filename}.eml")
|
791
|
+
|
792
|
+
try:
|
793
|
+
with open(eml_file_path, 'w', encoding='utf-8') as eml_file:
|
794
|
+
eml_file.write(f"From: {from_email}\n")
|
795
|
+
eml_file.write(f"To: {to_email}\n")
|
796
|
+
if cc_email:
|
797
|
+
eml_file.write(f"Cc: {cc_email}\n")
|
798
|
+
eml_file.write(f"Subject: {subject}\n")
|
799
|
+
eml_file.write(f"Date: {date}\n")
|
800
|
+
eml_file.write(f"MIME-Version: 1.0\n")
|
801
|
+
|
802
|
+
if is_html:
|
803
|
+
eml_file.write(f"Content-Type: text/html; charset=utf-8\n\n")
|
804
|
+
eml_file.write(body_content)
|
805
|
+
else:
|
806
|
+
eml_file.write(f"Content-Type: text/plain; charset=utf-8\n\n")
|
807
|
+
eml_file.write(body_content)
|
808
|
+
|
809
|
+
logger.log(f"Stored simplified EML file for UID '{uid}' at {eml_file_path}")
|
810
|
+
|
811
|
+
except Exception as e:
|
812
|
+
logger.log(f"Failed to save EML for UID '{uid}': {str(e)}")
|
813
|
+
|
814
|
+
|
815
|
+
"""Retrieves the user ID from the session or cookies."""
|
816
|
+
# For simplicity, let's assume we get the user_id from cookies
|
817
|
+
if "Cookie" in self.headers:
|
818
|
+
cookie = SimpleCookie(self.headers["Cookie"])
|
819
|
+
user_id = cookie.get("user_id") # Assuming user_id is stored in a cookie
|
820
|
+
if user_id:
|
821
|
+
return user_id.value
|
822
|
+
return None
|
823
|
+
|
824
|
+
|