@amaster.ai/asr-client 1.1.0-beta.7 → 1.1.0-beta.70
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 +333 -58
- package/dist/index.cjs +465 -50
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +284 -20
- package/dist/index.d.ts +284 -20
- package/dist/index.js +464 -49
- package/dist/index.js.map +1 -1
- package/package.json +11 -3
package/dist/index.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
// src/asr-client.ts
|
|
2
2
|
var ASR_PATH = "/api/proxy/builtin/platform/qwen-asr-realtime/api-ws/v1/realtime";
|
|
3
3
|
async function createRealtimeRecorder() {
|
|
4
|
-
let stream;
|
|
5
|
-
let ctx;
|
|
6
|
-
let source;
|
|
7
|
-
let processor;
|
|
4
|
+
let stream = null;
|
|
5
|
+
let ctx = null;
|
|
6
|
+
let source = null;
|
|
7
|
+
let processor = null;
|
|
8
|
+
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
|
9
|
+
throw new Error("\u672A\u627E\u5230\u9EA6\u514B\u98CE\u6216\u65E0\u6743\u9650");
|
|
10
|
+
}
|
|
8
11
|
return {
|
|
9
12
|
async start(onAudio) {
|
|
10
13
|
stream = await navigator.mediaDevices.getUserMedia({
|
|
@@ -36,60 +39,191 @@ async function createRealtimeRecorder() {
|
|
|
36
39
|
stream?.getTracks().forEach((t) => t.stop());
|
|
37
40
|
source?.disconnect();
|
|
38
41
|
processor?.disconnect();
|
|
39
|
-
|
|
42
|
+
if (ctx) {
|
|
43
|
+
await ctx.close();
|
|
44
|
+
}
|
|
45
|
+
stream = null;
|
|
46
|
+
ctx = null;
|
|
47
|
+
source = null;
|
|
48
|
+
processor = null;
|
|
40
49
|
}
|
|
41
50
|
};
|
|
42
51
|
}
|
|
43
52
|
var log = (message, type = "") => {
|
|
44
53
|
console.log(`[${type}]`, message);
|
|
45
54
|
};
|
|
55
|
+
var eventIdCounter = 0;
|
|
56
|
+
function generateEventId() {
|
|
57
|
+
return `event_${Date.now()}_${++eventIdCounter}`;
|
|
58
|
+
}
|
|
46
59
|
function createASRClient(config) {
|
|
47
60
|
const {
|
|
48
61
|
onReady,
|
|
49
62
|
onSpeechStart,
|
|
50
63
|
onSpeechEnd,
|
|
51
64
|
onTranscript,
|
|
65
|
+
onAudioBufferCommitted,
|
|
66
|
+
onSessionFinished,
|
|
52
67
|
onError,
|
|
53
68
|
onClose,
|
|
54
|
-
|
|
69
|
+
getAccessToken,
|
|
70
|
+
audioFormat = "pcm",
|
|
71
|
+
sampleRate = 16e3,
|
|
72
|
+
language = "zh",
|
|
73
|
+
enableVAD = true,
|
|
74
|
+
vadThreshold = 0.2,
|
|
75
|
+
vadSilenceDurationMs = 400
|
|
55
76
|
} = config;
|
|
56
77
|
let ws = null;
|
|
57
78
|
let recorder = null;
|
|
79
|
+
let isRecordingFlag = false;
|
|
80
|
+
let isClosing = false;
|
|
81
|
+
const path = ASR_PATH;
|
|
82
|
+
function sendEvent(event) {
|
|
83
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
84
|
+
throw new Error("WebSocket not connected");
|
|
85
|
+
}
|
|
86
|
+
ws.send(JSON.stringify(event));
|
|
87
|
+
}
|
|
88
|
+
function buildSessionConfig() {
|
|
89
|
+
const sessionConfig = {
|
|
90
|
+
input_audio_format: audioFormat,
|
|
91
|
+
sample_rate: sampleRate,
|
|
92
|
+
input_audio_transcription: {
|
|
93
|
+
language
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
if (enableVAD) {
|
|
97
|
+
sessionConfig.turn_detection = {
|
|
98
|
+
type: "server_vad",
|
|
99
|
+
threshold: vadThreshold,
|
|
100
|
+
silence_duration_ms: vadSilenceDurationMs
|
|
101
|
+
};
|
|
102
|
+
} else {
|
|
103
|
+
sessionConfig.turn_detection = null;
|
|
104
|
+
}
|
|
105
|
+
return sessionConfig;
|
|
106
|
+
}
|
|
107
|
+
function sendSessionUpdate() {
|
|
108
|
+
const event = {
|
|
109
|
+
event_id: generateEventId(),
|
|
110
|
+
type: "session.update",
|
|
111
|
+
session: buildSessionConfig()
|
|
112
|
+
};
|
|
113
|
+
sendEvent(event);
|
|
114
|
+
}
|
|
115
|
+
function sendAudioBufferAppend(audio) {
|
|
116
|
+
const event = {
|
|
117
|
+
event_id: generateEventId(),
|
|
118
|
+
type: "input_audio_buffer.append",
|
|
119
|
+
audio
|
|
120
|
+
};
|
|
121
|
+
sendEvent(event);
|
|
122
|
+
}
|
|
123
|
+
function sendAudioBufferCommit() {
|
|
124
|
+
const event = {
|
|
125
|
+
event_id: generateEventId(),
|
|
126
|
+
type: "input_audio_buffer.commit"
|
|
127
|
+
};
|
|
128
|
+
sendEvent(event);
|
|
129
|
+
}
|
|
130
|
+
function sendSessionFinish() {
|
|
131
|
+
const event = {
|
|
132
|
+
event_id: generateEventId(),
|
|
133
|
+
type: "session.finish"
|
|
134
|
+
};
|
|
135
|
+
sendEvent(event);
|
|
136
|
+
}
|
|
137
|
+
function handleServerEvent(data) {
|
|
138
|
+
switch (data.type) {
|
|
139
|
+
case "session.created":
|
|
140
|
+
try {
|
|
141
|
+
sendSessionUpdate();
|
|
142
|
+
} catch (err2) {
|
|
143
|
+
onError?.(
|
|
144
|
+
new Error(
|
|
145
|
+
"Failed to send session.update: " + (err2 instanceof Error ? err2.message : String(err2))
|
|
146
|
+
)
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
break;
|
|
150
|
+
case "session.updated":
|
|
151
|
+
onReady?.();
|
|
152
|
+
break;
|
|
153
|
+
case "input_audio_buffer.speech_started":
|
|
154
|
+
onSpeechStart?.();
|
|
155
|
+
break;
|
|
156
|
+
case "input_audio_buffer.speech_stopped":
|
|
157
|
+
onSpeechEnd?.();
|
|
158
|
+
break;
|
|
159
|
+
case "input_audio_buffer.committed":
|
|
160
|
+
onAudioBufferCommitted?.();
|
|
161
|
+
break;
|
|
162
|
+
case "conversation.item.input_audio_transcription.text":
|
|
163
|
+
onTranscript?.(data.text || data.stash || data.transcript || "", false);
|
|
164
|
+
break;
|
|
165
|
+
case "conversation.item.input_audio_transcription.completed":
|
|
166
|
+
onTranscript?.(data.text || data.transcript || "", true);
|
|
167
|
+
break;
|
|
168
|
+
case "session.finished":
|
|
169
|
+
onSessionFinished?.();
|
|
170
|
+
close();
|
|
171
|
+
break;
|
|
172
|
+
case "error":
|
|
173
|
+
const err = new Error(data.error?.message || "ASR error");
|
|
174
|
+
onError?.(err);
|
|
175
|
+
break;
|
|
176
|
+
default:
|
|
177
|
+
console.warn("[ASR] Unknown server event:", data.type);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
58
180
|
async function connect() {
|
|
59
|
-
|
|
181
|
+
let wsUrl = path;
|
|
182
|
+
if (getAccessToken) {
|
|
183
|
+
const token = getAccessToken();
|
|
184
|
+
if (token) {
|
|
185
|
+
const separator = path.includes("?") ? "&" : "?";
|
|
186
|
+
wsUrl = `${path}${separator}token=${encodeURIComponent(token)}`;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (typeof window !== "undefined" && window.location) {
|
|
190
|
+
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
|
191
|
+
if (!wsUrl.startsWith("ws://") && !wsUrl.startsWith("wss://")) {
|
|
192
|
+
wsUrl = `${protocol}//${window.location.host}${wsUrl}`;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
ws = new WebSocket(wsUrl);
|
|
60
196
|
return new Promise((resolve, reject) => {
|
|
197
|
+
if (!ws) {
|
|
198
|
+
reject(new Error("Failed to create WebSocket"));
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
ws.onopen = () => {
|
|
202
|
+
log("WebSocket connected", "success");
|
|
203
|
+
};
|
|
61
204
|
ws.onmessage = (event) => {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
onTranscript?.(data.text || data.stash || data.transcript || "", false);
|
|
75
|
-
}
|
|
76
|
-
if (data.type === "conversation.item.input_audio_transcription.completed") {
|
|
77
|
-
onTranscript?.(data.text || data.transcript || "", true);
|
|
78
|
-
}
|
|
79
|
-
if (data.type === "error") {
|
|
80
|
-
const err = new Error(data.error?.message || "ASR error");
|
|
81
|
-
onError?.(err);
|
|
82
|
-
reject(err);
|
|
205
|
+
try {
|
|
206
|
+
const data = JSON.parse(event.data);
|
|
207
|
+
handleServerEvent(data);
|
|
208
|
+
if (data.type === "session.updated") {
|
|
209
|
+
resolve();
|
|
210
|
+
}
|
|
211
|
+
} catch (err) {
|
|
212
|
+
const error = new Error(
|
|
213
|
+
"Failed to parse server message: " + (err instanceof Error ? err.message : String(err))
|
|
214
|
+
);
|
|
215
|
+
onError?.(error);
|
|
216
|
+
reject(error);
|
|
83
217
|
}
|
|
84
218
|
};
|
|
85
|
-
ws.onerror = () => {
|
|
219
|
+
ws.onerror = (error) => {
|
|
220
|
+
console.error("WebSocket error:", error);
|
|
86
221
|
const err = new Error("WebSocket error");
|
|
87
222
|
onError?.(err);
|
|
88
223
|
reject(err);
|
|
89
224
|
};
|
|
90
225
|
ws.onclose = () => {
|
|
91
|
-
|
|
92
|
-
recorder = null;
|
|
226
|
+
isRecordingFlag = false;
|
|
93
227
|
ws = null;
|
|
94
228
|
onClose?.();
|
|
95
229
|
};
|
|
@@ -99,41 +233,322 @@ function createASRClient(config) {
|
|
|
99
233
|
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
100
234
|
throw new Error("WebSocket not connected");
|
|
101
235
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
236
|
+
if (isRecordingFlag) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
try {
|
|
240
|
+
recorder = await createRealtimeRecorder();
|
|
241
|
+
isRecordingFlag = true;
|
|
242
|
+
await recorder.start((audio) => {
|
|
243
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) return;
|
|
244
|
+
try {
|
|
245
|
+
sendAudioBufferAppend(audio);
|
|
246
|
+
} catch (err) {
|
|
247
|
+
console.error("[ASR] Failed to send audio:", err);
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
} catch (err) {
|
|
251
|
+
console.error("[ASR] Failed to start recorder:", err);
|
|
252
|
+
onError?.(err instanceof Error ? err : new Error(String(err)));
|
|
253
|
+
throw err;
|
|
254
|
+
}
|
|
112
255
|
}
|
|
113
256
|
async function stopRecording() {
|
|
257
|
+
if (!isRecordingFlag) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
114
260
|
try {
|
|
115
261
|
await recorder?.stop();
|
|
116
262
|
} catch (err) {
|
|
263
|
+
console.error("[ASR] Error stopping recorder:", err);
|
|
117
264
|
}
|
|
118
265
|
recorder = null;
|
|
119
|
-
|
|
120
|
-
|
|
266
|
+
isRecordingFlag = false;
|
|
267
|
+
if (!enableVAD && ws?.readyState === WebSocket.OPEN) {
|
|
268
|
+
try {
|
|
269
|
+
sendAudioBufferCommit();
|
|
270
|
+
} catch (err) {
|
|
271
|
+
console.error("[ASR] Failed to send commit:", err);
|
|
272
|
+
}
|
|
121
273
|
}
|
|
122
274
|
}
|
|
123
|
-
function close() {
|
|
124
|
-
|
|
125
|
-
|
|
275
|
+
async function close() {
|
|
276
|
+
if (isClosing) {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
isClosing = true;
|
|
280
|
+
await stopRecording();
|
|
281
|
+
if (ws?.readyState === WebSocket.OPEN) {
|
|
282
|
+
try {
|
|
283
|
+
sendSessionFinish();
|
|
284
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
285
|
+
} catch (err) {
|
|
286
|
+
console.error("[ASR] Failed to send session.finish:", err);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (ws && ws?.readyState !== WebSocket.CLOSING && ws?.readyState !== WebSocket.CLOSED) {
|
|
290
|
+
ws?.close();
|
|
291
|
+
}
|
|
126
292
|
ws = null;
|
|
293
|
+
isClosing = false;
|
|
294
|
+
}
|
|
295
|
+
function isRecording() {
|
|
296
|
+
return isRecordingFlag;
|
|
297
|
+
}
|
|
298
|
+
function isConnected() {
|
|
299
|
+
return ws !== null && ws.readyState === WebSocket.OPEN;
|
|
127
300
|
}
|
|
128
301
|
return {
|
|
129
302
|
connect,
|
|
130
303
|
startRecording,
|
|
131
304
|
stopRecording,
|
|
132
|
-
close
|
|
305
|
+
close,
|
|
306
|
+
isRecording,
|
|
307
|
+
isConnected
|
|
133
308
|
};
|
|
134
309
|
}
|
|
310
|
+
var asr_client_default = (authConfig) => (config) => createASRClient({ ...authConfig, ...config });
|
|
311
|
+
|
|
312
|
+
// src/http-asr-client.ts
|
|
313
|
+
import { createHttpClient } from "@amaster.ai/http-client";
|
|
314
|
+
var ASR_HTTP_PATH = "/api/proxy/builtin/platform/qwen-asr/compatible-mode/v1/chat/completions";
|
|
315
|
+
var RECORDER_WORKLET = `
|
|
316
|
+
class RecorderProcessor extends AudioWorkletProcessor {
|
|
317
|
+
process(inputs) {
|
|
318
|
+
const input = inputs[0];
|
|
319
|
+
if (input && input[0]) {
|
|
320
|
+
this.port.postMessage(input[0].slice(0));
|
|
321
|
+
}
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
registerProcessor('recorder-processor', RecorderProcessor);
|
|
326
|
+
`;
|
|
327
|
+
async function createWebRecorder(props) {
|
|
328
|
+
let stream;
|
|
329
|
+
let ctx;
|
|
330
|
+
let node;
|
|
331
|
+
let source;
|
|
332
|
+
const chunks = [];
|
|
333
|
+
const cleanup = () => {
|
|
334
|
+
try {
|
|
335
|
+
source?.disconnect();
|
|
336
|
+
node?.disconnect();
|
|
337
|
+
stream?.getTracks().forEach((t) => t.stop());
|
|
338
|
+
ctx?.close();
|
|
339
|
+
} catch (e) {
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
return {
|
|
343
|
+
async start() {
|
|
344
|
+
try {
|
|
345
|
+
stream = await navigator.mediaDevices.getUserMedia({
|
|
346
|
+
audio: {
|
|
347
|
+
channelCount: 1,
|
|
348
|
+
echoCancellation: true,
|
|
349
|
+
noiseSuppression: true,
|
|
350
|
+
autoGainControl: true
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
ctx = new AudioContext();
|
|
354
|
+
const blob = new Blob([RECORDER_WORKLET], {
|
|
355
|
+
type: "application/javascript"
|
|
356
|
+
});
|
|
357
|
+
const url = URL.createObjectURL(blob);
|
|
358
|
+
await ctx.audioWorklet.addModule(url);
|
|
359
|
+
URL.revokeObjectURL(url);
|
|
360
|
+
source = ctx.createMediaStreamSource(stream);
|
|
361
|
+
node = new AudioWorkletNode(ctx, "recorder-processor");
|
|
362
|
+
node.port.onmessage = (e) => {
|
|
363
|
+
const input = e.data;
|
|
364
|
+
const pcm = new Int16Array(input.length);
|
|
365
|
+
for (let i = 0; i < input.length; i++) {
|
|
366
|
+
const s = Math.max(-1, Math.min(1, input[i] || 0));
|
|
367
|
+
pcm[i] = s < 0 ? s * 32768 : s * 32767;
|
|
368
|
+
}
|
|
369
|
+
chunks.push(pcm);
|
|
370
|
+
};
|
|
371
|
+
source.connect(node);
|
|
372
|
+
props?.onStart?.();
|
|
373
|
+
} catch (error) {
|
|
374
|
+
props?.onError?.(
|
|
375
|
+
error instanceof Error ? error : new Error(String(error))
|
|
376
|
+
);
|
|
377
|
+
cleanup();
|
|
378
|
+
}
|
|
379
|
+
},
|
|
380
|
+
async stop() {
|
|
381
|
+
cleanup();
|
|
382
|
+
const total = chunks.reduce((s, c) => s + c.length, 0);
|
|
383
|
+
const pcm = new Int16Array(total);
|
|
384
|
+
let offset = 0;
|
|
385
|
+
for (const c of chunks) {
|
|
386
|
+
pcm.set(c, offset);
|
|
387
|
+
offset += c.length;
|
|
388
|
+
}
|
|
389
|
+
const result = { pcm, sampleRate: ctx?.sampleRate ?? 16e3 };
|
|
390
|
+
const base64 = await blobToBase64(
|
|
391
|
+
pcmToWav(result.pcm, result.sampleRate)
|
|
392
|
+
);
|
|
393
|
+
props?.onStop?.(base64);
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
function pcmToWav(pcm, sampleRate) {
|
|
398
|
+
const buffer = new ArrayBuffer(44 + pcm.length * 2);
|
|
399
|
+
const view = new DataView(buffer);
|
|
400
|
+
const write = (o, s) => {
|
|
401
|
+
for (let i = 0; i < s.length; i++) view.setUint8(o + i, s.charCodeAt(i));
|
|
402
|
+
};
|
|
403
|
+
write(0, "RIFF");
|
|
404
|
+
view.setUint32(4, 36 + pcm.length * 2, true);
|
|
405
|
+
write(8, "WAVE");
|
|
406
|
+
write(12, "fmt ");
|
|
407
|
+
view.setUint32(16, 16, true);
|
|
408
|
+
view.setUint16(20, 1, true);
|
|
409
|
+
view.setUint16(22, 1, true);
|
|
410
|
+
view.setUint32(24, sampleRate, true);
|
|
411
|
+
view.setUint32(28, sampleRate * 2, true);
|
|
412
|
+
view.setUint16(32, 2, true);
|
|
413
|
+
view.setUint16(34, 16, true);
|
|
414
|
+
write(36, "data");
|
|
415
|
+
view.setUint32(40, pcm.length * 2, true);
|
|
416
|
+
for (let i = 0; i < pcm.length; i++) {
|
|
417
|
+
view.setInt16(44 + i * 2, pcm[i] || 0, true);
|
|
418
|
+
}
|
|
419
|
+
return new Blob([buffer], { type: "audio/wav" });
|
|
420
|
+
}
|
|
421
|
+
function blobToBase64(blob) {
|
|
422
|
+
return new Promise((resolve, reject) => {
|
|
423
|
+
const reader = new FileReader();
|
|
424
|
+
reader.onloadend = () => {
|
|
425
|
+
const result = reader.result;
|
|
426
|
+
resolve(result.split(",")[1] || "");
|
|
427
|
+
};
|
|
428
|
+
reader.onerror = reject;
|
|
429
|
+
reader.readAsDataURL(blob);
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
var AsrHttpClient = class {
|
|
433
|
+
constructor(config, path) {
|
|
434
|
+
this.recorder = null;
|
|
435
|
+
this.path = "";
|
|
436
|
+
this.recognizing = false;
|
|
437
|
+
this.http = config.http ?? createHttpClient();
|
|
438
|
+
this.config = config;
|
|
439
|
+
this.path = path;
|
|
440
|
+
}
|
|
441
|
+
async startRecording() {
|
|
442
|
+
if (this.recorder) {
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
const options = {
|
|
446
|
+
onStart: () => {
|
|
447
|
+
this.config.onRecordingStart?.();
|
|
448
|
+
this.config.onStatusChange?.("recording");
|
|
449
|
+
},
|
|
450
|
+
onStop: async (base64) => {
|
|
451
|
+
this.config.onStatusChange?.("recognizing");
|
|
452
|
+
const text = await this.recognizeFile(base64);
|
|
453
|
+
this.config.onResult?.(text);
|
|
454
|
+
this.config.onRecordingStop?.();
|
|
455
|
+
this.config.onStatusChange?.("idle");
|
|
456
|
+
this.recorder = null;
|
|
457
|
+
},
|
|
458
|
+
onError: (err) => {
|
|
459
|
+
this.config.onError?.(err);
|
|
460
|
+
this.config.onStatusChange?.("idle");
|
|
461
|
+
this.recorder = null;
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
this.recorder = await (this.config.createRecorder?.(options) ?? createWebRecorder(options));
|
|
465
|
+
await this.recorder.start();
|
|
466
|
+
}
|
|
467
|
+
async stopRecording() {
|
|
468
|
+
if (this.recorder) {
|
|
469
|
+
await this.recorder.stop();
|
|
470
|
+
this.recorder = null;
|
|
471
|
+
} else {
|
|
472
|
+
this.config.onResult?.("");
|
|
473
|
+
this.config.onRecordingStop?.();
|
|
474
|
+
this.config.onStatusChange?.("idle");
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
async recognizeFile(base64) {
|
|
478
|
+
if (this.recognizing) {
|
|
479
|
+
return "";
|
|
480
|
+
}
|
|
481
|
+
this.recognizing = true;
|
|
482
|
+
try {
|
|
483
|
+
const response = await this.http.request({
|
|
484
|
+
url: this.path,
|
|
485
|
+
method: "POST",
|
|
486
|
+
headers: { "Content-Type": "application/json" },
|
|
487
|
+
data: JSON.stringify({
|
|
488
|
+
model: "qwen3-asr-flash",
|
|
489
|
+
messages: [
|
|
490
|
+
{
|
|
491
|
+
role: "user",
|
|
492
|
+
content: [
|
|
493
|
+
{
|
|
494
|
+
type: "input_audio",
|
|
495
|
+
input_audio: { data: `data:audio/wav;base64,${base64}` }
|
|
496
|
+
}
|
|
497
|
+
]
|
|
498
|
+
}
|
|
499
|
+
]
|
|
500
|
+
})
|
|
501
|
+
});
|
|
502
|
+
return response?.data?.choices?.[0]?.message?.content || "";
|
|
503
|
+
} catch (e) {
|
|
504
|
+
console.error("ASR recognition error:", e);
|
|
505
|
+
return "";
|
|
506
|
+
} finally {
|
|
507
|
+
this.recognizing = false;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
async recordAndRecognize(ms) {
|
|
511
|
+
await this.startRecording();
|
|
512
|
+
await new Promise((r) => setTimeout(r, ms));
|
|
513
|
+
await this.stopRecording();
|
|
514
|
+
}
|
|
515
|
+
async recognizeUrl(url) {
|
|
516
|
+
try {
|
|
517
|
+
const res = await this.http.request({
|
|
518
|
+
url: this.path,
|
|
519
|
+
method: "POST",
|
|
520
|
+
headers: { "Content-Type": "application/json" },
|
|
521
|
+
data: JSON.stringify({
|
|
522
|
+
model: "qwen3-asr-flash",
|
|
523
|
+
messages: [
|
|
524
|
+
{
|
|
525
|
+
role: "user",
|
|
526
|
+
content: [{ type: "input_audio", input_audio: { url } }]
|
|
527
|
+
}
|
|
528
|
+
]
|
|
529
|
+
})
|
|
530
|
+
});
|
|
531
|
+
return res?.data?.choices?.[0]?.message?.content || "";
|
|
532
|
+
} catch (e) {
|
|
533
|
+
console.error("ASR recognition error:", e);
|
|
534
|
+
return "";
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
};
|
|
538
|
+
function createASRHttpClient(config) {
|
|
539
|
+
let path = ASR_HTTP_PATH;
|
|
540
|
+
if (config.getAccessToken) {
|
|
541
|
+
const token = config.getAccessToken();
|
|
542
|
+
if (token) {
|
|
543
|
+
const separator = path.includes("?") ? "&" : "?";
|
|
544
|
+
path = `${path}${separator}token=${encodeURIComponent(token)}`;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
return new AsrHttpClient(config, path);
|
|
548
|
+
}
|
|
549
|
+
var http_asr_client_default = (authConfig) => (config) => createASRHttpClient({ ...authConfig, ...config });
|
|
135
550
|
export {
|
|
136
|
-
createASRClient,
|
|
137
|
-
|
|
551
|
+
asr_client_default as createASRClient,
|
|
552
|
+
http_asr_client_default as createASRHttpClient
|
|
138
553
|
};
|
|
139
554
|
//# sourceMappingURL=index.js.map
|