import streamlit as st import speech_recognition as sr import requests from twilio.rest import Client import re from word2number import w2n import os import datetime import json from dotenv import load_dotenv # Load environment variables from .env file load_dotenv() # Twilio credentials from environment variables TWILIO_SID = os.getenv('TWILIO_SID') TWILIO_AUTH_TOKEN = os.getenv('TWILIO_AUTH_TOKEN') TWILIO_PHONE_NUMBER = os.getenv('TWILIO_PHONE_NUMBER') # Invoice Generator API credentials from environment variables INVOICE_GEN_API_URL = os.getenv('INVOICE_GEN_API_URL') INVOICE_GEN_API_KEY = os.getenv('INVOICE_GEN_API_KEY') # Gemini API credentials from environment variables GEMINI_API_KEY = os.getenv('GEMINI_API_KEY') # Initialize the speech recognizer recognizer = sr.Recognizer() # Session state initialization if "is_listening" not in st.session_state: st.session_state.is_listening = False if "customer_name" not in st.session_state: st.session_state.customer_name = "" if "customer_number" not in st.session_state: st.session_state.customer_number = "" if "bill_content" not in st.session_state: st.session_state.bill_content = "" if "currency" not in st.session_state: st.session_state.currency = "USD" # Language mapping for Google Speech Recognition language_map = { 'English': 'en-US', 'Urdu': 'ur' } # Function to recognize speech from the microphone def recognize_speech(language_code): """Listen to the microphone and return the recognized text in the selected language""" with sr.Microphone() as source: st.write("Listening...") audio = recognizer.listen(source) try: text = recognizer.recognize_google(audio, language=language_code) st.write(f"You said: {text}") return text except Exception as e: st.write("Sorry, I couldn't understand the audio.") return None # Function to toggle the listening state def toggle_listen(button_key): """Toggle listening on and off""" st.session_state.is_listening = not st.session_state.is_listening def convert_number_words_to_digits(text, language): """Converts numbers written in words to digits in the text while preserving leading zeros.""" if language == 'English': # Check if the text might be a phone number (contains only digits, spaces, or common separators) stripped_text = ''.join(c for c in text if c.isdigit() or c.isspace() or c in '+-') if stripped_text and all(c.isdigit() or c.isspace() or c in '+-' for c in text): # This is likely already a number, just return it as is to preserve any leading zeros return text # Find and replace number words with digits words = text.split() for i, word in enumerate(words): try: # Try to convert the word to a number num = w2n.word_to_num(word) words[i] = str(num) except ValueError: # If not a number word, keep original continue return ' '.join(words) elif language == 'Urdu': # First check if this is already a phone number to preserve it if re.match(r'^[\d\s\+\-]+$', text): return text # Urdu number word mapping persian_numbers = { "ایک": "1", "دو": "2", "تین": "3", "چار": "4", "پانچ": "5", "چھے": "6", "سات": "7", "آٹھ": "8", "نو": "9", "دس": "10", "گیارہ": "11", "بارہ": "12", "تیرہ": "13", "چودہ": "14", "پندرہ": "15", "سولہ": "16", "سترہ": "17", "اٹھارہ": "18", "انیس": "19", "بیس": "20", "تیس": "30", "چالیس": "40", "پچاس": "50", "ساٹھ": "60", "ستر": "70", "اسّی": "80", "نوے": "90", "سو": "100", "صفر": "0" # Adding zero explicitly } # Replace Persian/Urdu numbers with digits using regex for word, digit in persian_numbers.items(): text = re.sub(r'\b' + word + r'\b', digit, text, flags=re.IGNORECASE) return text else: return text # Function to extract structured item details from Gemini API response def extract_item_details_from_gemini(bill_content): """Extract structured item details from Gemini API.""" prompt = f"Extract structured JSON for bill items, including item names, quantities, and prices from the following text: '{bill_content}'" # Corrected Gemini API URL gemini_api_url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key={GEMINI_API_KEY}" headers = { "Content-Type": "application/json" } payload = { "contents": [{ "parts": [{"text": prompt}] }] } try: response = requests.post(gemini_api_url, json=payload, headers=headers) response.raise_for_status() data = response.json() # # Display the structured response from Gemini # st.write("Structured Data Extracted from Gemini API:") # st.json(data) # The JSON response is inside the text field as a string, so we need to parse it raw_json = data['candidates'][0]['content']['parts'][0]['text'] # Clean the response from unwanted characters and backticks that are not part of JSON cleaned_json = raw_json.strip('```json').strip().strip('```').strip() # Try to parse the cleaned JSON try: structured_items = json.loads(cleaned_json) return structured_items except json.JSONDecodeError as e: st.write(f"Error parsing JSON: {e}") return None except requests.exceptions.RequestException as e: st.write(f"Error extracting item details: {e}") return None # Function to generate an invoice using Invoice Generator API def generate_invoice_pdf(customer_name, customer_number, items, currency): """Generate invoice PDF using Invoice Generator API.""" current_date = datetime.datetime.now().strftime("%b %d, %Y") due_date = (datetime.datetime.now() + datetime.timedelta(days=7)).strftime("%b %d, %Y") invoice_number = f"INV-{datetime.datetime.now().strftime('%Y%m%d%H%M%S')}" data = { "from": "Saqib Zeen House (Textile)", "to": customer_name, "logo": "https://example.com/logo.png", "number": invoice_number, "date": current_date, "due_date": due_date, "currency": currency, "notes": """● Thank you for your business! We appreciate your trust and look forward to serving you again. ● Great choice! Quality products, fair pricing, and timely service—what more could you ask for? ● If this invoice were a novel, the ending would be "Paid in Full." Let's make it a bestseller! ● Questions? Concerns? Compliments? We're just a message away.""", "terms": """● Payment is due by the due date to keep our accountants happy (and to avoid late fees). ● Late payments may result in a [X]% charge per month—or worse, a strongly worded email. ● If you notice any discrepancies, please inform us within [X] days. We promise we didn't do it on purpose. ● We accept payments via [Bank Transfer, PayPal, Credit Card, etc.]. Choose wisely, but choose soon.""" } # Add items to the invoice for i, item in enumerate(items): data[f"items[{i}][name]"] = item["item_name"] data[f"items[{i}][quantity]"] = item["quantity"] # Fix the price field handling to handle different field names if "price" in item: data[f"items[{i}][unit_cost]"] = item["price"] elif "price_per_item" in item: data[f"items[{i}][unit_cost]"] = item["price_per_item"] else: # Fallback if neither field is present st.write("Warning: Price field not found in item data") data[f"items[{i}][unit_cost]"] = 0 headers = { 'Authorization': f'Bearer {INVOICE_GEN_API_KEY}', 'Content-Type': 'application/x-www-form-urlencoded' } try: response = requests.post(INVOICE_GEN_API_URL, headers=headers, data=data) response.raise_for_status() with open("invoice.pdf", "wb") as f: f.write(response.content) return "invoice.pdf" except requests.exceptions.RequestException as e: st.write(f"Error generating invoice: {e}") return None def upload_to_tempfiles(file_path): """Uploads the file to tempfiles.org and returns the public link.""" url = "https://tmpfiles.org/api/v1/upload" # Open the file in binary mode with open(file_path, 'rb') as file: files = {'file': file} try: response = requests.post(url, files=files) response.raise_for_status() # Extract the file URL from the response response_json = response.json() if response_json.get("status") == "success": file_url = response_json["data"].get("url") # Convert the URL to direct download link by inserting '/dl' after tmpfiles.org if file_url and "tmpfiles.org" in file_url: file_url = file_url.replace("tmpfiles.org/", "tmpfiles.org/dl/") return file_url else: st.write(f"Error: {response_json.get('status')}") return None except requests.exceptions.RequestException as e: st.write(f"Error uploading file: {e}") return None def send_pdf_via_whatsapp(pdf_file, customer_number): """Uploads the PDF to tempfiles.org and sends the generated PDF to the customer via WhatsApp.""" # Upload the PDF to tempfiles.org pdf_file_url = upload_to_tempfiles(pdf_file) st.write(pdf_file_url) if not pdf_file_url: return "Error: Could not upload file to tempfiles.org." client = Client(TWILIO_SID, TWILIO_AUTH_TOKEN) try: message = client.messages.create( body="Boss, this bill is honored to be in your inbox. Now, do it a favor and make it disappear.", from_=TWILIO_PHONE_NUMBER, media_url=[pdf_file_url], # Use the tempfiles.org link here to=f'whatsapp:{customer_number}' ) return f"Bill successfully sent to {customer_number}" except Exception as e: return f"Error sending bill via WhatsApp: {str(e)}" # Apply modern dark theme styling with the provided color palette st.markdown( """ """, unsafe_allow_html=True) # Title st.markdown('
Speech Recognition Language
", unsafe_allow_html=True) language_options = ['English', 'Urdu'] selected_language = st.selectbox(" ", language_options, label_visibility="collapsed") st.markdown("", unsafe_allow_html=True) st.markdown("Select Currency
", unsafe_allow_html=True) currency_options = ['USD', 'PKR'] selected_currency = st.selectbox(" ", currency_options, label_visibility="collapsed") st.session_state.currency = selected_currency st.markdown('Customer Name
", unsafe_allow_html=True) customer_name = st.text_area("Enter Customer Name", st.session_state.customer_name, height=70, key="name_input", label_visibility="collapsed") st.session_state.customer_name = customer_name col_record, col_space = st.columns([1, 1]) with col_record: record_name_btn = st.button("Record Name", key="name_button") if record_name_btn: toggle_listen("name_button") if st.session_state.is_listening: recognized_name = recognize_speech(language_map[selected_language]) if recognized_name: st.session_state.customer_name = recognized_name st.markdown('Customer Number
", unsafe_allow_html=True) customer_number = st.text_area("Enter Customer Number", st.session_state.customer_number, height=70, key="number_input", label_visibility="collapsed") st.session_state.customer_number = customer_number col_record, col_space = st.columns([1, 1]) with col_record: record_number_btn = st.button("Record Number", key="number_button") if record_number_btn: toggle_listen("number_button") if st.session_state.is_listening: recognized_number = recognize_speech(language_map[selected_language]) if recognized_number: recognized_number = convert_number_words_to_digits(recognized_number, selected_language) st.session_state.customer_number = recognized_number st.markdown('Bill Content
", unsafe_allow_html=True) bill_content = st.text_area("Enter Bill Content", st.session_state.bill_content, height=140, key="bill_input", label_visibility="collapsed") st.session_state.bill_content = bill_content col_record, col_space = st.columns([1, 1]) with col_record: record_content_btn = st.button("Record Bill Content", key="content_button") if record_content_btn: toggle_listen("content_button") if st.session_state.is_listening: recognized_bill_content = recognize_speech(language_map[selected_language]) if recognized_bill_content: recognized_bill_content = convert_number_words_to_digits(recognized_bill_content, selected_language) st.session_state.bill_content = recognized_bill_content st.markdown('