zaiffi commited on
Commit
f9150d0
·
1 Parent(s): 0889fde

voice-powered BillBot invoice app with complete code, docs and assets

Browse files
README.md CHANGED
@@ -1,14 +1,207 @@
1
  ---
2
  title: BillBot
3
- emoji: 🔥
4
- colorFrom: indigo
5
- colorTo: blue
6
  sdk: streamlit
7
- sdk_version: 1.43.2
8
  app_file: app.py
9
  pinned: false
10
- license: apache-2.0
11
- short_description: 'BillBot: Voice-to-invoice solution with WhatsApp delivery'
12
  ---
13
 
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: BillBot
3
+ emoji: 🎙️
4
+ colorFrom: green
5
+ colorTo: brown
6
  sdk: streamlit
7
+ sdk_version: 1.42.2
8
  app_file: app.py
9
  pinned: false
 
 
10
  ---
11
 
12
+ # BillBot: Speak, and Let AI Create Your Bill
13
+
14
+ BillBot is a voice-powered invoicing application that transforms spoken descriptions into professionally formatted invoices, making billing effortless for businesses of all sizes. Simply speak your customer details and items, and BillBot handles the rest.
15
+
16
+ ![BillBot Main Interface](images/main_interface.png)
17
+ ![BillBot Main Interface](images/main_interface2.png)
18
+
19
+ ## Problem Statement
20
+
21
+ Small retailers and market vendors face significant challenges with traditional billing processes:
22
+ - Manual invoice creation is time-consuming and error-prone
23
+ - Paper-based systems lead to document loss and disorganization
24
+ - Tracking customer information relies on memory or scattered notes
25
+ - Limited ability to send digital receipts to customers immediately
26
+
27
+ BillBot addresses these pain points by providing a voice-first, digital invoicing solution that's accessible even during busy customer interactions.
28
+
29
+ ## Features
30
+
31
+ - **Voice Recognition**: Capture customer details and bill contents through voice input in multiple languages
32
+ - **Multilingual Support**: Currently supports English and Urdu
33
+ - **Currency Options**: Generate invoices in USD or PKR
34
+ - **Smart Number Processing**: Automatically converts spoken numbers (e.g., "five") to digits (5)
35
+ - **AI-Powered Item Parsing**: Uses Gemini AI to extract structured data from natural language descriptions
36
+ - **Professional Invoice Generation**: Creates PDF invoices with company branding and all required details
37
+ - **Instant Delivery**: Sends invoices directly to customers via WhatsApp
38
+ - **Modern UI**: Clean, responsive interface with a dark-themed design
39
+
40
+ ![Voice Recognition in Action](images/voice_recognition.png)
41
+
42
+ ## Tech Stack
43
+
44
+ - **Frontend & Application**: Streamlit
45
+ - **Speech Recognition**: Google Speech Recognition API via SpeechRecognition library
46
+ - **Item Parsing & Structuring**: Google Gemini 2.0 API
47
+ - **Invoice Generation**: Invoice Generator API
48
+ - **Messaging**: Twilio API (WhatsApp integration)
49
+ - **File Hosting**: tmpfiles.org API for temporary file hosting
50
+ - **Styling**: Custom CSS with Streamlit components
51
+
52
+ ## Getting Started
53
+
54
+ ### Prerequisites
55
+
56
+ - Python 3.7 or later
57
+ - A microphone for voice input
58
+ - API keys for Gemini, Invoice Generator, and Twilio
59
+ - WhatsApp Business account connected to Twilio
60
+
61
+ ### Installation
62
+
63
+ 1. Clone the repository:
64
+ ```bash
65
+ git clone https://github.com/zaiffishiekh01/billbot.git
66
+ cd billbot
67
+ ```
68
+
69
+ 2. Install required packages:
70
+ ```bash
71
+ pip install -r requirements.txt
72
+ ```
73
+
74
+ 3. Create a `.env` file in the project root with your API credentials:
75
+ ```
76
+ TWILIO_SID=your_twilio_sid
77
+ TWILIO_AUTH_TOKEN=your_twilio_auth_token
78
+ TWILIO_PHONE_NUMBER=whatsapp:+number
79
+ INVOICE_GEN_API_URL=https://invoice-generator.com
80
+ INVOICE_GEN_API_KEY=your_invoice_generator_api_key
81
+ GEMINI_API_KEY=your_gemini_api_key
82
+ ```
83
+
84
+ ### API Setup Guide
85
+
86
+ #### 1. Gemini API
87
+ - Visit [Google AI Studio](https://makersuite.google.com/app/apikey)
88
+ - Create an account if you don't have one
89
+ - Generate an API key
90
+ - Add it to your `.env` file as `GEMINI_API_KEY`
91
+
92
+ #### 2. Invoice Generator API
93
+ - Go to [Invoice Generator](https://invoice-generator.com/)
94
+ - Create an account and subscribe to their API service
95
+ - Locate your API key in your account dashboard
96
+ - Add the API URL and key to your `.env` file
97
+
98
+ #### 3. Twilio API (for WhatsApp)
99
+ - Sign up at [Twilio](https://www.twilio.com/)
100
+ - Navigate to the WhatsApp section and follow setup instructions
101
+ - Get your SID and Auth Token from the dashboard
102
+ - Add these credentials to your `.env` file
103
+ - Format your WhatsApp number as `whatsapp:+number` in the .env file
104
+
105
+ ### Hugging Face Spaces Integration
106
+
107
+ When deploying BillBot on Hugging Face Spaces, use Hugging Face's Secret Management to securely store your API credentials:
108
+
109
+ 1. Navigate to your Space settings
110
+ 2. Go to the Secrets section
111
+ 3. Add each environment variable from your `.env` file as a secret
112
+ 4. Hugging Face Spaces will automatically make these secrets available to your application
113
+
114
+ This approach ensures your API keys remain secure and aren't exposed in your code repository.
115
+
116
+ ### Running the Application
117
+
118
+ Start the Streamlit app:
119
+ ```bash
120
+ streamlit run app.py
121
+ ```
122
+
123
+ The application will open in your default web browser.
124
+
125
+ ## Usage Guide
126
+
127
+ ![BillBot Field Inputs](images/billbot-fields.png)
128
+
129
+ 1. **Select Language**: Choose English or Urdu for voice recognition
130
+ 2. **Select Currency**: Choose USD or PKR for the invoice
131
+ 3. **Enter Customer Information**:
132
+ - Type or click "Record Name" to capture customer name via voice
133
+ - Type or click "Record Number" to capture customer phone number
134
+ 4. **Enter Bill Content**:
135
+ - Type or click "Record Bill Content" to describe items, quantities, and prices
136
+ - Example: "2 cotton shirts at 15 dollars each and 1 pair of jeans at 40 dollars"
137
+ 5. **Generate and Send**: Click the button to process and send the invoice
138
+ 6. **Delivery**: The customer receives a professional PDF invoice via WhatsApp
139
+
140
+ ![Invoice on Mobile](images/whatsapp-msg.png)
141
+ ![Invoice on Mobile](images/mobile-invoice.png)
142
+
143
+ ## Understanding tmpfiles.org Integration
144
+
145
+ BillBot uses tmpfiles.org for temporary file hosting to deliver PDF invoices via WhatsApp. Here's what you need to know:
146
+
147
+ - **Temporary Storage**: Files uploaded to tmpfiles.org are only stored for 60 minutes
148
+ - **Privacy Considerations**: Since files are publicly accessible via the generated URL, sensitive information should be handled with care
149
+ - **How It Works**:
150
+ 1. BillBot generates the invoice PDF locally
151
+ 2. The PDF is uploaded to tmpfiles.org to get a publicly accessible URL
152
+ 3. Twilio uses this URL to send the PDF via WhatsApp
153
+ 4. After 60 minutes, the file is automatically deleted from tmpfiles.org
154
+
155
+ > **⚠️ Privacy Warning**: Files uploaded to tmpfiles.org are publicly accessible for 60 minutes. For enhanced privacy and security in a production environment, consider implementing a more secure file storage solution with proper authentication.
156
+
157
+ ## Current Limitations
158
+
159
+ - **Temporary File Storage**: Files on tmpfiles.org are only available for 60 minutes and are publicly accessible
160
+ - **Phone Number Validation**: No validation for phone number formats
161
+ - **Error Handling**: Limited error handling for API failures
162
+ - **Voice Recognition Accuracy**: May struggle with strong accents or background noise
163
+ - **Item Parsing**: Complex item descriptions might not always be structured correctly
164
+ - **Template Customization**: Limited invoice template customization options
165
+ - **Offline Operation**: Requires internet connection for all operations
166
+
167
+ ## Future Improvements
168
+
169
+ - **Secure File Storage**: Implement a more secure file storage solution with proper authentication
170
+ - **Enhanced Voice Recognition**: Improve accuracy and add more languages
171
+ - **Inventory Integration**: Connect to inventory systems to update stock levels
172
+ - **Customer Database**: Save customer information for quick selection in future transactions
173
+ - **Receipt Templates**: Add multiple customizable templates
174
+ - **Financial Reporting**: Generate sales reports and analytics
175
+ - **Payment Integration**: Allow customers to pay directly from the invoice
176
+ - **Offline Mode**: Enable basic functionality without internet connection
177
+ - **Mobile App**: Develop dedicated mobile applications
178
+ - **Bulk Operations**: Process multiple invoices simultaneously
179
+ - **Custom Branding**: More options for business branding on invoices
180
+
181
+ ## Environment Variables
182
+
183
+ For security reasons, we don't share the actual `.env` file. Here's a template with the required variables:
184
+
185
+ ```
186
+ TWILIO_SID=your_twilio_sid
187
+ TWILIO_AUTH_TOKEN=your_twilio_auth_token
188
+ TWILIO_PHONE_NUMBER=whatsapp:+number
189
+ INVOICE_GEN_API_URL=https://invoice-generator.com
190
+ INVOICE_GEN_API_KEY=your_invoice_generator_api_key
191
+ GEMINI_API_KEY=your_gemini_api_key
192
+ ```
193
+
194
+ ## Troubleshooting
195
+
196
+ - **WhatsApp Messages Not Sending**: Ensure your Twilio WhatsApp account is properly set up and the customer's number is in the correct format (with country code)
197
+ - **Voice Recognition Issues**: Make sure your microphone is working and try speaking clearly in a quiet environment
198
+ - **PDF Generation Fails**: Check your Invoice Generator API credentials
199
+ - **Gemini API Errors**: Verify your API key and ensure you're within usage limits
200
+
201
+ ## About the Developer
202
+
203
+ This project was developed by Muhammad Huzaifa Saqib (zaiffi), a developer passionate about creating practical AI-powered solutions for everyday business problems.
204
+
205
+ - GitHub: [zaiffishiekh01](https://github.com/zaiffishiekh01)
206
+ - LinkedIn: [Muhammad Huzaifa Saqib](https://www.linkedin.com/in/muhammad-huzaifa-saqib-90a1a9324/)
207
+ - Email: [email protected]
app.py ADDED
@@ -0,0 +1,650 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import speech_recognition as sr
3
+ import requests
4
+ from twilio.rest import Client
5
+ import re
6
+ from word2number import w2n
7
+ import os
8
+ import datetime
9
+ import json
10
+ from dotenv import load_dotenv
11
+
12
+ # Load environment variables from .env file
13
+ load_dotenv()
14
+
15
+ # Twilio credentials from environment variables
16
+ TWILIO_SID = os.getenv('TWILIO_SID')
17
+ TWILIO_AUTH_TOKEN = os.getenv('TWILIO_AUTH_TOKEN')
18
+ TWILIO_PHONE_NUMBER = os.getenv('TWILIO_PHONE_NUMBER')
19
+
20
+ # Invoice Generator API credentials from environment variables
21
+ INVOICE_GEN_API_URL = os.getenv('INVOICE_GEN_API_URL')
22
+ INVOICE_GEN_API_KEY = os.getenv('INVOICE_GEN_API_KEY')
23
+
24
+ # Gemini API credentials from environment variables
25
+ GEMINI_API_KEY = os.getenv('GEMINI_API_KEY')
26
+
27
+
28
+
29
+ # Initialize the speech recognizer
30
+ recognizer = sr.Recognizer()
31
+
32
+ # Session state initialization
33
+ if "is_listening" not in st.session_state:
34
+ st.session_state.is_listening = False
35
+ if "customer_name" not in st.session_state:
36
+ st.session_state.customer_name = ""
37
+ if "customer_number" not in st.session_state:
38
+ st.session_state.customer_number = ""
39
+ if "bill_content" not in st.session_state:
40
+ st.session_state.bill_content = ""
41
+ if "currency" not in st.session_state:
42
+ st.session_state.currency = "USD"
43
+
44
+ # Language mapping for Google Speech Recognition
45
+ language_map = {
46
+ 'English': 'en-US',
47
+ 'Urdu': 'ur'
48
+ }
49
+
50
+ # Function to recognize speech from the microphone
51
+ def recognize_speech(language_code):
52
+ """Listen to the microphone and return the recognized text in the selected language"""
53
+ with sr.Microphone() as source:
54
+ st.write("Listening...")
55
+ audio = recognizer.listen(source)
56
+ try:
57
+ text = recognizer.recognize_google(audio, language=language_code)
58
+ st.write(f"You said: {text}")
59
+ return text
60
+ except Exception as e:
61
+ st.write("Sorry, I couldn't understand the audio.")
62
+ return None
63
+
64
+ # Function to toggle the listening state
65
+ def toggle_listen(button_key):
66
+ """Toggle listening on and off"""
67
+ st.session_state.is_listening = not st.session_state.is_listening
68
+
69
+
70
+
71
+ def convert_number_words_to_digits(text, language):
72
+ """Converts numbers written in words to digits in the text while preserving leading zeros."""
73
+ if language == 'English':
74
+ # Check if the text might be a phone number (contains only digits, spaces, or common separators)
75
+ stripped_text = ''.join(c for c in text if c.isdigit() or c.isspace() or c in '+-')
76
+ if stripped_text and all(c.isdigit() or c.isspace() or c in '+-' for c in text):
77
+ # This is likely already a number, just return it as is to preserve any leading zeros
78
+ return text
79
+
80
+ # Find and replace number words with digits
81
+ words = text.split()
82
+ for i, word in enumerate(words):
83
+ try:
84
+ # Try to convert the word to a number
85
+ num = w2n.word_to_num(word)
86
+ words[i] = str(num)
87
+ except ValueError:
88
+ # If not a number word, keep original
89
+ continue
90
+ return ' '.join(words)
91
+ elif language == 'Urdu':
92
+ # First check if this is already a phone number to preserve it
93
+ if re.match(r'^[\d\s\+\-]+$', text):
94
+ return text
95
+
96
+ # Urdu number word mapping
97
+ persian_numbers = {
98
+ "ایک": "1", "دو": "2", "تین": "3", "چار": "4", "پانچ": "5",
99
+ "چھے": "6", "سات": "7", "آٹھ": "8", "نو": "9", "دس": "10",
100
+ "گیارہ": "11", "بارہ": "12", "تیرہ": "13", "چودہ": "14",
101
+ "پندرہ": "15", "سولہ": "16", "سترہ": "17", "اٹھارہ": "18",
102
+ "انیس": "19", "بیس": "20",
103
+ "تیس": "30", "چالیس": "40", "پچاس": "50", "ساٹھ": "60",
104
+ "ستر": "70", "اسّی": "80", "نوے": "90", "سو": "100",
105
+ "صفر": "0" # Adding zero explicitly
106
+ }
107
+ # Replace Persian/Urdu numbers with digits using regex
108
+ for word, digit in persian_numbers.items():
109
+ text = re.sub(r'\b' + word + r'\b', digit, text, flags=re.IGNORECASE)
110
+ return text
111
+ else:
112
+ return text
113
+
114
+
115
+
116
+ # Function to extract structured item details from Gemini API response
117
+ def extract_item_details_from_gemini(bill_content):
118
+ """Extract structured item details from Gemini API."""
119
+ prompt = f"Extract structured JSON for bill items, including item names, quantities, and prices from the following text: '{bill_content}'"
120
+
121
+ # Corrected Gemini API URL
122
+ gemini_api_url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key={GEMINI_API_KEY}"
123
+
124
+ headers = {
125
+ "Content-Type": "application/json"
126
+ }
127
+
128
+ payload = {
129
+ "contents": [{
130
+ "parts": [{"text": prompt}]
131
+ }]
132
+ }
133
+
134
+ try:
135
+ response = requests.post(gemini_api_url, json=payload, headers=headers)
136
+ response.raise_for_status()
137
+ data = response.json()
138
+
139
+ # # Display the structured response from Gemini
140
+ # st.write("Structured Data Extracted from Gemini API:")
141
+ # st.json(data)
142
+
143
+ # The JSON response is inside the text field as a string, so we need to parse it
144
+ raw_json = data['candidates'][0]['content']['parts'][0]['text']
145
+
146
+ # Clean the response from unwanted characters and backticks that are not part of JSON
147
+ cleaned_json = raw_json.strip('```json').strip().strip('```').strip()
148
+
149
+ # Try to parse the cleaned JSON
150
+ try:
151
+ structured_items = json.loads(cleaned_json)
152
+ return structured_items
153
+ except json.JSONDecodeError as e:
154
+ st.write(f"Error parsing JSON: {e}")
155
+ return None
156
+
157
+ except requests.exceptions.RequestException as e:
158
+ st.write(f"Error extracting item details: {e}")
159
+ return None
160
+
161
+ # Function to generate an invoice using Invoice Generator API
162
+ def generate_invoice_pdf(customer_name, customer_number, items, currency):
163
+ """Generate invoice PDF using Invoice Generator API."""
164
+
165
+ current_date = datetime.datetime.now().strftime("%b %d, %Y")
166
+ due_date = (datetime.datetime.now() + datetime.timedelta(days=7)).strftime("%b %d, %Y")
167
+ invoice_number = f"INV-{datetime.datetime.now().strftime('%Y%m%d%H%M%S')}"
168
+
169
+ data = {
170
+ "from": "Saqib Zeen House (Textile)",
171
+ "to": customer_name,
172
+ "logo": "https://example.com/logo.png",
173
+ "number": invoice_number,
174
+ "date": current_date,
175
+ "due_date": due_date,
176
+ "currency": currency,
177
+ "notes": """● Thank you for your business! We appreciate your trust and look forward to serving you again.
178
+ ● Great choice! Quality products, fair pricing, and timely service—what more could you ask for?
179
+ ● If this invoice were a novel, the ending would be "Paid in Full." Let's make it a bestseller!
180
+ ● Questions? Concerns? Compliments? We're just a message away.""",
181
+ "terms": """● Payment is due by the due date to keep our accountants happy (and to avoid late fees).
182
+ ● Late payments may result in a [X]% charge per month—or worse, a strongly worded email.
183
+ ● If you notice any discrepancies, please inform us within [X] days. We promise we didn't do it on purpose.
184
+ ● We accept payments via [Bank Transfer, PayPal, Credit Card, etc.]. Choose wisely, but choose soon."""
185
+ }
186
+
187
+ # Add items to the invoice
188
+ for i, item in enumerate(items):
189
+ data[f"items[{i}][name]"] = item["item_name"]
190
+ data[f"items[{i}][quantity]"] = item["quantity"]
191
+
192
+ # Fix the price field handling to handle different field names
193
+ if "price" in item:
194
+ data[f"items[{i}][unit_cost]"] = item["price"]
195
+ elif "price_per_item" in item:
196
+ data[f"items[{i}][unit_cost]"] = item["price_per_item"]
197
+ else:
198
+ # Fallback if neither field is present
199
+ st.write("Warning: Price field not found in item data")
200
+ data[f"items[{i}][unit_cost]"] = 0
201
+
202
+ headers = {
203
+ 'Authorization': f'Bearer {INVOICE_GEN_API_KEY}',
204
+ 'Content-Type': 'application/x-www-form-urlencoded'
205
+ }
206
+
207
+ try:
208
+ response = requests.post(INVOICE_GEN_API_URL, headers=headers, data=data)
209
+ response.raise_for_status()
210
+
211
+ with open("invoice.pdf", "wb") as f:
212
+ f.write(response.content)
213
+
214
+ return "invoice.pdf"
215
+ except requests.exceptions.RequestException as e:
216
+ st.write(f"Error generating invoice: {e}")
217
+ return None
218
+
219
+
220
+ def upload_to_tempfiles(file_path):
221
+ """Uploads the file to tempfiles.org and returns the public link."""
222
+ url = "https://tmpfiles.org/api/v1/upload"
223
+
224
+ # Open the file in binary mode
225
+ with open(file_path, 'rb') as file:
226
+ files = {'file': file}
227
+ try:
228
+ response = requests.post(url, files=files)
229
+ response.raise_for_status()
230
+
231
+ # Extract the file URL from the response
232
+ response_json = response.json()
233
+ if response_json.get("status") == "success":
234
+ file_url = response_json["data"].get("url")
235
+ # Convert the URL to direct download link by inserting '/dl' after tmpfiles.org
236
+ if file_url and "tmpfiles.org" in file_url:
237
+ file_url = file_url.replace("tmpfiles.org/", "tmpfiles.org/dl/")
238
+ return file_url
239
+ else:
240
+ st.write(f"Error: {response_json.get('status')}")
241
+ return None
242
+ except requests.exceptions.RequestException as e:
243
+ st.write(f"Error uploading file: {e}")
244
+ return None
245
+
246
+
247
+
248
+ def send_pdf_via_whatsapp(pdf_file, customer_number):
249
+ """Uploads the PDF to tempfiles.org and sends the generated PDF to the customer via WhatsApp."""
250
+ # Upload the PDF to tempfiles.org
251
+ pdf_file_url = upload_to_tempfiles(pdf_file)
252
+ st.write(pdf_file_url)
253
+
254
+ if not pdf_file_url:
255
+ return "Error: Could not upload file to tempfiles.org."
256
+
257
+ client = Client(TWILIO_SID, TWILIO_AUTH_TOKEN)
258
+
259
+ try:
260
+ message = client.messages.create(
261
+ body="Boss, this bill is honored to be in your inbox. Now, do it a favor and make it disappear.",
262
+ from_=TWILIO_PHONE_NUMBER,
263
+ media_url=[pdf_file_url], # Use the tempfiles.org link here
264
+ to=f'whatsapp:{customer_number}'
265
+ )
266
+ return f"Bill successfully sent to {customer_number}"
267
+ except Exception as e:
268
+ return f"Error sending bill via WhatsApp: {str(e)}"
269
+
270
+
271
+
272
+
273
+ # Apply modern dark theme styling with the provided color palette
274
+ st.markdown(
275
+ """
276
+ <style>
277
+ @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
278
+
279
+ :root {
280
+ --dark-green: #2C3930;
281
+ --medium-green: #3F4F44;
282
+ --accent-brown: #A27B5C;
283
+ --light-cream: #DCD7C9;
284
+ }
285
+
286
+ body {
287
+ font-family: 'Poppins', sans-serif;
288
+ background-color: var(--dark-green);
289
+ color: var(--light-cream);
290
+ }
291
+
292
+ /* Main container styling */
293
+ .stApp {
294
+ background: linear-gradient(135deg, var(--dark-green) 0%, #263228 100%);
295
+ }
296
+
297
+ /* Header styling */
298
+ .title {
299
+ font-family: 'Poppins', sans-serif;
300
+ text-align: center;
301
+ font-size: 3.5rem;
302
+ font-weight: 700;
303
+ background: linear-gradient(90deg, var(--accent-brown), var(--light-cream) 70%);
304
+ -webkit-background-clip: text;
305
+ -webkit-text-fill-color: transparent;
306
+ text-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1);
307
+ letter-spacing: 1px;
308
+ margin-top: 1.5rem;
309
+ margin-bottom: 0.5rem;
310
+ transform: scale(1);
311
+ transition: transform 0.3s ease-in-out;
312
+ }
313
+
314
+ .title:hover {
315
+ transform: scale(1.02);
316
+ }
317
+
318
+ .tagline {
319
+ font-family: 'Poppins', sans-serif;
320
+ text-align: center;
321
+ font-size: 1.5rem;
322
+ color: var(--light-cream);
323
+ font-weight: 300;
324
+ margin-bottom: 2.5rem;
325
+ opacity: 0.9;
326
+ letter-spacing: 0.5px;
327
+ }
328
+
329
+ /* Card styling */
330
+ .card {
331
+ background: rgba(63, 79, 68, 0.25);
332
+ backdrop-filter: blur(8px);
333
+ -webkit-backdrop-filter: blur(8px);
334
+ border-radius: 16px;
335
+ border: 1px solid rgba(162, 123, 92, 0.2);
336
+ padding: 28px;
337
+ margin-bottom: 24px;
338
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
339
+ transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
340
+ }
341
+
342
+ .card:hover {
343
+ transform: translateY(-5px);
344
+ box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
345
+ border: 1px solid rgba(162, 123, 92, 0.4);
346
+ }
347
+
348
+
349
+
350
+ /* Input field styling */
351
+ .stTextInput > div > div > input, .stTextArea > div > div > textarea {
352
+ background-color: rgba(44, 57, 48, 0.8) !important; /* Increased opacity */
353
+ border: 1px solid rgba(162, 123, 92, 0.3) !important;
354
+ color: var(--light-cream) !important;
355
+ border-radius: 8px !important;
356
+ padding: 12px 16px !important;
357
+ font-size: 16px !important;
358
+ transition: all 0.3s ease !important;
359
+ }
360
+
361
+ .stTextInput > div > div > input:hover, .stTextArea > div > div > textarea:hover {
362
+ background-color: rgba(44, 57, 48, 0.9) !important; /* Even higher opacity on hover */
363
+ border: 1px solid rgba(162, 123, 92, 0.5) !important; /* More visible border on hover */
364
+ }
365
+
366
+ .stTextInput > div > div > input:focus, .stTextArea > div > div > textarea:focus {
367
+ background-color: rgba(44, 57, 48, 1) !important; /* Full opacity on focus */
368
+ border: 1px solid var(--accent-brown) !important;
369
+ box-shadow: 0 0 0 2px rgba(162, 123, 92, 0.2) !important;
370
+ }
371
+
372
+ /* Select box styling */
373
+ .stSelectbox > div > div {
374
+ background-color: rgba(44, 57, 48, 0.6) !important;
375
+ border: 1px solid rgba(162, 123, 92, 0.3) !important;
376
+ border-radius: 8px !important;
377
+ color: var(--light-cream) !important;
378
+ }
379
+
380
+ .stSelectbox > div > div > div {
381
+ color: var(--light-cream) !important;
382
+ font-size: 16px !important;
383
+ }
384
+
385
+ .stSelectbox > div > div:hover {
386
+ border: 1px solid var(--accent-brown) !important;
387
+ }
388
+
389
+ /* Button styling */
390
+ .stButton > button {
391
+ background: linear-gradient(135deg, var(--accent-brown) 0%, #8a6a4d 100%) !important;
392
+ color: var(--light-cream) !important;
393
+ font-weight: 500 !important;
394
+ border: none !important;
395
+ border-radius: 8px !important;
396
+ padding: 0.6rem 1.2rem !important;
397
+ font-size: 1rem !important;
398
+ transition: all 0.3s ease !important;
399
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1) !important;
400
+ text-transform: uppercase !important;
401
+ letter-spacing: 0.5px !important;
402
+ }
403
+
404
+ .stButton > button:hover {
405
+ transform: translateY(-2px) !important;
406
+ box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15) !important;
407
+ background: linear-gradient(135deg, #b38b68 0%, var(--accent-brown) 100%) !important;
408
+ }
409
+
410
+ .stButton > button:active {
411
+ transform: translateY(1px) !important;
412
+ }
413
+
414
+ /* Generate button styling */
415
+ div[data-testid="element-container"]:has(button#generate_button) button {
416
+ background: linear-gradient(135deg, var(--medium-green) 0%, var(--dark-green) 100%) !important;
417
+ border: 1px solid var(--accent-brown) !important;
418
+ color: var(--light-cream) !important;
419
+ font-weight: 600 !important;
420
+ font-size: 1.2rem !important;
421
+ padding: 0.8rem 1.6rem !important;
422
+ box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15) !important;
423
+ position: relative;
424
+ overflow: hidden;
425
+ }
426
+
427
+ div[data-testid="element-container"]:has(button#generate_button) button:before {
428
+ content: '';
429
+ position: absolute;
430
+ top: 0;
431
+ left: -100%;
432
+ width: 100%;
433
+ height: 100%;
434
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent);
435
+ transition: 0.5s;
436
+ }
437
+
438
+ div[data-testid="element-container"]:has(button#generate_button) button:hover:before {
439
+ left: 100%;
440
+ }
441
+
442
+ /* Label styling */
443
+ .input-label {
444
+ color: var(--accent-brown);
445
+ font-size: 1rem;
446
+ font-weight: 500;
447
+ margin-bottom: 0.5rem;
448
+ display: block;
449
+ }
450
+
451
+ /* Subheader styling */
452
+ .stSubheader, .css-10trblm {
453
+ color: var(--accent-brown) !important;
454
+ font-size: 1.5rem !important;
455
+ font-weight: 600 !important;
456
+ margin: 1rem 0 !important;
457
+ letter-spacing: 0.5px !important;
458
+ }
459
+
460
+ /* Success/Error message styling */
461
+ .success {
462
+ background-color: rgba(46, 125, 50, 0.1);
463
+ color: #81c784;
464
+ font-size: 1rem;
465
+ font-weight: 500;
466
+ padding: 1rem;
467
+ border-radius: 8px;
468
+ border-left: 4px solid #4caf50;
469
+ margin-top: 1rem;
470
+ }
471
+
472
+ .error {
473
+ background-color: rgba(211, 47, 47, 0.1);
474
+ color: #e57373;
475
+ font-size: 1rem;
476
+ font-weight: 500;
477
+ padding: 1rem;
478
+ border-radius: 8px;
479
+ border-left: 4px solid #f44336;
480
+ margin-top: 1rem;
481
+ }
482
+
483
+ /* Audio recording indication */
484
+ .stMarkdown p {
485
+ font-size: 1rem;
486
+ color: var(--light-cream);
487
+ }
488
+
489
+ /* JSON display styling */
490
+ .element-container .stJson {
491
+ background-color: rgba(44, 57, 48, 0.7) !important;
492
+ border-radius: 8px !important;
493
+ border: 1px solid rgba(162, 123, 92, 0.3) !important;
494
+ }
495
+
496
+ /* Divider styling */
497
+ hr {
498
+ border: 0;
499
+ height: 1px;
500
+ background: linear-gradient(to right, transparent, var(--accent-brown), transparent);
501
+ margin: 2rem 0;
502
+ }
503
+
504
+ /* Animation for the listening text */
505
+ @keyframes pulse {
506
+ 0% { opacity: 0.6; }
507
+ 50% { opacity: 1; }
508
+ 100% { opacity: 0.6; }
509
+ }
510
+
511
+ .listening {
512
+ animation: pulse 1.5s infinite;
513
+ color: var(--accent-brown);
514
+ font-weight: 500;
515
+ }
516
+
517
+ /* Scrollbar styling */
518
+ ::-webkit-scrollbar {
519
+ width: 8px;
520
+ height: 8px;
521
+ }
522
+
523
+ ::-webkit-scrollbar-track {
524
+ background: var(--dark-green);
525
+ }
526
+
527
+ ::-webkit-scrollbar-thumb {
528
+ background: var(--accent-brown);
529
+ border-radius: 4px;
530
+ }
531
+
532
+ ::-webkit-scrollbar-thumb:hover {
533
+ background: #8a6a4d;
534
+ }
535
+ </style>
536
+ """, unsafe_allow_html=True)
537
+
538
+ # Title
539
+ st.markdown('<div class="title">BillBot</div>', unsafe_allow_html=True)
540
+ st.markdown('<div class="tagline">Speak, and Let AI Create Your Bill</div>', unsafe_allow_html=True)
541
+
542
+ # Main layout with two columns
543
+ col1, col2 = st.columns([1, 3])
544
+
545
+ # Left Column (Settings Panel)
546
+ with col1:
547
+ # st.markdown('<div class="card">', unsafe_allow_html=True)
548
+
549
+ st.markdown("<p class='input-label'>Speech Recognition Language</p>", unsafe_allow_html=True)
550
+ language_options = ['English', 'Urdu']
551
+ selected_language = st.selectbox(" ", language_options, label_visibility="collapsed")
552
+
553
+ st.markdown("<div style='height:24px;'></div>", unsafe_allow_html=True)
554
+
555
+ st.markdown("<p class='input-label'>Select Currency</p>", unsafe_allow_html=True)
556
+ currency_options = ['USD', 'PKR']
557
+ selected_currency = st.selectbox(" ", currency_options, label_visibility="collapsed")
558
+ st.session_state.currency = selected_currency
559
+
560
+ st.markdown('</div>', unsafe_allow_html=True)
561
+
562
+ # Right Column (Customer Information Fields)
563
+ with col2:
564
+ # Customer Name
565
+ # st.markdown('<div class="card">', unsafe_allow_html=True)
566
+ st.markdown("<p class='stSubheader'>Customer Name</p>", unsafe_allow_html=True)
567
+ customer_name = st.text_area("Enter Customer Name", st.session_state.customer_name, height=70, key="name_input", label_visibility="collapsed")
568
+ st.session_state.customer_name = customer_name
569
+
570
+ col_record, col_space = st.columns([1, 1])
571
+ with col_record:
572
+ record_name_btn = st.button("Record Name", key="name_button")
573
+
574
+ if record_name_btn:
575
+ toggle_listen("name_button")
576
+ if st.session_state.is_listening:
577
+ recognized_name = recognize_speech(language_map[selected_language])
578
+ if recognized_name:
579
+ st.session_state.customer_name = recognized_name
580
+ st.markdown('</div>', unsafe_allow_html=True)
581
+
582
+ # Customer Number
583
+ # st.markdown('<div class="card">', unsafe_allow_html=True)
584
+ st.markdown("<p class='stSubheader'>Customer Number</p>", unsafe_allow_html=True)
585
+ customer_number = st.text_area("Enter Customer Number", st.session_state.customer_number, height=70, key="number_input", label_visibility="collapsed")
586
+ st.session_state.customer_number = customer_number
587
+
588
+ col_record, col_space = st.columns([1, 1])
589
+ with col_record:
590
+ record_number_btn = st.button("Record Number", key="number_button")
591
+
592
+ if record_number_btn:
593
+ toggle_listen("number_button")
594
+ if st.session_state.is_listening:
595
+ recognized_number = recognize_speech(language_map[selected_language])
596
+ if recognized_number:
597
+ recognized_number = convert_number_words_to_digits(recognized_number, selected_language)
598
+ st.session_state.customer_number = recognized_number
599
+ st.markdown('</div>', unsafe_allow_html=True)
600
+
601
+ # Bill Content
602
+ # st.markdown('<div class="card">', unsafe_allow_html=True)
603
+ st.markdown("<p class='stSubheader'>Bill Content</p>", unsafe_allow_html=True)
604
+ bill_content = st.text_area("Enter Bill Content", st.session_state.bill_content, height=140, key="bill_input", label_visibility="collapsed")
605
+ st.session_state.bill_content = bill_content
606
+
607
+ col_record, col_space = st.columns([1, 1])
608
+ with col_record:
609
+ record_content_btn = st.button("Record Bill Content", key="content_button")
610
+
611
+ if record_content_btn:
612
+ toggle_listen("content_button")
613
+ if st.session_state.is_listening:
614
+ recognized_bill_content = recognize_speech(language_map[selected_language])
615
+ if recognized_bill_content:
616
+ recognized_bill_content = convert_number_words_to_digits(recognized_bill_content, selected_language)
617
+ st.session_state.bill_content = recognized_bill_content
618
+ st.markdown('</div>', unsafe_allow_html=True)
619
+
620
+ # Generate Bill Button (centered)
621
+ st.markdown('<div style="margin-top:2rem;"></div>', unsafe_allow_html=True)
622
+ _, center_col, _ = st.columns([1, 2, 1])
623
+ with center_col:
624
+ generate_btn = st.button("Generate and Send Bill", key="generate_button", use_container_width=True)
625
+
626
+ # Process the bill if button is clicked
627
+ if generate_btn:
628
+ if st.session_state.customer_name and st.session_state.customer_number and st.session_state.bill_content:
629
+ # Use Gemini to structure the bill content
630
+ structured_bill_content = extract_item_details_from_gemini(st.session_state.bill_content)
631
+
632
+ if structured_bill_content:
633
+ # Generate invoice PDF
634
+ pdf_file = generate_invoice_pdf(
635
+ st.session_state.customer_name,
636
+ st.session_state.customer_number,
637
+ structured_bill_content,
638
+ st.session_state.currency
639
+ )
640
+
641
+ if pdf_file:
642
+ result = send_pdf_via_whatsapp(pdf_file, st.session_state.customer_number)
643
+ st.markdown(f"<div class='success'>{result}</div>", unsafe_allow_html=True)
644
+ else:
645
+ st.markdown("<div class='error'>Error: Could not generate invoice PDF.</div>", unsafe_allow_html=True)
646
+ else:
647
+ st.markdown("<div class='error'>Error: Could not extract structured content from bill text.</div>", unsafe_allow_html=True)
648
+ else:
649
+ st.markdown("<div class='error'>Please fill in all required fields: Customer Name, Customer Number, and Bill Content.</div>", unsafe_allow_html=True)
650
+
images/billbot-fields.png ADDED
images/main_interface.png ADDED
images/main_interface2.png ADDED
images/mobile-invoice.png ADDED
images/voice_recognition.png ADDED
images/whatsapp-msg.png ADDED
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ python-dotenv==1.0.1
2
+ SpeechRecognition==3.14.1
3
+ requests==2.32.3
4
+ twilio==9.4.5
5
+ streamlit==1.42.2
6
+ PyAudio==0.2.14
7
+ word2number==1.1