@vapi-ai/web 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/api.ts +755 -0
- package/client.ts +19 -0
- package/dist/api.d.ts +414 -0
- package/dist/api.js +309 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.js +29 -0
- package/dist/player.d.ts +10 -0
- package/dist/player.js +56 -0
- package/dist/vapi.d.ts +13 -0
- package/dist/vapi.js +82 -0
- package/package.json +38 -0
- package/player.ts +66 -0
- package/tsconfig.json +109 -0
- package/vapi.ts +92 -0
package/dist/api.js
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
/* tslint:disable */
|
|
4
|
+
/*
|
|
5
|
+
* ---------------------------------------------------------------
|
|
6
|
+
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
|
|
7
|
+
* ## ##
|
|
8
|
+
* ## AUTHOR: acacode ##
|
|
9
|
+
* ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
|
|
10
|
+
* ---------------------------------------------------------------
|
|
11
|
+
*/
|
|
12
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
13
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
14
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
15
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
16
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
17
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
18
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
22
|
+
var t = {};
|
|
23
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
24
|
+
t[p] = s[p];
|
|
25
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
26
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
27
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
28
|
+
t[p[i]] = s[p[i]];
|
|
29
|
+
}
|
|
30
|
+
return t;
|
|
31
|
+
};
|
|
32
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
33
|
+
exports.Api = exports.HttpClient = exports.ContentType = void 0;
|
|
34
|
+
var ContentType;
|
|
35
|
+
(function (ContentType) {
|
|
36
|
+
ContentType["Json"] = "application/json";
|
|
37
|
+
ContentType["FormData"] = "multipart/form-data";
|
|
38
|
+
ContentType["UrlEncoded"] = "application/x-www-form-urlencoded";
|
|
39
|
+
ContentType["Text"] = "text/plain";
|
|
40
|
+
})(ContentType || (exports.ContentType = ContentType = {}));
|
|
41
|
+
class HttpClient {
|
|
42
|
+
constructor(apiConfig = {}) {
|
|
43
|
+
this.baseUrl = "";
|
|
44
|
+
this.securityData = null;
|
|
45
|
+
this.abortControllers = new Map();
|
|
46
|
+
this.customFetch = (...fetchParams) => fetch(...fetchParams);
|
|
47
|
+
this.baseApiParams = {
|
|
48
|
+
credentials: "same-origin",
|
|
49
|
+
headers: {},
|
|
50
|
+
redirect: "follow",
|
|
51
|
+
referrerPolicy: "no-referrer",
|
|
52
|
+
};
|
|
53
|
+
this.setSecurityData = (data) => {
|
|
54
|
+
this.securityData = data;
|
|
55
|
+
};
|
|
56
|
+
this.contentFormatters = {
|
|
57
|
+
[ContentType.Json]: (input) => input !== null && (typeof input === "object" || typeof input === "string") ? JSON.stringify(input) : input,
|
|
58
|
+
[ContentType.Text]: (input) => (input !== null && typeof input !== "string" ? JSON.stringify(input) : input),
|
|
59
|
+
[ContentType.FormData]: (input) => Object.keys(input || {}).reduce((formData, key) => {
|
|
60
|
+
const property = input[key];
|
|
61
|
+
formData.append(key, property instanceof Blob
|
|
62
|
+
? property
|
|
63
|
+
: typeof property === "object" && property !== null
|
|
64
|
+
? JSON.stringify(property)
|
|
65
|
+
: `${property}`);
|
|
66
|
+
return formData;
|
|
67
|
+
}, new FormData()),
|
|
68
|
+
[ContentType.UrlEncoded]: (input) => this.toQueryString(input),
|
|
69
|
+
};
|
|
70
|
+
this.createAbortSignal = (cancelToken) => {
|
|
71
|
+
if (this.abortControllers.has(cancelToken)) {
|
|
72
|
+
const abortController = this.abortControllers.get(cancelToken);
|
|
73
|
+
if (abortController) {
|
|
74
|
+
return abortController.signal;
|
|
75
|
+
}
|
|
76
|
+
return void 0;
|
|
77
|
+
}
|
|
78
|
+
const abortController = new AbortController();
|
|
79
|
+
this.abortControllers.set(cancelToken, abortController);
|
|
80
|
+
return abortController.signal;
|
|
81
|
+
};
|
|
82
|
+
this.abortRequest = (cancelToken) => {
|
|
83
|
+
const abortController = this.abortControllers.get(cancelToken);
|
|
84
|
+
if (abortController) {
|
|
85
|
+
abortController.abort();
|
|
86
|
+
this.abortControllers.delete(cancelToken);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
this.request = (_a) => __awaiter(this, void 0, void 0, function* () {
|
|
90
|
+
var { body, secure, path, type, query, format, baseUrl, cancelToken } = _a, params = __rest(_a, ["body", "secure", "path", "type", "query", "format", "baseUrl", "cancelToken"]);
|
|
91
|
+
const secureParams = ((typeof secure === "boolean" ? secure : this.baseApiParams.secure) &&
|
|
92
|
+
this.securityWorker &&
|
|
93
|
+
(yield this.securityWorker(this.securityData))) ||
|
|
94
|
+
{};
|
|
95
|
+
const requestParams = this.mergeRequestParams(params, secureParams);
|
|
96
|
+
const queryString = query && this.toQueryString(query);
|
|
97
|
+
const payloadFormatter = this.contentFormatters[type || ContentType.Json];
|
|
98
|
+
const responseFormat = format || requestParams.format;
|
|
99
|
+
return this.customFetch(`${baseUrl || this.baseUrl || ""}${path}${queryString ? `?${queryString}` : ""}`, Object.assign(Object.assign({}, requestParams), { headers: Object.assign(Object.assign({}, (requestParams.headers || {})), (type && type !== ContentType.FormData ? { "Content-Type": type } : {})), signal: (cancelToken ? this.createAbortSignal(cancelToken) : requestParams.signal) || null, body: typeof body === "undefined" || body === null ? null : payloadFormatter(body) })).then((response) => __awaiter(this, void 0, void 0, function* () {
|
|
100
|
+
const r = response;
|
|
101
|
+
r.data = null;
|
|
102
|
+
r.error = null;
|
|
103
|
+
const data = !responseFormat
|
|
104
|
+
? r
|
|
105
|
+
: yield response[responseFormat]()
|
|
106
|
+
.then((data) => {
|
|
107
|
+
if (r.ok) {
|
|
108
|
+
r.data = data;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
r.error = data;
|
|
112
|
+
}
|
|
113
|
+
return r;
|
|
114
|
+
})
|
|
115
|
+
.catch((e) => {
|
|
116
|
+
r.error = e;
|
|
117
|
+
return r;
|
|
118
|
+
});
|
|
119
|
+
if (cancelToken) {
|
|
120
|
+
this.abortControllers.delete(cancelToken);
|
|
121
|
+
}
|
|
122
|
+
if (!response.ok)
|
|
123
|
+
throw data;
|
|
124
|
+
return data;
|
|
125
|
+
}));
|
|
126
|
+
});
|
|
127
|
+
Object.assign(this, apiConfig);
|
|
128
|
+
}
|
|
129
|
+
encodeQueryParam(key, value) {
|
|
130
|
+
const encodedKey = encodeURIComponent(key);
|
|
131
|
+
return `${encodedKey}=${encodeURIComponent(typeof value === "number" ? value : `${value}`)}`;
|
|
132
|
+
}
|
|
133
|
+
addQueryParam(query, key) {
|
|
134
|
+
return this.encodeQueryParam(key, query[key]);
|
|
135
|
+
}
|
|
136
|
+
addArrayQueryParam(query, key) {
|
|
137
|
+
const value = query[key];
|
|
138
|
+
return value.map((v) => this.encodeQueryParam(key, v)).join("&");
|
|
139
|
+
}
|
|
140
|
+
toQueryString(rawQuery) {
|
|
141
|
+
const query = rawQuery || {};
|
|
142
|
+
const keys = Object.keys(query).filter((key) => "undefined" !== typeof query[key]);
|
|
143
|
+
return keys
|
|
144
|
+
.map((key) => (Array.isArray(query[key]) ? this.addArrayQueryParam(query, key) : this.addQueryParam(query, key)))
|
|
145
|
+
.join("&");
|
|
146
|
+
}
|
|
147
|
+
addQueryParams(rawQuery) {
|
|
148
|
+
const queryString = this.toQueryString(rawQuery);
|
|
149
|
+
return queryString ? `?${queryString}` : "";
|
|
150
|
+
}
|
|
151
|
+
mergeRequestParams(params1, params2) {
|
|
152
|
+
return Object.assign(Object.assign(Object.assign(Object.assign({}, this.baseApiParams), params1), (params2 || {})), { headers: Object.assign(Object.assign(Object.assign({}, (this.baseApiParams.headers || {})), (params1.headers || {})), ((params2 && params2.headers) || {})) });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
exports.HttpClient = HttpClient;
|
|
156
|
+
/**
|
|
157
|
+
* @title Vapi API
|
|
158
|
+
* @version 1.0
|
|
159
|
+
* @contact
|
|
160
|
+
*
|
|
161
|
+
* API for talking AIs
|
|
162
|
+
*/
|
|
163
|
+
class Api extends HttpClient {
|
|
164
|
+
constructor() {
|
|
165
|
+
super(...arguments);
|
|
166
|
+
this.call = {
|
|
167
|
+
/**
|
|
168
|
+
* No description
|
|
169
|
+
*
|
|
170
|
+
* @tags Calls
|
|
171
|
+
* @name CallControllerFindAll
|
|
172
|
+
* @summary List Calls
|
|
173
|
+
* @request GET:/call
|
|
174
|
+
*/
|
|
175
|
+
callControllerFindAll: (params = {}) => this.request(Object.assign({ path: `/call`, method: "GET", format: "json" }, params)),
|
|
176
|
+
/**
|
|
177
|
+
* No description
|
|
178
|
+
*
|
|
179
|
+
* @tags Calls
|
|
180
|
+
* @name CallControllerFindOne
|
|
181
|
+
* @summary Get Call
|
|
182
|
+
* @request GET:/call/{id}
|
|
183
|
+
*/
|
|
184
|
+
callControllerFindOne: (id, params = {}) => this.request(Object.assign({ path: `/call/${id}`, method: "GET", format: "json" }, params)),
|
|
185
|
+
/**
|
|
186
|
+
* No description
|
|
187
|
+
*
|
|
188
|
+
* @tags Calls
|
|
189
|
+
* @name CallControllerCreatePhoneCall
|
|
190
|
+
* @summary Create Phone Call
|
|
191
|
+
* @request POST:/call/phone
|
|
192
|
+
*/
|
|
193
|
+
callControllerCreatePhoneCall: (data, params = {}) => this.request(Object.assign({ path: `/call/phone`, method: "POST", body: data, type: ContentType.Json, format: "json" }, params)),
|
|
194
|
+
/**
|
|
195
|
+
* No description
|
|
196
|
+
*
|
|
197
|
+
* @tags Calls
|
|
198
|
+
* @name CallControllerCreateWebCall
|
|
199
|
+
* @summary Create Web Call
|
|
200
|
+
* @request POST:/call/web
|
|
201
|
+
*/
|
|
202
|
+
callControllerCreateWebCall: (data, params = {}) => this.request(Object.assign({ path: `/call/web`, method: "POST", body: data, type: ContentType.Json, format: "json" }, params)),
|
|
203
|
+
};
|
|
204
|
+
this.agent = {
|
|
205
|
+
/**
|
|
206
|
+
* No description
|
|
207
|
+
*
|
|
208
|
+
* @tags Agents
|
|
209
|
+
* @name AgentControllerCreate
|
|
210
|
+
* @summary Create Agent
|
|
211
|
+
* @request POST:/agent
|
|
212
|
+
*/
|
|
213
|
+
agentControllerCreate: (data, params = {}) => this.request(Object.assign({ path: `/agent`, method: "POST", body: data, type: ContentType.Json, format: "json" }, params)),
|
|
214
|
+
/**
|
|
215
|
+
* No description
|
|
216
|
+
*
|
|
217
|
+
* @tags Agents
|
|
218
|
+
* @name AgentControllerFindAll
|
|
219
|
+
* @summary List Agents
|
|
220
|
+
* @request GET:/agent
|
|
221
|
+
*/
|
|
222
|
+
agentControllerFindAll: (params = {}) => this.request(Object.assign({ path: `/agent`, method: "GET", format: "json" }, params)),
|
|
223
|
+
/**
|
|
224
|
+
* No description
|
|
225
|
+
*
|
|
226
|
+
* @tags Agents
|
|
227
|
+
* @name AgentControllerFindOne
|
|
228
|
+
* @summary Get Agent
|
|
229
|
+
* @request GET:/agent/{id}
|
|
230
|
+
*/
|
|
231
|
+
agentControllerFindOne: (id, params = {}) => this.request(Object.assign({ path: `/agent/${id}`, method: "GET", format: "json" }, params)),
|
|
232
|
+
/**
|
|
233
|
+
* No description
|
|
234
|
+
*
|
|
235
|
+
* @tags Agents
|
|
236
|
+
* @name AgentControllerUpdate
|
|
237
|
+
* @summary Update Agent
|
|
238
|
+
* @request PATCH:/agent/{id}
|
|
239
|
+
*/
|
|
240
|
+
agentControllerUpdate: (id, data, params = {}) => this.request(Object.assign({ path: `/agent/${id}`, method: "PATCH", body: data, type: ContentType.Json, format: "json" }, params)),
|
|
241
|
+
/**
|
|
242
|
+
* No description
|
|
243
|
+
*
|
|
244
|
+
* @tags Agents
|
|
245
|
+
* @name AgentControllerRemove
|
|
246
|
+
* @summary Delete Agent
|
|
247
|
+
* @request DELETE:/agent/{id}
|
|
248
|
+
*/
|
|
249
|
+
agentControllerRemove: (id, params = {}) => this.request(Object.assign({ path: `/agent/${id}`, method: "DELETE" }, params)),
|
|
250
|
+
};
|
|
251
|
+
this.phoneNumber = {
|
|
252
|
+
/**
|
|
253
|
+
* No description
|
|
254
|
+
*
|
|
255
|
+
* @tags Phone Numbers
|
|
256
|
+
* @name PhoneNumberControllerBuy
|
|
257
|
+
* @summary Buy Phone Number
|
|
258
|
+
* @request POST:/phone-number/buy
|
|
259
|
+
*/
|
|
260
|
+
phoneNumberControllerBuy: (data, params = {}) => this.request(Object.assign({ path: `/phone-number/buy`, method: "POST", body: data, type: ContentType.Json, format: "json" }, params)),
|
|
261
|
+
/**
|
|
262
|
+
* No description
|
|
263
|
+
*
|
|
264
|
+
* @tags Phone Numbers
|
|
265
|
+
* @name PhoneNumberControllerImport
|
|
266
|
+
* @summary Import Twilio Number
|
|
267
|
+
* @request POST:/phone-number/import
|
|
268
|
+
*/
|
|
269
|
+
phoneNumberControllerImport: (data, params = {}) => this.request(Object.assign({ path: `/phone-number/import`, method: "POST", body: data, type: ContentType.Json, format: "json" }, params)),
|
|
270
|
+
/**
|
|
271
|
+
* No description
|
|
272
|
+
*
|
|
273
|
+
* @tags Phone Numbers
|
|
274
|
+
* @name PhoneNumberControllerFindAll
|
|
275
|
+
* @summary List Phone Numbers
|
|
276
|
+
* @request GET:/phone-number
|
|
277
|
+
*/
|
|
278
|
+
phoneNumberControllerFindAll: (params = {}) => this.request(Object.assign({ path: `/phone-number`, method: "GET", format: "json" }, params)),
|
|
279
|
+
/**
|
|
280
|
+
* No description
|
|
281
|
+
*
|
|
282
|
+
* @tags Phone Numbers
|
|
283
|
+
* @name PhoneNumberControllerFindOne
|
|
284
|
+
* @summary Get Phone Number
|
|
285
|
+
* @request GET:/phone-number/{id}
|
|
286
|
+
*/
|
|
287
|
+
phoneNumberControllerFindOne: (id, params = {}) => this.request(Object.assign({ path: `/phone-number/${id}`, method: "GET", format: "json" }, params)),
|
|
288
|
+
/**
|
|
289
|
+
* No description
|
|
290
|
+
*
|
|
291
|
+
* @tags Phone Numbers
|
|
292
|
+
* @name PhoneNumberControllerUpdate
|
|
293
|
+
* @summary Update Phone Number
|
|
294
|
+
* @request PATCH:/phone-number/{id}
|
|
295
|
+
*/
|
|
296
|
+
phoneNumberControllerUpdate: (id, data, params = {}) => this.request(Object.assign({ path: `/phone-number/${id}`, method: "PATCH", body: data, type: ContentType.Json, format: "json" }, params)),
|
|
297
|
+
/**
|
|
298
|
+
* No description
|
|
299
|
+
*
|
|
300
|
+
* @tags Phone Numbers
|
|
301
|
+
* @name PhoneNumberControllerRemove
|
|
302
|
+
* @summary Delete Phone Number
|
|
303
|
+
* @request DELETE:/phone-number/{id}
|
|
304
|
+
*/
|
|
305
|
+
phoneNumberControllerRemove: (id, params = {}) => this.request(Object.assign({ path: `/phone-number/${id}`, method: "DELETE" }, params)),
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
exports.Api = Api;
|
package/dist/client.d.ts
ADDED
package/dist/client.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.client = void 0;
|
|
13
|
+
const api_1 = require("./api");
|
|
14
|
+
const api = new api_1.Api({
|
|
15
|
+
baseUrl: "https://api.vapi.ai",
|
|
16
|
+
baseApiParams: {
|
|
17
|
+
secure: true,
|
|
18
|
+
},
|
|
19
|
+
securityWorker: (securityData) => __awaiter(void 0, void 0, void 0, function* () {
|
|
20
|
+
if (securityData) {
|
|
21
|
+
return {
|
|
22
|
+
headers: {
|
|
23
|
+
Authorization: `Bearer ${securityData}`,
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}),
|
|
28
|
+
});
|
|
29
|
+
exports.client = api;
|
package/dist/player.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare class ContinuousPlayer {
|
|
2
|
+
private audioCtx;
|
|
3
|
+
private nextStartTime;
|
|
4
|
+
private audioQueue;
|
|
5
|
+
private crossFadeDuration;
|
|
6
|
+
constructor(crossFadeDuration?: number);
|
|
7
|
+
private createQueue;
|
|
8
|
+
playChunk(audioData: ArrayBuffer): void;
|
|
9
|
+
clear(): Promise<void>;
|
|
10
|
+
}
|
package/dist/player.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.ContinuousPlayer = void 0;
|
|
13
|
+
const async_1 = require("async"); // You need to install the async package
|
|
14
|
+
class ContinuousPlayer {
|
|
15
|
+
constructor(crossFadeDuration = 0.5) {
|
|
16
|
+
this.audioCtx = new AudioContext();
|
|
17
|
+
this.nextStartTime = 0;
|
|
18
|
+
this.audioQueue = this.createQueue();
|
|
19
|
+
this.crossFadeDuration = crossFadeDuration;
|
|
20
|
+
}
|
|
21
|
+
createQueue() {
|
|
22
|
+
return (0, async_1.queue)((audioPromise, callback) => __awaiter(this, void 0, void 0, function* () {
|
|
23
|
+
const audioBuffer = yield audioPromise;
|
|
24
|
+
const source = this.audioCtx.createBufferSource();
|
|
25
|
+
source.buffer = audioBuffer;
|
|
26
|
+
source.connect(this.audioCtx.destination);
|
|
27
|
+
// Create a gain node for crossfading
|
|
28
|
+
const gainNode = this.audioCtx.createGain();
|
|
29
|
+
source.connect(gainNode);
|
|
30
|
+
gainNode.connect(this.audioCtx.destination);
|
|
31
|
+
// Start the source at the next start time
|
|
32
|
+
source.start(this.nextStartTime);
|
|
33
|
+
// Schedule the crossfade
|
|
34
|
+
gainNode.gain.setValueAtTime(0, this.nextStartTime);
|
|
35
|
+
gainNode.gain.linearRampToValueAtTime(1, this.nextStartTime + this.crossFadeDuration);
|
|
36
|
+
gainNode.gain.setValueAtTime(1, this.nextStartTime + audioBuffer.duration - this.crossFadeDuration);
|
|
37
|
+
gainNode.gain.linearRampToValueAtTime(0, this.nextStartTime + audioBuffer.duration);
|
|
38
|
+
source.onended = () => {
|
|
39
|
+
this.nextStartTime += audioBuffer.duration;
|
|
40
|
+
callback();
|
|
41
|
+
};
|
|
42
|
+
}), 1);
|
|
43
|
+
}
|
|
44
|
+
playChunk(audioData) {
|
|
45
|
+
const decodePromise = this.audioCtx.decodeAudioData(audioData);
|
|
46
|
+
this.audioQueue.push(decodePromise);
|
|
47
|
+
}
|
|
48
|
+
clear() {
|
|
49
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
50
|
+
this.audioQueue.kill();
|
|
51
|
+
this.audioQueue = this.createQueue();
|
|
52
|
+
this.nextStartTime = 0;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
exports.ContinuousPlayer = ContinuousPlayer;
|
package/dist/vapi.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { CreateAgentDTO } from "./api";
|
|
2
|
+
export default class Vapi {
|
|
3
|
+
private started;
|
|
4
|
+
private ws;
|
|
5
|
+
private mediaRecorder;
|
|
6
|
+
private player;
|
|
7
|
+
constructor(apiToken: string);
|
|
8
|
+
start(agent: CreateAgentDTO): void;
|
|
9
|
+
private startRecording;
|
|
10
|
+
private onMessage;
|
|
11
|
+
clear(): void;
|
|
12
|
+
stop(): void;
|
|
13
|
+
}
|
package/dist/vapi.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const player_1 = require("./player");
|
|
4
|
+
const client_1 = require("./client");
|
|
5
|
+
const base64_arraybuffer_1 = require("base64-arraybuffer");
|
|
6
|
+
class Vapi {
|
|
7
|
+
constructor(apiToken) {
|
|
8
|
+
this.started = false;
|
|
9
|
+
this.ws = null;
|
|
10
|
+
this.mediaRecorder = null;
|
|
11
|
+
this.player = new player_1.ContinuousPlayer();
|
|
12
|
+
client_1.client.setSecurityData(apiToken);
|
|
13
|
+
}
|
|
14
|
+
start(agent) {
|
|
15
|
+
if (this.started) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
this.started = true;
|
|
19
|
+
client_1.client.call
|
|
20
|
+
.callControllerCreateWebCall({
|
|
21
|
+
agent,
|
|
22
|
+
})
|
|
23
|
+
.then(({ data }) => {
|
|
24
|
+
const { callId, url } = data;
|
|
25
|
+
const socket = new WebSocket(url);
|
|
26
|
+
this.ws = socket;
|
|
27
|
+
socket.onopen = () => {
|
|
28
|
+
socket.send(JSON.stringify({ event: "start", callId }));
|
|
29
|
+
this.startRecording();
|
|
30
|
+
};
|
|
31
|
+
socket.onmessage = (event) => {
|
|
32
|
+
if (!socket)
|
|
33
|
+
return;
|
|
34
|
+
this.onMessage(socket, event);
|
|
35
|
+
};
|
|
36
|
+
socket.onclose = () => {
|
|
37
|
+
this.stop();
|
|
38
|
+
};
|
|
39
|
+
})
|
|
40
|
+
.catch((error) => {
|
|
41
|
+
console.error(error);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
startRecording() {
|
|
45
|
+
navigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => {
|
|
46
|
+
this.mediaRecorder = new MediaRecorder(stream, {
|
|
47
|
+
mimeType: "audio/webm;codecs=opus",
|
|
48
|
+
});
|
|
49
|
+
this.mediaRecorder.start(100);
|
|
50
|
+
this.mediaRecorder.ondataavailable = (event) => {
|
|
51
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
52
|
+
this.ws.send(event.data);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
onMessage(ws, event) {
|
|
58
|
+
const data = JSON.parse(event.data);
|
|
59
|
+
if (data.event === "media") {
|
|
60
|
+
const audioData = (0, base64_arraybuffer_1.decode)(data.media.payload);
|
|
61
|
+
this.player.playChunk(audioData);
|
|
62
|
+
}
|
|
63
|
+
if (data.event === "clear") {
|
|
64
|
+
this.clear();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
clear() {
|
|
68
|
+
this.player.clear();
|
|
69
|
+
}
|
|
70
|
+
stop() {
|
|
71
|
+
this.started = false;
|
|
72
|
+
if (this.ws) {
|
|
73
|
+
this.ws.close();
|
|
74
|
+
}
|
|
75
|
+
this.ws = null;
|
|
76
|
+
if (this.mediaRecorder) {
|
|
77
|
+
this.mediaRecorder.stop();
|
|
78
|
+
}
|
|
79
|
+
this.mediaRecorder = null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
exports.default = Vapi;
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vapi-ai/web",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "dist/vapi.js",
|
|
6
|
+
"types": "dist/vapi.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
9
|
+
"build": "tsc && tsc --declaration",
|
|
10
|
+
"start": "npm run generate-api && npm run build && node dist/vapi.js",
|
|
11
|
+
"generate-api": "npx swagger-typescript-api -p https://api-tv2l.onrender.com/api-json -o . -n api.ts"
|
|
12
|
+
},
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/VapiAI/snippet.git"
|
|
16
|
+
},
|
|
17
|
+
"author": "vapi",
|
|
18
|
+
"license": "ISC",
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/VapiAI/snippet/issues"
|
|
21
|
+
},
|
|
22
|
+
"homepage": "https://github.com/VapiAI/snippet#readme",
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"async": "^3.2.4",
|
|
25
|
+
"base64-arraybuffer": "^1.0.2",
|
|
26
|
+
"extendable-media-recorder": "^9.1.0",
|
|
27
|
+
"extendable-media-recorder-wav-encoder": "^7.0.96",
|
|
28
|
+
"msr": "^1.3.4",
|
|
29
|
+
"openai": "^4.0.1",
|
|
30
|
+
"standardized-audio-context": "^25.3.56",
|
|
31
|
+
"swagger-typescript-api": "^13.0.3"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/async": "^3.2.21",
|
|
35
|
+
"@types/node": "^20.6.0",
|
|
36
|
+
"@types/ws": "^8.5.5"
|
|
37
|
+
}
|
|
38
|
+
}
|
package/player.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { QueueObject, queue } from "async"; // You need to install the async package
|
|
2
|
+
|
|
3
|
+
export class ContinuousPlayer {
|
|
4
|
+
private audioCtx: AudioContext;
|
|
5
|
+
private nextStartTime: number;
|
|
6
|
+
private audioQueue: QueueObject<Promise<AudioBuffer>>;
|
|
7
|
+
private crossFadeDuration: number; // Duration of the crossfade
|
|
8
|
+
|
|
9
|
+
constructor(crossFadeDuration = 0.5) {
|
|
10
|
+
this.audioCtx = new AudioContext();
|
|
11
|
+
this.nextStartTime = 0;
|
|
12
|
+
this.audioQueue = this.createQueue();
|
|
13
|
+
this.crossFadeDuration = crossFadeDuration;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
private createQueue() {
|
|
17
|
+
return queue(
|
|
18
|
+
async (audioPromise: Promise<AudioBuffer>, callback: Function) => {
|
|
19
|
+
const audioBuffer = await audioPromise;
|
|
20
|
+
const source = this.audioCtx.createBufferSource();
|
|
21
|
+
source.buffer = audioBuffer;
|
|
22
|
+
source.connect(this.audioCtx.destination);
|
|
23
|
+
|
|
24
|
+
// Create a gain node for crossfading
|
|
25
|
+
const gainNode = this.audioCtx.createGain();
|
|
26
|
+
source.connect(gainNode);
|
|
27
|
+
gainNode.connect(this.audioCtx.destination);
|
|
28
|
+
|
|
29
|
+
// Start the source at the next start time
|
|
30
|
+
source.start(this.nextStartTime);
|
|
31
|
+
|
|
32
|
+
// Schedule the crossfade
|
|
33
|
+
gainNode.gain.setValueAtTime(0, this.nextStartTime);
|
|
34
|
+
gainNode.gain.linearRampToValueAtTime(
|
|
35
|
+
1,
|
|
36
|
+
this.nextStartTime + this.crossFadeDuration
|
|
37
|
+
);
|
|
38
|
+
gainNode.gain.setValueAtTime(
|
|
39
|
+
1,
|
|
40
|
+
this.nextStartTime + audioBuffer.duration - this.crossFadeDuration
|
|
41
|
+
);
|
|
42
|
+
gainNode.gain.linearRampToValueAtTime(
|
|
43
|
+
0,
|
|
44
|
+
this.nextStartTime + audioBuffer.duration
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
source.onended = () => {
|
|
48
|
+
this.nextStartTime += audioBuffer.duration;
|
|
49
|
+
callback();
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
1
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
playChunk(audioData: ArrayBuffer) {
|
|
57
|
+
const decodePromise = this.audioCtx.decodeAudioData(audioData);
|
|
58
|
+
this.audioQueue.push(decodePromise);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async clear() {
|
|
62
|
+
this.audioQueue.kill();
|
|
63
|
+
this.audioQueue = this.createQueue();
|
|
64
|
+
this.nextStartTime = 0;
|
|
65
|
+
}
|
|
66
|
+
}
|