Spaces:
Sleeping
Sleeping
| import os | |
| import requests | |
| from fastapi import FastAPI | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from pydantic import BaseModel | |
| app = FastAPI() | |
| # ---- Allow frontend access ---- | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["https://voice-call-agent.vercel.app/"], # β Restrict in production | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| # ---- Environment variables ---- | |
| OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") | |
| AIRTABLE_API_KEY = os.getenv("AIRTABLE_API_KEY") | |
| AIRTABLE_BASE_ID = os.getenv("AIRTABLE_BASE_ID") | |
| AIRTABLE_TABLE = "Appointments" | |
| # ---- Ephemeral Key Endpoint ---- | |
| def get_ephemeral_key(): | |
| """Generate an ephemeral client secret for OpenAI Realtime API""" | |
| url = "https://api.openai.com/v1/realtime/client_secrets" | |
| headers = { | |
| "Authorization": f"Bearer {OPENAI_API_KEY}", | |
| "Content-Type": "application/json", | |
| } | |
| body = { | |
| "session": { | |
| "type": "realtime", | |
| "model": "gpt-4o-mini-realtime-preview", | |
| "audio": { | |
| "output": {"voice": "verse"} | |
| }, | |
| } | |
| } | |
| res = requests.post(url, headers=headers, json=body) | |
| print("π Ephemeral key request status:", res.status_code) | |
| data = res.json() | |
| print("π Ephemeral key response:", data) | |
| return data | |
| # ---- Add Appointment ---- | |
| class Appointment(BaseModel): | |
| name: str | |
| phone: str | |
| date: str | |
| time: str | |
| service: str | |
| def add_appointment(appo: Appointment): | |
| """Add a new appointment to Airtable (auto-increment ID handled by Airtable)""" | |
| url = f"https://api.airtable.com/v0/{AIRTABLE_BASE_ID}/{AIRTABLE_TABLE}" | |
| headers = { | |
| "Authorization": f"Bearer {AIRTABLE_API_KEY}", | |
| "Content-Type": "application/json", | |
| } | |
| data = { | |
| "fields": { | |
| "Name": appo.name, | |
| "Phone": appo.phone, | |
| "Date": appo.date, | |
| "Time": appo.time, | |
| "Service": appo.service, | |
| } | |
| } | |
| r = requests.post(url, headers=headers, json=data) | |
| print("πΉ Airtable Add Response:", r.status_code, r.text) | |
| return r.json() | |
| # ---- Update Appointment ---- | |
| class UpdateAppointment(BaseModel): | |
| appointment_id: str | |
| name: str | None = None | |
| phone: str | None = None | |
| date: str | None = None | |
| time: str | None = None | |
| service: str | None = None | |
| def update_appointment(update: UpdateAppointment): | |
| """Update an existing appointment in Airtable using custom Appointment ID""" | |
| headers = { | |
| "Authorization": f"Bearer {AIRTABLE_API_KEY}", | |
| "Content-Type": "application/json", | |
| } | |
| # Step 1: Find record by Appointment ID field | |
| filter_formula = f"{{Appointment ID}} = '{update.appointment_id}'" | |
| search_url = f"https://api.airtable.com/v0/{AIRTABLE_BASE_ID}/{AIRTABLE_TABLE}?filterByFormula={filter_formula}" | |
| search_res = requests.get(search_url, headers=headers).json() | |
| if not search_res.get("records"): | |
| print("β No record found for Appointment ID:", update.appointment_id) | |
| return {"status": "error", "message": "Appointment not found"} | |
| record_id = search_res["records"][0]["id"] | |
| # Step 2: Prepare updated fields | |
| fields = {} | |
| if update.name: | |
| fields["Name"] = update.name | |
| if update.phone: | |
| fields["Phone"] = update.phone | |
| if update.date: | |
| fields["Date"] = update.date | |
| if update.time: | |
| fields["Time"] = update.time | |
| if update.service: | |
| fields["Service"] = update.service | |
| # Step 3: Patch record by record ID | |
| update_url = f"https://api.airtable.com/v0/{AIRTABLE_BASE_ID}/{AIRTABLE_TABLE}/{record_id}" | |
| patch_res = requests.patch(update_url, headers=headers, json={"fields": fields}) | |
| print("πΈ Airtable Update Response:", patch_res.status_code, patch_res.text) | |
| if patch_res.status_code == 200: | |
| return {"status": "updated"} | |
| else: | |
| return {"status": "error", "message": "Update failed"} | |
| class CancelAppointment(BaseModel): | |
| appointment_id: str | |
| def cancel_appointment(cancel: CancelAppointment): | |
| """Cancel appointment by custom Appointment ID field""" | |
| # Step 1: Search record by Appointment ID field | |
| filter_formula = f"{{Appointment ID}} = '{cancel.appointment_id}'" | |
| search_url = f"https://api.airtable.com/v0/{AIRTABLE_BASE_ID}/{AIRTABLE_TABLE}?filterByFormula={filter_formula}" | |
| headers = {"Authorization": f"Bearer {AIRTABLE_API_KEY}"} | |
| search_res = requests.get(search_url, headers=headers).json() | |
| if not search_res.get("records"): | |
| print("β No record found for Appointment ID:", cancel.appointment_id) | |
| return {"status": "error", "message": "Appointment not found"} | |
| record_id = search_res["records"][0]["id"] | |
| print(f"β Found Record ID: {record_id} for Appointment ID: {cancel.appointment_id}") | |
| # Step 2: Delete record by Record ID | |
| delete_url = f"https://api.airtable.com/v0/{AIRTABLE_BASE_ID}/{AIRTABLE_TABLE}/{record_id}" | |
| delete_res = requests.delete(delete_url, headers=headers) | |
| print("ποΈ Airtable Delete Response:", delete_res.status_code, delete_res.text) | |
| if delete_res.status_code == 200: | |
| return {"status": "deleted"} | |
| else: | |
| return {"status": "error", "message": "Delete failed"} |