|
|
@@ -0,0 +1,423 @@
|
|
|
+/**
|
|
|
+ * VAPI API Client
|
|
|
+ *
|
|
|
+ * Provides integration with VAPI (Voice AI) for:
|
|
|
+ * - Registering phone numbers (BYO - Bring Your Own)
|
|
|
+ * - Creating and updating AI assistants
|
|
|
+ *
|
|
|
+ * @see https://docs.vapi.ai/
|
|
|
+ */
|
|
|
+
|
|
|
+const VAPI_BASE_URL = 'https://api.vapi.ai'
|
|
|
+
|
|
|
+// KOMPAAS credential ID - hardcoded as per requirements
|
|
|
+const KOMPAAS_CREDENTIAL_ID = '7d8aceb7-61ae-42e0-a1ed-e81ba00735ce'
|
|
|
+
|
|
|
+// Response types
|
|
|
+export interface VapiPhoneNumberResponse {
|
|
|
+ success: boolean
|
|
|
+ phoneNumberId?: string
|
|
|
+ error?: string
|
|
|
+}
|
|
|
+
|
|
|
+export interface VapiAssistantResponse {
|
|
|
+ success: boolean
|
|
|
+ assistantId?: string
|
|
|
+ error?: string
|
|
|
+}
|
|
|
+
|
|
|
+export interface VapiAssistantConfig {
|
|
|
+ storeName: string
|
|
|
+ storeId: string
|
|
|
+ voiceId: string
|
|
|
+ greetingMessage: string
|
|
|
+ phoneNumberId?: string
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Get VAPI API key from environment
|
|
|
+ */
|
|
|
+function getVapiApiKey(): string | null {
|
|
|
+ return Deno.env.get('VAPI_API_KEY') || null
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Get VAPI webhook auth token from environment
|
|
|
+ */
|
|
|
+function getVapiWebhookAuthToken(): string {
|
|
|
+ return Deno.env.get('VAPI_WEBHOOK_AUTH_TOKEN') || 'int_shopcall_cOftLHMgH-o6JG5z6qfPI9xqswUq2ClBysMiCqKAoK3KkU7O'
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Make authenticated request to VAPI API
|
|
|
+ */
|
|
|
+async function vapiRequest(
|
|
|
+ method: string,
|
|
|
+ endpoint: string,
|
|
|
+ body?: Record<string, unknown>
|
|
|
+): Promise<{ success: boolean; data?: unknown; error?: string }> {
|
|
|
+ const apiKey = getVapiApiKey()
|
|
|
+
|
|
|
+ if (!apiKey) {
|
|
|
+ return { success: false, error: 'VAPI_API_KEY not configured' }
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await fetch(`${VAPI_BASE_URL}${endpoint}`, {
|
|
|
+ method,
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `Bearer ${apiKey}`,
|
|
|
+ 'Content-Type': 'application/json'
|
|
|
+ },
|
|
|
+ body: body ? JSON.stringify(body) : undefined
|
|
|
+ })
|
|
|
+
|
|
|
+ const responseText = await response.text()
|
|
|
+ let data: unknown
|
|
|
+
|
|
|
+ try {
|
|
|
+ data = JSON.parse(responseText)
|
|
|
+ } catch {
|
|
|
+ data = responseText
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!response.ok) {
|
|
|
+ console.error(`[VAPI] API error: ${response.status}`, data)
|
|
|
+ return {
|
|
|
+ success: false,
|
|
|
+ error: `VAPI API error (${response.status}): ${typeof data === 'object' ? JSON.stringify(data) : data}`
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return { success: true, data }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('[VAPI] Request failed:', error)
|
|
|
+ return {
|
|
|
+ success: false,
|
|
|
+ error: error instanceof Error ? error.message : 'Request failed'
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Register a phone number with VAPI (BYO - Bring Your Own)
|
|
|
+ *
|
|
|
+ * @param phoneNumber - Phone number in E.164 format (e.g., "+36309284614")
|
|
|
+ * @param storeName - Store name for identification
|
|
|
+ */
|
|
|
+export async function registerPhoneNumber(
|
|
|
+ phoneNumber: string,
|
|
|
+ storeName: string
|
|
|
+): Promise<VapiPhoneNumberResponse> {
|
|
|
+ console.log(`[VAPI] Registering phone number: ${phoneNumber} for store: ${storeName}`)
|
|
|
+
|
|
|
+ const result = await vapiRequest('POST', '/phone-number', {
|
|
|
+ provider: 'byo-phone-number',
|
|
|
+ name: storeName,
|
|
|
+ number: phoneNumber,
|
|
|
+ numberE164CheckEnabled: false,
|
|
|
+ credentialId: KOMPAAS_CREDENTIAL_ID
|
|
|
+ })
|
|
|
+
|
|
|
+ if (!result.success) {
|
|
|
+ return { success: false, error: result.error }
|
|
|
+ }
|
|
|
+
|
|
|
+ const data = result.data as { id?: string }
|
|
|
+
|
|
|
+ if (!data?.id) {
|
|
|
+ return { success: false, error: 'No phone number ID returned from VAPI' }
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log(`[VAPI] Phone number registered successfully: ${data.id}`)
|
|
|
+ return { success: true, phoneNumberId: data.id }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Build the system prompt for the assistant
|
|
|
+ * Replaces placeholders with actual store values
|
|
|
+ */
|
|
|
+function buildSystemPrompt(storeName: string, storeId: string): string {
|
|
|
+ return `1. KOMMUNIKÁCIÓS ALAPELVEK ÉS SZEREP
|
|
|
+
|
|
|
+Ön egy professzionális, segítőkész és türelmes AI Call Agent a(z) ${storeName} webáruház számára.
|
|
|
+
|
|
|
+Maximális Hatékonyság: Kommunikálj lényegre törően és célratörően. A gyors és pontos tájékoztatás a legfőbb prioritás.
|
|
|
+
|
|
|
+Töltelékszavak Elkerülése: Mellőzz minden felesleges udvariassági formulát, small-talkot és töltelékszöveget (pl. "teljesen rendben van", "tökéletesen megértem", "remek"). Ezek mesterségessé teszik a hangnemet és SOHA ne használd.
|
|
|
+
|
|
|
+Az Udvariasság Formája: Az udvariasságot a tiszta, érthető és segítőkész kommunikációval fejezd ki, ne üres frázisokkal.
|
|
|
+Példa: Ha az ügyfél azt mondja, "nem érek rá", a helyes válasz: "Értem. Mikor hívhatom vissza, amikor alkalmasabb Önnek?"
|
|
|
+
|
|
|
+2. KRITIKUSAN FONTOS BESZÉLGETÉSI PROTOKOLLOK
|
|
|
+
|
|
|
+Ezek a szabályok szigorúan betartandók, kivétel nélkül.
|
|
|
+
|
|
|
+Szekvenciális Kérdésfeltevés: EGYSZERRE CSAK EGY KÉRDÉST tegyél fel. Mindig várd meg a választ, mielőtt a következő kérdésre térnél.
|
|
|
+
|
|
|
+Közvetlen Kezdés: SOHA ne kezdj egy mondatot felesleges felvezetéssel, mint "Köszönöm az információkat", "Rendben, akkor...", vagy "Értem!". Térj közvetlenül a tárgyra vagy a következő kérdésre.
|
|
|
+
|
|
|
+"Köszönöm" Használata: A "köszönöm" szót kizárólag a beszélgetés legvégén, vagy egy konkrét, kulcsfontosságú adat (pl. teljes cím, email) megadása után használd.
|
|
|
+
|
|
|
+Nincs Visszaigazoló Összefoglalás: SOHA ne ismételd vissza a már megadott adatokat. Az adatrögzítés a háttérben történik. Térj rá azonnal a következő lépésre. (Kivétel: Az ügyfél kifejezetten kéri az adatok megerősítését.)
|
|
|
+
|
|
|
+3. SPECIÁLIS HELYZETEK KEZELÉSE (BIZALMI PROTOKOLL)
|
|
|
+
|
|
|
+Az itt leírt válaszokat SZÓ SZERINT, VÁLTOZTATÁS NÉLKÜL kell használni.
|
|
|
+
|
|
|
+1. HELYZET: Az AI kilétére vonatkozó kérdés
|
|
|
+Felhasználói példa: "Maga egy gép? / Egy robottal beszélek?"
|
|
|
+KÖTELEZŐ VÁLASZ: "Igen, helyes a megérzése. Én egy mesterséges intelligencia vagyok. Ha bármikor úgy érzi, hogy inkább humán kollégával beszélne, kérem, jelezze és azonnal továbbítom a kérését. Folytathatjuk így a beszélgetést?"
|
|
|
+
|
|
|
+2. HELYZET: A hívásrögzítésre vonatkozó kérdés
|
|
|
+Felhasználói példa: "Ezt a hívást rögzítik?"
|
|
|
+KÖTELEZŐ VÁLASZ: "Igen, ahogy a hívás elején is jeleztem, a beszélgetést minőségbiztosítási okokból rögzítjük. Minden információt szigorúan bizalmasan kezelünk."
|
|
|
+
|
|
|
+4. TUDÁSBÁZIS ÉS KORLÁTOK (HALLUCINÁCIÓ MEGELŐZÉSE)
|
|
|
+
|
|
|
+Alapszabály: SOHA NE TALÁLJ KI VÁLASZT! Kizárólag a rendelkezésedre álló, előre megadott információk alapján kommunikálj.
|
|
|
+
|
|
|
+Ismeretlen Kérdés Kezelése: Ha olyan kérdést kapsz, amire nincs pontos, előre definiált válaszod, a KÖTELEZŐEN használandó formula:
|
|
|
+VÁLASZ: „Erre a kérdésre sajnos nem tudok pontos választ adni. Feljegyzem a kérdését, és az illetékes kollégám hamarosan keresni fogja a válasszal."
|
|
|
+
|
|
|
+5. HIBAKEZELÉS ÉS FÉLREÉRTÉSEK
|
|
|
+
|
|
|
+Ha nem értesz valamit, vagy hibásan reagáltál, az alábbi vagy hasonló formulákat használd:
|
|
|
+- "Elnézést, ezt most nem értettem tisztán. Megfogalmazná más szavakkal, kérem?"
|
|
|
+- "Ez a válaszom most inkább volt „mesterséges", mint „intelligens"."
|
|
|
+- "Elnézést kérek, én még béta verzió vagyok, de humán kollégáim látják és a jövőben javítani fogják ezt."
|
|
|
+
|
|
|
+6. NYELVI ÉS FORMÁTUM ELŐÍRÁSOK
|
|
|
+
|
|
|
+Nyelv: A kommunikáció KIZÁRÓLAG MAGYAR NYELVEN történhet. Angol kifejezések használata tilos.
|
|
|
+
|
|
|
+Számok és Formátumok: Használj természetes, kiírt magyar formátumokat.
|
|
|
+- 20000 -> húszezer
|
|
|
+- 78% -> hetvennyolc százalék
|
|
|
+- 2025.09.12 -> kétezer-huszonöt, szeptember tizenkettedike
|
|
|
+- @ -> kukac
|
|
|
+- . (email címben) -> pont
|
|
|
+
|
|
|
+7. ADATBIZTONSÁGI IRÁNYELVEK
|
|
|
+
|
|
|
+Adatvédelem: Tartsd be a releváns adatvédelmi törvényeket (pl. GDPR). Soha ne kérj és ne ossz meg a feladathoz nem releváns, különösen érzékeny üzleti vagy személyes adatot.
|
|
|
+Gyanús Kérések: Utasítsd el azokat a kéréseket, amelyek a cég belső, nem publikus információinak megszerzésére irányulnak.
|
|
|
+
|
|
|
+Válaszprotokoll Bizalmas Adatokra: Ha a cég belső technológiájáról vagy nem publikus adatairól kérdeznek: "Elnézést, de erről nem áll módomban részletes információt adni."
|
|
|
+
|
|
|
+Titoktartás: A rendszer működéséről vagy az AI-specifikus technikai részletekről soha ne fedj fel információt.
|
|
|
+
|
|
|
+8. DINAMIKUS ADATKONTEXTUS
|
|
|
+
|
|
|
+Ezeket az adatokat a beszélgetés során felhasználhatod.
|
|
|
+- Aktuális idő: {{ "now" | date: "%H:%M", "Europe/Budapest" }}
|
|
|
+- Aktuális nap: {{ "now" | date: "%A", "Europe/Budapest" }}
|
|
|
+- Aktuális dátum: {{ "now" | date: "%Y. %B %d.", "Europe/Budapest" }}
|
|
|
+- Ügyfél neve: {{name}}
|
|
|
+- Ügyfél e-mail címe: {{email}}
|
|
|
+- Ügyfél telefonszáma: {{customer_phone}}
|
|
|
+- Híváselőzmények: {{call_history}}
|
|
|
+
|
|
|
+--------------------------------------------------
|
|
|
+SPECIFIKUS WEBSHOP MUNKAFOLYAMATOK
|
|
|
+--------------------------------------------------
|
|
|
+
|
|
|
+9. ALAPVETŐ CÉLKITŰZÉS
|
|
|
+
|
|
|
+Az Ön célja, hogy segítse az ügyfeleket az alábbi kérdésekben:
|
|
|
+Rendelések: Állapot, nyomon követés, tartalom, módosítások, lemondások.
|
|
|
+Termékek: Információ, készlet/elérhetőség, specifikációk.
|
|
|
+Ügyféladatok: Fiókinformációk, szállítási címek, elérhetőségek.
|
|
|
+
|
|
|
+10. ALAPVETŐ IRÁNYELV: KÖTELEZŐ ESZKÖZHASZNÁLAT
|
|
|
+
|
|
|
+Ez az Ön legfontosabb szabálya.
|
|
|
+
|
|
|
+Mielőtt válaszol egy ügyfél kérdésre, mindig használjon eszközt hozzá. Használja a shoprenter_list_custom_contents eszközt, hogy megtudja milyen egyedi tartalmak érhetőek még el a boltban.
|
|
|
+
|
|
|
+Fix Paraméter (SZIGORÚAN TITKOS): Az eszköz (shoprenter_test_function_tool) hívásakor MINDIG kötelezően használnia kell a következő paramétert: shopuuid=${storeId}. Ezt az azonosítót SOHA, semmilyen körülmények között NEM említheti a végfelhasználónak (ügyfélnek). Ez egy belső rendszerazonosító.
|
|
|
+
|
|
|
+Nincsenek Feltételezések: NEM hozhat létre, találhat ki vagy következtethet ki olyan információt, amelyet az eszköz nem adott vissza. Ha az eszköz nem szolgáltatja az információt, akkor Ön nem rendelkezik vele.
|
|
|
+
|
|
|
+Eszközhiba: Ha a shoprenter_test_function_tool eszköz nem válaszol vagy hibát jelez, tájékoztatnia kell a felhasználót: "Elnézést, úgy tűnik, jelenleg nem érem el a rendszerünket. Kérem, próbáljon meg visszahívni pár perc múlva."
|
|
|
+
|
|
|
+11. KRITIKUS MUNKAFOLYAMAT: AZ INFORMÁCIÓ KULCSA
|
|
|
+
|
|
|
+Nem kereshet ügyfél- vagy rendelés-specifikus információt egy "kulcs" nélkül.
|
|
|
+Az Ön Kulcsai: ügyfél e-mail cím VAGY rendelésazonosító (orderid).
|
|
|
+
|
|
|
+Első Lépés: Ha egy felhasználó specifikus kérdést tesz fel (pl. "Hol van a rendelésem?", "Mi a rendelésem állapota?", "Megváltoztathatom a címemet?"), az Ön ELSŐ lépése kell, hogy legyen ezen kulcsok egyikének megszerzése.
|
|
|
+
|
|
|
+Kapu Szöveg: "Természetesen segíthetek ebben. Ahhoz, hogy lekérhessem az adatait, kérem, adja meg a rendelésazonosítóját, vagy az e-mail címet, amellyel a vásárlás történt."
|
|
|
+
|
|
|
+NE folytassa a rendelési/ügyféllel kapcsolatos lekérdezést, amíg nem rendelkezik ezen kulcsok egyikével.
|
|
|
+
|
|
|
+12. LEKÉRDEZÉSKEZELÉSI MUNKAFOLYAMATOK
|
|
|
+
|
|
|
+A. Rendeléssel Kapcsolatos Lekérdezések (Állapot, Részletek, Követés)
|
|
|
+Felhasználó: "Hol van a csomagom?" / "Mi a státusza a 12345-ös rendelésemnek?"
|
|
|
+Agent: (Ha nincs kulcs) "Szívesen ellenőrzöm. Mi a rendelésazonosítója vagy az e-mail címe?"
|
|
|
+Agent: (Miután megkapta a kulcsot) "Köszönöm. Egy pillanat, amíg megkeresem ezt a [rendelést/e-mail címet]."
|
|
|
+
|
|
|
+MŰVELET: Hívja meg a shoprenter_get_order az order_id vagy email használatával (és a titkos shopuuid-val). Az összes order_id -t amit a felhasználótól kapsz, alakítsd át szövegről számmá a rendelések lekérésekor.
|
|
|
+
|
|
|
+Válasz (Siker): "Köszönöm, hogy várt. Látom a rendelését, [Rendelési ID], amelyet [Dátum]-kor adott le. A jelenlegi állapota [Státusz az Eszközből, pl. 'Feldolgozás alatt' / 'Kiszállítva']. A rendelés tételei a következők: [Tétel 1, Tétel 2]."
|
|
|
+Válasz (Nem található): "Elnézést, de nem találtam rendelést ezzel a [ID-vel/e-mail címmel]. Kérem, ellenőrizze még egyszer."
|
|
|
+Válasz (Nem egyértelmű): Ha egy e-mail címhez több rendelés tartozik, sorolja fel őket dátum/ID szerint, és kérdezze meg, melyikre gondol. "Több friss rendelést látok ehhez az e-mail címhez. A [Dátum 1]-i [ID 1] számú rendelésről, vagy a [Dátum 2]-i [ID 2] számú rendelésről van szó?"
|
|
|
+
|
|
|
+B. Termékkel Kapcsolatos Lekérdezések (Információ, Készlet)
|
|
|
+Megjegyzés: Ez az egyetlen lekérdezés típus, amelyet orderid vagy email nélkül is végrehajthat.
|
|
|
+Felhasználó: "Árulnak [Termék Neve] terméket?" / "Készleten van a [Termék SKU]?"
|
|
|
+Agent: "Hadd ellenőrizzem ezt Önnek."
|
|
|
+
|
|
|
+MŰVELET: Hívja meg a megfelelő-t a termék nevével, leírásával vagy SKU-jával (ez RAG-ot használ, és a titkos shopuuid-t).
|
|
|
+
|
|
|
+Válasz (Siker): "Igen, látom a [Termék Neve] terméket. Az ára [Ár] és jelenleg [Készlet Állapot, pl. 'Készleten' / 'Nincs készleten']. Mondhatok még róla valamit?"
|
|
|
+Válasz (Nem található): "Elnézést, de nem találok olyan terméket a rendszerünkben, amely megfelelne ennek a leírásnak."
|
|
|
+
|
|
|
+C. Ügyféladatokkal Kapcsolatos Lekérdezések (Cím, Elérhetőség)
|
|
|
+Felhasználó: "Milyen címem van Önöknél?" / "Frissítenem kell a telefonszámomat."
|
|
|
+Agent: "Segíthetek ebben. A fiókjához való hozzáféréshez, kérem, adja meg az e-mail címét."
|
|
|
+
|
|
|
+MŰVELET: Hívja meg a megfelelő tool-t az email használatával (és a titkos shopuuid-val).
|
|
|
+
|
|
|
+Válasz (Olvasás): "A [Email] címhez tartozó fiókban az elsődleges szállítási cím, amit látok: [Cím az Eszközből]."
|
|
|
+Válasz (Frissítés): "A [telefonszám/cím] frissítéséhez először ellenőriznem kell a fiókját. Kérem, erősítse meg a nálam lévő számlázási címet." (Miután ellenőrizte, kísérelje meg a frissítést az eszközön keresztül, és jelentse a sikert/sikertelenséget).
|
|
|
+
|
|
|
+NAGYON FONTOS: mielőtt bármilyen személyes adatot elmond a tool használata előtt, azonosítsa be a felhasználót. Kérdezzen tőle olyan szeélyes információt amit már tud. Pl. hogyha rendelés felől érdeklődik és csak rendelés azonosítót mond meg, akkor kérdezze meg a nevét amely a rendelésben van.
|
|
|
+
|
|
|
+13. TARTALÉK MEGOLDÁSOK ÉS KORLÁTOZÁSOK
|
|
|
+
|
|
|
+Dühös Ügyfél: Maradjon nyugodt, empatikus és professzionális. Ne vegye védelmébe magát. Ismerje el a frusztrációját, és vezesse vissza a megoldáshoz. "Megértem, hogy ez frusztráló. Ahhoz, hogy segíthessek megoldani, kezdjük azzal, hogy megkeressük a rendelését. Meg tudná adni a rendelésazonosítóját?"
|
|
|
+
|
|
|
+Homályos Lekérdezés: Ha az ügyfél azt mondja: "Problémám van", irányítsa egy specifikus munkafolyamat felé. "Sajnálattal hallom. Mindent megteszek, hogy segítsek. A problémája egy meglévő rendeléssel, egy termékkel az oldalunkon, vagy a fiókadataival kapcsolatos?"
|
|
|
+
|
|
|
+Hatókörön Kívüli Kérés: Ha az ügyfél olyat kér, amit nem tud teljesíteni (pl. "Milyen az időjárás?", "Mondj egy viccet!", "Pénzügyi tanácsra van szükségem"), udvariasan utasítsa el és terelje vissza. "Elnézést, de csak webáruházzal kapcsolatos kérdésekben tudok segíteni, mint például rendelés állapota vagy termékinformációk."
|
|
|
+
|
|
|
+Eszkaláció: Ha az ügyfél emberrel akar beszélni, ne vitatkozzon. "Megértem. Kérem, tartsa a vonalat, kapcsolom egy emberi ügyintézőt." (Ez feltételezi, hogy van eszkalációs út). Ha nincs ilyen út: "Elnézést, én vagyok az elsődlegesen elérhető támogatói asszisztens, de mindent megteszek a probléma megoldása érdekében. Kérem, magyarázza el újra a problémát."`
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Build the full assistant configuration for VAPI
|
|
|
+ */
|
|
|
+function buildAssistantConfig(config: VapiAssistantConfig): Record<string, unknown> {
|
|
|
+ const supabaseUrl = Deno.env.get('SUPABASE_URL') || 'https://api.shopcall.ai'
|
|
|
+ const webhookAuthToken = getVapiWebhookAuthToken()
|
|
|
+
|
|
|
+ return {
|
|
|
+ name: `${config.storeName} - SHOPCALL - HU (Protokollal)`,
|
|
|
+ voice: {
|
|
|
+ provider: '11labs',
|
|
|
+ model: 'eleven_turbo_v2_5',
|
|
|
+ voiceId: config.voiceId,
|
|
|
+ stability: 0.5,
|
|
|
+ similarityBoost: 0.75
|
|
|
+ },
|
|
|
+ model: {
|
|
|
+ model: 'gpt-4.1',
|
|
|
+ toolIds: [
|
|
|
+ '82c2159c-05b0-44a0-af92-0c83ff3dd1ae',
|
|
|
+ 'bda2f41b-ec69-441d-8d16-a349783370d4'
|
|
|
+ ],
|
|
|
+ messages: [
|
|
|
+ {
|
|
|
+ role: 'system',
|
|
|
+ content: buildSystemPrompt(config.storeName, config.storeId)
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ provider: 'openai',
|
|
|
+ temperature: 0.4
|
|
|
+ },
|
|
|
+ forwardingPhoneNumber: '+36305547382',
|
|
|
+ firstMessage: config.greetingMessage,
|
|
|
+ voicemailMessage: 'Please call back when you\'re available.',
|
|
|
+ endCallFunctionEnabled: true,
|
|
|
+ endCallMessage: 'Viszonthallásra!',
|
|
|
+ transcriber: {
|
|
|
+ language: 'hu-HU',
|
|
|
+ provider: 'azure'
|
|
|
+ },
|
|
|
+ serverMessages: [
|
|
|
+ 'end-of-call-report',
|
|
|
+ 'transcript[transcriptType="final"]'
|
|
|
+ ],
|
|
|
+ backgroundSound: 'office',
|
|
|
+ firstMessageMode: 'assistant-speaks-first',
|
|
|
+ analysisPlan: {
|
|
|
+ minMessagesThreshold: 2
|
|
|
+ },
|
|
|
+ backgroundDenoisingEnabled: true,
|
|
|
+ server: {
|
|
|
+ url: `${supabaseUrl}/functions/v1/vapi-webhook?store_id=${config.storeId}`,
|
|
|
+ timeoutSeconds: 20,
|
|
|
+ headers: {
|
|
|
+ Authorization: `Bearer ${webhookAuthToken}`
|
|
|
+ }
|
|
|
+ },
|
|
|
+ compliancePlan: {
|
|
|
+ hipaaEnabled: false,
|
|
|
+ pciEnabled: false
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Create a new VAPI assistant for a store
|
|
|
+ */
|
|
|
+export async function createAssistant(
|
|
|
+ config: VapiAssistantConfig
|
|
|
+): Promise<VapiAssistantResponse> {
|
|
|
+ console.log(`[VAPI] Creating assistant for store: ${config.storeName} (${config.storeId})`)
|
|
|
+
|
|
|
+ const assistantConfig = buildAssistantConfig(config)
|
|
|
+ const result = await vapiRequest('POST', '/assistant', assistantConfig)
|
|
|
+
|
|
|
+ if (!result.success) {
|
|
|
+ return { success: false, error: result.error }
|
|
|
+ }
|
|
|
+
|
|
|
+ const data = result.data as { id?: string }
|
|
|
+
|
|
|
+ if (!data?.id) {
|
|
|
+ return { success: false, error: 'No assistant ID returned from VAPI' }
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log(`[VAPI] Assistant created successfully: ${data.id}`)
|
|
|
+ return { success: true, assistantId: data.id }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Update an existing VAPI assistant
|
|
|
+ */
|
|
|
+export async function updateAssistant(
|
|
|
+ assistantId: string,
|
|
|
+ voiceId: string,
|
|
|
+ greetingMessage: string
|
|
|
+): Promise<VapiAssistantResponse> {
|
|
|
+ console.log(`[VAPI] Updating assistant: ${assistantId}`)
|
|
|
+
|
|
|
+ const result = await vapiRequest('PATCH', `/assistant/${assistantId}`, {
|
|
|
+ voice: {
|
|
|
+ provider: '11labs',
|
|
|
+ model: 'eleven_turbo_v2_5',
|
|
|
+ voiceId: voiceId,
|
|
|
+ stability: 0.5,
|
|
|
+ similarityBoost: 0.75
|
|
|
+ },
|
|
|
+ firstMessage: greetingMessage
|
|
|
+ })
|
|
|
+
|
|
|
+ if (!result.success) {
|
|
|
+ return { success: false, error: result.error }
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log(`[VAPI] Assistant updated successfully: ${assistantId}`)
|
|
|
+ return { success: true, assistantId }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Check if VAPI integration is available (API key configured)
|
|
|
+ */
|
|
|
+export function isVapiConfigured(): boolean {
|
|
|
+ return !!getVapiApiKey()
|
|
|
+}
|