Spaces:
Running
Running
Upload folder using huggingface_hub
Browse files- __pycache__/search_engine.cpython-311.pyc +0 -0
- app/__pycache__/ai_features.cpython-311.pyc +0 -0
- app/__pycache__/config.cpython-311.pyc +0 -0
- app/__pycache__/xero_client.cpython-311.pyc +0 -0
- app/__pycache__/xero_routes.cpython-311.pyc +0 -0
- app/ai_features.py +56 -15
- app/xero_client.py +33 -3
- app/xero_routes.py +4 -2
- flask_session/0e919d76317babaddd6cd255d5683d18 +0 -0
- flask_session/2029240f6d1128be89ddc32729463129 +0 -0
- run.py +1 -1
__pycache__/search_engine.cpython-311.pyc
CHANGED
Binary files a/__pycache__/search_engine.cpython-311.pyc and b/__pycache__/search_engine.cpython-311.pyc differ
|
|
app/__pycache__/ai_features.cpython-311.pyc
CHANGED
Binary files a/app/__pycache__/ai_features.cpython-311.pyc and b/app/__pycache__/ai_features.cpython-311.pyc differ
|
|
app/__pycache__/config.cpython-311.pyc
CHANGED
Binary files a/app/__pycache__/config.cpython-311.pyc and b/app/__pycache__/config.cpython-311.pyc differ
|
|
app/__pycache__/xero_client.cpython-311.pyc
CHANGED
Binary files a/app/__pycache__/xero_client.cpython-311.pyc and b/app/__pycache__/xero_client.cpython-311.pyc differ
|
|
app/__pycache__/xero_routes.cpython-311.pyc
CHANGED
Binary files a/app/__pycache__/xero_routes.cpython-311.pyc and b/app/__pycache__/xero_routes.cpython-311.pyc differ
|
|
app/ai_features.py
CHANGED
@@ -271,6 +271,18 @@ register_new_customer_function = {
|
|
271 |
}
|
272 |
}
|
273 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
274 |
get_my_orders_function_website = {
|
275 |
"name": "get_my_orders",
|
276 |
"description": "Retrieves a summary of the user's most recent orders to display in the chat.",
|
@@ -517,14 +529,15 @@ def whatsapp_reply():
|
|
517 |
chat_history = history_doc.get('history', []) if history_doc else []
|
518 |
|
519 |
tools = types.Tool(function_declarations=[
|
520 |
-
create_direct_order_function,
|
521 |
add_items_to_cart_function,
|
522 |
remove_items_from_cart_function,
|
523 |
clear_cart_function,
|
524 |
get_cart_items_function,
|
525 |
place_order_function,
|
526 |
get_my_orders_function,
|
527 |
-
register_new_customer_function
|
|
|
528 |
])
|
529 |
|
530 |
instruction_prompt = f"""
|
@@ -542,12 +555,16 @@ def whatsapp_reply():
|
|
542 |
|
543 |
3. **Registering New Customers:**
|
544 |
- If the admin asks to register a new customer, use the `register_new_customer` function.
|
|
|
|
|
|
|
545 |
|
546 |
**Function Guide & Rules:**
|
547 |
- `create_direct_order`: Use for complete, one-shot orders with a delivery date.
|
548 |
- `add_items_to_cart`: To add items for a specific customer when NO delivery date is given.
|
549 |
- `place_order`: To finalize and place a customer's order *from their cart*.
|
550 |
- `register_new_customer`: To sign up a new customer.
|
|
|
551 |
- **Product List:** {', '.join(product_context_list)}.
|
552 |
- **Company Info:** For general questions: {company_info}.
|
553 |
- **Communication:** Always be professional. Confirm actions clearly. Respond using WhatsApp-compatible markdown (*bold*, _italic_).
|
@@ -591,6 +608,18 @@ def whatsapp_reply():
|
|
591 |
details = f"*Details for Order #{order['serial_no']}*\n*Customer:* {order_doc.get('user_email')}\n*Status:* {order_doc.get('status', 'N/A')}\n*Delivery:* {order_doc.get('delivery_date', 'N/A')}\n\n*Items:*\n" + "\n".join(item_lines)
|
592 |
final_response_text = details
|
593 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
594 |
# For all other functions, perform user lookup
|
595 |
else:
|
596 |
user, error_msg = _find_user_by_identifier(args.get("user_identifier"))
|
@@ -727,20 +756,32 @@ def whatsapp_reply():
|
|
727 |
current_app.logger.error(f"WhatsApp endpoint error: {e}")
|
728 |
final_response_text = "I'm having a little trouble right now. Please try again in a moment."
|
729 |
|
730 |
-
|
731 |
-
|
732 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
733 |
|
734 |
-
|
735 |
-
|
736 |
-
split_point = 1600
|
737 |
-
|
738 |
-
part1 = final_response_text[:split_point]
|
739 |
-
# Use .strip() to remove any leading space from the second part.
|
740 |
-
part2 = final_response_text[split_point:].strip()
|
741 |
-
|
742 |
-
twilio_resp.message(part1)
|
743 |
-
twilio_resp.message("❌TWILLIO ERROR:MESSAGE WAS TOO LONG, SO SOME PARTS OF THE MESSAGE WERE NOT SENT.")
|
744 |
else:
|
745 |
twilio_resp.message(final_response_text)
|
746 |
|
|
|
271 |
}
|
272 |
}
|
273 |
|
274 |
+
# +++ START: NEW FUNCTION DEFINITION FOR LISTING ACCOUNTS +++
|
275 |
+
list_all_accounts_function = {
|
276 |
+
"name": "list_all_accounts",
|
277 |
+
"description": "Lists all registered customer accounts with their name, business name, and email. Use this when the admin asks for a list of all users or accounts.",
|
278 |
+
"parameters": {
|
279 |
+
"type": "object",
|
280 |
+
"properties": {}
|
281 |
+
}
|
282 |
+
}
|
283 |
+
# +++ END: NEW FUNCTION DEFINITION FOR LISTING ACCOUNTS +++
|
284 |
+
|
285 |
+
|
286 |
get_my_orders_function_website = {
|
287 |
"name": "get_my_orders",
|
288 |
"description": "Retrieves a summary of the user's most recent orders to display in the chat.",
|
|
|
529 |
chat_history = history_doc.get('history', []) if history_doc else []
|
530 |
|
531 |
tools = types.Tool(function_declarations=[
|
532 |
+
create_direct_order_function,
|
533 |
add_items_to_cart_function,
|
534 |
remove_items_from_cart_function,
|
535 |
clear_cart_function,
|
536 |
get_cart_items_function,
|
537 |
place_order_function,
|
538 |
get_my_orders_function,
|
539 |
+
register_new_customer_function,
|
540 |
+
list_all_accounts_function
|
541 |
])
|
542 |
|
543 |
instruction_prompt = f"""
|
|
|
555 |
|
556 |
3. **Registering New Customers:**
|
557 |
- If the admin asks to register a new customer, use the `register_new_customer` function.
|
558 |
+
|
559 |
+
4. **Listing All Customers:**
|
560 |
+
- If the admin asks for a list of all accounts or users, use the `list_all_accounts` function.
|
561 |
|
562 |
**Function Guide & Rules:**
|
563 |
- `create_direct_order`: Use for complete, one-shot orders with a delivery date.
|
564 |
- `add_items_to_cart`: To add items for a specific customer when NO delivery date is given.
|
565 |
- `place_order`: To finalize and place a customer's order *from their cart*.
|
566 |
- `register_new_customer`: To sign up a new customer.
|
567 |
+
- `list_all_accounts`: To get a list of all registered customers.
|
568 |
- **Product List:** {', '.join(product_context_list)}.
|
569 |
- **Company Info:** For general questions: {company_info}.
|
570 |
- **Communication:** Always be professional. Confirm actions clearly. Respond using WhatsApp-compatible markdown (*bold*, _italic_).
|
|
|
608 |
details = f"*Details for Order #{order['serial_no']}*\n*Customer:* {order_doc.get('user_email')}\n*Status:* {order_doc.get('status', 'N/A')}\n*Delivery:* {order_doc.get('delivery_date', 'N/A')}\n\n*Items:*\n" + "\n".join(item_lines)
|
609 |
final_response_text = details
|
610 |
|
611 |
+
elif function_call.name == 'list_all_accounts':
|
612 |
+
all_users = list(mongo.db.users.find({}, {'email': 1, 'contactPerson': 1, 'businessName': 1, '_id': 0}))
|
613 |
+
if not all_users:
|
614 |
+
final_response_text = "There are no customer accounts registered in the system yet."
|
615 |
+
else:
|
616 |
+
account_list_text = "*Here is the list of all customer accounts:*\n\n"
|
617 |
+
for user in all_users:
|
618 |
+
account_list_text += f"- *Name:* {user.get('contactPerson', 'N/A')}\n"
|
619 |
+
account_list_text += f" *Business:* {user.get('businessName', 'N/A')}\n"
|
620 |
+
account_list_text += f" *Email:* {user.get('email', 'N/A')}\n\n"
|
621 |
+
final_response_text = account_list_text.strip()
|
622 |
+
|
623 |
# For all other functions, perform user lookup
|
624 |
else:
|
625 |
user, error_msg = _find_user_by_identifier(args.get("user_identifier"))
|
|
|
756 |
current_app.logger.error(f"WhatsApp endpoint error: {e}")
|
757 |
final_response_text = "I'm having a little trouble right now. Please try again in a moment."
|
758 |
|
759 |
+
# --- 7. Send Response via Twilio ---
|
760 |
+
MAX_LENGTH = 1600
|
761 |
+
if len(final_response_text) > MAX_LENGTH:
|
762 |
+
# Split the message into parts and send them sequentially
|
763 |
+
parts = []
|
764 |
+
temp_string = final_response_text
|
765 |
+
while len(temp_string) > 0:
|
766 |
+
if len(temp_string) > MAX_LENGTH:
|
767 |
+
# Find the last newline or space to avoid splitting mid-word
|
768 |
+
split_point = temp_string[:MAX_LENGTH].rfind('\n')
|
769 |
+
if split_point == -1:
|
770 |
+
split_point = temp_string[:MAX_LENGTH].rfind('. ')
|
771 |
+
if split_point == -1:
|
772 |
+
split_point = temp_string[:MAX_LENGTH].rfind(' ')
|
773 |
+
# If no natural break point, just split at the max length
|
774 |
+
if split_point == -1:
|
775 |
+
split_point = MAX_LENGTH
|
776 |
+
|
777 |
+
parts.append(temp_string[:split_point])
|
778 |
+
temp_string = temp_string[split_point:].strip()
|
779 |
+
else:
|
780 |
+
parts.append(temp_string)
|
781 |
+
break
|
782 |
|
783 |
+
for part in parts:
|
784 |
+
twilio_resp.message(part)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
785 |
else:
|
786 |
twilio_resp.message(final_response_text)
|
787 |
|
app/xero_client.py
CHANGED
@@ -108,7 +108,16 @@ def initialize_zoho_sdk(grant_token=None, accounts_server_url=None):
|
|
108 |
|
109 |
# Store the accounts server URL for refreshing the token later
|
110 |
token_data['accounts_server'] = base_accounts_url
|
111 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
112 |
logger.info(f"Token data received: {token_data}")
|
113 |
token_data['expires_at'] = int(time.time()) + token_data['expires_in']
|
114 |
_save_token(token_data)
|
@@ -136,11 +145,22 @@ def _get_stored_token():
|
|
136 |
def _save_token(token_data):
|
137 |
"""Saves token data to MongoDB."""
|
138 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
139 |
mongo.db.zoho_tokens.update_one(
|
140 |
{'_id': 'zoho_oauth_token'},
|
141 |
{'$set': token_data},
|
142 |
upsert=True
|
143 |
)
|
|
|
144 |
except Exception as e:
|
145 |
logger.error(f"Error saving Zoho token to MongoDB: {e}", exc_info=True)
|
146 |
|
@@ -173,7 +193,16 @@ def _refresh_access_token():
|
|
173 |
# Preserve crucial details not always present in the refresh response
|
174 |
new_token_data['refresh_token'] = new_token_data.get('refresh_token', stored_token['refresh_token'])
|
175 |
new_token_data['accounts_server'] = new_token_data.get('accounts_server', base_accounts_url)
|
176 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
177 |
|
178 |
_save_token(new_token_data)
|
179 |
logger.info("Successfully refreshed and saved new access token.")
|
@@ -191,6 +220,7 @@ def get_access_token():
|
|
191 |
return None
|
192 |
|
193 |
if token.get('expires_at', 0) < time.time() + 60:
|
|
|
194 |
token = _refresh_access_token()
|
195 |
if not token:
|
196 |
return None
|
@@ -224,4 +254,4 @@ def zoho_token_required(function):
|
|
224 |
session['next_url'] = request.url
|
225 |
return redirect(url_for("zoho.login"))
|
226 |
return function(*args, **kwargs)
|
227 |
-
return decorator
|
|
|
108 |
|
109 |
# Store the accounts server URL for refreshing the token later
|
110 |
token_data['accounts_server'] = base_accounts_url
|
111 |
+
|
112 |
+
# Log and print refresh token if returned
|
113 |
+
if 'refresh_token' in token_data:
|
114 |
+
logger.info("Received refresh_token from Zoho during token exchange.")
|
115 |
+
logger.info(f"refresh_token: {token_data.get('refresh_token')}")
|
116 |
+
print(f"--- Received Zoho refresh_token: {token_data.get('refresh_token')} ---")
|
117 |
+
else:
|
118 |
+
logger.info("No refresh_token present in Zoho response for this exchange.")
|
119 |
+
print("--- No refresh_token received from Zoho in this exchange. ---")
|
120 |
+
|
121 |
logger.info(f"Token data received: {token_data}")
|
122 |
token_data['expires_at'] = int(time.time()) + token_data['expires_in']
|
123 |
_save_token(token_data)
|
|
|
145 |
def _save_token(token_data):
|
146 |
"""Saves token data to MongoDB."""
|
147 |
try:
|
148 |
+
# Preserve an existing refresh_token if the current token_data doesn't contain one.
|
149 |
+
existing = mongo.db.zoho_tokens.find_one({'_id': 'zoho_oauth_token'})
|
150 |
+
if existing and 'refresh_token' in existing and 'refresh_token' not in token_data:
|
151 |
+
# Preserve the previously stored refresh token
|
152 |
+
token_data['refresh_token'] = existing['refresh_token']
|
153 |
+
logger.info("Preserved existing refresh_token because the new token response did not include one.")
|
154 |
+
print("--- Preserved existing Zoho refresh_token (not returned by current response). ---")
|
155 |
+
|
156 |
+
# Set expires_at if present as int already; otherwise leave as-is
|
157 |
+
# Ensure the DB document uses fixed _id
|
158 |
mongo.db.zoho_tokens.update_one(
|
159 |
{'_id': 'zoho_oauth_token'},
|
160 |
{'$set': token_data},
|
161 |
upsert=True
|
162 |
)
|
163 |
+
logger.info("Zoho token saved to MongoDB (upsert).")
|
164 |
except Exception as e:
|
165 |
logger.error(f"Error saving Zoho token to MongoDB: {e}", exc_info=True)
|
166 |
|
|
|
193 |
# Preserve crucial details not always present in the refresh response
|
194 |
new_token_data['refresh_token'] = new_token_data.get('refresh_token', stored_token['refresh_token'])
|
195 |
new_token_data['accounts_server'] = new_token_data.get('accounts_server', base_accounts_url)
|
196 |
+
# Use .get for expires_in to avoid KeyError if absent
|
197 |
+
new_token_data['expires_at'] = int(time.time()) + int(new_token_data.get('expires_in', 3600))
|
198 |
+
|
199 |
+
# Log whether Zoho returned a refresh_token on refresh calls (often not returned).
|
200 |
+
if 'refresh_token' in new_token_data and new_token_data.get('refresh_token') != stored_token.get('refresh_token'):
|
201 |
+
logger.info("Zoho returned a new refresh_token during refresh.")
|
202 |
+
print(f"--- Zoho returned a new refresh_token during refresh: {new_token_data.get('refresh_token')} ---")
|
203 |
+
else:
|
204 |
+
logger.info("Zoho did not return a new refresh_token during refresh; preserving existing one.")
|
205 |
+
print("--- Zoho did not return a new refresh_token during refresh; using stored refresh_token. ---")
|
206 |
|
207 |
_save_token(new_token_data)
|
208 |
logger.info("Successfully refreshed and saved new access token.")
|
|
|
220 |
return None
|
221 |
|
222 |
if token.get('expires_at', 0) < time.time() + 60:
|
223 |
+
logger.info("TRYING TO GENERATE A REFRESH TOKEN")
|
224 |
token = _refresh_access_token()
|
225 |
if not token:
|
226 |
return None
|
|
|
254 |
session['next_url'] = request.url
|
255 |
return redirect(url_for("zoho.login"))
|
256 |
return function(*args, **kwargs)
|
257 |
+
return decorator
|
app/xero_routes.py
CHANGED
@@ -61,7 +61,9 @@ def login():
|
|
61 |
'client_id': config['ZOHO_CLIENT_ID'],
|
62 |
'response_type': 'code',
|
63 |
'access_type': 'offline',
|
64 |
-
'redirect_uri': config['ZOHO_REDIRECT_URL']
|
|
|
|
|
65 |
}
|
66 |
accounts_url = 'https://accounts.zohocloud.ca/oauth/v2/auth'
|
67 |
auth_url = f"{accounts_url}?{urllib.parse.urlencode(params)}"
|
@@ -232,4 +234,4 @@ def edit_inventory():
|
|
232 |
return redirect(url_for("zoho.edit_inventory"))
|
233 |
|
234 |
products = list(mongo.db.products.find({}, {"_id": 0, "name": 1, "image_url": 1}).sort("name", 1))
|
235 |
-
return render_template("edit_inventory.html", title="Edit Inventory Image", products=products)
|
|
|
61 |
'client_id': config['ZOHO_CLIENT_ID'],
|
62 |
'response_type': 'code',
|
63 |
'access_type': 'offline',
|
64 |
+
'redirect_uri': config['ZOHO_REDIRECT_URL'],
|
65 |
+
# Force consent so that Zoho returns a refresh token when possible
|
66 |
+
'prompt': 'consent'
|
67 |
}
|
68 |
accounts_url = 'https://accounts.zohocloud.ca/oauth/v2/auth'
|
69 |
auth_url = f"{accounts_url}?{urllib.parse.urlencode(params)}"
|
|
|
234 |
return redirect(url_for("zoho.edit_inventory"))
|
235 |
|
236 |
products = list(mongo.db.products.find({}, {"_id": 0, "name": 1, "image_url": 1}).sort("name", 1))
|
237 |
+
return render_template("edit_inventory.html", title="Edit Inventory Image", products=products)
|
flask_session/0e919d76317babaddd6cd255d5683d18
ADDED
Binary file (139 Bytes). View file
|
|
flask_session/2029240f6d1128be89ddc32729463129
CHANGED
Binary files a/flask_session/2029240f6d1128be89ddc32729463129 and b/flask_session/2029240f6d1128be89ddc32729463129 differ
|
|
run.py
CHANGED
@@ -4,4 +4,4 @@ os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'
|
|
4 |
app = create_app()
|
5 |
|
6 |
if __name__ == '__main__':
|
7 |
-
app.run(host="0.0.0.0",debug=
|
|
|
4 |
app = create_app()
|
5 |
|
6 |
if __name__ == '__main__':
|
7 |
+
app.run(host="0.0.0.0",debug=True, port=7860)
|