whisperai-sdk 1.0.0 → 1.0.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/dist/client.d.ts +14 -7
- package/dist/index.js +149 -78
- package/package.json +14 -18
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ClientOptions,
|
|
1
|
+
import type { ClientOptions, FinalizeChunkResponse, InitChunkResponse, InitMetaFile, RecordingResponse, RecordingsQuery, RecordingsResponse, SummaryResponse, SubscriptionDetailsResponse, TranscriptionResponse, TranslateResponse, UploadChunkResponse, UsageInfo, UserInfo } from "./types.js";
|
|
2
2
|
export declare class WhisperClient {
|
|
3
3
|
private cookies?;
|
|
4
4
|
private readonly clientOptions;
|
|
@@ -7,15 +7,24 @@ export declare class WhisperClient {
|
|
|
7
7
|
user(): Promise<UserInfo>;
|
|
8
8
|
usage(): Promise<UsageInfo>;
|
|
9
9
|
subscriptionDetails(): Promise<SubscriptionDetailsResponse>;
|
|
10
|
-
upload(
|
|
10
|
+
upload(file: Uint8Array | ReadableStream<Uint8Array>, meta: InitMetaFile): Promise<FinalizeChunkResponse>;
|
|
11
11
|
initChunk(fileBuffer: Uint8Array, meta: InitMetaFile): Promise<InitChunkResponse>;
|
|
12
|
-
uploadChunk(
|
|
12
|
+
uploadChunk(chunk: Uint8Array, recordingId: number, chunkIndex: number): Promise<UploadChunkResponse>;
|
|
13
13
|
finalizeChunk(recordingId: number): Promise<FinalizeChunkResponse>;
|
|
14
14
|
transcription(recordingId: number): Promise<TranscriptionResponse>;
|
|
15
15
|
translate(recordingId: number, language: string): Promise<TranslateResponse>;
|
|
16
16
|
recording(recordingId: number): Promise<RecordingResponse>;
|
|
17
17
|
recordings(query?: RecordingsQuery): Promise<RecordingsResponse>;
|
|
18
18
|
summary(): Promise<SummaryResponse>;
|
|
19
|
+
private uploadFromStream;
|
|
20
|
+
private uploadChunkSlice;
|
|
21
|
+
private initChunkRequest;
|
|
22
|
+
private uploadChunkRequest;
|
|
23
|
+
private recall;
|
|
24
|
+
private get;
|
|
25
|
+
private post;
|
|
26
|
+
private postForm;
|
|
27
|
+
private request;
|
|
19
28
|
private get loginLink();
|
|
20
29
|
private get userLink();
|
|
21
30
|
private get usageLink();
|
|
@@ -25,11 +34,9 @@ export declare class WhisperClient {
|
|
|
25
34
|
private get finalizeChunkLink();
|
|
26
35
|
private get transcriptionLink();
|
|
27
36
|
private get summaryLink();
|
|
37
|
+
private get recordingsLink();
|
|
28
38
|
private translateLink;
|
|
29
39
|
private recordingLink;
|
|
30
|
-
private
|
|
40
|
+
private mergeCookies;
|
|
31
41
|
private getCountChunks;
|
|
32
|
-
private get requestOptions();
|
|
33
|
-
private recall;
|
|
34
|
-
private handleRequest;
|
|
35
42
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
// src/client.ts
|
|
2
|
-
import axios from "axios";
|
|
3
|
-
|
|
4
1
|
// src/errors.ts
|
|
5
2
|
class WhisperError extends Error {
|
|
6
3
|
constructor(message) {
|
|
@@ -47,65 +44,167 @@ class WhisperClient {
|
|
|
47
44
|
};
|
|
48
45
|
}
|
|
49
46
|
login() {
|
|
50
|
-
return this.
|
|
47
|
+
return this.post(this.loginLink, this.clientOptions.login);
|
|
51
48
|
}
|
|
52
49
|
user() {
|
|
53
|
-
return this.recall(() =>
|
|
50
|
+
return this.recall(() => this.get(this.userLink));
|
|
54
51
|
}
|
|
55
52
|
usage() {
|
|
56
|
-
return this.recall(() =>
|
|
53
|
+
return this.recall(() => this.get(this.usageLink));
|
|
57
54
|
}
|
|
58
55
|
subscriptionDetails() {
|
|
59
|
-
return this.recall(() =>
|
|
56
|
+
return this.recall(() => this.get(this.subscriptionDetailsLink));
|
|
57
|
+
}
|
|
58
|
+
async upload(file, meta) {
|
|
59
|
+
if (file instanceof Uint8Array) {
|
|
60
|
+
const totalChunks2 = this.getCountChunks(file.length);
|
|
61
|
+
const { recordingId: recordingId2 } = await this.recall(() => this.initChunkRequest(file.length, totalChunks2, meta));
|
|
62
|
+
await Promise.all(Array.from({ length: totalChunks2 }, (_, i) => this.uploadChunkSlice(file, recordingId2, i)));
|
|
63
|
+
return this.finalizeChunk(recordingId2);
|
|
64
|
+
}
|
|
65
|
+
if (meta.totalSize === undefined) {
|
|
66
|
+
const buffer = new Uint8Array(await new Response(file).arrayBuffer());
|
|
67
|
+
return this.upload(buffer, { ...meta, totalSize: buffer.length });
|
|
68
|
+
}
|
|
69
|
+
const totalChunks = this.getCountChunks(meta.totalSize);
|
|
70
|
+
const { recordingId } = await this.recall(() => this.initChunkRequest(meta.totalSize, totalChunks, meta));
|
|
71
|
+
await this.uploadFromStream(file, recordingId);
|
|
72
|
+
return this.finalizeChunk(recordingId);
|
|
60
73
|
}
|
|
61
|
-
|
|
74
|
+
initChunk(fileBuffer, meta) {
|
|
62
75
|
const totalChunks = this.getCountChunks(fileBuffer.length);
|
|
63
|
-
return this.
|
|
76
|
+
return this.recall(() => this.initChunkRequest(fileBuffer.length, totalChunks, meta));
|
|
64
77
|
}
|
|
65
|
-
|
|
66
|
-
return this.recall(() =>
|
|
78
|
+
uploadChunk(chunk, recordingId, chunkIndex) {
|
|
79
|
+
return this.recall(() => this.uploadChunkRequest(chunk, recordingId, chunkIndex));
|
|
80
|
+
}
|
|
81
|
+
finalizeChunk(recordingId) {
|
|
82
|
+
return this.recall(() => this.post(this.finalizeChunkLink, { recordingId }));
|
|
83
|
+
}
|
|
84
|
+
transcription(recordingId) {
|
|
85
|
+
return this.recall(() => this.post(this.transcriptionLink, { recordingId }));
|
|
86
|
+
}
|
|
87
|
+
translate(recordingId, language) {
|
|
88
|
+
return this.recall(() => this.post(this.translateLink(recordingId), { targetLanguage: language }));
|
|
89
|
+
}
|
|
90
|
+
recording(recordingId) {
|
|
91
|
+
return this.recall(() => this.get(this.recordingLink(recordingId)));
|
|
92
|
+
}
|
|
93
|
+
recordings(query) {
|
|
94
|
+
const params = new URLSearchParams({
|
|
95
|
+
page: String(query?.page ?? 1),
|
|
96
|
+
limit: String(query?.limit ?? 5)
|
|
97
|
+
});
|
|
98
|
+
return this.recall(() => this.get(`${this.recordingsLink}?${params}`));
|
|
99
|
+
}
|
|
100
|
+
summary() {
|
|
101
|
+
return this.recall(() => this.get(this.summaryLink));
|
|
102
|
+
}
|
|
103
|
+
async uploadFromStream(stream, recordingId) {
|
|
104
|
+
const reader = stream.getReader();
|
|
105
|
+
let buffer = new Uint8Array(0);
|
|
106
|
+
let chunkIndex = 0;
|
|
107
|
+
try {
|
|
108
|
+
while (true) {
|
|
109
|
+
const { done, value } = await reader.read();
|
|
110
|
+
if (done) {
|
|
111
|
+
if (buffer.length > 0) {
|
|
112
|
+
await this.uploadChunk(buffer, recordingId, chunkIndex);
|
|
113
|
+
}
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
const merged = new Uint8Array(buffer.length + value.length);
|
|
117
|
+
merged.set(buffer);
|
|
118
|
+
merged.set(value, buffer.length);
|
|
119
|
+
buffer = merged;
|
|
120
|
+
while (buffer.length >= this.clientOptions.chunkSize) {
|
|
121
|
+
await this.uploadChunk(buffer.subarray(0, this.clientOptions.chunkSize), recordingId, chunkIndex);
|
|
122
|
+
buffer = buffer.slice(this.clientOptions.chunkSize);
|
|
123
|
+
chunkIndex++;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
} finally {
|
|
127
|
+
reader.releaseLock();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
uploadChunkSlice(fileBuffer, recordingId, chunkIndex) {
|
|
131
|
+
const start = chunkIndex * this.clientOptions.chunkSize;
|
|
132
|
+
const end = Math.min(start + this.clientOptions.chunkSize, fileBuffer.length);
|
|
133
|
+
return this.uploadChunk(fileBuffer.subarray(start, end), recordingId, chunkIndex);
|
|
134
|
+
}
|
|
135
|
+
initChunkRequest(totalSize, totalChunks, meta) {
|
|
136
|
+
return this.post(this.initChunkLink, {
|
|
67
137
|
filename: meta.filename,
|
|
68
138
|
durationSeconds: meta.durationSeconds,
|
|
69
|
-
totalSize
|
|
139
|
+
totalSize,
|
|
70
140
|
mimeType: meta.mimeType ?? "",
|
|
71
141
|
title: meta.title ?? meta.filename,
|
|
72
142
|
enableSpeakerDetection: meta.enableSpeakerDetection ?? false,
|
|
73
143
|
speakerCount: meta.speakerCount ?? "auto",
|
|
74
144
|
language: meta.language ?? "",
|
|
75
|
-
totalChunks
|
|
76
|
-
}
|
|
145
|
+
totalChunks
|
|
146
|
+
});
|
|
77
147
|
}
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
const chunk = fileBuffer.slice(start, end);
|
|
82
|
-
const blob = new Blob([chunk], { type: "application/octet-stream" });
|
|
148
|
+
uploadChunkRequest(chunk, recordingId, chunkIndex) {
|
|
149
|
+
const buffer = new ArrayBuffer(chunk.byteLength);
|
|
150
|
+
new Uint8Array(buffer).set(chunk);
|
|
83
151
|
const formData = new FormData;
|
|
84
|
-
formData.append("chunk",
|
|
152
|
+
formData.append("chunk", new Blob([buffer], { type: "application/octet-stream" }), `chunk_${chunkIndex}`);
|
|
85
153
|
formData.append("recordingId", recordingId.toString());
|
|
86
154
|
formData.append("chunkIndex", chunkIndex.toString());
|
|
87
|
-
return this.
|
|
88
|
-
}
|
|
89
|
-
finalizeChunk(recordingId) {
|
|
90
|
-
return this.recall(() => axios.post(this.finalizeChunkLink, { recordingId }, this.requestOptions));
|
|
155
|
+
return this.postForm(this.uploadChunkLink, formData);
|
|
91
156
|
}
|
|
92
|
-
|
|
93
|
-
|
|
157
|
+
async recall(call) {
|
|
158
|
+
try {
|
|
159
|
+
if (!this.cookies)
|
|
160
|
+
await this.login();
|
|
161
|
+
return await call();
|
|
162
|
+
} catch (error) {
|
|
163
|
+
if (!(error instanceof WhisperAuthError))
|
|
164
|
+
throw error;
|
|
165
|
+
await this.login();
|
|
166
|
+
return call();
|
|
167
|
+
}
|
|
94
168
|
}
|
|
95
|
-
|
|
96
|
-
return this.
|
|
169
|
+
get(url) {
|
|
170
|
+
return this.request(url, { method: "GET" });
|
|
97
171
|
}
|
|
98
|
-
|
|
99
|
-
return this.
|
|
172
|
+
post(url, body) {
|
|
173
|
+
return this.request(url, {
|
|
174
|
+
method: "POST",
|
|
175
|
+
body: JSON.stringify(body),
|
|
176
|
+
extraHeaders: { "Content-Type": "application/json" }
|
|
177
|
+
});
|
|
100
178
|
}
|
|
101
|
-
|
|
102
|
-
return this.
|
|
103
|
-
...this.requestOptions,
|
|
104
|
-
params: { page: 1, limit: 5, ...query ?? {} }
|
|
105
|
-
}));
|
|
179
|
+
postForm(url, body) {
|
|
180
|
+
return this.request(url, { method: "POST", body });
|
|
106
181
|
}
|
|
107
|
-
|
|
108
|
-
|
|
182
|
+
async request(url, init) {
|
|
183
|
+
const { extraHeaders, ...fetchInit } = init;
|
|
184
|
+
const cookieHeader = this.cookies?.join("; ");
|
|
185
|
+
let response;
|
|
186
|
+
try {
|
|
187
|
+
response = await fetch(url, {
|
|
188
|
+
...fetchInit,
|
|
189
|
+
headers: {
|
|
190
|
+
"cache-control": "no-cache",
|
|
191
|
+
...cookieHeader ? { Cookie: cookieHeader } : {},
|
|
192
|
+
...extraHeaders
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
} catch (cause) {
|
|
196
|
+
throw new WhisperNetworkError(cause);
|
|
197
|
+
}
|
|
198
|
+
const setCookies = response.headers.getSetCookie?.() ?? [];
|
|
199
|
+
if (setCookies.length > 0)
|
|
200
|
+
this.mergeCookies(setCookies);
|
|
201
|
+
if (!response.ok) {
|
|
202
|
+
if (response.status === 401 || response.status === 403)
|
|
203
|
+
throw new WhisperAuthError;
|
|
204
|
+
const data = await response.json().catch(() => null);
|
|
205
|
+
throw new WhisperApiError(response.status, data);
|
|
206
|
+
}
|
|
207
|
+
return response.json();
|
|
109
208
|
}
|
|
110
209
|
get loginLink() {
|
|
111
210
|
return `${this.clientOptions.whisperUrl}/api/login`;
|
|
@@ -134,57 +233,29 @@ class WhisperClient {
|
|
|
134
233
|
get summaryLink() {
|
|
135
234
|
return `${this.clientOptions.whisperUrl}/api/analytics/summary`;
|
|
136
235
|
}
|
|
236
|
+
get recordingsLink() {
|
|
237
|
+
return `${this.clientOptions.whisperUrl}/api/recordings-paginated`;
|
|
238
|
+
}
|
|
137
239
|
translateLink(recordingId) {
|
|
138
240
|
return `${this.clientOptions.whisperUrl}/api/v2/translation/${recordingId}/translate`;
|
|
139
241
|
}
|
|
140
242
|
recordingLink(recordingId) {
|
|
141
243
|
return `${this.clientOptions.whisperUrl}/api/recordings/${recordingId}`;
|
|
142
244
|
}
|
|
143
|
-
|
|
144
|
-
|
|
245
|
+
mergeCookies(incoming) {
|
|
246
|
+
const map = new Map;
|
|
247
|
+
for (const cookie of [...this.cookies ?? [], ...incoming]) {
|
|
248
|
+
const pair = cookie.split(";")[0]?.trim();
|
|
249
|
+
if (!pair)
|
|
250
|
+
continue;
|
|
251
|
+
const name = pair.split("=")[0].trim();
|
|
252
|
+
map.set(name, pair);
|
|
253
|
+
}
|
|
254
|
+
this.cookies = [...map.values()];
|
|
145
255
|
}
|
|
146
256
|
getCountChunks(size) {
|
|
147
257
|
return Math.ceil(size / this.clientOptions.chunkSize);
|
|
148
258
|
}
|
|
149
|
-
get requestOptions() {
|
|
150
|
-
return {
|
|
151
|
-
headers: {
|
|
152
|
-
Cookie: this.cookies
|
|
153
|
-
}
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
async recall(call) {
|
|
157
|
-
try {
|
|
158
|
-
if (typeof this.cookies !== "string")
|
|
159
|
-
await this.login();
|
|
160
|
-
return await this.handleRequest(call());
|
|
161
|
-
} catch (error) {
|
|
162
|
-
if (error.code !== "AUTH_ERROR") {
|
|
163
|
-
throw error;
|
|
164
|
-
}
|
|
165
|
-
await this.login();
|
|
166
|
-
return this.handleRequest(call());
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
async handleRequest(request) {
|
|
170
|
-
try {
|
|
171
|
-
const response = await request;
|
|
172
|
-
this.cookies = (response.headers["set-cookie"] ?? []).join("; ");
|
|
173
|
-
return response.data;
|
|
174
|
-
} catch (err) {
|
|
175
|
-
console.log("err", err);
|
|
176
|
-
if (axios.isAxiosError(err)) {
|
|
177
|
-
if (!err.response) {
|
|
178
|
-
throw new WhisperNetworkError(err);
|
|
179
|
-
}
|
|
180
|
-
if (err.response.status === 401) {
|
|
181
|
-
throw new WhisperAuthError;
|
|
182
|
-
}
|
|
183
|
-
throw new WhisperApiError(err.response.status, err.response.data);
|
|
184
|
-
}
|
|
185
|
-
throw err;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
259
|
}
|
|
189
260
|
// src/constant.ts
|
|
190
261
|
var WhisperStatus;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "whisperai-sdk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "TypeScript SDK for WhisperAI: methods and interfaces for interacting with the service without external runtime dependencies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"whisper",
|
|
@@ -32,37 +32,33 @@
|
|
|
32
32
|
"dist"
|
|
33
33
|
],
|
|
34
34
|
"scripts": {
|
|
35
|
-
"build": "npm run clean && bun build ./src/index.ts --outdir ./dist --target=node --format=esm
|
|
35
|
+
"build": "npm run clean && bun build ./src/index.ts --outdir ./dist --target=node --format=esm && tsc --emitDeclarationOnly",
|
|
36
36
|
"format": "prettier --write \"src/**/*.ts\"",
|
|
37
37
|
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
|
38
38
|
"clean": "rm -rf dist",
|
|
39
39
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
40
40
|
"release": "semantic-release"
|
|
41
41
|
},
|
|
42
|
-
"dependencies": {
|
|
43
|
-
"axios": "^1.13.2"
|
|
44
|
-
},
|
|
42
|
+
"dependencies": {},
|
|
45
43
|
"devDependencies": {
|
|
46
|
-
"@commitlint/cli": "20.
|
|
47
|
-
"@commitlint/config-conventional": "20.
|
|
44
|
+
"@commitlint/cli": "20.5.0",
|
|
45
|
+
"@commitlint/config-conventional": "20.5.0",
|
|
48
46
|
"@eslint/js": "9.39.2",
|
|
49
47
|
"@semantic-release/changelog": "6.0.3",
|
|
50
48
|
"@semantic-release/exec": "7.1.0",
|
|
51
49
|
"@semantic-release/git": "10.0.1",
|
|
52
|
-
"@semantic-release/github": "12.0.
|
|
53
|
-
"@semantic-release/npm": "13.1.
|
|
54
|
-
"@types/bun": "^1.3.
|
|
55
|
-
"@types/node": "25.
|
|
56
|
-
"
|
|
57
|
-
"@typescript-eslint/parser": "8.50.1",
|
|
58
|
-
"conventional-changelog-conventionalcommits": "9.1.0",
|
|
50
|
+
"@semantic-release/github": "12.0.6",
|
|
51
|
+
"@semantic-release/npm": "13.1.5",
|
|
52
|
+
"@types/bun": "^1.3.11",
|
|
53
|
+
"@types/node": "25.5.2",
|
|
54
|
+
"conventional-changelog-conventionalcommits": "9.3.1",
|
|
59
55
|
"eslint": "9.39.2",
|
|
60
56
|
"eslint-config-prettier": "10.1.8",
|
|
61
|
-
"eslint-plugin-prettier": "5.5.
|
|
62
|
-
"prettier": "3.
|
|
63
|
-
"semantic-release": "25.0.
|
|
57
|
+
"eslint-plugin-prettier": "5.5.5",
|
|
58
|
+
"prettier": "3.8.1",
|
|
59
|
+
"semantic-release": "25.0.3",
|
|
64
60
|
"typescript": "5.9.3",
|
|
65
|
-
"typescript-eslint": "8.
|
|
61
|
+
"typescript-eslint": "8.58.0"
|
|
66
62
|
},
|
|
67
63
|
"engines": {
|
|
68
64
|
"node": ">=22.0.0"
|