DivyaShah2025's picture
Update app.py
3ba14ab verified
import gradio as gr
from datetime import datetime
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter
import os
from openai import OpenAI
from dotenv import load_dotenv
import json
import base64
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Mail, Attachment, FileContent, FileName, FileType, Disposition
from babel.numbers import format_currency
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.platypus import Paragraph
load_dotenv(override=True)
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
import gradio as gr
from datetime import datetime
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter
import os
from openai import OpenAI
from dotenv import load_dotenv
import json
import base64
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Mail, Attachment, FileContent, FileName, FileType, Disposition
from babel.numbers import format_currency
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.platypus import Paragraph
load_dotenv(override=True)
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
def format_inr(amount):
"""Format amount as Indian Rupees (INR) using 'Rs.' instead of symbol"""
formatted = format_currency(amount, 'INR', locale='en_IN')
# Replace the ₹ symbol with 'Rs.' to avoid font issues
return formatted.replace('₹', 'Rs.')
def parse_natural_language(prompt_text):
"""Use OpenAI to parse plain text into structured product data with per-item GST"""
system_prompt = (
"You are a helpful assistant. Extract the customer name, address (if any), gstin (if any), "
"and a list of products with description, code (optional), quantity, price per unit, "
"and GST percentage for each product from the following text. "
"Respond ONLY with a JSON object like this: "
"{"
"\"customer_name\": \"...\", "
"\"gstin\": \"...\", "
"\"address\": \"...\", "
"\"products\": [{\"description\": \"...\", \"code\": \"...\", \"qty\": ..., \"rate\": ..., \"gst_percentage\": ...}] "
"}"
)
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": prompt_text}
]
response = client.chat.completions.create(
model="gpt-3.5-turbo", # Changed from non-existent "gpt-4o-mini"
messages=messages
)
text = response.choices[0].message.content
print("LLM returned:", text)
try:
parsed = json.loads(text)
except json.JSONDecodeError:
raise ValueError(f"Could not decode response: {text}")
return parsed
def generate_proforma_invoice_from_prompt(prompt_text):
parsed = parse_natural_language(prompt_text)
customer_name = parsed.get("customer_name", "Unknown")
address = parsed.get("address", "Not provided")
gstin = parsed.get("gstin", "")
products = parsed.get("products", [])
today = datetime.today().strftime("%Y%m%d")
safe_customer_name = customer_name.replace(" ", "_")
filename = f"Proforma_Invoice_{safe_customer_name}_{today}.pdf"
grand_total = 0
product_rows = []
for row in products:
try:
description = str(row["description"])
code = str(row.get("code", ""))
qty = float(row["qty"])
rate = float(row["rate"])
gst_pct = float(row.get("gst_percentage", 0))
row_subtotal = qty * rate
row_gst = (row_subtotal * gst_pct) / 100
row_total_with_gst = row_subtotal + row_gst
grand_total += row_total_with_gst
product_rows.append({
'description': description,
'code': code,
'qty': qty,
'rate': rate,
'subtotal': row_subtotal,
'gst_pct': gst_pct,
'gst_value': row_gst,
'total_with_gst': row_total_with_gst
})
except (KeyError, ValueError) as e:
print(f"Error processing product row {row}: {e}")
continue
c = canvas.Canvas(filename, pagesize=letter)
width, height = letter
# Logo
logo_path = "Truevybeslogo.png"
if os.path.exists(logo_path):
LOGO_WIDTH = 120
LOGO_HEIGHT = 40
x_pos = width - 50 - LOGO_WIDTH
y_pos = height - 50 - LOGO_HEIGHT
c.drawImage(
logo_path,
x_pos,
y_pos,
width=LOGO_WIDTH,
height=LOGO_HEIGHT,
preserveAspectRatio=True
)
c.setFont("Helvetica-Bold", 16)
c.drawString(50, height - 70, "PROFORMA INVOICE")
c.setFont("Helvetica", 10)
c.drawString(50, height - 100, "Consignor:")
c.drawString(120, height - 100, "True Vybes")
c.drawString(50, height - 115, "D-1/902, Nilkanth Greens Maple CHS,")
c.drawString(50, height - 130, "GB Road, Manpada,")
c.drawString(50, height - 145, "Thane - 400607")
c.drawString(50, height - 160, "GSTIN: 27BV0PS7767J2ZF")
c.drawString(50, height - 190, "Consignee:")
c.drawString(120, height - 190, customer_name)
address_lines = address.split('\n')
for i, line in enumerate(address_lines):
c.drawString(120, height - 205 - (i * 15), line)
c.drawString(120, height - 205 - (len(address_lines) * 15), f"GSTIN: {gstin}")
c.drawString(400, height - 190, f"Date: {datetime.today().strftime('%d-%b-%Y')}")
# Table header
table_top = height - 250 - (len(address_lines) * 15)
c.line(50, table_top, width - 50, table_top)
c.setFont("Helvetica-Bold", 9)
c.drawString(50, table_top - 15, "Description")
c.drawString(170, table_top - 15, "Code")
c.drawString(240, table_top - 15, "Qty")
c.drawString(280, table_top - 15, "Rate/Unit")
c.drawString(360, table_top - 15, "GST %")
c.drawString(410, table_top - 15, "GST Value")
c.drawString(480, table_top - 15, "Total w/ GST")
c.line(50, table_top - 20, width - 50, table_top - 20)
# Product rows
c.setFont("Helvetica", 9)
row_y = table_top - 35
# Create a style for the description text
styles = getSampleStyleSheet()
style = styles["Normal"]
style.fontName = "Helvetica"
style.fontSize = 9
style.leading = 10
style.splitLongWords = True
style.alignment = 0 # Left align
for product in product_rows:
# Create a Paragraph for the description to enable word wrapping
desc_para = Paragraph(product['description'], style)
desc_width = 110 # Width of description column
desc_height = 20 # Initial height
# Draw the wrapped description
desc_para.wrapOn(c, desc_width, 800)
desc_para.drawOn(c, 50, row_y - desc_para.height + 5)
# Calculate the height needed for this row
row_height = max(desc_para.height, 20)
# Draw other columns
c.drawString(170, row_y, product['code'])
c.drawString(240, row_y, str(product['qty']))
c.drawString(280, row_y, format_inr(product['rate']))
c.drawString(360, row_y, f"{product['gst_pct']}%")
c.drawString(410, row_y, format_inr(product['gst_value']))
c.drawString(480, row_y, format_inr(product['total_with_gst']))
# Move to next row position based on the height needed
row_y -= row_height + 5
# Final grand total
c.line(350, row_y - 10, width - 50, row_y - 10)
c.setFont("Helvetica-Bold", 10)
c.drawString(400, row_y - 25, "GRAND TOTAL")
c.drawString(480, row_y - 25, format_inr(grand_total))
# Bank & Terms
bank_y = row_y - 60
c.setFont("Helvetica", 10)
c.drawString(50, bank_y, "Bank Details For True Vybes")
c.drawString(50, bank_y - 15, "A/C Name: True Vybes")
c.drawString(50, bank_y - 30, "A/C No.: 9747864920")
c.drawString(50, bank_y - 45, "A/C Type: Current")
c.drawString(50, bank_y - 60, "IFSC Code: KKBK0000656")
terms_y = bank_y - 90
c.drawString(50, terms_y, "Other terms and conditions:")
c.drawString(50, terms_y - 15, "GST: Extra at actuals")
c.drawString(50, terms_y - 30, "Payment terms: 50% advance and 50% before dispatch")
c.drawString(50, terms_y - 45, "Lead Time: 1 to 2 days from the date of confirmed order and receipt of payment")
c.drawString(50, terms_y - 60, "Validity: The validity of this price is 10 days from today")
c.drawString(50, terms_y - 75, "Freight: Extra on To-Pay basis")
c.line(width - 150, terms_y - 90, width - 50, terms_y - 90)
c.drawString(width - 140, terms_y - 105, "Authorised Signatory")
c.save()
send_invoice_email(filename)
return filename
def send_invoice_email(pdf_filename):
SENDGRID_API_KEY = os.getenv("SENDGRID_API_KEY")
if not SENDGRID_API_KEY:
raise RuntimeError("SENDGRID_API_KEY is not set in your environment")
message = Mail(
from_email="truevybescg@gmail.com",
to_emails="truevybescg@gmail.com",
subject="New Proforma Invoice",
html_content="<p>Dear True Vybes Team,<br><br>Please find attached the new Proforma Invoice.<br><br>Regards,<br>Automated System</p>"
)
with open(pdf_filename, "rb") as f:
data = f.read()
encoded = base64.b64encode(data).decode()
attachment = Attachment()
attachment.file_content = FileContent(encoded)
attachment.file_type = FileType("application/pdf")
attachment.file_name = FileName(os.path.basename(pdf_filename))
attachment.disposition = Disposition("attachment")
message.attachment = attachment
try:
sg = SendGridAPIClient(SENDGRID_API_KEY)
response = sg.send(message)
print(f"Email sent. Status code: {response.status_code}")
except Exception as e:
print(f"Error sending email: {e}")
# Gradio Interface
with gr.Blocks() as demo:
gr.Markdown("# 📄 True Vybes NLP Proforma Invoice Generator")
user_prompt = gr.Textbox(
label="Proforma Invoice Request",
placeholder="E.g. Create PI for Customer, GST-18902801280128, Addresss -Andheri Mumbai, 300 candles at 20 each with 5% GST, 500 umbrella at 200 each with GST 18%",
lines=4
)
generate_btn = gr.Button("Generate Invoice")
output_file = gr.File(label="Download Invoice")
generate_btn.click(
fn=generate_proforma_invoice_from_prompt,
inputs=user_prompt,
outputs=output_file
)
if __name__ == "__main__":
demo.launch(share=True)