import os
import json
import gspread
import google.generativeai as genai
from flask import Flask, request, jsonify, render_template_string
from oauth2client.service_account import ServiceAccountCredentials
from vapi import Vapi
from datetime import datetime
# --- App Initialization ---
app = Flask(__name__)
# --- Configuration Loading ---
def load_config():
config_json_str = os.getenv("APP_CONFIG_JSON")
if not config_json_str:
raise ValueError("FATAL: The 'APP_CONFIG_JSON' secret is not set in your Space settings!")
return json.loads(config_json_str)
# --- HTML/CSS/JS Front-End Template ---
HTML_TEMPLATE = """
Voice AI Coordinator
🚀 Voice AI Coordinator
Deploy a voice agent to make calls on your behalf.
"""
# --- Routes ---
@app.route('/', methods=['GET'])
def index():
return render_template_string(HTML_TEMPLATE)
@app.route('/place-call', methods=['POST'])
def place_call_handler():
try:
config = load_config()
data = request.get_json()
name, number, reason = data['name'], data['number'], data['reason']
genai.configure(api_key=config["GEMINI_API_KEY"])
model = genai.GenerativeModel('gemini-1.5-flash')
prompt_details = f"User's intent: '{reason}'. Calling: '{name if name else 'N/A'}'."
gemini_prompt = f"Generate a single, polite opening sentence for a phone call. {prompt_details} If a name is provided, ask for them. Output ONLY the sentence string."
response = model.generate_content(gemini_prompt)
first_message = response.text.strip().replace('"', '')
# --- FIX 1: Initialize the Vapi client using the `token` keyword argument. ---
vapi = Vapi(token=config["VAPI_API_KEY"])
system_prompt = "You are Alex, a polite AI assistant. Your first sentence has been spoken. Now, listen and respond naturally in English or Hindi."
# --- FIX 2: Create the call using the `client.calls.create` attribute (plural). ---
call_response = vapi.calls.create(
assistant_id=config["VAPI_ASSISTANT_ID"], phone_number_id=config["VAPI_PHONE_NUMBER_ID"],
customer={"number": number},
assistant={
"serverUrl": config["WEBHOOK_SERVER_URL"], "firstMessage": first_message,
"voice": {"provider": "11labs", "voiceId": "pola", "speed": 0.9},
"model": {"provider": "openai", "model": "gpt-4o", "messages": [{"role": "system", "content": system_prompt}]},
"metadata": {"raw_intent": f"{reason} (for {name})"}
})
if call_response and call_response.id:
return jsonify({"message": "Call deployed successfully!", "call_id": call_response.id}), 200
else:
raise Exception("Vapi did not return a valid call object.")
except Exception as e:
print(f"[ERROR] in /place-call: {e}")
return jsonify({"error": str(e)}), 500
@app.route('/webhook', methods=['POST'])
def webhook_handler():
try:
config = load_config()
payload = request.json
if payload.get('message', {}).get('type') == 'hang':
call_data = payload.get('message', {}).get('call', {})
row = [
call_data.get("id", "N/A"), datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
call_data.get("customer", {}).get("number", "N/A"), call_data.get("metadata", {}).get("raw_intent", "N/A"),
payload.get('message', {}).get('endedReason', 'N/A'), call_data.get('summary', 'No summary provided.'),
"Yes" if "follow-up" in call_data.get('summary', '').lower() else "No",
json.dumps(call_data.get('transcript', []), indent=2)
]
creds = ServiceAccountCredentials.from_json_keyfile_name("credentials.json", ["https://spreadsheets.google.com/feeds", 'https://www.googleapis.com/auth/spreadsheets', "https://www.googleapis.com/auth/drive.file", "https://www.googleapis.com/auth/drive"])
client = gspread.authorize(creds)
sheet = client.open(config["GOOGLE_SHEET_NAME"]).sheet1
sheet.append_row(row)
print(f"[Log] Successfully logged call data for {call_data.get('id')}.")
except Exception as e:
print(f"[ERROR] in /webhook: {e}")
return jsonify({'status': 'success'}), 200