@amaster.ai/asr-http-client 1.0.0-beta.2 → 1.0.0-beta.22
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/index.cjs +110 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -3
- package/dist/index.d.ts +10 -3
- package/dist/index.js +106 -11
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -20,14 +20,109 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
-
createASRHttpClient: () => createASRHttpClient
|
|
23
|
+
createASRHttpClient: () => createASRHttpClient,
|
|
24
|
+
createAsrHttpClient: () => createASRHttpClient,
|
|
25
|
+
recognizeFile: () => recognizeFile,
|
|
26
|
+
record: () => record
|
|
24
27
|
});
|
|
25
28
|
module.exports = __toCommonJS(index_exports);
|
|
26
29
|
|
|
27
30
|
// src/asr-http-client.ts
|
|
31
|
+
var ASR_HTTP_PATH = "/api/proxy/builtin/platform/qwen-asr/compatible-mode/v1/chat/completions";
|
|
32
|
+
async function record(durationMs = 3e3) {
|
|
33
|
+
if (typeof navigator === "undefined" || !navigator.mediaDevices?.getUserMedia) {
|
|
34
|
+
throw new Error("Microphone not supported. Requires HTTPS and browser with MediaDevices API.");
|
|
35
|
+
}
|
|
36
|
+
const sampleRate = 16e3;
|
|
37
|
+
const audioChunks = [];
|
|
38
|
+
const mediaStream = await navigator.mediaDevices.getUserMedia({
|
|
39
|
+
audio: { sampleRate, channelCount: 1, echoCancellation: true }
|
|
40
|
+
});
|
|
41
|
+
const audioContext = new AudioContext({ sampleRate });
|
|
42
|
+
const source = audioContext.createMediaStreamSource(mediaStream);
|
|
43
|
+
const processor = audioContext.createScriptProcessor(4096, 1, 1);
|
|
44
|
+
processor.onaudioprocess = (e) => {
|
|
45
|
+
const input = e.inputBuffer.getChannelData(0);
|
|
46
|
+
const pcm = new Int16Array(input.length);
|
|
47
|
+
for (let i = 0; i < input.length; i++) {
|
|
48
|
+
const s = Math.max(-1, Math.min(1, input[i]));
|
|
49
|
+
pcm[i] = s < 0 ? s * 32768 : s * 32767;
|
|
50
|
+
}
|
|
51
|
+
audioChunks.push(pcm);
|
|
52
|
+
};
|
|
53
|
+
source.connect(processor);
|
|
54
|
+
processor.connect(audioContext.destination);
|
|
55
|
+
await new Promise((resolve) => setTimeout(resolve, durationMs));
|
|
56
|
+
mediaStream.getTracks().forEach((t) => t.stop());
|
|
57
|
+
processor.disconnect();
|
|
58
|
+
audioContext.close();
|
|
59
|
+
const totalLength = audioChunks.reduce((acc, chunk) => acc + chunk.length, 0);
|
|
60
|
+
const combined = new Int16Array(totalLength);
|
|
61
|
+
let offset = 0;
|
|
62
|
+
for (const chunk of audioChunks) {
|
|
63
|
+
combined.set(chunk, offset);
|
|
64
|
+
offset += chunk.length;
|
|
65
|
+
}
|
|
66
|
+
const wavBlob = createWavBlob(combined, sampleRate);
|
|
67
|
+
return recognizeBlob(wavBlob);
|
|
68
|
+
}
|
|
69
|
+
async function recognizeFile(file) {
|
|
70
|
+
return recognizeBlob(file);
|
|
71
|
+
}
|
|
72
|
+
async function recognizeBlob(blob) {
|
|
73
|
+
const base64 = await blobToBase64(blob);
|
|
74
|
+
const response = await fetch(ASR_HTTP_PATH, {
|
|
75
|
+
method: "POST",
|
|
76
|
+
headers: { "Content-Type": "application/json" },
|
|
77
|
+
body: JSON.stringify({
|
|
78
|
+
model: "qwen3-asr-flash",
|
|
79
|
+
messages: [{
|
|
80
|
+
role: "user",
|
|
81
|
+
content: [{
|
|
82
|
+
type: "input_audio",
|
|
83
|
+
input_audio: { data: `data:audio/wav;base64,${base64}` }
|
|
84
|
+
}]
|
|
85
|
+
}]
|
|
86
|
+
})
|
|
87
|
+
});
|
|
88
|
+
const data = await response.json();
|
|
89
|
+
return data.choices?.[0]?.message?.content || "";
|
|
90
|
+
}
|
|
91
|
+
function blobToBase64(blob) {
|
|
92
|
+
return new Promise((resolve, reject) => {
|
|
93
|
+
const reader = new FileReader();
|
|
94
|
+
reader.onloadend = () => {
|
|
95
|
+
const result = reader.result;
|
|
96
|
+
resolve(result.split(",")[1] || "");
|
|
97
|
+
};
|
|
98
|
+
reader.onerror = reject;
|
|
99
|
+
reader.readAsDataURL(blob);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function createWavBlob(pcm, sampleRate) {
|
|
103
|
+
const buffer = new ArrayBuffer(44 + pcm.length * 2);
|
|
104
|
+
const view = new DataView(buffer);
|
|
105
|
+
const writeString = (offset, str) => {
|
|
106
|
+
for (let i = 0; i < str.length; i++) view.setUint8(offset + i, str.charCodeAt(i));
|
|
107
|
+
};
|
|
108
|
+
writeString(0, "RIFF");
|
|
109
|
+
view.setUint32(4, 36 + pcm.length * 2, true);
|
|
110
|
+
writeString(8, "WAVE");
|
|
111
|
+
writeString(12, "fmt ");
|
|
112
|
+
view.setUint32(16, 16, true);
|
|
113
|
+
view.setUint16(20, 1, true);
|
|
114
|
+
view.setUint16(22, 1, true);
|
|
115
|
+
view.setUint32(24, sampleRate, true);
|
|
116
|
+
view.setUint32(28, sampleRate * 2, true);
|
|
117
|
+
view.setUint16(32, 2, true);
|
|
118
|
+
view.setUint16(34, 16, true);
|
|
119
|
+
writeString(36, "data");
|
|
120
|
+
view.setUint32(40, pcm.length * 2, true);
|
|
121
|
+
for (let i = 0; i < pcm.length; i++) view.setInt16(44 + i * 2, pcm[i], true);
|
|
122
|
+
return new Blob([buffer], { type: "audio/wav" });
|
|
123
|
+
}
|
|
28
124
|
function createASRHttpClient(config) {
|
|
29
125
|
const {
|
|
30
|
-
url,
|
|
31
126
|
language = "zh",
|
|
32
127
|
sampleRate = 16e3,
|
|
33
128
|
onRecordingStart,
|
|
@@ -93,20 +188,20 @@ function createASRHttpClient(config) {
|
|
|
93
188
|
offset += chunk.length;
|
|
94
189
|
}
|
|
95
190
|
audioChunks = [];
|
|
96
|
-
const wavBlob =
|
|
97
|
-
return
|
|
191
|
+
const wavBlob = createWavBlob2(combined, sampleRate);
|
|
192
|
+
return recognizeBlob2(wavBlob);
|
|
98
193
|
}
|
|
99
194
|
async function recordAndRecognize(durationMs) {
|
|
100
195
|
await startRecording();
|
|
101
196
|
await new Promise((resolve) => setTimeout(resolve, durationMs));
|
|
102
197
|
return stopRecording();
|
|
103
198
|
}
|
|
104
|
-
async function
|
|
105
|
-
return
|
|
199
|
+
async function recognizeFile2(file) {
|
|
200
|
+
return recognizeBlob2(file);
|
|
106
201
|
}
|
|
107
202
|
async function recognizeUrl(audioUrl) {
|
|
108
203
|
try {
|
|
109
|
-
const response = await fetch(
|
|
204
|
+
const response = await fetch(ASR_HTTP_PATH, {
|
|
110
205
|
method: "POST",
|
|
111
206
|
headers: { "Content-Type": "application/json" },
|
|
112
207
|
body: JSON.stringify({
|
|
@@ -129,7 +224,7 @@ function createASRHttpClient(config) {
|
|
|
129
224
|
throw err;
|
|
130
225
|
}
|
|
131
226
|
}
|
|
132
|
-
async function
|
|
227
|
+
async function recognizeBlob2(blob) {
|
|
133
228
|
try {
|
|
134
229
|
const arrayBuffer = await blob.arrayBuffer();
|
|
135
230
|
const bytes = new Uint8Array(arrayBuffer);
|
|
@@ -139,7 +234,7 @@ function createASRHttpClient(config) {
|
|
|
139
234
|
}
|
|
140
235
|
const base64 = btoa(binary);
|
|
141
236
|
const dataUrl = `data:audio/wav;base64,${base64}`;
|
|
142
|
-
const response = await fetch(
|
|
237
|
+
const response = await fetch(ASR_HTTP_PATH, {
|
|
143
238
|
method: "POST",
|
|
144
239
|
headers: { "Content-Type": "application/json" },
|
|
145
240
|
body: JSON.stringify({
|
|
@@ -162,7 +257,7 @@ function createASRHttpClient(config) {
|
|
|
162
257
|
throw err;
|
|
163
258
|
}
|
|
164
259
|
}
|
|
165
|
-
function
|
|
260
|
+
function createWavBlob2(samples, rate) {
|
|
166
261
|
const buffer = new ArrayBuffer(44 + samples.length * 2);
|
|
167
262
|
const view = new DataView(buffer);
|
|
168
263
|
const writeString = (offset2, str) => {
|
|
@@ -193,12 +288,15 @@ function createASRHttpClient(config) {
|
|
|
193
288
|
startRecording,
|
|
194
289
|
stopRecording,
|
|
195
290
|
recordAndRecognize,
|
|
196
|
-
recognizeFile,
|
|
291
|
+
recognizeFile: recognizeFile2,
|
|
197
292
|
recognizeUrl
|
|
198
293
|
};
|
|
199
294
|
}
|
|
200
295
|
// Annotate the CommonJS export names for ESM import in node:
|
|
201
296
|
0 && (module.exports = {
|
|
202
|
-
createASRHttpClient
|
|
297
|
+
createASRHttpClient,
|
|
298
|
+
createAsrHttpClient,
|
|
299
|
+
recognizeFile,
|
|
300
|
+
record
|
|
203
301
|
});
|
|
204
302
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/asr-http-client.ts"],"sourcesContent":["export type { ASRHttpClient, ASRHttpClientConfig } from './asr-http-client';\nexport { createASRHttpClient } from './asr-http-client';\n","/**\n * HTTP ASR Client - Press-to-talk style speech recognition\n */\n\nexport interface ASRHttpClientConfig {\n /** API endpoint URL */\n url: string;\n /** Language, default 'zh' */\n language?: string;\n /** Sample rate, default 16000 */\n sampleRate?: number;\n /** Called when recording starts */\n onRecordingStart?: () => void;\n /** Called when recording stops */\n onRecordingStop?: () => void;\n /** Called with recognition result */\n onResult?: (text: string) => void;\n /** Called on error */\n onError?: (error: Error) => void;\n}\n\nexport interface ASRHttpClient {\n /** Start recording (press-to-talk) */\n startRecording(): Promise<void>;\n /** Stop recording and get result */\n stopRecording(): Promise<string>;\n /** Record for specific duration then recognize */\n recordAndRecognize(durationMs: number): Promise<string>;\n /** Recognize audio file (File or Blob) */\n recognizeFile(file: File | Blob): Promise<string>;\n /** Recognize audio from URL */\n recognizeUrl(audioUrl: string): Promise<string>;\n}\n\nexport function createASRHttpClient(config: ASRHttpClientConfig): ASRHttpClient {\n const {\n url,\n language = 'zh',\n sampleRate = 16000,\n onRecordingStart,\n onRecordingStop,\n onResult,\n onError,\n } = config;\n\n let mediaStream: MediaStream | null = null;\n let audioContext: AudioContext | null = null;\n let processor: ScriptProcessorNode | null = null;\n let audioChunks: Int16Array[] = [];\n let isRecording = false;\n\n async function startRecording(): Promise<void> {\n if (isRecording) return;\n\n try {\n mediaStream = await navigator.mediaDevices.getUserMedia({\n audio: { sampleRate, channelCount: 1, echoCancellation: true }\n });\n\n audioContext = new AudioContext({ sampleRate });\n const source = audioContext.createMediaStreamSource(mediaStream);\n processor = audioContext.createScriptProcessor(4096, 1, 1);\n audioChunks = [];\n\n processor.onaudioprocess = (e) => {\n if (!isRecording) return;\n const inputData = e.inputBuffer.getChannelData(0);\n const pcm = new Int16Array(inputData.length);\n for (let i = 0; i < inputData.length; i++) {\n const s = Math.max(-1, Math.min(1, inputData[i]));\n pcm[i] = s < 0 ? s * 32768 : s * 32767;\n }\n audioChunks.push(pcm);\n };\n\n source.connect(processor);\n processor.connect(audioContext.destination);\n isRecording = true;\n onRecordingStart?.();\n } catch (err) {\n onError?.(err as Error);\n throw err;\n }\n }\n\n async function stopRecording(): Promise<string> {\n if (!isRecording) throw new Error('Not recording');\n\n isRecording = false;\n onRecordingStop?.();\n\n // Stop media\n if (mediaStream) {\n mediaStream.getTracks().forEach(t => t.stop());\n mediaStream = null;\n }\n if (processor) {\n processor.disconnect();\n processor = null;\n }\n if (audioContext) {\n await audioContext.close();\n audioContext = null;\n }\n\n // Combine audio chunks\n const totalLength = audioChunks.reduce((sum, chunk) => sum + chunk.length, 0);\n const combined = new Int16Array(totalLength);\n let offset = 0;\n for (const chunk of audioChunks) {\n combined.set(chunk, offset);\n offset += chunk.length;\n }\n audioChunks = [];\n\n // Convert to WAV\n const wavBlob = createWavBlob(combined, sampleRate);\n return recognizeBlob(wavBlob);\n }\n\n async function recordAndRecognize(durationMs: number): Promise<string> {\n await startRecording();\n await new Promise(resolve => setTimeout(resolve, durationMs));\n return stopRecording();\n }\n\n async function recognizeFile(file: File | Blob): Promise<string> {\n return recognizeBlob(file);\n }\n\n async function recognizeUrl(audioUrl: string): Promise<string> {\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n model: 'qwen3-asr-flash',\n messages: [{\n role: 'user',\n content: [{ type: 'input_audio', input_audio: { url: audioUrl } }]\n }]\n })\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n\n const data = await response.json();\n const text = data.choices?.[0]?.message?.content || '';\n onResult?.(text);\n return text;\n } catch (err) {\n onError?.(err as Error);\n throw err;\n }\n }\n\n async function recognizeBlob(blob: Blob): Promise<string> {\n try {\n // Convert to base64\n const arrayBuffer = await blob.arrayBuffer();\n const bytes = new Uint8Array(arrayBuffer);\n let binary = '';\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n const base64 = btoa(binary);\n const dataUrl = `data:audio/wav;base64,${base64}`;\n\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n model: 'qwen3-asr-flash',\n messages: [{\n role: 'user',\n content: [{ type: 'input_audio', input_audio: { data: dataUrl } }]\n }]\n })\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n\n const data = await response.json();\n const text = data.choices?.[0]?.message?.content || '';\n onResult?.(text);\n return text;\n } catch (err) {\n onError?.(err as Error);\n throw err;\n }\n }\n\n function createWavBlob(samples: Int16Array, rate: number): Blob {\n const buffer = new ArrayBuffer(44 + samples.length * 2);\n const view = new DataView(buffer);\n\n // WAV header\n const writeString = (offset: number, str: string) => {\n for (let i = 0; i < str.length; i++) {\n view.setUint8(offset + i, str.charCodeAt(i));\n }\n };\n\n writeString(0, 'RIFF');\n view.setUint32(4, 36 + samples.length * 2, true);\n writeString(8, 'WAVE');\n writeString(12, 'fmt ');\n view.setUint32(16, 16, true);\n view.setUint16(20, 1, true);\n view.setUint16(22, 1, true);\n view.setUint32(24, rate, true);\n view.setUint32(28, rate * 2, true);\n view.setUint16(32, 2, true);\n view.setUint16(34, 16, true);\n writeString(36, 'data');\n view.setUint32(40, samples.length * 2, true);\n\n // Audio data\n const offset = 44;\n for (let i = 0; i < samples.length; i++) {\n view.setInt16(offset + i * 2, samples[i], true);\n }\n\n return new Blob([buffer], { type: 'audio/wav' });\n }\n\n return {\n startRecording,\n stopRecording,\n recordAndRecognize,\n recognizeFile,\n recognizeUrl,\n };\n}\n"],"mappings":"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","names":["offset"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/asr-http-client.ts"],"sourcesContent":["export { record, recognizeFile } from './asr-http-client';\nexport type { ASRHttpClient, ASRHttpClientConfig } from './asr-http-client';\nexport { createASRHttpClient, createASRHttpClient as createAsrHttpClient } from './asr-http-client';\n","/**\n * HTTP ASR Client - Press-to-talk style speech recognition\n */\n\nconst ASR_HTTP_PATH = '/api/proxy/builtin/platform/qwen-asr/compatible-mode/v1/chat/completions';\n\n/**\n * Simple record and recognize\n * @param durationMs - Recording duration in milliseconds (default 3000)\n * @returns Recognized text string\n * @example const text = await record(3000) // record 3 seconds\n */\nexport async function record(durationMs: number = 3000): Promise<string> {\n // Check browser support\n if (typeof navigator === 'undefined' || !navigator.mediaDevices?.getUserMedia) {\n throw new Error('Microphone not supported. Requires HTTPS and browser with MediaDevices API.');\n }\n\n const sampleRate = 16000;\n const audioChunks: Int16Array[] = [];\n\n const mediaStream = await navigator.mediaDevices.getUserMedia({\n audio: { sampleRate, channelCount: 1, echoCancellation: true }\n });\n\n const audioContext = new AudioContext({ sampleRate });\n const source = audioContext.createMediaStreamSource(mediaStream);\n const processor = audioContext.createScriptProcessor(4096, 1, 1);\n\n processor.onaudioprocess = (e) => {\n const input = e.inputBuffer.getChannelData(0);\n const pcm = new Int16Array(input.length);\n for (let i = 0; i < input.length; i++) {\n const s = Math.max(-1, Math.min(1, input[i]));\n pcm[i] = s < 0 ? s * 32768 : s * 32767;\n }\n audioChunks.push(pcm);\n };\n\n source.connect(processor);\n processor.connect(audioContext.destination);\n\n // Wait for duration\n await new Promise(resolve => setTimeout(resolve, durationMs));\n\n // Stop\n mediaStream.getTracks().forEach(t => t.stop());\n processor.disconnect();\n audioContext.close();\n\n // Convert to WAV and send\n const totalLength = audioChunks.reduce((acc, chunk) => acc + chunk.length, 0);\n const combined = new Int16Array(totalLength);\n let offset = 0;\n for (const chunk of audioChunks) { combined.set(chunk, offset); offset += chunk.length; }\n\n const wavBlob = createWavBlob(combined, sampleRate);\n return recognizeBlob(wavBlob);\n}\n\n/** Recognize audio file */\nexport async function recognizeFile(file: File | Blob): Promise<string> {\n return recognizeBlob(file);\n}\n\nasync function recognizeBlob(blob: Blob): Promise<string> {\n const base64 = await blobToBase64(blob);\n const response = await fetch(ASR_HTTP_PATH, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n model: 'qwen3-asr-flash',\n messages: [{\n role: 'user',\n content: [{\n type: 'input_audio',\n input_audio: { data: `data:audio/wav;base64,${base64}` }\n }]\n }]\n })\n });\n const data = await response.json();\n return data.choices?.[0]?.message?.content || '';\n}\n\nfunction blobToBase64(blob: Blob): Promise<string> {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onloadend = () => {\n const result = reader.result as string;\n resolve(result.split(',')[1] || '');\n };\n reader.onerror = reject;\n reader.readAsDataURL(blob);\n });\n}\n\nfunction createWavBlob(pcm: Int16Array, sampleRate: number): Blob {\n const buffer = new ArrayBuffer(44 + pcm.length * 2);\n const view = new DataView(buffer);\n const writeString = (offset: number, str: string) => {\n for (let i = 0; i < str.length; i++) view.setUint8(offset + i, str.charCodeAt(i));\n };\n writeString(0, 'RIFF');\n view.setUint32(4, 36 + pcm.length * 2, true);\n writeString(8, 'WAVE');\n writeString(12, 'fmt ');\n view.setUint32(16, 16, true);\n view.setUint16(20, 1, true);\n view.setUint16(22, 1, true);\n view.setUint32(24, sampleRate, true);\n view.setUint32(28, sampleRate * 2, true);\n view.setUint16(32, 2, true);\n view.setUint16(34, 16, true);\n writeString(36, 'data');\n view.setUint32(40, pcm.length * 2, true);\n for (let i = 0; i < pcm.length; i++) view.setInt16(44 + i * 2, pcm[i], true);\n return new Blob([buffer], { type: 'audio/wav' });\n}\n\nexport interface ASRHttpClientConfig {\n /** Language, default 'zh' */\n language?: string;\n /** Sample rate, default 16000 */\n sampleRate?: number;\n /** Called when recording starts */\n onRecordingStart?: () => void;\n /** Called when recording stops */\n onRecordingStop?: () => void;\n /** Called with recognition result */\n onResult?: (text: string) => void;\n /** Called on error */\n onError?: (error: Error) => void;\n}\n\nexport interface ASRHttpClient {\n /** Start recording (press-to-talk) */\n startRecording(): Promise<void>;\n /** Stop recording and get result */\n stopRecording(): Promise<string>;\n /** Record for specific duration then recognize */\n recordAndRecognize(durationMs: number): Promise<string>;\n /** Recognize audio file (File or Blob) */\n recognizeFile(file: File | Blob): Promise<string>;\n /** Recognize audio from URL */\n recognizeUrl(audioUrl: string): Promise<string>;\n}\n\nexport function createASRHttpClient(config: ASRHttpClientConfig): ASRHttpClient {\n const {\n language = 'zh',\n sampleRate = 16000,\n onRecordingStart,\n onRecordingStop,\n onResult,\n onError,\n } = config;\n\n let mediaStream: MediaStream | null = null;\n let audioContext: AudioContext | null = null;\n let processor: ScriptProcessorNode | null = null;\n let audioChunks: Int16Array[] = [];\n let isRecording = false;\n\n async function startRecording(): Promise<void> {\n if (isRecording) return;\n\n try {\n mediaStream = await navigator.mediaDevices.getUserMedia({\n audio: { sampleRate, channelCount: 1, echoCancellation: true }\n });\n\n audioContext = new AudioContext({ sampleRate });\n const source = audioContext.createMediaStreamSource(mediaStream);\n processor = audioContext.createScriptProcessor(4096, 1, 1);\n audioChunks = [];\n\n processor.onaudioprocess = (e) => {\n if (!isRecording) return;\n const inputData = e.inputBuffer.getChannelData(0);\n const pcm = new Int16Array(inputData.length);\n for (let i = 0; i < inputData.length; i++) {\n const s = Math.max(-1, Math.min(1, inputData[i]));\n pcm[i] = s < 0 ? s * 32768 : s * 32767;\n }\n audioChunks.push(pcm);\n };\n\n source.connect(processor);\n processor.connect(audioContext.destination);\n isRecording = true;\n onRecordingStart?.();\n } catch (err) {\n onError?.(err as Error);\n throw err;\n }\n }\n\n async function stopRecording(): Promise<string> {\n if (!isRecording) throw new Error('Not recording');\n\n isRecording = false;\n onRecordingStop?.();\n\n // Stop media\n if (mediaStream) {\n mediaStream.getTracks().forEach(t => t.stop());\n mediaStream = null;\n }\n if (processor) {\n processor.disconnect();\n processor = null;\n }\n if (audioContext) {\n await audioContext.close();\n audioContext = null;\n }\n\n // Combine audio chunks\n const totalLength = audioChunks.reduce((sum, chunk) => sum + chunk.length, 0);\n const combined = new Int16Array(totalLength);\n let offset = 0;\n for (const chunk of audioChunks) {\n combined.set(chunk, offset);\n offset += chunk.length;\n }\n audioChunks = [];\n\n // Convert to WAV\n const wavBlob = createWavBlob(combined, sampleRate);\n return recognizeBlob(wavBlob);\n }\n\n async function recordAndRecognize(durationMs: number): Promise<string> {\n await startRecording();\n await new Promise(resolve => setTimeout(resolve, durationMs));\n return stopRecording();\n }\n\n async function recognizeFile(file: File | Blob): Promise<string> {\n return recognizeBlob(file);\n }\n\n async function recognizeUrl(audioUrl: string): Promise<string> {\n try {\n const response = await fetch(ASR_HTTP_PATH, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n model: 'qwen3-asr-flash',\n messages: [{\n role: 'user',\n content: [{ type: 'input_audio', input_audio: { url: audioUrl } }]\n }]\n })\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n\n const data = await response.json();\n const text = data.choices?.[0]?.message?.content || '';\n onResult?.(text);\n return text;\n } catch (err) {\n onError?.(err as Error);\n throw err;\n }\n }\n\n async function recognizeBlob(blob: Blob): Promise<string> {\n try {\n // Convert to base64\n const arrayBuffer = await blob.arrayBuffer();\n const bytes = new Uint8Array(arrayBuffer);\n let binary = '';\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n const base64 = btoa(binary);\n const dataUrl = `data:audio/wav;base64,${base64}`;\n\n const response = await fetch(ASR_HTTP_PATH, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n model: 'qwen3-asr-flash',\n messages: [{\n role: 'user',\n content: [{ type: 'input_audio', input_audio: { data: dataUrl } }]\n }]\n })\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n\n const data = await response.json();\n const text = data.choices?.[0]?.message?.content || '';\n onResult?.(text);\n return text;\n } catch (err) {\n onError?.(err as Error);\n throw err;\n }\n }\n\n function createWavBlob(samples: Int16Array, rate: number): Blob {\n const buffer = new ArrayBuffer(44 + samples.length * 2);\n const view = new DataView(buffer);\n\n // WAV header\n const writeString = (offset: number, str: string) => {\n for (let i = 0; i < str.length; i++) {\n view.setUint8(offset + i, str.charCodeAt(i));\n }\n };\n\n writeString(0, 'RIFF');\n view.setUint32(4, 36 + samples.length * 2, true);\n writeString(8, 'WAVE');\n writeString(12, 'fmt ');\n view.setUint32(16, 16, true);\n view.setUint16(20, 1, true);\n view.setUint16(22, 1, true);\n view.setUint32(24, rate, true);\n view.setUint32(28, rate * 2, true);\n view.setUint16(32, 2, true);\n view.setUint16(34, 16, true);\n writeString(36, 'data');\n view.setUint32(40, samples.length * 2, true);\n\n // Audio data\n const offset = 44;\n for (let i = 0; i < samples.length; i++) {\n view.setInt16(offset + i * 2, samples[i], true);\n }\n\n return new Blob([buffer], { type: 'audio/wav' });\n }\n\n return {\n startRecording,\n stopRecording,\n recordAndRecognize,\n recognizeFile,\n recognizeUrl,\n };\n}\n"],"mappings":"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","names":["createWavBlob","recognizeBlob","recognizeFile","offset"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* HTTP ASR Client - Press-to-talk style speech recognition
|
|
3
3
|
*/
|
|
4
|
+
/**
|
|
5
|
+
* Simple record and recognize
|
|
6
|
+
* @param durationMs - Recording duration in milliseconds (default 3000)
|
|
7
|
+
* @returns Recognized text string
|
|
8
|
+
* @example const text = await record(3000) // record 3 seconds
|
|
9
|
+
*/
|
|
10
|
+
declare function record(durationMs?: number): Promise<string>;
|
|
11
|
+
/** Recognize audio file */
|
|
12
|
+
declare function recognizeFile(file: File | Blob): Promise<string>;
|
|
4
13
|
interface ASRHttpClientConfig {
|
|
5
|
-
/** API endpoint URL */
|
|
6
|
-
url: string;
|
|
7
14
|
/** Language, default 'zh' */
|
|
8
15
|
language?: string;
|
|
9
16
|
/** Sample rate, default 16000 */
|
|
@@ -31,4 +38,4 @@ interface ASRHttpClient {
|
|
|
31
38
|
}
|
|
32
39
|
declare function createASRHttpClient(config: ASRHttpClientConfig): ASRHttpClient;
|
|
33
40
|
|
|
34
|
-
export { type ASRHttpClient, type ASRHttpClientConfig, createASRHttpClient };
|
|
41
|
+
export { type ASRHttpClient, type ASRHttpClientConfig, createASRHttpClient, createASRHttpClient as createAsrHttpClient, recognizeFile, record };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* HTTP ASR Client - Press-to-talk style speech recognition
|
|
3
3
|
*/
|
|
4
|
+
/**
|
|
5
|
+
* Simple record and recognize
|
|
6
|
+
* @param durationMs - Recording duration in milliseconds (default 3000)
|
|
7
|
+
* @returns Recognized text string
|
|
8
|
+
* @example const text = await record(3000) // record 3 seconds
|
|
9
|
+
*/
|
|
10
|
+
declare function record(durationMs?: number): Promise<string>;
|
|
11
|
+
/** Recognize audio file */
|
|
12
|
+
declare function recognizeFile(file: File | Blob): Promise<string>;
|
|
4
13
|
interface ASRHttpClientConfig {
|
|
5
|
-
/** API endpoint URL */
|
|
6
|
-
url: string;
|
|
7
14
|
/** Language, default 'zh' */
|
|
8
15
|
language?: string;
|
|
9
16
|
/** Sample rate, default 16000 */
|
|
@@ -31,4 +38,4 @@ interface ASRHttpClient {
|
|
|
31
38
|
}
|
|
32
39
|
declare function createASRHttpClient(config: ASRHttpClientConfig): ASRHttpClient;
|
|
33
40
|
|
|
34
|
-
export { type ASRHttpClient, type ASRHttpClientConfig, createASRHttpClient };
|
|
41
|
+
export { type ASRHttpClient, type ASRHttpClientConfig, createASRHttpClient, createASRHttpClient as createAsrHttpClient, recognizeFile, record };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,99 @@
|
|
|
1
1
|
// src/asr-http-client.ts
|
|
2
|
+
var ASR_HTTP_PATH = "/api/proxy/builtin/platform/qwen-asr/compatible-mode/v1/chat/completions";
|
|
3
|
+
async function record(durationMs = 3e3) {
|
|
4
|
+
if (typeof navigator === "undefined" || !navigator.mediaDevices?.getUserMedia) {
|
|
5
|
+
throw new Error("Microphone not supported. Requires HTTPS and browser with MediaDevices API.");
|
|
6
|
+
}
|
|
7
|
+
const sampleRate = 16e3;
|
|
8
|
+
const audioChunks = [];
|
|
9
|
+
const mediaStream = await navigator.mediaDevices.getUserMedia({
|
|
10
|
+
audio: { sampleRate, channelCount: 1, echoCancellation: true }
|
|
11
|
+
});
|
|
12
|
+
const audioContext = new AudioContext({ sampleRate });
|
|
13
|
+
const source = audioContext.createMediaStreamSource(mediaStream);
|
|
14
|
+
const processor = audioContext.createScriptProcessor(4096, 1, 1);
|
|
15
|
+
processor.onaudioprocess = (e) => {
|
|
16
|
+
const input = e.inputBuffer.getChannelData(0);
|
|
17
|
+
const pcm = new Int16Array(input.length);
|
|
18
|
+
for (let i = 0; i < input.length; i++) {
|
|
19
|
+
const s = Math.max(-1, Math.min(1, input[i]));
|
|
20
|
+
pcm[i] = s < 0 ? s * 32768 : s * 32767;
|
|
21
|
+
}
|
|
22
|
+
audioChunks.push(pcm);
|
|
23
|
+
};
|
|
24
|
+
source.connect(processor);
|
|
25
|
+
processor.connect(audioContext.destination);
|
|
26
|
+
await new Promise((resolve) => setTimeout(resolve, durationMs));
|
|
27
|
+
mediaStream.getTracks().forEach((t) => t.stop());
|
|
28
|
+
processor.disconnect();
|
|
29
|
+
audioContext.close();
|
|
30
|
+
const totalLength = audioChunks.reduce((acc, chunk) => acc + chunk.length, 0);
|
|
31
|
+
const combined = new Int16Array(totalLength);
|
|
32
|
+
let offset = 0;
|
|
33
|
+
for (const chunk of audioChunks) {
|
|
34
|
+
combined.set(chunk, offset);
|
|
35
|
+
offset += chunk.length;
|
|
36
|
+
}
|
|
37
|
+
const wavBlob = createWavBlob(combined, sampleRate);
|
|
38
|
+
return recognizeBlob(wavBlob);
|
|
39
|
+
}
|
|
40
|
+
async function recognizeFile(file) {
|
|
41
|
+
return recognizeBlob(file);
|
|
42
|
+
}
|
|
43
|
+
async function recognizeBlob(blob) {
|
|
44
|
+
const base64 = await blobToBase64(blob);
|
|
45
|
+
const response = await fetch(ASR_HTTP_PATH, {
|
|
46
|
+
method: "POST",
|
|
47
|
+
headers: { "Content-Type": "application/json" },
|
|
48
|
+
body: JSON.stringify({
|
|
49
|
+
model: "qwen3-asr-flash",
|
|
50
|
+
messages: [{
|
|
51
|
+
role: "user",
|
|
52
|
+
content: [{
|
|
53
|
+
type: "input_audio",
|
|
54
|
+
input_audio: { data: `data:audio/wav;base64,${base64}` }
|
|
55
|
+
}]
|
|
56
|
+
}]
|
|
57
|
+
})
|
|
58
|
+
});
|
|
59
|
+
const data = await response.json();
|
|
60
|
+
return data.choices?.[0]?.message?.content || "";
|
|
61
|
+
}
|
|
62
|
+
function blobToBase64(blob) {
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
const reader = new FileReader();
|
|
65
|
+
reader.onloadend = () => {
|
|
66
|
+
const result = reader.result;
|
|
67
|
+
resolve(result.split(",")[1] || "");
|
|
68
|
+
};
|
|
69
|
+
reader.onerror = reject;
|
|
70
|
+
reader.readAsDataURL(blob);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
function createWavBlob(pcm, sampleRate) {
|
|
74
|
+
const buffer = new ArrayBuffer(44 + pcm.length * 2);
|
|
75
|
+
const view = new DataView(buffer);
|
|
76
|
+
const writeString = (offset, str) => {
|
|
77
|
+
for (let i = 0; i < str.length; i++) view.setUint8(offset + i, str.charCodeAt(i));
|
|
78
|
+
};
|
|
79
|
+
writeString(0, "RIFF");
|
|
80
|
+
view.setUint32(4, 36 + pcm.length * 2, true);
|
|
81
|
+
writeString(8, "WAVE");
|
|
82
|
+
writeString(12, "fmt ");
|
|
83
|
+
view.setUint32(16, 16, true);
|
|
84
|
+
view.setUint16(20, 1, true);
|
|
85
|
+
view.setUint16(22, 1, true);
|
|
86
|
+
view.setUint32(24, sampleRate, true);
|
|
87
|
+
view.setUint32(28, sampleRate * 2, true);
|
|
88
|
+
view.setUint16(32, 2, true);
|
|
89
|
+
view.setUint16(34, 16, true);
|
|
90
|
+
writeString(36, "data");
|
|
91
|
+
view.setUint32(40, pcm.length * 2, true);
|
|
92
|
+
for (let i = 0; i < pcm.length; i++) view.setInt16(44 + i * 2, pcm[i], true);
|
|
93
|
+
return new Blob([buffer], { type: "audio/wav" });
|
|
94
|
+
}
|
|
2
95
|
function createASRHttpClient(config) {
|
|
3
96
|
const {
|
|
4
|
-
url,
|
|
5
97
|
language = "zh",
|
|
6
98
|
sampleRate = 16e3,
|
|
7
99
|
onRecordingStart,
|
|
@@ -67,20 +159,20 @@ function createASRHttpClient(config) {
|
|
|
67
159
|
offset += chunk.length;
|
|
68
160
|
}
|
|
69
161
|
audioChunks = [];
|
|
70
|
-
const wavBlob =
|
|
71
|
-
return
|
|
162
|
+
const wavBlob = createWavBlob2(combined, sampleRate);
|
|
163
|
+
return recognizeBlob2(wavBlob);
|
|
72
164
|
}
|
|
73
165
|
async function recordAndRecognize(durationMs) {
|
|
74
166
|
await startRecording();
|
|
75
167
|
await new Promise((resolve) => setTimeout(resolve, durationMs));
|
|
76
168
|
return stopRecording();
|
|
77
169
|
}
|
|
78
|
-
async function
|
|
79
|
-
return
|
|
170
|
+
async function recognizeFile2(file) {
|
|
171
|
+
return recognizeBlob2(file);
|
|
80
172
|
}
|
|
81
173
|
async function recognizeUrl(audioUrl) {
|
|
82
174
|
try {
|
|
83
|
-
const response = await fetch(
|
|
175
|
+
const response = await fetch(ASR_HTTP_PATH, {
|
|
84
176
|
method: "POST",
|
|
85
177
|
headers: { "Content-Type": "application/json" },
|
|
86
178
|
body: JSON.stringify({
|
|
@@ -103,7 +195,7 @@ function createASRHttpClient(config) {
|
|
|
103
195
|
throw err;
|
|
104
196
|
}
|
|
105
197
|
}
|
|
106
|
-
async function
|
|
198
|
+
async function recognizeBlob2(blob) {
|
|
107
199
|
try {
|
|
108
200
|
const arrayBuffer = await blob.arrayBuffer();
|
|
109
201
|
const bytes = new Uint8Array(arrayBuffer);
|
|
@@ -113,7 +205,7 @@ function createASRHttpClient(config) {
|
|
|
113
205
|
}
|
|
114
206
|
const base64 = btoa(binary);
|
|
115
207
|
const dataUrl = `data:audio/wav;base64,${base64}`;
|
|
116
|
-
const response = await fetch(
|
|
208
|
+
const response = await fetch(ASR_HTTP_PATH, {
|
|
117
209
|
method: "POST",
|
|
118
210
|
headers: { "Content-Type": "application/json" },
|
|
119
211
|
body: JSON.stringify({
|
|
@@ -136,7 +228,7 @@ function createASRHttpClient(config) {
|
|
|
136
228
|
throw err;
|
|
137
229
|
}
|
|
138
230
|
}
|
|
139
|
-
function
|
|
231
|
+
function createWavBlob2(samples, rate) {
|
|
140
232
|
const buffer = new ArrayBuffer(44 + samples.length * 2);
|
|
141
233
|
const view = new DataView(buffer);
|
|
142
234
|
const writeString = (offset2, str) => {
|
|
@@ -167,11 +259,14 @@ function createASRHttpClient(config) {
|
|
|
167
259
|
startRecording,
|
|
168
260
|
stopRecording,
|
|
169
261
|
recordAndRecognize,
|
|
170
|
-
recognizeFile,
|
|
262
|
+
recognizeFile: recognizeFile2,
|
|
171
263
|
recognizeUrl
|
|
172
264
|
};
|
|
173
265
|
}
|
|
174
266
|
export {
|
|
175
|
-
createASRHttpClient
|
|
267
|
+
createASRHttpClient,
|
|
268
|
+
createASRHttpClient as createAsrHttpClient,
|
|
269
|
+
recognizeFile,
|
|
270
|
+
record
|
|
176
271
|
};
|
|
177
272
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/asr-http-client.ts"],"sourcesContent":["/**\n * HTTP ASR Client - Press-to-talk style speech recognition\n */\n\nexport interface ASRHttpClientConfig {\n /** API endpoint URL */\n url: string;\n /** Language, default 'zh' */\n language?: string;\n /** Sample rate, default 16000 */\n sampleRate?: number;\n /** Called when recording starts */\n onRecordingStart?: () => void;\n /** Called when recording stops */\n onRecordingStop?: () => void;\n /** Called with recognition result */\n onResult?: (text: string) => void;\n /** Called on error */\n onError?: (error: Error) => void;\n}\n\nexport interface ASRHttpClient {\n /** Start recording (press-to-talk) */\n startRecording(): Promise<void>;\n /** Stop recording and get result */\n stopRecording(): Promise<string>;\n /** Record for specific duration then recognize */\n recordAndRecognize(durationMs: number): Promise<string>;\n /** Recognize audio file (File or Blob) */\n recognizeFile(file: File | Blob): Promise<string>;\n /** Recognize audio from URL */\n recognizeUrl(audioUrl: string): Promise<string>;\n}\n\nexport function createASRHttpClient(config: ASRHttpClientConfig): ASRHttpClient {\n const {\n url,\n language = 'zh',\n sampleRate = 16000,\n onRecordingStart,\n onRecordingStop,\n onResult,\n onError,\n } = config;\n\n let mediaStream: MediaStream | null = null;\n let audioContext: AudioContext | null = null;\n let processor: ScriptProcessorNode | null = null;\n let audioChunks: Int16Array[] = [];\n let isRecording = false;\n\n async function startRecording(): Promise<void> {\n if (isRecording) return;\n\n try {\n mediaStream = await navigator.mediaDevices.getUserMedia({\n audio: { sampleRate, channelCount: 1, echoCancellation: true }\n });\n\n audioContext = new AudioContext({ sampleRate });\n const source = audioContext.createMediaStreamSource(mediaStream);\n processor = audioContext.createScriptProcessor(4096, 1, 1);\n audioChunks = [];\n\n processor.onaudioprocess = (e) => {\n if (!isRecording) return;\n const inputData = e.inputBuffer.getChannelData(0);\n const pcm = new Int16Array(inputData.length);\n for (let i = 0; i < inputData.length; i++) {\n const s = Math.max(-1, Math.min(1, inputData[i]));\n pcm[i] = s < 0 ? s * 32768 : s * 32767;\n }\n audioChunks.push(pcm);\n };\n\n source.connect(processor);\n processor.connect(audioContext.destination);\n isRecording = true;\n onRecordingStart?.();\n } catch (err) {\n onError?.(err as Error);\n throw err;\n }\n }\n\n async function stopRecording(): Promise<string> {\n if (!isRecording) throw new Error('Not recording');\n\n isRecording = false;\n onRecordingStop?.();\n\n // Stop media\n if (mediaStream) {\n mediaStream.getTracks().forEach(t => t.stop());\n mediaStream = null;\n }\n if (processor) {\n processor.disconnect();\n processor = null;\n }\n if (audioContext) {\n await audioContext.close();\n audioContext = null;\n }\n\n // Combine audio chunks\n const totalLength = audioChunks.reduce((sum, chunk) => sum + chunk.length, 0);\n const combined = new Int16Array(totalLength);\n let offset = 0;\n for (const chunk of audioChunks) {\n combined.set(chunk, offset);\n offset += chunk.length;\n }\n audioChunks = [];\n\n // Convert to WAV\n const wavBlob = createWavBlob(combined, sampleRate);\n return recognizeBlob(wavBlob);\n }\n\n async function recordAndRecognize(durationMs: number): Promise<string> {\n await startRecording();\n await new Promise(resolve => setTimeout(resolve, durationMs));\n return stopRecording();\n }\n\n async function recognizeFile(file: File | Blob): Promise<string> {\n return recognizeBlob(file);\n }\n\n async function recognizeUrl(audioUrl: string): Promise<string> {\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n model: 'qwen3-asr-flash',\n messages: [{\n role: 'user',\n content: [{ type: 'input_audio', input_audio: { url: audioUrl } }]\n }]\n })\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n\n const data = await response.json();\n const text = data.choices?.[0]?.message?.content || '';\n onResult?.(text);\n return text;\n } catch (err) {\n onError?.(err as Error);\n throw err;\n }\n }\n\n async function recognizeBlob(blob: Blob): Promise<string> {\n try {\n // Convert to base64\n const arrayBuffer = await blob.arrayBuffer();\n const bytes = new Uint8Array(arrayBuffer);\n let binary = '';\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n const base64 = btoa(binary);\n const dataUrl = `data:audio/wav;base64,${base64}`;\n\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n model: 'qwen3-asr-flash',\n messages: [{\n role: 'user',\n content: [{ type: 'input_audio', input_audio: { data: dataUrl } }]\n }]\n })\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n\n const data = await response.json();\n const text = data.choices?.[0]?.message?.content || '';\n onResult?.(text);\n return text;\n } catch (err) {\n onError?.(err as Error);\n throw err;\n }\n }\n\n function createWavBlob(samples: Int16Array, rate: number): Blob {\n const buffer = new ArrayBuffer(44 + samples.length * 2);\n const view = new DataView(buffer);\n\n // WAV header\n const writeString = (offset: number, str: string) => {\n for (let i = 0; i < str.length; i++) {\n view.setUint8(offset + i, str.charCodeAt(i));\n }\n };\n\n writeString(0, 'RIFF');\n view.setUint32(4, 36 + samples.length * 2, true);\n writeString(8, 'WAVE');\n writeString(12, 'fmt ');\n view.setUint32(16, 16, true);\n view.setUint16(20, 1, true);\n view.setUint16(22, 1, true);\n view.setUint32(24, rate, true);\n view.setUint32(28, rate * 2, true);\n view.setUint16(32, 2, true);\n view.setUint16(34, 16, true);\n writeString(36, 'data');\n view.setUint32(40, samples.length * 2, true);\n\n // Audio data\n const offset = 44;\n for (let i = 0; i < samples.length; i++) {\n view.setInt16(offset + i * 2, samples[i], true);\n }\n\n return new Blob([buffer], { type: 'audio/wav' });\n }\n\n return {\n startRecording,\n stopRecording,\n recordAndRecognize,\n recognizeFile,\n recognizeUrl,\n };\n}\n"],"mappings":"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","names":["offset"]}
|
|
1
|
+
{"version":3,"sources":["../src/asr-http-client.ts"],"sourcesContent":["/**\n * HTTP ASR Client - Press-to-talk style speech recognition\n */\n\nconst ASR_HTTP_PATH = '/api/proxy/builtin/platform/qwen-asr/compatible-mode/v1/chat/completions';\n\n/**\n * Simple record and recognize\n * @param durationMs - Recording duration in milliseconds (default 3000)\n * @returns Recognized text string\n * @example const text = await record(3000) // record 3 seconds\n */\nexport async function record(durationMs: number = 3000): Promise<string> {\n // Check browser support\n if (typeof navigator === 'undefined' || !navigator.mediaDevices?.getUserMedia) {\n throw new Error('Microphone not supported. Requires HTTPS and browser with MediaDevices API.');\n }\n\n const sampleRate = 16000;\n const audioChunks: Int16Array[] = [];\n\n const mediaStream = await navigator.mediaDevices.getUserMedia({\n audio: { sampleRate, channelCount: 1, echoCancellation: true }\n });\n\n const audioContext = new AudioContext({ sampleRate });\n const source = audioContext.createMediaStreamSource(mediaStream);\n const processor = audioContext.createScriptProcessor(4096, 1, 1);\n\n processor.onaudioprocess = (e) => {\n const input = e.inputBuffer.getChannelData(0);\n const pcm = new Int16Array(input.length);\n for (let i = 0; i < input.length; i++) {\n const s = Math.max(-1, Math.min(1, input[i]));\n pcm[i] = s < 0 ? s * 32768 : s * 32767;\n }\n audioChunks.push(pcm);\n };\n\n source.connect(processor);\n processor.connect(audioContext.destination);\n\n // Wait for duration\n await new Promise(resolve => setTimeout(resolve, durationMs));\n\n // Stop\n mediaStream.getTracks().forEach(t => t.stop());\n processor.disconnect();\n audioContext.close();\n\n // Convert to WAV and send\n const totalLength = audioChunks.reduce((acc, chunk) => acc + chunk.length, 0);\n const combined = new Int16Array(totalLength);\n let offset = 0;\n for (const chunk of audioChunks) { combined.set(chunk, offset); offset += chunk.length; }\n\n const wavBlob = createWavBlob(combined, sampleRate);\n return recognizeBlob(wavBlob);\n}\n\n/** Recognize audio file */\nexport async function recognizeFile(file: File | Blob): Promise<string> {\n return recognizeBlob(file);\n}\n\nasync function recognizeBlob(blob: Blob): Promise<string> {\n const base64 = await blobToBase64(blob);\n const response = await fetch(ASR_HTTP_PATH, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n model: 'qwen3-asr-flash',\n messages: [{\n role: 'user',\n content: [{\n type: 'input_audio',\n input_audio: { data: `data:audio/wav;base64,${base64}` }\n }]\n }]\n })\n });\n const data = await response.json();\n return data.choices?.[0]?.message?.content || '';\n}\n\nfunction blobToBase64(blob: Blob): Promise<string> {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onloadend = () => {\n const result = reader.result as string;\n resolve(result.split(',')[1] || '');\n };\n reader.onerror = reject;\n reader.readAsDataURL(blob);\n });\n}\n\nfunction createWavBlob(pcm: Int16Array, sampleRate: number): Blob {\n const buffer = new ArrayBuffer(44 + pcm.length * 2);\n const view = new DataView(buffer);\n const writeString = (offset: number, str: string) => {\n for (let i = 0; i < str.length; i++) view.setUint8(offset + i, str.charCodeAt(i));\n };\n writeString(0, 'RIFF');\n view.setUint32(4, 36 + pcm.length * 2, true);\n writeString(8, 'WAVE');\n writeString(12, 'fmt ');\n view.setUint32(16, 16, true);\n view.setUint16(20, 1, true);\n view.setUint16(22, 1, true);\n view.setUint32(24, sampleRate, true);\n view.setUint32(28, sampleRate * 2, true);\n view.setUint16(32, 2, true);\n view.setUint16(34, 16, true);\n writeString(36, 'data');\n view.setUint32(40, pcm.length * 2, true);\n for (let i = 0; i < pcm.length; i++) view.setInt16(44 + i * 2, pcm[i], true);\n return new Blob([buffer], { type: 'audio/wav' });\n}\n\nexport interface ASRHttpClientConfig {\n /** Language, default 'zh' */\n language?: string;\n /** Sample rate, default 16000 */\n sampleRate?: number;\n /** Called when recording starts */\n onRecordingStart?: () => void;\n /** Called when recording stops */\n onRecordingStop?: () => void;\n /** Called with recognition result */\n onResult?: (text: string) => void;\n /** Called on error */\n onError?: (error: Error) => void;\n}\n\nexport interface ASRHttpClient {\n /** Start recording (press-to-talk) */\n startRecording(): Promise<void>;\n /** Stop recording and get result */\n stopRecording(): Promise<string>;\n /** Record for specific duration then recognize */\n recordAndRecognize(durationMs: number): Promise<string>;\n /** Recognize audio file (File or Blob) */\n recognizeFile(file: File | Blob): Promise<string>;\n /** Recognize audio from URL */\n recognizeUrl(audioUrl: string): Promise<string>;\n}\n\nexport function createASRHttpClient(config: ASRHttpClientConfig): ASRHttpClient {\n const {\n language = 'zh',\n sampleRate = 16000,\n onRecordingStart,\n onRecordingStop,\n onResult,\n onError,\n } = config;\n\n let mediaStream: MediaStream | null = null;\n let audioContext: AudioContext | null = null;\n let processor: ScriptProcessorNode | null = null;\n let audioChunks: Int16Array[] = [];\n let isRecording = false;\n\n async function startRecording(): Promise<void> {\n if (isRecording) return;\n\n try {\n mediaStream = await navigator.mediaDevices.getUserMedia({\n audio: { sampleRate, channelCount: 1, echoCancellation: true }\n });\n\n audioContext = new AudioContext({ sampleRate });\n const source = audioContext.createMediaStreamSource(mediaStream);\n processor = audioContext.createScriptProcessor(4096, 1, 1);\n audioChunks = [];\n\n processor.onaudioprocess = (e) => {\n if (!isRecording) return;\n const inputData = e.inputBuffer.getChannelData(0);\n const pcm = new Int16Array(inputData.length);\n for (let i = 0; i < inputData.length; i++) {\n const s = Math.max(-1, Math.min(1, inputData[i]));\n pcm[i] = s < 0 ? s * 32768 : s * 32767;\n }\n audioChunks.push(pcm);\n };\n\n source.connect(processor);\n processor.connect(audioContext.destination);\n isRecording = true;\n onRecordingStart?.();\n } catch (err) {\n onError?.(err as Error);\n throw err;\n }\n }\n\n async function stopRecording(): Promise<string> {\n if (!isRecording) throw new Error('Not recording');\n\n isRecording = false;\n onRecordingStop?.();\n\n // Stop media\n if (mediaStream) {\n mediaStream.getTracks().forEach(t => t.stop());\n mediaStream = null;\n }\n if (processor) {\n processor.disconnect();\n processor = null;\n }\n if (audioContext) {\n await audioContext.close();\n audioContext = null;\n }\n\n // Combine audio chunks\n const totalLength = audioChunks.reduce((sum, chunk) => sum + chunk.length, 0);\n const combined = new Int16Array(totalLength);\n let offset = 0;\n for (const chunk of audioChunks) {\n combined.set(chunk, offset);\n offset += chunk.length;\n }\n audioChunks = [];\n\n // Convert to WAV\n const wavBlob = createWavBlob(combined, sampleRate);\n return recognizeBlob(wavBlob);\n }\n\n async function recordAndRecognize(durationMs: number): Promise<string> {\n await startRecording();\n await new Promise(resolve => setTimeout(resolve, durationMs));\n return stopRecording();\n }\n\n async function recognizeFile(file: File | Blob): Promise<string> {\n return recognizeBlob(file);\n }\n\n async function recognizeUrl(audioUrl: string): Promise<string> {\n try {\n const response = await fetch(ASR_HTTP_PATH, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n model: 'qwen3-asr-flash',\n messages: [{\n role: 'user',\n content: [{ type: 'input_audio', input_audio: { url: audioUrl } }]\n }]\n })\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n\n const data = await response.json();\n const text = data.choices?.[0]?.message?.content || '';\n onResult?.(text);\n return text;\n } catch (err) {\n onError?.(err as Error);\n throw err;\n }\n }\n\n async function recognizeBlob(blob: Blob): Promise<string> {\n try {\n // Convert to base64\n const arrayBuffer = await blob.arrayBuffer();\n const bytes = new Uint8Array(arrayBuffer);\n let binary = '';\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n const base64 = btoa(binary);\n const dataUrl = `data:audio/wav;base64,${base64}`;\n\n const response = await fetch(ASR_HTTP_PATH, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n model: 'qwen3-asr-flash',\n messages: [{\n role: 'user',\n content: [{ type: 'input_audio', input_audio: { data: dataUrl } }]\n }]\n })\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n\n const data = await response.json();\n const text = data.choices?.[0]?.message?.content || '';\n onResult?.(text);\n return text;\n } catch (err) {\n onError?.(err as Error);\n throw err;\n }\n }\n\n function createWavBlob(samples: Int16Array, rate: number): Blob {\n const buffer = new ArrayBuffer(44 + samples.length * 2);\n const view = new DataView(buffer);\n\n // WAV header\n const writeString = (offset: number, str: string) => {\n for (let i = 0; i < str.length; i++) {\n view.setUint8(offset + i, str.charCodeAt(i));\n }\n };\n\n writeString(0, 'RIFF');\n view.setUint32(4, 36 + samples.length * 2, true);\n writeString(8, 'WAVE');\n writeString(12, 'fmt ');\n view.setUint32(16, 16, true);\n view.setUint16(20, 1, true);\n view.setUint16(22, 1, true);\n view.setUint32(24, rate, true);\n view.setUint32(28, rate * 2, true);\n view.setUint16(32, 2, true);\n view.setUint16(34, 16, true);\n writeString(36, 'data');\n view.setUint32(40, samples.length * 2, true);\n\n // Audio data\n const offset = 44;\n for (let i = 0; i < samples.length; i++) {\n view.setInt16(offset + i * 2, samples[i], true);\n }\n\n return new Blob([buffer], { type: 'audio/wav' });\n }\n\n return {\n startRecording,\n stopRecording,\n recordAndRecognize,\n recognizeFile,\n recognizeUrl,\n };\n}\n"],"mappings":"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","names":["createWavBlob","recognizeBlob","recognizeFile","offset"]}
|