assemblyai 4.0.0-beta.0 → 4.0.0-beta.2
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/CHANGELOG.md +92 -0
- package/dist/assemblyai.umd.js +26 -33
- package/dist/assemblyai.umd.min.js +1 -1
- package/dist/{index.browser.js → bun.mjs} +167 -303
- package/dist/deno.mjs +540 -0
- package/dist/fs/bun.d.ts +1 -0
- package/dist/fs/deno.d.ts +1 -0
- package/dist/fs/index.d.ts +1 -0
- package/dist/fs/node.d.ts +1 -0
- package/dist/index.cjs +11 -4
- package/dist/index.mjs +11 -4
- package/dist/node.cjs +549 -0
- package/dist/node.mjs +542 -0
- package/dist/services/realtime/service.d.ts +0 -2
- package/docs/compat.md +59 -0
- package/package.json +38 -11
- package/src/fs/bun.ts +8 -0
- package/src/fs/deno.ts +9 -0
- package/src/fs/index.ts +8 -0
- package/src/fs/node.ts +7 -0
- package/src/services/files/index.ts +2 -2
- package/src/services/realtime/service.ts +2 -2
- package/dist/browser/fs.d.ts +0 -6
- package/src/browser/fs.ts +0 -8
package/dist/node.mjs
ADDED
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
import { WritableStream } from '@swimburger/isomorphic-streams';
|
|
2
|
+
import WebSocket from 'isomorphic-ws';
|
|
3
|
+
import { createReadStream } from 'fs';
|
|
4
|
+
import { Readable } from 'stream';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Base class for services that communicate with the API.
|
|
8
|
+
*/
|
|
9
|
+
class BaseService {
|
|
10
|
+
/**
|
|
11
|
+
* Create a new service.
|
|
12
|
+
* @param params The parameters to use for the service.
|
|
13
|
+
*/
|
|
14
|
+
constructor(params) {
|
|
15
|
+
this.params = params;
|
|
16
|
+
}
|
|
17
|
+
async fetch(input, init) {
|
|
18
|
+
init = init ?? {};
|
|
19
|
+
init.headers = init.headers ?? {};
|
|
20
|
+
init.headers = {
|
|
21
|
+
Authorization: this.params.apiKey,
|
|
22
|
+
"Content-Type": "application/json",
|
|
23
|
+
...init.headers,
|
|
24
|
+
};
|
|
25
|
+
if (!input.startsWith("http"))
|
|
26
|
+
input = this.params.baseUrl + input;
|
|
27
|
+
const response = await fetch(input, init);
|
|
28
|
+
if (response.status >= 400) {
|
|
29
|
+
let json;
|
|
30
|
+
const text = await response.text();
|
|
31
|
+
if (text) {
|
|
32
|
+
try {
|
|
33
|
+
json = JSON.parse(text);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
/* empty */
|
|
37
|
+
}
|
|
38
|
+
if (json?.error)
|
|
39
|
+
throw new Error(json.error);
|
|
40
|
+
throw new Error(text);
|
|
41
|
+
}
|
|
42
|
+
throw new Error(`HTTP Error: ${response.status} ${response.statusText}`);
|
|
43
|
+
}
|
|
44
|
+
return response;
|
|
45
|
+
}
|
|
46
|
+
async fetchJson(input, init) {
|
|
47
|
+
const response = await this.fetch(input, init);
|
|
48
|
+
return response.json();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
class LemurService extends BaseService {
|
|
53
|
+
summary(params) {
|
|
54
|
+
return this.fetchJson("/lemur/v3/generate/summary", {
|
|
55
|
+
method: "POST",
|
|
56
|
+
body: JSON.stringify(params),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
questionAnswer(params) {
|
|
60
|
+
return this.fetchJson("/lemur/v3/generate/question-answer", {
|
|
61
|
+
method: "POST",
|
|
62
|
+
body: JSON.stringify(params),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
actionItems(params) {
|
|
66
|
+
return this.fetchJson("/lemur/v3/generate/action-items", {
|
|
67
|
+
method: "POST",
|
|
68
|
+
body: JSON.stringify(params),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
task(params) {
|
|
72
|
+
return this.fetchJson("/lemur/v3/generate/task", {
|
|
73
|
+
method: "POST",
|
|
74
|
+
body: JSON.stringify(params),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Delete the data for a previously submitted LeMUR request.
|
|
79
|
+
* @param id ID of the LeMUR request
|
|
80
|
+
*/
|
|
81
|
+
purgeRequestData(id) {
|
|
82
|
+
return this.fetchJson(`/lemur/v3/${id}`, {
|
|
83
|
+
method: "DELETE",
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
var RealtimeErrorType;
|
|
89
|
+
(function (RealtimeErrorType) {
|
|
90
|
+
RealtimeErrorType[RealtimeErrorType["BadSampleRate"] = 4000] = "BadSampleRate";
|
|
91
|
+
RealtimeErrorType[RealtimeErrorType["AuthFailed"] = 4001] = "AuthFailed";
|
|
92
|
+
// Both InsufficientFunds and FreeAccount error use 4002
|
|
93
|
+
RealtimeErrorType[RealtimeErrorType["InsufficientFundsOrFreeAccount"] = 4002] = "InsufficientFundsOrFreeAccount";
|
|
94
|
+
RealtimeErrorType[RealtimeErrorType["NonexistentSessionId"] = 4004] = "NonexistentSessionId";
|
|
95
|
+
RealtimeErrorType[RealtimeErrorType["SessionExpired"] = 4008] = "SessionExpired";
|
|
96
|
+
RealtimeErrorType[RealtimeErrorType["ClosedSession"] = 4010] = "ClosedSession";
|
|
97
|
+
RealtimeErrorType[RealtimeErrorType["RateLimited"] = 4029] = "RateLimited";
|
|
98
|
+
RealtimeErrorType[RealtimeErrorType["UniqueSessionViolation"] = 4030] = "UniqueSessionViolation";
|
|
99
|
+
RealtimeErrorType[RealtimeErrorType["SessionTimeout"] = 4031] = "SessionTimeout";
|
|
100
|
+
RealtimeErrorType[RealtimeErrorType["AudioTooShort"] = 4032] = "AudioTooShort";
|
|
101
|
+
RealtimeErrorType[RealtimeErrorType["AudioTooLong"] = 4033] = "AudioTooLong";
|
|
102
|
+
RealtimeErrorType[RealtimeErrorType["BadJson"] = 4100] = "BadJson";
|
|
103
|
+
RealtimeErrorType[RealtimeErrorType["BadSchema"] = 4101] = "BadSchema";
|
|
104
|
+
RealtimeErrorType[RealtimeErrorType["TooManyStreams"] = 4102] = "TooManyStreams";
|
|
105
|
+
RealtimeErrorType[RealtimeErrorType["Reconnected"] = 4103] = "Reconnected";
|
|
106
|
+
RealtimeErrorType[RealtimeErrorType["ReconnectAttemptsExhausted"] = 1013] = "ReconnectAttemptsExhausted";
|
|
107
|
+
})(RealtimeErrorType || (RealtimeErrorType = {}));
|
|
108
|
+
const RealtimeErrorMessages = {
|
|
109
|
+
[RealtimeErrorType.BadSampleRate]: "Sample rate must be a positive integer",
|
|
110
|
+
[RealtimeErrorType.AuthFailed]: "Not Authorized",
|
|
111
|
+
[RealtimeErrorType.InsufficientFundsOrFreeAccount]: "Insufficient funds or you are using a free account. This feature is paid-only and requires you to add a credit card. Please visit https://assemblyai.com/dashboard/ to add a credit card to your account.",
|
|
112
|
+
[RealtimeErrorType.NonexistentSessionId]: "Session ID does not exist",
|
|
113
|
+
[RealtimeErrorType.SessionExpired]: "Session has expired",
|
|
114
|
+
[RealtimeErrorType.ClosedSession]: "Session is closed",
|
|
115
|
+
[RealtimeErrorType.RateLimited]: "Rate limited",
|
|
116
|
+
[RealtimeErrorType.UniqueSessionViolation]: "Unique session violation",
|
|
117
|
+
[RealtimeErrorType.SessionTimeout]: "Session Timeout",
|
|
118
|
+
[RealtimeErrorType.AudioTooShort]: "Audio too short",
|
|
119
|
+
[RealtimeErrorType.AudioTooLong]: "Audio too long",
|
|
120
|
+
[RealtimeErrorType.BadJson]: "Bad JSON",
|
|
121
|
+
[RealtimeErrorType.BadSchema]: "Bad schema",
|
|
122
|
+
[RealtimeErrorType.TooManyStreams]: "Too many streams",
|
|
123
|
+
[RealtimeErrorType.Reconnected]: "Reconnected",
|
|
124
|
+
[RealtimeErrorType.ReconnectAttemptsExhausted]: "Reconnect attempts exhausted",
|
|
125
|
+
};
|
|
126
|
+
class RealtimeError extends Error {
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const defaultRealtimeUrl = "wss://api.assemblyai.com/v2/realtime/ws";
|
|
130
|
+
class RealtimeService {
|
|
131
|
+
constructor(params) {
|
|
132
|
+
this.listeners = {};
|
|
133
|
+
this.realtimeUrl = params.realtimeUrl ?? defaultRealtimeUrl;
|
|
134
|
+
this.sampleRate = params.sampleRate ?? 16000;
|
|
135
|
+
this.wordBoost = params.wordBoost;
|
|
136
|
+
if ("apiKey" in params && params.apiKey)
|
|
137
|
+
this.apiKey = params.apiKey;
|
|
138
|
+
if ("token" in params && params.token)
|
|
139
|
+
this.token = params.token;
|
|
140
|
+
if (!(this.apiKey || this.token)) {
|
|
141
|
+
throw new Error("API key or temporary token is required.");
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
connectionUrl() {
|
|
145
|
+
const url = new URL(this.realtimeUrl);
|
|
146
|
+
if (url.protocol !== "wss:") {
|
|
147
|
+
throw new Error("Invalid protocol, must be wss");
|
|
148
|
+
}
|
|
149
|
+
const searchParams = new URLSearchParams();
|
|
150
|
+
if (this.token) {
|
|
151
|
+
searchParams.set("token", this.token);
|
|
152
|
+
}
|
|
153
|
+
searchParams.set("sample_rate", this.sampleRate.toString());
|
|
154
|
+
if (this.wordBoost && this.wordBoost.length > 0) {
|
|
155
|
+
searchParams.set("word_boost", JSON.stringify(this.wordBoost));
|
|
156
|
+
}
|
|
157
|
+
url.search = searchParams.toString();
|
|
158
|
+
return url;
|
|
159
|
+
}
|
|
160
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
161
|
+
on(event, listener) {
|
|
162
|
+
this.listeners[event] = listener;
|
|
163
|
+
}
|
|
164
|
+
connect() {
|
|
165
|
+
return new Promise((resolve) => {
|
|
166
|
+
if (this.socket) {
|
|
167
|
+
throw new Error("Already connected");
|
|
168
|
+
}
|
|
169
|
+
const url = this.connectionUrl();
|
|
170
|
+
if (this.token) {
|
|
171
|
+
this.socket = new WebSocket(url.toString());
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
this.socket = new WebSocket(url.toString(), {
|
|
175
|
+
headers: { Authorization: this.apiKey },
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
this.socket.onclose = ({ code, reason }) => {
|
|
179
|
+
if (!reason) {
|
|
180
|
+
if (code in RealtimeErrorType) {
|
|
181
|
+
reason = RealtimeErrorMessages[code];
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
this.listeners.close?.(code, reason);
|
|
185
|
+
};
|
|
186
|
+
this.socket.onerror = (event) => {
|
|
187
|
+
if (event.error)
|
|
188
|
+
this.listeners.error?.(event.error);
|
|
189
|
+
else
|
|
190
|
+
this.listeners.error?.(new Error(event.message));
|
|
191
|
+
};
|
|
192
|
+
this.socket.onmessage = ({ data }) => {
|
|
193
|
+
const message = JSON.parse(data.toString());
|
|
194
|
+
if ("error" in message) {
|
|
195
|
+
this.listeners.error?.(new RealtimeError(message.error));
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
switch (message.message_type) {
|
|
199
|
+
case "SessionBegins": {
|
|
200
|
+
const openObject = {
|
|
201
|
+
sessionId: message.session_id,
|
|
202
|
+
expiresAt: new Date(message.expires_at),
|
|
203
|
+
};
|
|
204
|
+
resolve(openObject);
|
|
205
|
+
this.listeners.open?.(openObject);
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
case "PartialTranscript": {
|
|
209
|
+
// message.created is actually a string when coming from the socket
|
|
210
|
+
message.created = new Date(message.created);
|
|
211
|
+
this.listeners.transcript?.(message);
|
|
212
|
+
this.listeners["transcript.partial"]?.(message);
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
case "FinalTranscript": {
|
|
216
|
+
// message.created is actually a string when coming from the socket
|
|
217
|
+
message.created = new Date(message.created);
|
|
218
|
+
this.listeners.transcript?.(message);
|
|
219
|
+
this.listeners["transcript.final"]?.(message);
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
case "SessionTerminated": {
|
|
223
|
+
this.sessionTerminatedResolve?.();
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
sendAudio(audio) {
|
|
231
|
+
if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
|
|
232
|
+
throw new Error("Socket is not open for communication");
|
|
233
|
+
}
|
|
234
|
+
let audioData;
|
|
235
|
+
if (typeof Buffer !== "undefined") {
|
|
236
|
+
audioData = Buffer.from(audio).toString("base64");
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
// Buffer is not available in the browser by default
|
|
240
|
+
// https://stackoverflow.com/a/42334410/2919731
|
|
241
|
+
audioData = btoa(new Uint8Array(audio).reduce((data, byte) => data + String.fromCharCode(byte), ""));
|
|
242
|
+
}
|
|
243
|
+
const payload = {
|
|
244
|
+
audio_data: audioData,
|
|
245
|
+
};
|
|
246
|
+
this.socket.send(JSON.stringify(payload));
|
|
247
|
+
}
|
|
248
|
+
stream() {
|
|
249
|
+
return new WritableStream({
|
|
250
|
+
write: (chunk) => {
|
|
251
|
+
this.sendAudio(chunk);
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
async close(waitForSessionTermination = true) {
|
|
256
|
+
if (this.socket) {
|
|
257
|
+
if (this.socket.readyState === WebSocket.OPEN) {
|
|
258
|
+
const terminateSessionMessage = `{"terminate_session": true}`;
|
|
259
|
+
if (waitForSessionTermination) {
|
|
260
|
+
const sessionTerminatedPromise = new Promise((resolve) => {
|
|
261
|
+
this.sessionTerminatedResolve = resolve;
|
|
262
|
+
});
|
|
263
|
+
this.socket.send(terminateSessionMessage);
|
|
264
|
+
await sessionTerminatedPromise;
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
this.socket.send(terminateSessionMessage);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if ("removeAllListeners" in this.socket)
|
|
271
|
+
this.socket.removeAllListeners();
|
|
272
|
+
this.socket.close();
|
|
273
|
+
}
|
|
274
|
+
this.listeners = {};
|
|
275
|
+
this.socket = undefined;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
class RealtimeServiceFactory extends BaseService {
|
|
280
|
+
constructor(params) {
|
|
281
|
+
super(params);
|
|
282
|
+
this.rtFactoryParams = params;
|
|
283
|
+
}
|
|
284
|
+
createService(params) {
|
|
285
|
+
if (!params)
|
|
286
|
+
params = { apiKey: this.rtFactoryParams.apiKey };
|
|
287
|
+
else if (!("token" in params) && !params.apiKey) {
|
|
288
|
+
params.apiKey = this.rtFactoryParams.apiKey;
|
|
289
|
+
}
|
|
290
|
+
return new RealtimeService(params);
|
|
291
|
+
}
|
|
292
|
+
async createTemporaryToken(params) {
|
|
293
|
+
const data = await this.fetchJson("/v2/realtime/token", {
|
|
294
|
+
method: "POST",
|
|
295
|
+
body: JSON.stringify(params),
|
|
296
|
+
});
|
|
297
|
+
return data.token;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
class TranscriptService extends BaseService {
|
|
302
|
+
constructor(params, files) {
|
|
303
|
+
super(params);
|
|
304
|
+
this.files = files;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Transcribe an audio file. This will create a transcript and wait until the transcript status is "completed" or "error".
|
|
308
|
+
* @param params The parameters to transcribe an audio file.
|
|
309
|
+
* @param options The options to transcribe an audio file.
|
|
310
|
+
* @returns A promise that resolves to the transcript. The transcript status is "completed" or "error".
|
|
311
|
+
*/
|
|
312
|
+
async transcribe(params, options) {
|
|
313
|
+
const transcript = await this.submit(params);
|
|
314
|
+
return await this.waitUntilReady(transcript.id, options);
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Submits a transcription job for an audio file. This will not wait until the transcript status is "completed" or "error".
|
|
318
|
+
* @param params The parameters to start the transcription of an audio file.
|
|
319
|
+
* @returns A promise that resolves to the queued transcript.
|
|
320
|
+
*/
|
|
321
|
+
async submit(params) {
|
|
322
|
+
const { audio, ...createParams } = params;
|
|
323
|
+
let audioUrl;
|
|
324
|
+
if (typeof audio === "string") {
|
|
325
|
+
const path = getPath(audio);
|
|
326
|
+
if (path !== null) {
|
|
327
|
+
// audio is local path, upload local file
|
|
328
|
+
audioUrl = await this.files.upload(path);
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
// audio is not a local path, assume it's a URL
|
|
332
|
+
audioUrl = audio;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
// audio is of uploadable type
|
|
337
|
+
audioUrl = await this.files.upload(audio);
|
|
338
|
+
}
|
|
339
|
+
const data = await this.fetchJson("/v2/transcript", {
|
|
340
|
+
method: "POST",
|
|
341
|
+
body: JSON.stringify({ ...createParams, audio_url: audioUrl }),
|
|
342
|
+
});
|
|
343
|
+
return data;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Create a transcript.
|
|
347
|
+
* @param params The parameters to create a transcript.
|
|
348
|
+
* @param options The options used for creating the new transcript.
|
|
349
|
+
* @returns A promise that resolves to the transcript.
|
|
350
|
+
* @deprecated Use `transcribe` instead to transcribe a audio file that includes polling, or `submit` to transcribe a audio file without polling.
|
|
351
|
+
*/
|
|
352
|
+
async create(params, options) {
|
|
353
|
+
const path = getPath(params.audio_url);
|
|
354
|
+
if (path !== null) {
|
|
355
|
+
const uploadUrl = await this.files.upload(path);
|
|
356
|
+
params.audio_url = uploadUrl;
|
|
357
|
+
}
|
|
358
|
+
const data = await this.fetchJson("/v2/transcript", {
|
|
359
|
+
method: "POST",
|
|
360
|
+
body: JSON.stringify(params),
|
|
361
|
+
});
|
|
362
|
+
if (options?.poll ?? true) {
|
|
363
|
+
return await this.waitUntilReady(data.id, options);
|
|
364
|
+
}
|
|
365
|
+
return data;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Wait until the transcript ready, either the status is "completed" or "error".
|
|
369
|
+
* @param transcriptId The ID of the transcript.
|
|
370
|
+
* @param options The options to wait until the transcript is ready.
|
|
371
|
+
* @returns A promise that resolves to the transcript. The transcript status is "completed" or "error".
|
|
372
|
+
*/
|
|
373
|
+
async waitUntilReady(transcriptId, options) {
|
|
374
|
+
const pollingInterval = options?.pollingInterval ?? 3000;
|
|
375
|
+
const pollingTimeout = options?.pollingTimeout ?? -1;
|
|
376
|
+
const startTime = Date.now();
|
|
377
|
+
// eslint-disable-next-line no-constant-condition
|
|
378
|
+
while (true) {
|
|
379
|
+
const transcript = await this.get(transcriptId);
|
|
380
|
+
if (transcript.status === "completed" || transcript.status === "error") {
|
|
381
|
+
return transcript;
|
|
382
|
+
}
|
|
383
|
+
else if (pollingTimeout > 0 &&
|
|
384
|
+
Date.now() - startTime > pollingTimeout) {
|
|
385
|
+
throw new Error("Polling timeout");
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
await new Promise((resolve) => setTimeout(resolve, pollingInterval));
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Retrieve a transcript.
|
|
394
|
+
* @param id The identifier of the transcript.
|
|
395
|
+
* @returns A promise that resolves to the transcript.
|
|
396
|
+
*/
|
|
397
|
+
get(id) {
|
|
398
|
+
return this.fetchJson(`/v2/transcript/${id}`);
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Retrieves a page of transcript listings.
|
|
402
|
+
* @param parameters The parameters to filter the transcript list by, or the URL to retrieve the transcript list from.
|
|
403
|
+
*/
|
|
404
|
+
async list(parameters) {
|
|
405
|
+
let url = "/v2/transcript";
|
|
406
|
+
if (typeof parameters === "string") {
|
|
407
|
+
url = parameters;
|
|
408
|
+
}
|
|
409
|
+
else if (parameters) {
|
|
410
|
+
url = `${url}?${new URLSearchParams(Object.keys(parameters).map((key) => [
|
|
411
|
+
key,
|
|
412
|
+
parameters[key]?.toString() || "",
|
|
413
|
+
]))}`;
|
|
414
|
+
}
|
|
415
|
+
const data = await this.fetchJson(url);
|
|
416
|
+
for (const transcriptListItem of data.transcripts) {
|
|
417
|
+
transcriptListItem.created = new Date(transcriptListItem.created);
|
|
418
|
+
if (transcriptListItem.completed) {
|
|
419
|
+
transcriptListItem.completed = new Date(transcriptListItem.completed);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return data;
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Delete a transcript
|
|
426
|
+
* @param id The identifier of the transcript.
|
|
427
|
+
* @returns A promise that resolves to the transcript.
|
|
428
|
+
*/
|
|
429
|
+
delete(id) {
|
|
430
|
+
return this.fetchJson(`/v2/transcript/${id}`, { method: "DELETE" });
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Search through the transcript for a specific set of keywords.
|
|
434
|
+
* You can search for individual words, numbers, or phrases containing up to five words or numbers.
|
|
435
|
+
* @param id The identifier of the transcript.
|
|
436
|
+
* @param words Keywords to search for.
|
|
437
|
+
* @return A promise that resolves to the sentences.
|
|
438
|
+
*/
|
|
439
|
+
wordSearch(id, words) {
|
|
440
|
+
const params = new URLSearchParams({ words: words.join(",") });
|
|
441
|
+
return this.fetchJson(`/v2/transcript/${id}/word-search?${params.toString()}`);
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Retrieve all sentences of a transcript.
|
|
445
|
+
* @param id The identifier of the transcript.
|
|
446
|
+
* @return A promise that resolves to the sentences.
|
|
447
|
+
*/
|
|
448
|
+
sentences(id) {
|
|
449
|
+
return this.fetchJson(`/v2/transcript/${id}/sentences`);
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Retrieve all paragraphs of a transcript.
|
|
453
|
+
* @param id The identifier of the transcript.
|
|
454
|
+
* @return A promise that resolves to the paragraphs.
|
|
455
|
+
*/
|
|
456
|
+
paragraphs(id) {
|
|
457
|
+
return this.fetchJson(`/v2/transcript/${id}/paragraphs`);
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Retrieve subtitles of a transcript.
|
|
461
|
+
* @param id The identifier of the transcript.
|
|
462
|
+
* @param format The format of the subtitles.
|
|
463
|
+
* @param chars_per_caption The maximum number of characters per caption.
|
|
464
|
+
* @return A promise that resolves to the subtitles text.
|
|
465
|
+
*/
|
|
466
|
+
async subtitles(id, format = "srt", chars_per_caption) {
|
|
467
|
+
let url = `/v2/transcript/${id}/${format}`;
|
|
468
|
+
if (chars_per_caption) {
|
|
469
|
+
const params = new URLSearchParams();
|
|
470
|
+
params.set("chars_per_caption", chars_per_caption.toString());
|
|
471
|
+
url += `?${params.toString()}`;
|
|
472
|
+
}
|
|
473
|
+
const response = await this.fetch(url);
|
|
474
|
+
return await response.text();
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Retrieve redactions of a transcript.
|
|
478
|
+
* @param id The identifier of the transcript.
|
|
479
|
+
* @return A promise that resolves to the subtitles text.
|
|
480
|
+
*/
|
|
481
|
+
redactions(id) {
|
|
482
|
+
return this.fetchJson(`/v2/transcript/${id}/redacted-audio`);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
function getPath(path) {
|
|
486
|
+
let url;
|
|
487
|
+
try {
|
|
488
|
+
url = new URL(path);
|
|
489
|
+
if (url.protocol === "file:")
|
|
490
|
+
return url.pathname;
|
|
491
|
+
else
|
|
492
|
+
return null;
|
|
493
|
+
}
|
|
494
|
+
catch {
|
|
495
|
+
return path;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const readFile = async (path) => Readable.toWeb(createReadStream(path));
|
|
500
|
+
|
|
501
|
+
class FileService extends BaseService {
|
|
502
|
+
/**
|
|
503
|
+
* Upload a local file to AssemblyAI.
|
|
504
|
+
* @param input The local file path to upload, or a stream or buffer of the file to upload.
|
|
505
|
+
* @return A promise that resolves to the uploaded file URL.
|
|
506
|
+
*/
|
|
507
|
+
async upload(input) {
|
|
508
|
+
let fileData;
|
|
509
|
+
if (typeof input === "string")
|
|
510
|
+
fileData = await readFile(input);
|
|
511
|
+
else
|
|
512
|
+
fileData = input;
|
|
513
|
+
const data = await this.fetchJson("/v2/upload", {
|
|
514
|
+
method: "POST",
|
|
515
|
+
body: fileData,
|
|
516
|
+
headers: {
|
|
517
|
+
"Content-Type": "application/octet-stream",
|
|
518
|
+
},
|
|
519
|
+
duplex: "half",
|
|
520
|
+
});
|
|
521
|
+
return data.upload_url;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const defaultBaseUrl = "https://api.assemblyai.com";
|
|
526
|
+
class AssemblyAI {
|
|
527
|
+
/**
|
|
528
|
+
* Create a new AssemblyAI client.
|
|
529
|
+
* @param params The parameters for the service, including the API key and base URL, if any.
|
|
530
|
+
*/
|
|
531
|
+
constructor(params) {
|
|
532
|
+
params.baseUrl = params.baseUrl || defaultBaseUrl;
|
|
533
|
+
if (params.baseUrl && params.baseUrl.endsWith("/"))
|
|
534
|
+
params.baseUrl = params.baseUrl.slice(0, -1);
|
|
535
|
+
this.files = new FileService(params);
|
|
536
|
+
this.transcripts = new TranscriptService(params, this.files);
|
|
537
|
+
this.lemur = new LemurService(params);
|
|
538
|
+
this.realtime = new RealtimeServiceFactory(params);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
export { AssemblyAI, FileService, LemurService, RealtimeService, RealtimeServiceFactory, TranscriptService };
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
import { WritableStream } from "@swimburger/isomorphic-streams";
|
|
3
1
|
import { RealtimeServiceParams, RealtimeTranscript, PartialTranscript, FinalTranscript, SessionBeginsEventData } from "../..";
|
|
4
2
|
export declare class RealtimeService {
|
|
5
3
|
private realtimeUrl;
|
package/docs/compat.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# SDK Compatibility
|
|
2
|
+
|
|
3
|
+
The JavaScript SDK is developed for Node.js but is also compatible with other runtimes
|
|
4
|
+
such as the browser, Deno, Bun, Cloudflare Workers, etc.
|
|
5
|
+
|
|
6
|
+
## Browser compatibility
|
|
7
|
+
|
|
8
|
+
To make the SDK compatible with the browser, the SDK aims to use web standards as much as possible.
|
|
9
|
+
However, there are still incompatibilities between Node.js and the browser.
|
|
10
|
+
|
|
11
|
+
- `RealtimeService` doesn't support the AssemblyAI API key in the browser.
|
|
12
|
+
Instead, you have to generate a temporary auth token using `client.realtime.createTemporaryToken`, and pass in the resulting token to the real-time transcriber.
|
|
13
|
+
|
|
14
|
+
Generate a temporary auth token on the server.
|
|
15
|
+
|
|
16
|
+
```js
|
|
17
|
+
import { AssemblyAI } from "assemblyai"
|
|
18
|
+
// Ideally, to avoid embedding your API key client side,
|
|
19
|
+
// you generate this token on the server, and pass it to the client via an API.
|
|
20
|
+
const client = new AssemblyAI({ apiKey: "YOUR_API_KEY" });
|
|
21
|
+
const token = await client.realtime.createTemporaryToken({ expires_in = 480 });
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
> [!NOTE]
|
|
25
|
+
> We recommend generating the token on the server, so you don't embed your AssemblyAI API key in your client app.
|
|
26
|
+
> If you embed the API key on the client, everyone can see it and use it for themselves.
|
|
27
|
+
|
|
28
|
+
Then pass the token via an API to the client.
|
|
29
|
+
On the client, create an instance of `RealtimeService` using the token.
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
import { RealtimeService } from "assemblyai";
|
|
33
|
+
// or the following if you're using UMD
|
|
34
|
+
// const { RealtimeService } = assemblyai;
|
|
35
|
+
|
|
36
|
+
const token = getToken(); // getToken is a function for you to implement
|
|
37
|
+
|
|
38
|
+
const rt = new RealtimeService({
|
|
39
|
+
token: token,
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
- You can't pass local audio file paths to `client.files.upload`, `client.transcripts.transcribe`, and `client.transcripts.submit`. If you do, you'll get the following error: "'fs' is not supported in this environment.".
|
|
44
|
+
If you want to transcribe audio files, you must use a public URL, a stream, or a buffer.
|
|
45
|
+
|
|
46
|
+
> [!WARNING]
|
|
47
|
+
> The SDK is usable from the browser, but we strongly recommend you don't embed the AssemblyAI API key into your client apps.
|
|
48
|
+
> If you embed the API key on the client, everyone can see it and use it for themselves.
|
|
49
|
+
> Instead, create use the SDK on the server and provide APIs for your client to call.
|
|
50
|
+
|
|
51
|
+
## Deno, Bun, Cloudflare Workers, etc.
|
|
52
|
+
|
|
53
|
+
Most server-side JavaScript runtimes include a compatibility layer with Node.js.
|
|
54
|
+
Our SDK is developed for Node.js, which makes it compatible with other runtimes through their compatibility layer.
|
|
55
|
+
The bugs in these compatibility layers may introduce issues in our SDK.
|
|
56
|
+
|
|
57
|
+
## Report issues
|
|
58
|
+
|
|
59
|
+
If you find any (undocumented) bugs when using the SDK, [submit a GitHub issue](https://github.com/AssemblyAI/assemblyai-node-sdk). We'll try to fix it or at least document the compatibility issue.
|