Spaces:
Runtime error
Runtime error
Commit
·
32107fd
1
Parent(s):
7ce5499
Uploaded
Browse files- Dockerfile +19 -0
- credentials.json +13 -0
- requirements.txt +4 -0
- webhook_server.py +89 -0
Dockerfile
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Use a slim, official Python image
|
| 2 |
+
FROM python:3.10-slim
|
| 3 |
+
|
| 4 |
+
# Set the working directory inside the container
|
| 5 |
+
WORKDIR /app
|
| 6 |
+
|
| 7 |
+
# Copy the requirements file and install dependencies
|
| 8 |
+
# This leverages Docker caching to speed up future builds
|
| 9 |
+
COPY requirements.txt .
|
| 10 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 11 |
+
|
| 12 |
+
# Copy the rest of your application files into the container
|
| 13 |
+
COPY . .
|
| 14 |
+
|
| 15 |
+
# Expose the port that Hugging Face uses
|
| 16 |
+
EXPOSE 7860
|
| 17 |
+
|
| 18 |
+
# Command to run the application using a production-grade server
|
| 19 |
+
CMD ["gunicorn", "--bind", "0.0.0.0:7860", "webhook_server:app"]
|
credentials.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"type": "service_account",
|
| 3 |
+
"project_id": "gen-lang-client-0245189086",
|
| 4 |
+
"private_key_id": "8f7095eb84e7ea8a2cf53634bd859e74ce4bb04d",
|
| 5 |
+
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC89SogQi981svk\nJZOVdgtvqk/79S5RIuSDNt9683Tsc/Y9T01t6e0QKDWIVN6nBZS8LB8oTSFvPwYJ\n5jPTDZ0rm4QZ/FTgCEV0k9CJRZNLIfBAI2xFAKez5bAT0Z+9mUqiZCI2paSqIzzz\n7U9Z8jdcpooUxE1YQTTXWN2PqIP+doAmHa/AUniXMiu2cdl8YrxmAuEN6VuqdFuR\nzrSUjXrCx6yonHtccNUFSBpmbq8fB3t7tuMjpz51wvIqYQvsM2laYX0xOh1jdbUb\n0E9xSuzBNvRfO9M9zfkFz3zWR7Ke+uJHgkzwGMAL0vTtyOszGG7jBqyVROZUIJj1\n75j2LB7TAgMBAAECggEAVFZMlhdUYLDyUgMfiw8j7ZQjnP6CzL35JkOgnZz6K+ta\nFWVG1u8Y2yRHOHFA62VHTHGY+oDqkl+bz2FK8kFaTDNeU8bXDyNB9NVgt1QxeNBO\nDiKBWY8ASwAShdYDKTm5IR/2UVO/WhzeQLVDvI6qfRTr/nbbWq/H6PIF+e+p8jGn\nXq3LRRZUydAfmIG/8VZXk0NljqPK2lv7yzcXH7DI5imn4XYcbtjXu+Z8RsV6tVGU\nmDg3islMpRFG+XMpwR0Qzowtmsz0VdMoUxWajvqHFppXDOAImb7dClp8nF75Ll8Q\nCdZLYpqkYvg81OkKRsQTHMlNAsIvQwbVlam3YtElaQKBgQDlp42hygN3GAzzXLzZ\nb8I1X5OVKduNEoSwPEVTjpwC3hBfq45T5PKJ/Oa2eZalAvnbudR1UOIXlitMiSCf\niRSetJoFWZW/SyONMzbGc2mkHWFYNF8VAOr7SzmXV8gYEwQ2hPsJ7WxifAr30f9X\n/H5g78WtcYcKDceOCqngMvRpxwKBgQDSom9gKRKgxSSzPziQCKa3O9R4IYzPMjBT\nliKM+hOYaC5nc/mvPj7CJ/+34RngGAHhPYg3lpFPmetvzLrFtL3SafFn1d6KMou9\n3oSsgqUG2ez/ez0JEWdLZkZDiKv2QRSo7xTAMpbhOkms2BoQZrmdW+nbOvVkA5IJ\nu7HZqjEClQKBgAlYfzgFS4zOKsDAlmLW3HVllVDtqiSci0/MtBmJZSnstYffKGSb\nnY8l/pGQcyP18gsSDeZUS08galSsA+raHj+zI81x7tkhCqpVWjZLPhJSq8J2JyRo\nrCdb0VUqWlc4duRtFvY5dj6vw5aAMj73ZIE3YLkFNLShCOzr3CvmhvHDAoGBAJsT\n4sI+myto2kNqSX+qDuybDSxBL0WhIvl3cQqwV9r+4SNjvLHsNxKFln1QKtfVdRTb\nU73xihy8Kx4N8nj7QEJ5o7WaPZUr77yj5rSIH24y5o2Ws3JIsO9PZm30Yv8UHVzf\nYTy8Ql+ipXmvRUlMCF61vDCfcOnoww2NdjzWU+0dAoGBAKMcIMBVeFSLqyE5vGhh\ng9BNs7peeB8qUNhoa7boShsSkVpR4BOBrBk9xpxO8NLbjzaWhOAjS5GG6xP31oMp\nnsnoSl9MH4VubQGdxjsVDQkl9FQx7DhJIJL23uBrzlVLOiNycOUftH+t/UU7K+7H\nQ0V/WfybmSnq9+LX7+3QMcmg\n-----END PRIVATE KEY-----\n",
|
| 6 |
+
"client_email": "vapilogger@gen-lang-client-0245189086.iam.gserviceaccount.com",
|
| 7 |
+
"client_id": "117483781817309554190",
|
| 8 |
+
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
| 9 |
+
"token_uri": "https://oauth2.googleapis.com/token",
|
| 10 |
+
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
| 11 |
+
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/vapilogger%40gen-lang-client-0245189086.iam.gserviceaccount.com",
|
| 12 |
+
"universe_domain": "googleapis.com"
|
| 13 |
+
}
|
requirements.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Flask
|
| 2 |
+
gspread
|
| 3 |
+
oauth2client
|
| 4 |
+
gunicorn
|
webhook_server.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import json
|
| 3 |
+
import gspread
|
| 4 |
+
from oauth2client.service_account import ServiceAccountCredentials
|
| 5 |
+
from flask import Flask, request, jsonify
|
| 6 |
+
from datetime import datetime
|
| 7 |
+
|
| 8 |
+
app = Flask(__name__)
|
| 9 |
+
|
| 10 |
+
# --- HTML Template for the Interactive Status Page ---
|
| 11 |
+
HTML_TEMPLATE = """
|
| 12 |
+
<!DOCTYPE html>
|
| 13 |
+
<html lang="en">
|
| 14 |
+
<head>
|
| 15 |
+
<meta charset="UTF-8">
|
| 16 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 17 |
+
<title>Vapi Webhook Server Status</title>
|
| 18 |
+
<style>
|
| 19 |
+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background-color: #f0f2f5; }
|
| 20 |
+
.container { text-align: center; padding: 40px; background-color: white; border-radius: 12px; box-shadow: 0 4px 20px rgba(0,0,0,0.1); }
|
| 21 |
+
.status { display: inline-block; padding: 12px 24px; background-color: #28a745; color: white; font-size: 20px; font-weight: 600; border-radius: 8px; margin-bottom: 20px; }
|
| 22 |
+
h1 { color: #333; }
|
| 23 |
+
p { color: #555; font-size: 16px; }
|
| 24 |
+
code { background-color: #e9ecef; padding: 4px 8px; border-radius: 4px; font-family: "SF Mono", "Fira Code", "Fira Mono", "Roboto Mono", monospace; }
|
| 25 |
+
</style>
|
| 26 |
+
</head>
|
| 27 |
+
<body>
|
| 28 |
+
<div class="container">
|
| 29 |
+
<div class="status">Live</div>
|
| 30 |
+
<h1>Your Vapi Webhook Server is Running!</h1>
|
| 31 |
+
<p>This server is successfully deployed and ready to receive requests from Vapi.</p>
|
| 32 |
+
<p>Use the following full URL in your application's configuration:</p>
|
| 33 |
+
<code>{full_webhook_url}</code>
|
| 34 |
+
</div>
|
| 35 |
+
</body>
|
| 36 |
+
</html>
|
| 37 |
+
"""
|
| 38 |
+
|
| 39 |
+
def get_google_sheets_client():
|
| 40 |
+
try:
|
| 41 |
+
if not os.path.exists("credentials.json"):
|
| 42 |
+
print("CRITICAL ERROR: 'credentials.json' file not found.")
|
| 43 |
+
return None
|
| 44 |
+
scope = ["https://spreadsheets.google.com/feeds", 'https://www.googleapis.com/auth/spreadsheets',
|
| 45 |
+
"https://www.googleapis.com/auth/drive.file", "https://www.googleapis.com/auth/drive"]
|
| 46 |
+
creds = ServiceAccountCredentials.from_json_keyfile_name("credentials.json", scope)
|
| 47 |
+
return gspread.authorize(creds)
|
| 48 |
+
except Exception as e:
|
| 49 |
+
print(f"CRITICAL ERROR: Could not authenticate with Google Sheets: {e}")
|
| 50 |
+
return None
|
| 51 |
+
|
| 52 |
+
@app.route('/', methods=['GET'])
|
| 53 |
+
def root():
|
| 54 |
+
# This creates the interactive status page
|
| 55 |
+
host = request.host_url
|
| 56 |
+
full_url = f"{host}webhook"
|
| 57 |
+
return HTML_TEMPLATE.format(full_webhook_url=full_url)
|
| 58 |
+
|
| 59 |
+
@app.route('/webhook', methods=['POST'])
|
| 60 |
+
def webhook_handler():
|
| 61 |
+
print(f"Webhook received a POST request at {datetime.now()}.")
|
| 62 |
+
try:
|
| 63 |
+
payload = request.json
|
| 64 |
+
message = payload.get('message', {})
|
| 65 |
+
if message.get('type') == 'hang':
|
| 66 |
+
print("Received 'hang' event. Processing...")
|
| 67 |
+
call_data = message.get('call', {})
|
| 68 |
+
row_to_insert = [
|
| 69 |
+
call_data.get("id", "N/A"),
|
| 70 |
+
datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
| 71 |
+
call_data.get("customer", {}).get("number", "N/A"),
|
| 72 |
+
call_data.get("metadata", {}).get("raw_intent", "N/A"),
|
| 73 |
+
message.get('endedReason', 'N/A'),
|
| 74 |
+
call_data.get('summary', 'No summary provided.'),
|
| 75 |
+
"Yes" if "follow-up" in call_data.get('summary', '').lower() else "No",
|
| 76 |
+
json.dumps(call_data.get('transcript', []), indent=2)
|
| 77 |
+
]
|
| 78 |
+
client = get_google_sheets_client()
|
| 79 |
+
if client:
|
| 80 |
+
sheet_name = os.getenv("GOOGLE_SHEET_NAME", "Call Logs")
|
| 81 |
+
sheet = client.open(sheet_name).sheet1
|
| 82 |
+
sheet.append_row(row_to_insert)
|
| 83 |
+
print(f"Successfully logged call {call_data.get('id')}.")
|
| 84 |
+
else:
|
| 85 |
+
print("Logging to Google Sheets failed: client not available.")
|
| 86 |
+
return jsonify({'status': 'success'}), 200
|
| 87 |
+
except Exception as e:
|
| 88 |
+
print(f"Error in webhook handler: {e}")
|
| 89 |
+
return jsonify({'status': 'error', 'message': str(e)}), 500
|