Spaces:
Sleeping
Sleeping
from flask import Flask, request, make_response | |
import os | |
from datetime import datetime | |
import logging | |
from dotenv import load_dotenv | |
from heyoo import WhatsApp | |
import assemblyai as aai | |
import openai | |
from utility import generateResponse, parse_multiple_transactions, create_inventory, create_sale, read_datalake, delete_transaction | |
from google.cloud import firestore | |
import ast | |
import base64 | |
import requests | |
# load env data | |
load_dotenv() | |
# messenger object | |
messenger = WhatsApp( | |
os.environ["whatsapp_token"], | |
phone_number_id=os.environ["phone_number_id"] | |
) | |
aai.settings.api_key = os.environ["aai_key"] | |
transcriber = aai.Transcriber() | |
# Authenticate to Firestore with the JSON account key | |
db = firestore.Client.from_service_account_json("firestore-key.json") | |
app = Flask(__name__) | |
VERIFY_TOKEN = "30cca545-3838-48b2-80a7-9e43b1ae8ce4" | |
img_ID = "344744a88ad1098" | |
img_secret = "3c542a40c215327045d7155bddfd8b8bc84aebbf" | |
url = "https://api.imgur.com/3/image" | |
headers = {"Authorization": f"Client-ID {img_ID}"} | |
# Interactive button | |
def messenger_button(recipient_phone, message, header='Transaction Confirmation', footer='', btn_name='Confirm Details'): | |
messenger.send_button( | |
recipient_id=recipient_phone, | |
button={ | |
"header": f"{header}", | |
"body": f"{message}", | |
"footer": f"{footer}", | |
"action": { | |
"button": f"{btn_name}", | |
"sections": [ | |
{ | |
"title": "iBank", | |
"rows": [ | |
{"id": "confirm", "title": "Record Transaction", "description": ""}, | |
{"id": "cancel", "title": "Cancel Transaction", "description": ""}, | |
], | |
} | |
], | |
}, | |
}, | |
) | |
def messenger_reply_button(recipient_phone, message, header_message = "*Please confirm the details below before we proceed*:\n\n"): | |
messenger.send_reply_button( | |
recipient_id=f"{recipient_phone}", | |
button={ | |
"type": "button", | |
"body": { | |
"text": f"{header_message} {message}" | |
}, | |
"action": { | |
"buttons": [ | |
{ | |
"type": "reply", | |
"reply": { | |
"id": "confirm", | |
"title": "Confirm" | |
} | |
}, | |
{ | |
"type": "reply", | |
"reply": { | |
"id": "cancel", | |
"title": "Cancel" | |
} | |
} | |
] | |
} | |
}, | |
) | |
def respond(query_str: str): | |
response = "hello, I don't have a brain yet" | |
return response | |
def persist_temporary_transaction(transactions, mobile): | |
""" | |
Persists the transaction data temporarily in Firestore. | |
""" | |
# intent = transactions[0]['intent'].lower() | |
temp_ref = db.collection("users").document(mobile).collection("temp_transactions").document('pending-user-action') | |
data = { | |
"transactions": transactions, | |
"status": "pending", | |
"created_at": datetime.now().isoformat() | |
} | |
temp_ref.set(data) | |
return True | |
def process_user_msg(message, mobile): | |
response = str(generateResponse(message)) | |
parsed_trans_data = parse_multiple_transactions(response) | |
# messenger.send_message(f"{response} \n\n {parsed_trans_data}", mobile) | |
logging.info(f"\nAnswer: {response}\n") | |
intent = parsed_trans_data[0]['intent'].lower() | |
if intent == 'read': | |
response2 = str(read_datalake(mobile, message)) | |
# Check if response is a string and represents a valid image path | |
if isinstance(response2, str) and os.path.isfile(os.path.join(response2)): | |
image_path = os.path.join(response2) | |
print("My image path:", image_path) | |
with open(image_path, "rb") as file: | |
data = file.read() | |
base64_data = base64.b64encode(data) | |
# Upload image to Imgur and get URL | |
response2 = requests.post(url, headers=headers, data={"image": base64_data}) | |
url1= response2.json()["data"]["link"] | |
print(url1) | |
messenger.send_image(image=url1, recipient_id=mobile) | |
else: | |
messenger.send_message(f"{response} \n\n {response2}", recipient_id=mobile) | |
else: | |
# Persist transaction data temporarily for Create, Update, or Delete operations | |
persist = persist_temporary_transaction(parsed_trans_data, mobile) | |
if persist: | |
# messenger.send_message(f"{response} \n\n {parsed_trans_data}", mobile) | |
# Give user the chance to confirm/cancel transaction before processing other intents | |
messenger_reply_button(mobile, f"{response}") | |
else: | |
messenger.send_message("Please try again!", mobile) | |
return True | |
def process_intent(parsed_trans_data, mobile): | |
intent = parsed_trans_data[0]['intent'].lower() | |
trans_type = parsed_trans_data[0]['transaction_type'].lower() | |
if intent == 'create': | |
if trans_type in ('purchase', 'purchases', 'inventory'): | |
if create_inventory(mobile, parsed_trans_data): | |
firestore_msg = "Transaction recorded successfully!" | |
else: | |
firestore_msg = "Sorry, could not record transaction!" | |
elif trans_type in ('sale', 'sales'): | |
if create_sale(mobile, parsed_trans_data): | |
firestore_msg = "Transaction recorded successfully!" | |
else: | |
firestore_msg = "Sorry, could not record transaction!" | |
elif intent == 'update': | |
pass | |
elif intent == 'delete': | |
item = parsed_trans_data[0]['details']['item'] | |
item_deleted = delete_transaction(mobile, parsed_trans_data) | |
if item_deleted: | |
firestore_msg = f"You successfully deleted {item} from {trans_type}!" | |
else: | |
firestore_msg = f"Sorry, could not delete the {item} from {trans_type}!" | |
# elif intent == 'read': | |
# response = str(read_datalake(mobile, message)) | |
# parsed_trans_data = "" | |
# firestore_msg = response | |
else: | |
firestore_msg = f'The detected intent, {intent}, is not currently supported!' | |
# return firestore_msg, parsed_trans_data, response | |
return firestore_msg | |
def handle_interactive_response(mobile, button_id): | |
""" | |
Handles the user's button response (Confirm or Cancel). | |
""" | |
doc_id = 'pending-user-action' | |
temp_ref = db.collection("users").document(mobile).collection("temp_transactions").document(doc_id) | |
transaction = temp_ref.get() | |
if transaction.exists: | |
transaction_data = transaction.to_dict() | |
if button_id == "confirm": | |
# Move to the appropriate collection | |
transactions = transaction_data["transactions"] | |
msg = process_intent(transactions, mobile) | |
# Mark as confirmed or delete temporary record | |
temp_ref.delete() | |
messenger.send_message(f"{msg}", recipient_id = mobile) | |
elif button_id == "cancel": | |
# Delete the temporary record | |
temp_ref.delete() | |
messenger.send_message("Transaction has been canceled.", recipient_id = mobile) | |
else: | |
messenger.send_message("Invalid action. Please try again.", recipient_id = mobile) | |
else: | |
messenger.send_message("No pending transaction found.", recipient_id = mobile) | |
def hook(): | |
if request.method == "GET": | |
if request.args.get("hub.verify_token") == VERIFY_TOKEN: | |
logging.info("Verified webhook") | |
response = make_response(request.args.get("hub.challenge"), 200) | |
response.mimetype = "text/plain" | |
return response | |
logging.error("Webhook Verification failed") | |
return "Invalid verification token" | |
# get message update.. | |
data = request.get_json() | |
changed_field = messenger.changed_field(data) | |
if changed_field == "messages": | |
new_message = messenger.get_mobile(data) | |
if new_message: | |
mobile = messenger.get_mobile(data) | |
message_type = messenger.get_message_type(data) | |
if message_type == "text": | |
message = messenger.get_message(data) | |
# Handle greetings | |
if message.lower() in ("hi", "hello", "hola", "help", "how are you", "sawubona"): | |
response = "Hi there! My name is Qx-SmartLedger. How can I help you today?" | |
messenger.send_message(message=f"{response}", recipient_id=mobile) | |
else: | |
process_user_msg(message, mobile) | |
# messenger.send_message(message=f"User message processed!", recipient_id=mobile) | |
elif message_type == "audio": | |
audio = messenger.get_audio(data) | |
audio_id, mime_type = audio["id"], audio["mime_type"] | |
audio_url = messenger.query_media_url(audio_id) | |
audio_filename = messenger.download_media(audio_url, mime_type) | |
transcript = transcriber.transcribe(audio_filename) | |
print(audio_filename) | |
print(transcript.text) | |
transcribed_message = transcript.text | |
# logging.info(f"\nAudio: {audio}\n") | |
process_user_msg(transcribed_message, mobile) | |
# firestore_msg1, parsed_trans_data1, response1 = process_user_msg(transcribed_message, mobile) | |
# messenger.send_message(message=f"Raw Response: {response1}, \n \n Parsed Response: {parsed_trans_data1}, \n \n Final Response: {firestore_msg1}", recipient_id=mobile) | |
elif message_type == "interactive": | |
message_response = messenger.get_interactive_response(data) | |
interactive_type = message_response.get("type") | |
message_id = message_response[interactive_type]["id"] | |
message_text = message_response[interactive_type]["title"] | |
logging.info(f"Interactive Message: {interactive_type} {message_id}: {message_text}") | |
# messenger.send_message(message = f"Interactive type: {interactive_type}\n Message ID: {message_id} \n Message body: {message_body}", recipient_id=mobile) | |
# Handle either create, update, or delete operation based on user action | |
handle_interactive_response(mobile, message_id) | |
else: | |
messenger.send_message(message=f"Please send me text or audio messages", recipient_id=mobile) | |
return "ok" | |
if __name__ == '__main__': | |
app.run(debug=True, host="0.0.0.0", port=7860) | |