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.
@@ -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
+