sonuprasad23 commited on
Commit
c9868a1
·
1 Parent(s): ab734ae
Files changed (1) hide show
  1. app.py +27 -130
app.py CHANGED
@@ -4,7 +4,7 @@ import gspread
4
  import google.generativeai as genai
5
  from flask import Flask, request, jsonify, render_template_string
6
  from oauth2client.service_account import ServiceAccountCredentials
7
- from vapi_python import Vapi
8
  from datetime import datetime
9
 
10
  # --- App Initialization ---
@@ -12,7 +12,6 @@ app = Flask(__name__)
12
 
13
  # --- Configuration Loading ---
14
  def load_config():
15
- """Loads configuration from a single Hugging Face secret (JSON)."""
16
  config_json_str = os.getenv("APP_CONFIG_JSON")
17
  if not config_json_str:
18
  raise ValueError("FATAL: The 'APP_CONFIG_JSON' secret is not set in your Space settings!")
@@ -30,159 +29,59 @@ HTML_TEMPLATE = """
30
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
31
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
32
  <style>
33
- body {
34
- font-family: 'Inter', sans-serif;
35
- display: flex;
36
- justify-content: center;
37
- align-items: center;
38
- min-height: 100vh;
39
- margin: 0;
40
- background-color: #f3f4f6;
41
- color: #1f2937;
42
- }
43
- .container {
44
- width: 100%;
45
- max-width: 450px;
46
- padding: 2rem;
47
- background-color: white;
48
- border-radius: 12px;
49
- box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -2px rgba(0,0,0,0.05);
50
- }
51
- .header {
52
- text-align: center;
53
- margin-bottom: 2rem;
54
- }
55
- .header h1 {
56
- font-size: 2rem;
57
- font-weight: 700;
58
- margin: 0;
59
- }
60
- .header p {
61
- margin-top: 0.5rem;
62
- color: #6b7280;
63
- }
64
- .form-group {
65
- margin-bottom: 1.5rem;
66
- }
67
- .form-group label {
68
- display: block;
69
- margin-bottom: 0.5rem;
70
- font-weight: 500;
71
- }
72
- .form-group input, .form-group textarea {
73
- width: 100%;
74
- padding: 0.75rem;
75
- border: 1px solid #d1d5db;
76
- border-radius: 8px;
77
- box-sizing: border-box;
78
- font-size: 1rem;
79
- transition: border-color 0.2s, box-shadow 0.2s;
80
- }
81
- .form-group input:focus, .form-group textarea:focus {
82
- outline: none;
83
- border-color: #3b82f6;
84
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3);
85
- }
86
- .deploy-button {
87
- width: 100%;
88
- padding: 0.8rem;
89
- border: none;
90
- border-radius: 8px;
91
- background-color: #ef4444;
92
- color: white;
93
- font-size: 1.1rem;
94
- font-weight: 600;
95
- cursor: pointer;
96
- transition: background-color 0.2s;
97
- }
98
- .deploy-button:hover {
99
- background-color: #dc2626;
100
- }
101
- .deploy-button:disabled {
102
- background-color: #fca5a5;
103
- cursor: not-allowed;
104
- }
105
- .status {
106
- margin-top: 1.5rem;
107
- padding: 0.75rem;
108
- border-radius: 8px;
109
- text-align: center;
110
- font-weight: 500;
111
- display: none; /* Hidden by default */
112
- }
113
- .status.success {
114
- background-color: #d1fae5;
115
- color: #065f46;
116
- display: block;
117
- }
118
- .status.error {
119
- background-color: #fee2e2;
120
- color: #991b1b;
121
- display: block;
122
- }
123
- .status.loading {
124
- background-color: #e0e7ff;
125
- color: #3730a3;
126
- display: block;
127
- }
128
  </style>
129
  </head>
130
  <body>
131
  <div class="container">
132
- <div class="header">
133
- <h1>🚀 Voice AI Coordinator</h1>
134
- <p>Deploy a voice agent to make calls on your behalf.</p>
135
- </div>
136
  <form id="call-form">
137
- <div class="form-group">
138
- <label for="target-name">Whom to call (Name)</label>
139
- <input type="text" id="target-name" placeholder="e.g., Jane Doe (Optional)">
140
- </div>
141
- <div class="form-group">
142
- <label for="phone-number">Phone Number</label>
143
- <input type="tel" id="phone-number" placeholder="+911234567890" required>
144
- </div>
145
- <div class="form-group">
146
- <label for="raw-intent">Reason for Calling</label>
147
- <textarea id="raw-intent" rows="3" placeholder="e.g., Ask if they would prefer tea or coffee" required></textarea>
148
- </div>
149
  <button type="submit" id="deploy-button" class="deploy-button">Deploy Call</button>
150
  </form>
151
  <div id="status-message" class="status"></div>
152
  </div>
153
-
154
  <script>
155
  const form = document.getElementById('call-form');
156
  const button = document.getElementById('deploy-button');
157
  const statusDiv = document.getElementById('status-message');
158
-
159
  form.addEventListener('submit', async (event) => {
160
  event.preventDefault();
161
-
162
  const name = document.getElementById('target-name').value;
163
  const number = document.getElementById('phone-number').value;
164
  const reason = document.getElementById('raw-intent').value;
165
-
166
  button.disabled = true;
167
  button.textContent = 'Deploying...';
168
  statusDiv.className = 'status loading';
169
  statusDiv.textContent = 'Processing intent and placing call...';
170
-
171
  try {
172
  const response = await fetch('/place-call', {
173
  method: 'POST',
174
  headers: { 'Content-Type': 'application/json' },
175
  body: JSON.stringify({ name, number, reason })
176
  });
177
-
178
  const result = await response.json();
179
-
180
  if (response.ok) {
181
  statusDiv.className = 'status success';
182
  statusDiv.textContent = `✅ Success! Call deployed with ID: ${result.call_id}`;
183
- } else {
184
- throw new Error(result.error || 'An unknown error occurred.');
185
- }
186
  } catch (error) {
187
  statusDiv.className = 'status error';
188
  statusDiv.textContent = `❌ Error: ${error.message}`;
@@ -199,12 +98,10 @@ HTML_TEMPLATE = """
199
  # --- Routes ---
200
  @app.route('/', methods=['GET'])
201
  def index():
202
- """Serves the main user interface."""
203
  return render_template_string(HTML_TEMPLATE)
204
 
205
  @app.route('/place-call', methods=['POST'])
206
  def place_call_handler():
207
- """Handles the API request from the front-end to place a call."""
208
  try:
209
  config = load_config()
210
  data = request.get_json()
@@ -217,12 +114,13 @@ def place_call_handler():
217
  response = model.generate_content(gemini_prompt)
218
  first_message = response.text.strip().replace('"', '')
219
 
220
- vapi = Vapi(api_key=config["VAPI_API_KEY"])
 
 
221
  system_prompt = "You are Alex, a polite AI assistant. Your first sentence has been spoken. Now, listen and respond naturally in English or Hindi."
222
 
223
- # --- THIS IS THE FIX ---
224
- # Changed `vapi.calls.create` to `vapi.call.create` (singular)
225
- call_response = vapi.call.create(
226
  assistant_id=config["VAPI_ASSISTANT_ID"], phone_number_id=config["VAPI_PHONE_NUMBER_ID"],
227
  customer={"number": number},
228
  assistant={
@@ -243,7 +141,6 @@ def place_call_handler():
243
 
244
  @app.route('/webhook', methods=['POST'])
245
  def webhook_handler():
246
- """Handles the webhook callback from Vapi after a call ends."""
247
  try:
248
  config = load_config()
249
  payload = request.json
 
4
  import google.generativeai as genai
5
  from flask import Flask, request, jsonify, render_template_string
6
  from oauth2client.service_account import ServiceAccountCredentials
7
+ from vapi import Vapi
8
  from datetime import datetime
9
 
10
  # --- App Initialization ---
 
12
 
13
  # --- Configuration Loading ---
14
  def load_config():
 
15
  config_json_str = os.getenv("APP_CONFIG_JSON")
16
  if not config_json_str:
17
  raise ValueError("FATAL: The 'APP_CONFIG_JSON' secret is not set in your Space settings!")
 
29
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
30
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
31
  <style>
32
+ body { font-family: 'Inter', sans-serif; display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; background-color: #f3f4f6; color: #1f2937; }
33
+ .container { width: 100%; max-width: 450px; padding: 2rem; background-color: white; border-radius: 12px; box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -2px rgba(0,0,0,0.05); }
34
+ .header { text-align: center; margin-bottom: 2rem; }
35
+ .header h1 { font-size: 2rem; font-weight: 700; margin: 0; }
36
+ .header p { margin-top: 0.5rem; color: #6b7280; }
37
+ .form-group { margin-bottom: 1.5rem; }
38
+ .form-group label { display: block; margin-bottom: 0.5rem; font-weight: 500; }
39
+ .form-group input, .form-group textarea { width: 100%; padding: 0.75rem; border: 1px solid #d1d5db; border-radius: 8px; box-sizing: border-box; font-size: 1rem; transition: border-color 0.2s, box-shadow 0.2s; }
40
+ .form-group input:focus, .form-group textarea:focus { outline: none; border-color: #3b82f6; box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3); }
41
+ .deploy-button { width: 100%; padding: 0.8rem; border: none; border-radius: 8px; background-color: #ef4444; color: white; font-size: 1.1rem; font-weight: 600; cursor: pointer; transition: background-color 0.2s; }
42
+ .deploy-button:hover { background-color: #dc2626; }
43
+ .deploy-button:disabled { background-color: #fca5a5; cursor: not-allowed; }
44
+ .status { margin-top: 1.5rem; padding: 0.75rem; border-radius: 8px; text-align: center; font-weight: 500; display: none; }
45
+ .status.success { background-color: #d1fae5; color: #065f46; display: block; }
46
+ .status.error { background-color: #fee2e2; color: #991b1b; display: block; }
47
+ .status.loading { background-color: #e0e7ff; color: #3730a3; display: block; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  </style>
49
  </head>
50
  <body>
51
  <div class="container">
52
+ <div class="header"><h1>🚀 Voice AI Coordinator</h1><p>Deploy a voice agent to make calls on your behalf.</p></div>
 
 
 
53
  <form id="call-form">
54
+ <div class="form-group"><label for="target-name">Whom to call (Name)</label><input type="text" id="target-name" placeholder="e.g., Jane Doe (Optional)"></div>
55
+ <div class="form-group"><label for="phone-number">Phone Number</label><input type="tel" id="phone-number" placeholder="+911234567890" required></div>
56
+ <div class="form-group"><label for="raw-intent">Reason for Calling</label><textarea id="raw-intent" rows="3" placeholder="e.g., Ask if they would prefer tea or coffee" required></textarea></div>
 
 
 
 
 
 
 
 
 
57
  <button type="submit" id="deploy-button" class="deploy-button">Deploy Call</button>
58
  </form>
59
  <div id="status-message" class="status"></div>
60
  </div>
 
61
  <script>
62
  const form = document.getElementById('call-form');
63
  const button = document.getElementById('deploy-button');
64
  const statusDiv = document.getElementById('status-message');
 
65
  form.addEventListener('submit', async (event) => {
66
  event.preventDefault();
 
67
  const name = document.getElementById('target-name').value;
68
  const number = document.getElementById('phone-number').value;
69
  const reason = document.getElementById('raw-intent').value;
 
70
  button.disabled = true;
71
  button.textContent = 'Deploying...';
72
  statusDiv.className = 'status loading';
73
  statusDiv.textContent = 'Processing intent and placing call...';
 
74
  try {
75
  const response = await fetch('/place-call', {
76
  method: 'POST',
77
  headers: { 'Content-Type': 'application/json' },
78
  body: JSON.stringify({ name, number, reason })
79
  });
 
80
  const result = await response.json();
 
81
  if (response.ok) {
82
  statusDiv.className = 'status success';
83
  statusDiv.textContent = `✅ Success! Call deployed with ID: ${result.call_id}`;
84
+ } else { throw new Error(result.error || 'An unknown error occurred.'); }
 
 
85
  } catch (error) {
86
  statusDiv.className = 'status error';
87
  statusDiv.textContent = `❌ Error: ${error.message}`;
 
98
  # --- Routes ---
99
  @app.route('/', methods=['GET'])
100
  def index():
 
101
  return render_template_string(HTML_TEMPLATE)
102
 
103
  @app.route('/place-call', methods=['POST'])
104
  def place_call_handler():
 
105
  try:
106
  config = load_config()
107
  data = request.get_json()
 
114
  response = model.generate_content(gemini_prompt)
115
  first_message = response.text.strip().replace('"', '')
116
 
117
+ # --- FIX 1: Initialize the Vapi client using the `token` keyword argument. ---
118
+ vapi = Vapi(token=config["VAPI_API_KEY"])
119
+
120
  system_prompt = "You are Alex, a polite AI assistant. Your first sentence has been spoken. Now, listen and respond naturally in English or Hindi."
121
 
122
+ # --- FIX 2: Create the call using the `client.calls.create` attribute (plural). ---
123
+ call_response = vapi.calls.create(
 
124
  assistant_id=config["VAPI_ASSISTANT_ID"], phone_number_id=config["VAPI_PHONE_NUMBER_ID"],
125
  customer={"number": number},
126
  assistant={
 
141
 
142
  @app.route('/webhook', methods=['POST'])
143
  def webhook_handler():
 
144
  try:
145
  config = load_config()
146
  payload = request.json