assemblyai 4.4.1 → 4.4.2-beta.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/CHANGELOG.md +10 -0
- package/README.md +17 -9
- package/dist/assemblyai.umd.js +43 -24
- package/dist/assemblyai.umd.min.js +1 -1
- package/dist/browser.mjs +639 -0
- package/dist/bun.mjs +37 -10
- package/dist/deno.mjs +37 -10
- package/dist/index.cjs +38 -10
- package/dist/index.mjs +38 -10
- package/dist/node.cjs +37 -10
- package/dist/node.mjs +37 -10
- package/dist/polyfills/websocket/browser.d.ts +3 -0
- package/dist/polyfills/websocket/default.d.ts +3 -0
- package/dist/polyfills/websocket/index.d.ts +27 -0
- package/dist/types/openapi.generated.d.ts +12 -16
- package/package.json +9 -12
- package/src/polyfills/websocket/browser.ts +18 -0
- package/src/polyfills/websocket/default.ts +8 -0
- package/src/polyfills/websocket/index.ts +41 -0
- package/src/services/base.ts +1 -0
- package/src/services/files/index.ts +19 -2
- package/src/services/realtime/service.ts +15 -12
- package/src/services/transcripts/index.ts +6 -2
- package/src/types/openapi.generated.ts +96 -18
- package/src/utils/path.ts +1 -0
- package/src/polyfills/ws/browser.mjs +0 -15
- package/src/polyfills/ws/index.cjs +0 -1
- package/src/polyfills/ws/index.d.ts +0 -2
- package/src/polyfills/ws/index.mjs +0 -2
package/dist/node.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { WritableStream } from 'stream/web';
|
|
2
|
-
import
|
|
2
|
+
import ws from 'ws';
|
|
3
3
|
import { createReadStream } from 'fs';
|
|
4
4
|
import { Readable } from 'stream';
|
|
5
5
|
|
|
@@ -22,6 +22,7 @@ class BaseService {
|
|
|
22
22
|
"Content-Type": "application/json",
|
|
23
23
|
...init.headers,
|
|
24
24
|
};
|
|
25
|
+
init.cache = "no-store";
|
|
25
26
|
if (!input.startsWith("http"))
|
|
26
27
|
input = this.params.baseUrl + input;
|
|
27
28
|
const response = await fetch(input, init);
|
|
@@ -85,6 +86,8 @@ class LemurService extends BaseService {
|
|
|
85
86
|
}
|
|
86
87
|
}
|
|
87
88
|
|
|
89
|
+
const factory = (url, params) => new ws(url, params);
|
|
90
|
+
|
|
88
91
|
var RealtimeErrorType;
|
|
89
92
|
(function (RealtimeErrorType) {
|
|
90
93
|
RealtimeErrorType[RealtimeErrorType["BadSampleRate"] = 4000] = "BadSampleRate";
|
|
@@ -180,10 +183,10 @@ class RealtimeTranscriber {
|
|
|
180
183
|
}
|
|
181
184
|
const url = this.connectionUrl();
|
|
182
185
|
if (this.token) {
|
|
183
|
-
this.socket =
|
|
186
|
+
this.socket = factory(url.toString());
|
|
184
187
|
}
|
|
185
188
|
else {
|
|
186
|
-
this.socket =
|
|
189
|
+
this.socket = factory(url.toString(), {
|
|
187
190
|
headers: { Authorization: this.apiKey },
|
|
188
191
|
});
|
|
189
192
|
}
|
|
@@ -276,14 +279,14 @@ class RealtimeTranscriber {
|
|
|
276
279
|
this.send(`{"end_utterance_silence_threshold":${threshold}}`);
|
|
277
280
|
}
|
|
278
281
|
send(data) {
|
|
279
|
-
if (!this.socket || this.socket.readyState !==
|
|
282
|
+
if (!this.socket || this.socket.readyState !== this.socket.OPEN) {
|
|
280
283
|
throw new Error("Socket is not open for communication");
|
|
281
284
|
}
|
|
282
285
|
this.socket.send(data);
|
|
283
286
|
}
|
|
284
287
|
async close(waitForSessionTermination = true) {
|
|
285
288
|
if (this.socket) {
|
|
286
|
-
if (this.socket.readyState ===
|
|
289
|
+
if (this.socket.readyState === this.socket.OPEN) {
|
|
287
290
|
if (waitForSessionTermination) {
|
|
288
291
|
const sessionTerminatedPromise = new Promise((resolve) => {
|
|
289
292
|
this.sessionTerminatedResolve = resolve;
|
|
@@ -295,7 +298,7 @@ class RealtimeTranscriber {
|
|
|
295
298
|
this.socket.send(terminateSessionMessage);
|
|
296
299
|
}
|
|
297
300
|
}
|
|
298
|
-
if (
|
|
301
|
+
if (this.socket?.removeAllListeners)
|
|
299
302
|
this.socket.removeAllListeners();
|
|
300
303
|
this.socket.close();
|
|
301
304
|
}
|
|
@@ -346,6 +349,8 @@ function getPath(path) {
|
|
|
346
349
|
return null;
|
|
347
350
|
if (path.startsWith("https"))
|
|
348
351
|
return null;
|
|
352
|
+
if (path.startsWith("data:"))
|
|
353
|
+
return null;
|
|
349
354
|
if (path.startsWith("file://"))
|
|
350
355
|
return path.substring(7);
|
|
351
356
|
if (path.startsWith("file:"))
|
|
@@ -387,8 +392,13 @@ class TranscriptService extends BaseService {
|
|
|
387
392
|
audioUrl = await this.files.upload(path);
|
|
388
393
|
}
|
|
389
394
|
else {
|
|
390
|
-
|
|
391
|
-
|
|
395
|
+
if (audio.startsWith("data:")) {
|
|
396
|
+
audioUrl = await this.files.upload(audio);
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
// audio is not a local path, and not a data-URI, assume it's a normal URL
|
|
400
|
+
audioUrl = audio;
|
|
401
|
+
}
|
|
392
402
|
}
|
|
393
403
|
}
|
|
394
404
|
else {
|
|
@@ -565,8 +575,14 @@ class FileService extends BaseService {
|
|
|
565
575
|
*/
|
|
566
576
|
async upload(input) {
|
|
567
577
|
let fileData;
|
|
568
|
-
if (typeof input === "string")
|
|
569
|
-
|
|
578
|
+
if (typeof input === "string") {
|
|
579
|
+
if (input.startsWith("data:")) {
|
|
580
|
+
fileData = dataUrlToBlob(input);
|
|
581
|
+
}
|
|
582
|
+
else {
|
|
583
|
+
fileData = await readFile(input);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
570
586
|
else
|
|
571
587
|
fileData = input;
|
|
572
588
|
const data = await this.fetchJson("/v2/upload", {
|
|
@@ -580,6 +596,17 @@ class FileService extends BaseService {
|
|
|
580
596
|
return data.upload_url;
|
|
581
597
|
}
|
|
582
598
|
}
|
|
599
|
+
function dataUrlToBlob(dataUrl) {
|
|
600
|
+
const arr = dataUrl.split(",");
|
|
601
|
+
const mime = arr[0].match(/:(.*?);/)[1];
|
|
602
|
+
const bstr = atob(arr[1]);
|
|
603
|
+
let n = bstr.length;
|
|
604
|
+
const u8arr = new Uint8Array(n);
|
|
605
|
+
while (n--) {
|
|
606
|
+
u8arr[n] = bstr.charCodeAt(n);
|
|
607
|
+
}
|
|
608
|
+
return new Blob([u8arr], { type: mime });
|
|
609
|
+
}
|
|
583
610
|
|
|
584
611
|
const defaultBaseUrl = "https://api.assemblyai.com";
|
|
585
612
|
class AssemblyAI {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import ws, { Event, ErrorEvent, CloseEvent, MessageEvent } from "ws";
|
|
3
|
+
export type PolyfillWebSocket = {
|
|
4
|
+
OPEN: typeof ws.OPEN;
|
|
5
|
+
binaryType: string;
|
|
6
|
+
onopen: ((event: Event) => void) | null;
|
|
7
|
+
onerror: ((event: ErrorEvent) => void) | null;
|
|
8
|
+
onclose: ((event: CloseEvent) => void) | null;
|
|
9
|
+
onmessage: ((event: MessageEvent) => void) | null;
|
|
10
|
+
readonly readyState: typeof ws.CONNECTING | typeof ws.OPEN | typeof ws.CLOSING | typeof ws.CLOSED;
|
|
11
|
+
removeAllListeners?: () => void;
|
|
12
|
+
send(data: string | number | Buffer | DataView | ArrayBufferView | Uint8Array | ArrayBuffer | SharedArrayBuffer | readonly unknown[] | readonly number[] | {
|
|
13
|
+
valueOf(): ArrayBuffer;
|
|
14
|
+
} | {
|
|
15
|
+
valueOf(): SharedArrayBuffer;
|
|
16
|
+
} | {
|
|
17
|
+
valueOf(): Uint8Array;
|
|
18
|
+
} | {
|
|
19
|
+
valueOf(): readonly number[];
|
|
20
|
+
} | {
|
|
21
|
+
valueOf(): string;
|
|
22
|
+
} | {
|
|
23
|
+
[Symbol.toPrimitive](hint: string): string;
|
|
24
|
+
}): unknown;
|
|
25
|
+
close(): unknown;
|
|
26
|
+
};
|
|
27
|
+
export type PolyfillWebSocketFactory = (url: string, params?: unknown) => PolyfillWebSocket;
|
|
@@ -721,7 +721,7 @@ export type LemurQuestionAnswerResponse = LemurBaseResponse & {
|
|
|
721
721
|
* ```js
|
|
722
722
|
* {
|
|
723
723
|
* "transcript_ids": [
|
|
724
|
-
* "
|
|
724
|
+
* "47b95ba5-8889-44d8-bc80-5de38306e582"
|
|
725
725
|
* ],
|
|
726
726
|
* "context": "This is an interview about wildfires.",
|
|
727
727
|
* "final_model": "default",
|
|
@@ -828,7 +828,6 @@ export type ListTranscriptParams = {
|
|
|
828
828
|
throttled_only?: boolean;
|
|
829
829
|
};
|
|
830
830
|
/**
|
|
831
|
-
* Details of the transcript page.
|
|
832
831
|
* Details of the transcript page. Transcripts are sorted from newest to oldest. The previous URL always points to a page with older transcripts.
|
|
833
832
|
* @example
|
|
834
833
|
* ```js
|
|
@@ -851,12 +850,10 @@ export type PageDetails = {
|
|
|
851
850
|
*/
|
|
852
851
|
limit: number;
|
|
853
852
|
/**
|
|
854
|
-
* The URL to the next page of transcripts
|
|
855
853
|
* The URL to the next page of transcripts. The next URL always points to a page with newer transcripts.
|
|
856
854
|
*/
|
|
857
855
|
next_url: string | null;
|
|
858
856
|
/**
|
|
859
|
-
* The URL to the previous page of transcripts
|
|
860
857
|
* The URL to the next page of transcripts. The previous URL always points to a page with older transcripts.
|
|
861
858
|
*/
|
|
862
859
|
prev_url: string | null;
|
|
@@ -1124,7 +1121,7 @@ export type SentencesResponse = {
|
|
|
1124
1121
|
};
|
|
1125
1122
|
export type Sentiment = "POSITIVE" | "NEUTRAL" | "NEGATIVE";
|
|
1126
1123
|
/**
|
|
1127
|
-
* The result of the
|
|
1124
|
+
* The result of the Sentiment Analysis model
|
|
1128
1125
|
* @example
|
|
1129
1126
|
* ```js
|
|
1130
1127
|
* {
|
|
@@ -2114,7 +2111,7 @@ export type Transcript = {
|
|
|
2114
2111
|
auto_highlights: boolean;
|
|
2115
2112
|
/**
|
|
2116
2113
|
* An array of results for the Key Phrases model, if it is enabled.
|
|
2117
|
-
* See {@link https://www.assemblyai.com/docs/models/key-phrases | Key
|
|
2114
|
+
* See {@link https://www.assemblyai.com/docs/models/key-phrases | Key Phrases } for more information.
|
|
2118
2115
|
*/
|
|
2119
2116
|
auto_highlights_result?: AutoHighlightsResult | null;
|
|
2120
2117
|
/**
|
|
@@ -2236,7 +2233,7 @@ export type Transcript = {
|
|
|
2236
2233
|
sentiment_analysis?: boolean | null;
|
|
2237
2234
|
/**
|
|
2238
2235
|
* An array of results for the Sentiment Analysis model, if it is enabled.
|
|
2239
|
-
* See {@link https://www.assemblyai.com/docs/models/sentiment-analysis | Sentiment
|
|
2236
|
+
* See {@link https://www.assemblyai.com/docs/models/sentiment-analysis | Sentiment Analysis } for more information.
|
|
2240
2237
|
*/
|
|
2241
2238
|
sentiment_analysis_results?: SentimentAnalysisResult[] | null;
|
|
2242
2239
|
/**
|
|
@@ -2313,7 +2310,7 @@ export type Transcript = {
|
|
|
2313
2310
|
*/
|
|
2314
2311
|
webhook_status_code?: number | null;
|
|
2315
2312
|
/**
|
|
2316
|
-
* The URL to which we send webhooks upon
|
|
2313
|
+
* The URL to which we send webhooks upon transcription completion
|
|
2317
2314
|
*/
|
|
2318
2315
|
webhook_url?: string | null;
|
|
2319
2316
|
/**
|
|
@@ -2358,9 +2355,8 @@ export type TranscriptCustomSpelling = {
|
|
|
2358
2355
|
*
|
|
2359
2356
|
* @defaultValue "en_us
|
|
2360
2357
|
*/
|
|
2361
|
-
export type TranscriptLanguageCode = "en" | "en_au" | "en_uk" | "en_us" | "es" | "fr" | "de" | "it" | "pt" | "nl" | "
|
|
2358
|
+
export type TranscriptLanguageCode = "en" | "en_au" | "en_uk" | "en_us" | "es" | "fr" | "de" | "it" | "pt" | "nl" | "af" | "sq" | "am" | "ar" | "hy" | "as" | "az" | "ba" | "eu" | "be" | "bn" | "bs" | "br" | "bg" | "my" | "ca" | "zh" | "hr" | "cs" | "da" | "et" | "fo" | "fi" | "gl" | "ka" | "el" | "gu" | "ht" | "ha" | "haw" | "he" | "hi" | "hu" | "is" | "id" | "ja" | "jw" | "kn" | "kk" | "km" | "ko" | "lo" | "la" | "lv" | "ln" | "lt" | "lb" | "mk" | "mg" | "ms" | "ml" | "mt" | "mi" | "mr" | "mn" | "ne" | "no" | "nn" | "oc" | "pa" | "ps" | "fa" | "pl" | "ro" | "ru" | "sa" | "sr" | "sn" | "sd" | "si" | "sk" | "sl" | "so" | "su" | "sw" | "sv" | "tl" | "tg" | "ta" | "tt" | "te" | "th" | "bo" | "tr" | "tk" | "uk" | "ur" | "uz" | "vi" | "cy" | "yi" | "yo";
|
|
2362
2359
|
/**
|
|
2363
|
-
* A list of transcripts
|
|
2364
2360
|
* A list of transcripts. Transcripts are sorted from newest to oldest. The previous URL always points to a page with older transcripts.
|
|
2365
2361
|
* @example
|
|
2366
2362
|
* ```js
|
|
@@ -2498,7 +2494,7 @@ export type TranscriptOptionalParams = {
|
|
|
2498
2494
|
*/
|
|
2499
2495
|
auto_chapters?: boolean;
|
|
2500
2496
|
/**
|
|
2501
|
-
*
|
|
2497
|
+
* Enable Key Phrases, either true or false
|
|
2502
2498
|
*/
|
|
2503
2499
|
auto_highlights?: boolean;
|
|
2504
2500
|
/**
|
|
@@ -2510,7 +2506,7 @@ export type TranscriptOptionalParams = {
|
|
|
2510
2506
|
*/
|
|
2511
2507
|
content_safety?: boolean;
|
|
2512
2508
|
/**
|
|
2513
|
-
* The confidence threshold for
|
|
2509
|
+
* The confidence threshold for the Content Moderation model. Values must be between 25 and 100.
|
|
2514
2510
|
*/
|
|
2515
2511
|
content_safety_confidence?: number;
|
|
2516
2512
|
/**
|
|
@@ -2518,7 +2514,7 @@ export type TranscriptOptionalParams = {
|
|
|
2518
2514
|
*/
|
|
2519
2515
|
custom_spelling?: TranscriptCustomSpelling[];
|
|
2520
2516
|
/**
|
|
2521
|
-
*
|
|
2517
|
+
* Enable custom topics, either true or false
|
|
2522
2518
|
*/
|
|
2523
2519
|
custom_topics?: boolean;
|
|
2524
2520
|
/**
|
|
@@ -2551,7 +2547,7 @@ export type TranscriptOptionalParams = {
|
|
|
2551
2547
|
*/
|
|
2552
2548
|
language_code?: LiteralUnion<TranscriptLanguageCode, string> | null;
|
|
2553
2549
|
/**
|
|
2554
|
-
*
|
|
2550
|
+
* Enable {@link https://www.assemblyai.com/docs/models/speech-recognition#automatic-language-detection | Automatic language detection }, either true or false.
|
|
2555
2551
|
*/
|
|
2556
2552
|
language_detection?: boolean;
|
|
2557
2553
|
/**
|
|
@@ -2619,7 +2615,7 @@ export type TranscriptOptionalParams = {
|
|
|
2619
2615
|
*/
|
|
2620
2616
|
summary_type?: SummaryType;
|
|
2621
2617
|
/**
|
|
2622
|
-
* The list of custom topics
|
|
2618
|
+
* The list of custom topics
|
|
2623
2619
|
*/
|
|
2624
2620
|
topics?: string[];
|
|
2625
2621
|
/**
|
|
@@ -2633,7 +2629,7 @@ export type TranscriptOptionalParams = {
|
|
|
2633
2629
|
*/
|
|
2634
2630
|
webhook_auth_header_value?: string | null;
|
|
2635
2631
|
/**
|
|
2636
|
-
* The URL to which AssemblyAI send webhooks upon
|
|
2632
|
+
* The URL to which AssemblyAI send webhooks upon transcription completion
|
|
2637
2633
|
*/
|
|
2638
2634
|
webhook_url?: string;
|
|
2639
2635
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "assemblyai",
|
|
3
|
-
"version": "4.4.
|
|
3
|
+
"version": "4.4.2-beta.0",
|
|
4
4
|
"description": "The AssemblyAI JavaScript SDK provides an easy-to-use interface for interacting with the AssemblyAI API, which supports async and real-time transcription, as well as the latest LeMUR models.",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=18"
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"default": "./dist/deno.mjs"
|
|
18
18
|
},
|
|
19
19
|
"workerd": "./dist/index.mjs",
|
|
20
|
-
"browser": "./dist/
|
|
20
|
+
"browser": "./dist/browser.mjs",
|
|
21
|
+
"react-native": "./dist/browser.mjs",
|
|
21
22
|
"node": {
|
|
22
23
|
"types": "./dist/index.d.ts",
|
|
23
24
|
"import": "./dist/node.mjs",
|
|
@@ -40,14 +41,10 @@
|
|
|
40
41
|
"node": "./src/polyfills/streams/node.ts",
|
|
41
42
|
"default": "./src/polyfills/streams/index.ts"
|
|
42
43
|
},
|
|
43
|
-
"#
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"default":
|
|
47
|
-
"types": "./src/polyfills/ws/index.d.ts",
|
|
48
|
-
"import": "./src/polyfills/ws/index.mjs",
|
|
49
|
-
"require": "./src/polyfills/ws/index.cjs"
|
|
50
|
-
}
|
|
44
|
+
"#websocket": {
|
|
45
|
+
"browser": "./src/polyfills/websocket/browser.ts",
|
|
46
|
+
"node": "./src/polyfills/websocket/default.ts",
|
|
47
|
+
"default": "./src/polyfills/websocket/default.ts"
|
|
51
48
|
}
|
|
52
49
|
},
|
|
53
50
|
"type": "commonjs",
|
|
@@ -61,7 +58,7 @@
|
|
|
61
58
|
"url": "git+https://github.com/AssemblyAI/assemblyai-node-sdk.git"
|
|
62
59
|
},
|
|
63
60
|
"publishConfig": {
|
|
64
|
-
"tag": "
|
|
61
|
+
"tag": "beta",
|
|
65
62
|
"access": "public",
|
|
66
63
|
"registry": "https://registry.npmjs.org/"
|
|
67
64
|
},
|
|
@@ -70,7 +67,7 @@
|
|
|
70
67
|
"clean": "rimraf dist/* && rimraf temp/* && rimraf temp-docs/*",
|
|
71
68
|
"lint": "eslint -c .eslintrc.json '{src,tests}/**/*.{js,ts}' && publint && tsc --noEmit -p tsconfig.json",
|
|
72
69
|
"test": "pnpm run test:unit && pnpm run test:integration",
|
|
73
|
-
"test:unit": "jest --config jest.unit.config.js",
|
|
70
|
+
"test:unit": "jest --config jest.unit.config.js --testTimeout 1000",
|
|
74
71
|
"test:integration": "jest --config jest.integration.config.js --testTimeout 360000",
|
|
75
72
|
"format": "prettier '**/*' --write",
|
|
76
73
|
"generate:types": "tsx ./scripts/generate-types.ts && prettier 'src/types/*.generated.ts' --write",
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { PolyfillWebSocketFactory, PolyfillWebSocket } from ".";
|
|
2
|
+
export { PolyfillWebSocket } from ".";
|
|
3
|
+
|
|
4
|
+
const PolyfillWebSocket =
|
|
5
|
+
WebSocket ?? global?.WebSocket ?? window?.WebSocket ?? self?.WebSocket;
|
|
6
|
+
|
|
7
|
+
export const factory: PolyfillWebSocketFactory = (
|
|
8
|
+
url: string,
|
|
9
|
+
params?: unknown,
|
|
10
|
+
) => {
|
|
11
|
+
if (params) {
|
|
12
|
+
return new PolyfillWebSocket(
|
|
13
|
+
url,
|
|
14
|
+
params as string | string[],
|
|
15
|
+
) as unknown as PolyfillWebSocket;
|
|
16
|
+
}
|
|
17
|
+
return new PolyfillWebSocket(url) as unknown as PolyfillWebSocket;
|
|
18
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import ws from "ws";
|
|
2
|
+
import { PolyfillWebSocket, PolyfillWebSocketFactory } from ".";
|
|
3
|
+
export { PolyfillWebSocket } from ".";
|
|
4
|
+
|
|
5
|
+
export const factory: PolyfillWebSocketFactory = (
|
|
6
|
+
url: string,
|
|
7
|
+
params?: unknown,
|
|
8
|
+
) => new ws(url, params as ws.ClientOptions) as unknown as PolyfillWebSocket;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import ws, { Event, ErrorEvent, CloseEvent, MessageEvent } from "ws";
|
|
2
|
+
|
|
3
|
+
export type PolyfillWebSocket = {
|
|
4
|
+
OPEN: typeof ws.OPEN;
|
|
5
|
+
binaryType: string;
|
|
6
|
+
onopen: ((event: Event) => void) | null;
|
|
7
|
+
onerror: ((event: ErrorEvent) => void) | null;
|
|
8
|
+
onclose: ((event: CloseEvent) => void) | null;
|
|
9
|
+
onmessage: ((event: MessageEvent) => void) | null;
|
|
10
|
+
readonly readyState:
|
|
11
|
+
| typeof ws.CONNECTING
|
|
12
|
+
| typeof ws.OPEN
|
|
13
|
+
| typeof ws.CLOSING
|
|
14
|
+
| typeof ws.CLOSED;
|
|
15
|
+
removeAllListeners?: () => void;
|
|
16
|
+
send(
|
|
17
|
+
data:
|
|
18
|
+
| string
|
|
19
|
+
| number
|
|
20
|
+
| Buffer
|
|
21
|
+
| DataView
|
|
22
|
+
| ArrayBufferView
|
|
23
|
+
| Uint8Array
|
|
24
|
+
| ArrayBuffer
|
|
25
|
+
| SharedArrayBuffer
|
|
26
|
+
| readonly unknown[]
|
|
27
|
+
| readonly number[]
|
|
28
|
+
| { valueOf(): ArrayBuffer }
|
|
29
|
+
| { valueOf(): SharedArrayBuffer }
|
|
30
|
+
| { valueOf(): Uint8Array }
|
|
31
|
+
| { valueOf(): readonly number[] }
|
|
32
|
+
| { valueOf(): string }
|
|
33
|
+
| { [Symbol.toPrimitive](hint: string): string },
|
|
34
|
+
): unknown;
|
|
35
|
+
close(): unknown;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type PolyfillWebSocketFactory = (
|
|
39
|
+
url: string,
|
|
40
|
+
params?: unknown,
|
|
41
|
+
) => PolyfillWebSocket;
|
package/src/services/base.ts
CHANGED
|
@@ -10,8 +10,13 @@ export class FileService extends BaseService {
|
|
|
10
10
|
*/
|
|
11
11
|
async upload(input: FileUploadParams): Promise<string> {
|
|
12
12
|
let fileData: FileUploadData;
|
|
13
|
-
if (typeof input === "string")
|
|
14
|
-
|
|
13
|
+
if (typeof input === "string") {
|
|
14
|
+
if (input.startsWith("data:")) {
|
|
15
|
+
fileData = dataUrlToBlob(input);
|
|
16
|
+
} else {
|
|
17
|
+
fileData = await readFile(input);
|
|
18
|
+
}
|
|
19
|
+
} else fileData = input;
|
|
15
20
|
|
|
16
21
|
const data = await this.fetchJson<UploadedFile>("/v2/upload", {
|
|
17
22
|
method: "POST",
|
|
@@ -24,3 +29,15 @@ export class FileService extends BaseService {
|
|
|
24
29
|
return data.upload_url;
|
|
25
30
|
}
|
|
26
31
|
}
|
|
32
|
+
|
|
33
|
+
function dataUrlToBlob(dataUrl: string) {
|
|
34
|
+
const arr = dataUrl.split(",");
|
|
35
|
+
const mime = arr[0].match(/:(.*?);/)![1];
|
|
36
|
+
const bstr = atob(arr[1]);
|
|
37
|
+
let n = bstr.length;
|
|
38
|
+
const u8arr = new Uint8Array(n);
|
|
39
|
+
while (n--) {
|
|
40
|
+
u8arr[n] = bstr.charCodeAt(n);
|
|
41
|
+
}
|
|
42
|
+
return new Blob([u8arr], { type: mime });
|
|
43
|
+
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { WritableStream } from "#streams";
|
|
2
|
-
import
|
|
2
|
+
import {
|
|
3
|
+
PolyfillWebSocket,
|
|
4
|
+
factory as polyfillWebSocketFactory,
|
|
5
|
+
} from "#websocket";
|
|
3
6
|
import { ErrorEvent, MessageEvent, CloseEvent } from "ws";
|
|
4
7
|
import {
|
|
5
8
|
RealtimeEvents,
|
|
@@ -52,7 +55,7 @@ export class RealtimeTranscriber {
|
|
|
52
55
|
private endUtteranceSilenceThreshold?: number;
|
|
53
56
|
private disablePartialTranscripts?: boolean;
|
|
54
57
|
|
|
55
|
-
private socket?:
|
|
58
|
+
private socket?: PolyfillWebSocket;
|
|
56
59
|
private listeners: RealtimeListeners = {};
|
|
57
60
|
private sessionTerminatedResolve?: () => void;
|
|
58
61
|
|
|
@@ -136,15 +139,15 @@ export class RealtimeTranscriber {
|
|
|
136
139
|
const url = this.connectionUrl();
|
|
137
140
|
|
|
138
141
|
if (this.token) {
|
|
139
|
-
this.socket =
|
|
142
|
+
this.socket = polyfillWebSocketFactory(url.toString());
|
|
140
143
|
} else {
|
|
141
|
-
this.socket =
|
|
144
|
+
this.socket = polyfillWebSocketFactory(url.toString(), {
|
|
142
145
|
headers: { Authorization: this.apiKey },
|
|
143
146
|
});
|
|
144
147
|
}
|
|
145
|
-
this.socket
|
|
148
|
+
this.socket!.binaryType = "arraybuffer";
|
|
146
149
|
|
|
147
|
-
this.socket
|
|
150
|
+
this.socket!.onopen = () => {
|
|
148
151
|
if (
|
|
149
152
|
this.endUtteranceSilenceThreshold === undefined ||
|
|
150
153
|
this.endUtteranceSilenceThreshold === null
|
|
@@ -156,7 +159,7 @@ export class RealtimeTranscriber {
|
|
|
156
159
|
);
|
|
157
160
|
};
|
|
158
161
|
|
|
159
|
-
this.socket
|
|
162
|
+
this.socket!.onclose = ({ code, reason }: CloseEvent) => {
|
|
160
163
|
if (!reason) {
|
|
161
164
|
if (code in RealtimeErrorType) {
|
|
162
165
|
reason = RealtimeErrorMessages[code as RealtimeErrorType];
|
|
@@ -165,12 +168,12 @@ export class RealtimeTranscriber {
|
|
|
165
168
|
this.listeners.close?.(code, reason);
|
|
166
169
|
};
|
|
167
170
|
|
|
168
|
-
this.socket
|
|
171
|
+
this.socket!.onerror = (event: ErrorEvent) => {
|
|
169
172
|
if (event.error) this.listeners.error?.(event.error as Error);
|
|
170
173
|
else this.listeners.error?.(new Error(event.message));
|
|
171
174
|
};
|
|
172
175
|
|
|
173
|
-
this.socket
|
|
176
|
+
this.socket!.onmessage = ({ data }: MessageEvent) => {
|
|
174
177
|
const message = JSON.parse(data.toString()) as RealtimeMessage;
|
|
175
178
|
if ("error" in message) {
|
|
176
179
|
this.listeners.error?.(new RealtimeError(message.error));
|
|
@@ -242,7 +245,7 @@ export class RealtimeTranscriber {
|
|
|
242
245
|
}
|
|
243
246
|
|
|
244
247
|
private send(data: BufferLike) {
|
|
245
|
-
if (!this.socket || this.socket.readyState !==
|
|
248
|
+
if (!this.socket || this.socket.readyState !== this.socket.OPEN) {
|
|
246
249
|
throw new Error("Socket is not open for communication");
|
|
247
250
|
}
|
|
248
251
|
this.socket.send(data);
|
|
@@ -250,7 +253,7 @@ export class RealtimeTranscriber {
|
|
|
250
253
|
|
|
251
254
|
async close(waitForSessionTermination = true) {
|
|
252
255
|
if (this.socket) {
|
|
253
|
-
if (this.socket.readyState ===
|
|
256
|
+
if (this.socket.readyState === this.socket.OPEN) {
|
|
254
257
|
if (waitForSessionTermination) {
|
|
255
258
|
const sessionTerminatedPromise = new Promise<void>((resolve) => {
|
|
256
259
|
this.sessionTerminatedResolve = resolve;
|
|
@@ -261,7 +264,7 @@ export class RealtimeTranscriber {
|
|
|
261
264
|
this.socket.send(terminateSessionMessage);
|
|
262
265
|
}
|
|
263
266
|
}
|
|
264
|
-
if (
|
|
267
|
+
if (this.socket?.removeAllListeners) this.socket.removeAllListeners();
|
|
265
268
|
this.socket.close();
|
|
266
269
|
}
|
|
267
270
|
|
|
@@ -60,8 +60,12 @@ export class TranscriptService extends BaseService {
|
|
|
60
60
|
// audio is local path, upload local file
|
|
61
61
|
audioUrl = await this.files.upload(path);
|
|
62
62
|
} else {
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
if (audio.startsWith("data:")) {
|
|
64
|
+
audioUrl = await this.files.upload(audio);
|
|
65
|
+
} else {
|
|
66
|
+
// audio is not a local path, and not a data-URI, assume it's a normal URL
|
|
67
|
+
audioUrl = audio;
|
|
68
|
+
}
|
|
65
69
|
}
|
|
66
70
|
} else {
|
|
67
71
|
// audio is of uploadable type
|