voice-powered BillBot invoice app with complete code, docs and assets
Browse files- README.md +200 -7
- app.py +650 -0
- images/billbot-fields.png +0 -0
- images/main_interface.png +0 -0
- images/main_interface2.png +0 -0
- images/mobile-invoice.png +0 -0
- images/voice_recognition.png +0 -0
- images/whatsapp-msg.png +0 -0
- requirements.txt +7 -0
README.md
CHANGED
@@ -1,14 +1,207 @@
|
|
1 |
---
|
2 |
title: BillBot
|
3 |
-
emoji:
|
4 |
-
colorFrom:
|
5 |
-
colorTo:
|
6 |
sdk: streamlit
|
7 |
-
sdk_version: 1.
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
+

|
17 |
+

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

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

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

|
141 |
+

|
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
|