@zhin.js/speech 2.0.0 → 2.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.
@@ -1,3 +1,8 @@
1
1
  import type { STTConfig } from '../types.js';
2
- export declare function transcribeWithOllama(audioData: Buffer, sttConfig: STTConfig, _mimeType?: string): Promise<string>;
2
+ /**
3
+ * Ollama 没有音频转写模型(whisper 不在 ollama 模型库中),
4
+ * 把音频 base64 塞进 chat images 字段的契约不成立,调用必然失败。
5
+ * 因此这里直接给出明确报错,而不是静默发出无效请求。
6
+ */
7
+ export declare function transcribeWithOllama(_audioData: Buffer, _sttConfig: STTConfig, _mimeType?: string): Promise<string>;
3
8
  //# sourceMappingURL=ollama.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ollama.d.ts","sourceRoot":"","sources":["../../src/stt/ollama.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,SAAS,EACpB,SAAS,GAAE,MAAoB,GAC9B,OAAO,CAAC,MAAM,CAAC,CAyBjB"}
1
+ {"version":3,"file":"ollama.d.ts","sourceRoot":"","sources":["../../src/stt/ollama.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,SAAS,EACrB,SAAS,GAAE,MAAoB,GAC9B,OAAO,CAAC,MAAM,CAAC,CAKjB"}
package/lib/stt/ollama.js CHANGED
@@ -1,24 +1,10 @@
1
- export async function transcribeWithOllama(audioData, sttConfig, _mimeType = 'audio/wav') {
2
- const host = sttConfig.host || 'http://localhost:11434';
3
- const model = sttConfig.model || 'whisper';
4
- const base64Audio = audioData.toString('base64');
5
- const response = await fetch(`${host}/api/chat`, {
6
- method: 'POST',
7
- headers: { 'Content-Type': 'application/json' },
8
- body: JSON.stringify({
9
- model,
10
- messages: [{
11
- role: 'user',
12
- content: '请将这段音频转写为文字,只输出转写的文字内容,不要加任何说明。',
13
- images: [base64Audio],
14
- }],
15
- stream: false,
16
- }),
17
- });
18
- if (!response.ok) {
19
- throw new Error(`Ollama STT failed: ${response.status} ${response.statusText}`);
20
- }
21
- const data = await response.json();
22
- return data.message?.content?.trim() || '';
1
+ /**
2
+ * Ollama 没有音频转写模型(whisper 不在 ollama 模型库中),
3
+ * 把音频 base64 塞进 chat images 字段的契约不成立,调用必然失败。
4
+ * 因此这里直接给出明确报错,而不是静默发出无效请求。
5
+ */
6
+ export async function transcribeWithOllama(_audioData, _sttConfig, _mimeType = 'audio/wav') {
7
+ throw new Error('Ollama STT 不可用:ollama 不支持音频转写(无 whisper 音频模型)。' +
8
+ '请将 speech.stt.provider 配置为 "openai"(OpenAI 兼容的 whisper 接口)。');
23
9
  }
24
10
  //# sourceMappingURL=ollama.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ollama.js","sourceRoot":"","sources":["../../src/stt/ollama.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,SAAiB,EACjB,SAAoB,EACpB,YAAoB,WAAW;IAE/B,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,IAAI,wBAAwB,CAAC;IACxD,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,IAAI,SAAS,CAAC;IAC3C,MAAM,WAAW,GAAG,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAEjD,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,WAAW,EAAE;QAC/C,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;YACnB,KAAK;YACL,QAAQ,EAAE,CAAC;oBACT,IAAI,EAAE,MAAM;oBACZ,OAAO,EAAE,iCAAiC;oBAC1C,MAAM,EAAE,CAAC,WAAW,CAAC;iBACtB,CAAC;YACF,MAAM,EAAE,KAAK;SACd,CAAC;KACH,CAAC,CAAC;IAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,sBAAsB,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;IAClF,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAwC,CAAC;IACzE,OAAO,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;AAC7C,CAAC"}
1
+ {"version":3,"file":"ollama.js","sourceRoot":"","sources":["../../src/stt/ollama.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,UAAkB,EAClB,UAAqB,EACrB,YAAoB,WAAW;IAE/B,MAAM,IAAI,KAAK,CACb,gDAAgD;QAC9C,6DAA6D,CAChE,CAAC;AACJ,CAAC"}
@@ -1,3 +1,4 @@
1
1
  import type { STTConfig } from '../types.js';
2
+ export declare function extFromMimeType(mimeType: string): string;
2
3
  export declare function transcribeWithOpenAI(audioData: Buffer, sttConfig: STTConfig, mimeType?: string): Promise<string>;
3
4
  //# sourceMappingURL=openai.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../src/stt/openai.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,SAAS,EACpB,QAAQ,GAAE,MAAoB,GAC7B,OAAO,CAAC,MAAM,CAAC,CA+BjB"}
1
+ {"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../src/stt/openai.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAsB7C,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGxD;AAED,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,SAAS,EACpB,QAAQ,GAAE,MAAoB,GAC7B,OAAO,CAAC,MAAM,CAAC,CAyBjB"}
package/lib/stt/openai.js CHANGED
@@ -1,33 +1,45 @@
1
- import { randomBytes } from 'node:crypto';
2
- import { tmpdir } from 'node:os';
3
- import { join } from 'node:path';
4
- import { unlink, writeFile } from 'node:fs/promises';
1
+ /** STT 请求超时(转写大音频可能较慢) */
2
+ const STT_FETCH_TIMEOUT_MS = 60_000;
3
+ /** mimeType 文件扩展名映射(OpenAI whisper 按扩展名识别格式) */
4
+ const MIME_TO_EXT = {
5
+ 'audio/wav': 'wav',
6
+ 'audio/x-wav': 'wav',
7
+ 'audio/wave': 'wav',
8
+ 'audio/mpeg': 'mp3',
9
+ 'audio/mp3': 'mp3',
10
+ 'audio/ogg': 'ogg',
11
+ 'audio/webm': 'webm',
12
+ 'audio/amr': 'amr',
13
+ 'audio/silk': 'silk',
14
+ 'audio/mp4': 'm4a',
15
+ 'audio/m4a': 'm4a',
16
+ 'audio/x-m4a': 'm4a',
17
+ 'audio/flac': 'flac',
18
+ };
19
+ export function extFromMimeType(mimeType) {
20
+ const base = mimeType.split(';')[0].trim().toLowerCase();
21
+ return MIME_TO_EXT[base] ?? 'wav';
22
+ }
5
23
  export async function transcribeWithOpenAI(audioData, sttConfig, mimeType = 'audio/wav') {
6
24
  const host = sttConfig.host || 'https://api.openai.com';
7
25
  const model = sttConfig.model || 'whisper-1';
8
26
  const apiKey = sttConfig.apiKey || '';
9
- const tmpFile = join(tmpdir(), `zhin-stt-${randomBytes(8).toString('hex')}.wav`);
10
- await writeFile(tmpFile, audioData);
11
- try {
12
- const ext = mimeType.includes('mp3') ? 'mp3' : 'wav';
13
- const formData = new FormData();
14
- const blob = new Blob([new Uint8Array(audioData)], { type: mimeType });
15
- formData.append('file', blob, `audio.${ext}`);
16
- formData.append('model', model);
17
- formData.append('language', 'zh');
18
- const response = await fetch(`${host}/v1/audio/transcriptions`, {
19
- method: 'POST',
20
- headers: { Authorization: `Bearer ${apiKey}` },
21
- body: formData,
22
- });
23
- if (!response.ok) {
24
- throw new Error(`OpenAI STT failed: ${response.status} ${response.statusText}`);
25
- }
26
- const data = await response.json();
27
- return data.text?.trim() || '';
28
- }
29
- finally {
30
- await unlink(tmpFile).catch(() => { });
27
+ const ext = extFromMimeType(mimeType);
28
+ const formData = new FormData();
29
+ const blob = new Blob([new Uint8Array(audioData)], { type: mimeType });
30
+ formData.append('file', blob, `audio.${ext}`);
31
+ formData.append('model', model);
32
+ formData.append('language', 'zh');
33
+ const response = await fetch(`${host}/v1/audio/transcriptions`, {
34
+ method: 'POST',
35
+ headers: { Authorization: `Bearer ${apiKey}` },
36
+ body: formData,
37
+ signal: AbortSignal.timeout(STT_FETCH_TIMEOUT_MS),
38
+ });
39
+ if (!response.ok) {
40
+ throw new Error(`OpenAI STT failed: ${response.status} ${response.statusText}`);
31
41
  }
42
+ const data = await response.json();
43
+ return data.text?.trim() || '';
32
44
  }
33
45
  //# sourceMappingURL=openai.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"openai.js","sourceRoot":"","sources":["../../src/stt/openai.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAGrD,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,SAAiB,EACjB,SAAoB,EACpB,WAAmB,WAAW;IAE9B,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,IAAI,wBAAwB,CAAC;IACxD,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,IAAI,WAAW,CAAC;IAC7C,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,IAAI,EAAE,CAAC;IAEtC,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,EAAE,EAAE,YAAY,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACjF,MAAM,SAAS,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IAEpC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC;QACrD,MAAM,QAAQ,GAAG,IAAI,QAAQ,EAAE,CAAC;QAChC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,IAAI,UAAU,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;QACvE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,EAAE,CAAC,CAAC;QAC9C,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QAChC,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;QAElC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,0BAA0B,EAAE;YAC9D,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,MAAM,EAAE,EAAE;YAC9C,IAAI,EAAE,QAAQ;SACf,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,sBAAsB,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;QAClF,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAuB,CAAC;QACxD,OAAO,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACjC,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACxC,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"openai.js","sourceRoot":"","sources":["../../src/stt/openai.ts"],"names":[],"mappings":"AAEA,0BAA0B;AAC1B,MAAM,oBAAoB,GAAG,MAAM,CAAC;AAEpC,kDAAkD;AAClD,MAAM,WAAW,GAA2B;IAC1C,WAAW,EAAE,KAAK;IAClB,aAAa,EAAE,KAAK;IACpB,YAAY,EAAE,KAAK;IACnB,YAAY,EAAE,KAAK;IACnB,WAAW,EAAE,KAAK;IAClB,WAAW,EAAE,KAAK;IAClB,YAAY,EAAE,MAAM;IACpB,WAAW,EAAE,KAAK;IAClB,YAAY,EAAE,MAAM;IACpB,WAAW,EAAE,KAAK;IAClB,WAAW,EAAE,KAAK;IAClB,aAAa,EAAE,KAAK;IACpB,YAAY,EAAE,MAAM;CACrB,CAAC;AAEF,MAAM,UAAU,eAAe,CAAC,QAAgB;IAC9C,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACzD,OAAO,WAAW,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC;AACpC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,SAAiB,EACjB,SAAoB,EACpB,WAAmB,WAAW;IAE9B,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,IAAI,wBAAwB,CAAC;IACxD,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,IAAI,WAAW,CAAC;IAC7C,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,IAAI,EAAE,CAAC;IAEtC,MAAM,GAAG,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;IACtC,MAAM,QAAQ,GAAG,IAAI,QAAQ,EAAE,CAAC;IAChC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,IAAI,UAAU,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;IACvE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,EAAE,CAAC,CAAC;IAC9C,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAChC,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;IAElC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,0BAA0B,EAAE;QAC9D,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,MAAM,EAAE,EAAE;QAC9C,IAAI,EAAE,QAAQ;QACd,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,oBAAoB,CAAC;KAClD,CAAC,CAAC;IAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,sBAAsB,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;IAClF,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAuB,CAAC;IACxD,OAAO,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;AACjC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"azure.d.ts","sourceRoot":"","sources":["../../src/tts/azure.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAA2C,MAAM,aAAa,CAAC;AAEnG,qGAAqG;AACrG,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,GAAG,WAAW,CA6BrE"}
1
+ {"version":3,"file":"azure.d.ts","sourceRoot":"","sources":["../../src/tts/azure.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAA2C,MAAM,aAAa,CAAC;AAKnG,qGAAqG;AACrG,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,GAAG,WAAW,CA8BrE"}
package/lib/tts/azure.js CHANGED
@@ -1,3 +1,5 @@
1
+ /** TTS 请求超时 */
2
+ const TTS_FETCH_TIMEOUT_MS = 30_000;
1
3
  /** Azure Cognitive Services text-to-speech REST (output format: audio-16khz-128kbitrate-mono-mp3) */
2
4
  export function createAzureTtsProvider(config) {
3
5
  return {
@@ -16,6 +18,7 @@ export function createAzureTtsProvider(config) {
16
18
  'X-Microsoft-OutputFormat': 'audio-16khz-128kbitrate-mono-mp3',
17
19
  },
18
20
  body: ssml,
21
+ signal: AbortSignal.timeout(TTS_FETCH_TIMEOUT_MS),
19
22
  });
20
23
  if (!response.ok) {
21
24
  throw new Error(`Azure TTS failed: ${response.status} ${response.statusText}`);
@@ -1 +1 @@
1
- {"version":3,"file":"azure.js","sourceRoot":"","sources":["../../src/tts/azure.ts"],"names":[],"mappings":"AAEA,qGAAqG;AACrG,MAAM,UAAU,sBAAsB,CAAC,MAAiB;IACtD,OAAO;QACL,EAAE,EAAE,OAAO;QACX,KAAK,CAAC,UAAU,CAAC,KAAyB;YACxC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,UAAU,CAAC;YAC3C,MAAM,eAAe,GAAG,MAAM,CAAC,eAAe,IAAI,EAAE,CAAC;YACrD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,MAAM,CAAC,KAAK,IAAI,sBAAsB,CAAC;YACpE,MAAM,GAAG,GAAG,WAAW,MAAM,gDAAgD,CAAC;YAE9E,MAAM,IAAI,GAAG,sDAAsD,KAAK,KAAK,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC;YAErH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;gBAChC,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACP,2BAA2B,EAAE,eAAe;oBAC5C,cAAc,EAAE,sBAAsB;oBACtC,0BAA0B,EAAE,kCAAkC;iBAC/D;gBACD,IAAI,EAAE,IAAI;aACX,CAAC,CAAC;YAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,MAAM,IAAI,KAAK,CAAC,qBAAqB,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;YACjF,CAAC;YAED,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC;YACvD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;QACjC,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,SAAS,CAAC,IAAY;IAC7B,OAAO,IAAI;SACR,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC7B,CAAC"}
1
+ {"version":3,"file":"azure.js","sourceRoot":"","sources":["../../src/tts/azure.ts"],"names":[],"mappings":"AAEA,eAAe;AACf,MAAM,oBAAoB,GAAG,MAAM,CAAC;AAEpC,qGAAqG;AACrG,MAAM,UAAU,sBAAsB,CAAC,MAAiB;IACtD,OAAO;QACL,EAAE,EAAE,OAAO;QACX,KAAK,CAAC,UAAU,CAAC,KAAyB;YACxC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,UAAU,CAAC;YAC3C,MAAM,eAAe,GAAG,MAAM,CAAC,eAAe,IAAI,EAAE,CAAC;YACrD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,MAAM,CAAC,KAAK,IAAI,sBAAsB,CAAC;YACpE,MAAM,GAAG,GAAG,WAAW,MAAM,gDAAgD,CAAC;YAE9E,MAAM,IAAI,GAAG,sDAAsD,KAAK,KAAK,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC;YAErH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;gBAChC,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACP,2BAA2B,EAAE,eAAe;oBAC5C,cAAc,EAAE,sBAAsB;oBACtC,0BAA0B,EAAE,kCAAkC;iBAC/D;gBACD,IAAI,EAAE,IAAI;gBACV,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,oBAAoB,CAAC;aAClD,CAAC,CAAC;YAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,MAAM,IAAI,KAAK,CAAC,qBAAqB,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;YACjF,CAAC;YAED,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC;YACvD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;QACjC,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,SAAS,CAAC,IAAY;IAC7B,OAAO,IAAI;SACR,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC7B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"custom.d.ts","sourceRoot":"","sources":["../../src/tts/custom.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAa,WAAW,EAA2C,MAAM,aAAa,CAAC;AAMjH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,YAAY,GAAG,WAAW,CA2CzE"}
1
+ {"version":3,"file":"custom.d.ts","sourceRoot":"","sources":["../../src/tts/custom.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAa,WAAW,EAA2C,MAAM,aAAa,CAAC;AASjH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,YAAY,GAAG,WAAW,CA4CzE"}
package/lib/tts/custom.js CHANGED
@@ -1,3 +1,5 @@
1
+ /** TTS 请求超时 */
2
+ const TTS_FETCH_TIMEOUT_MS = 30_000;
1
3
  function resolveOpenAiApiKey(config) {
2
4
  return config.tts?.apiKey || config.stt?.apiKey || '';
3
5
  }
@@ -34,6 +36,7 @@ export function createCustomTtsProvider(config) {
34
36
  method: 'POST',
35
37
  headers,
36
38
  body: JSON.stringify(body),
39
+ signal: AbortSignal.timeout(TTS_FETCH_TIMEOUT_MS),
37
40
  });
38
41
  if (!response.ok) {
39
42
  throw new Error(`Custom TTS failed: ${response.status} ${response.statusText}`);
@@ -1 +1 @@
1
- {"version":3,"file":"custom.js","sourceRoot":"","sources":["../../src/tts/custom.ts"],"names":[],"mappings":"AAEA,SAAS,mBAAmB,CAAC,MAAoB;IAC/C,OAAO,MAAM,CAAC,GAAG,EAAE,MAAM,IAAI,MAAM,CAAC,GAAG,EAAE,MAAM,IAAI,EAAE,CAAC;AACxD,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,MAAoB;IAC1D,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;IACnC,OAAO;QACL,EAAE,EAAE,QAAQ;QACZ,KAAK,CAAC,UAAU,CAAC,KAAyB;YACxC,MAAM,OAAO,GAAG,CAAC,SAAS,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YAC7D,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;YAC5D,CAAC;YACD,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,IAAI,OAAO,CAAC;YACzC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,SAAS,CAAC,KAAK,IAAI,SAAS,CAAC;YAC1D,MAAM,MAAM,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;YAC3C,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC;YAEtD,MAAM,OAAO,GAA2B;gBACtC,cAAc,EAAE,kBAAkB;gBAClC,GAAG,CAAC,SAAS,CAAC,OAAO,IAAI,EAAE,CAAC;aAC7B,CAAC;YACF,IAAI,MAAM;gBAAE,OAAO,CAAC,aAAa,GAAG,UAAU,MAAM,EAAE,CAAC;YAEvD,MAAM,IAAI,GAA4B;gBACpC,KAAK;gBACL,KAAK,EAAE,KAAK,CAAC,IAAI;gBACjB,KAAK;gBACL,eAAe,EAAE,MAAM;aACxB,CAAC;YACF,IAAI,KAAK,CAAC,KAAK,IAAI,IAAI;gBAAE,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;iBAC7C,IAAI,SAAS,CAAC,KAAK,IAAI,IAAI;gBAAE,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC;YAE/D,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,OAAO,EAAE;gBACpC,MAAM,EAAE,MAAM;gBACd,OAAO;gBACP,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;aAC3B,CAAC,CAAC;YAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,MAAM,IAAI,KAAK,CAAC,sBAAsB,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;YAClF,CAAC;YAED,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC;YACvD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAC1B,CAAC;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"custom.js","sourceRoot":"","sources":["../../src/tts/custom.ts"],"names":[],"mappings":"AAEA,eAAe;AACf,MAAM,oBAAoB,GAAG,MAAM,CAAC;AAEpC,SAAS,mBAAmB,CAAC,MAAoB;IAC/C,OAAO,MAAM,CAAC,GAAG,EAAE,MAAM,IAAI,MAAM,CAAC,GAAG,EAAE,MAAM,IAAI,EAAE,CAAC;AACxD,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,MAAoB;IAC1D,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;IACnC,OAAO;QACL,EAAE,EAAE,QAAQ;QACZ,KAAK,CAAC,UAAU,CAAC,KAAyB;YACxC,MAAM,OAAO,GAAG,CAAC,SAAS,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YAC7D,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;YAC5D,CAAC;YACD,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,IAAI,OAAO,CAAC;YACzC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,SAAS,CAAC,KAAK,IAAI,SAAS,CAAC;YAC1D,MAAM,MAAM,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;YAC3C,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC;YAEtD,MAAM,OAAO,GAA2B;gBACtC,cAAc,EAAE,kBAAkB;gBAClC,GAAG,CAAC,SAAS,CAAC,OAAO,IAAI,EAAE,CAAC;aAC7B,CAAC;YACF,IAAI,MAAM;gBAAE,OAAO,CAAC,aAAa,GAAG,UAAU,MAAM,EAAE,CAAC;YAEvD,MAAM,IAAI,GAA4B;gBACpC,KAAK;gBACL,KAAK,EAAE,KAAK,CAAC,IAAI;gBACjB,KAAK;gBACL,eAAe,EAAE,MAAM;aACxB,CAAC;YACF,IAAI,KAAK,CAAC,KAAK,IAAI,IAAI;gBAAE,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;iBAC7C,IAAI,SAAS,CAAC,KAAK,IAAI,IAAI;gBAAE,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC;YAE/D,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,OAAO,EAAE;gBACpC,MAAM,EAAE,MAAM;gBACd,OAAO;gBACP,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;gBAC1B,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,oBAAoB,CAAC;aAClD,CAAC,CAAC;YAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,MAAM,IAAI,KAAK,CAAC,sBAAsB,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;YAClF,CAAC;YAED,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC;YACvD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAC1B,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tts/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAM5E,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,YAAY,EAAE,QAAQ,CAAC,EAAE,aAAa,GAAG,WAAW,CAe9F"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tts/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAQ5E,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,YAAY,EAAE,QAAQ,CAAC,EAAE,aAAa,GAAG,WAAW,CAkB9F"}
package/lib/tts/index.js CHANGED
@@ -2,6 +2,7 @@ import { createAzureTtsProvider } from './azure.js';
2
2
  import { createCustomTtsProvider } from './custom.js';
3
3
  import { createEdgeTtsProvider } from './edge.js';
4
4
  import { createOpenAiTtsProvider } from './openai.js';
5
+ const VALID_TTS_PROVIDERS = ['edge', 'openai', 'azure', 'custom'];
5
6
  export function resolveTtsProvider(config, override) {
6
7
  const ttsConfig = config.tts || {};
7
8
  const id = override || ttsConfig.provider || 'edge';
@@ -13,8 +14,9 @@ export function resolveTtsProvider(config, override) {
13
14
  case 'custom':
14
15
  return createCustomTtsProvider(config);
15
16
  case 'edge':
16
- default:
17
17
  return createEdgeTtsProvider(ttsConfig);
18
+ default:
19
+ throw new Error(`未知 TTS provider: ${String(id)}。合法值: ${VALID_TTS_PROVIDERS.join(', ')}`);
18
20
  }
19
21
  }
20
22
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tts/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAEtD,MAAM,UAAU,kBAAkB,CAAC,MAAoB,EAAE,QAAwB;IAC/E,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;IACnC,MAAM,EAAE,GAAG,QAAQ,IAAI,SAAS,CAAC,QAAQ,IAAI,MAAM,CAAC;IAEpD,QAAQ,EAAE,EAAE,CAAC;QACX,KAAK,QAAQ;YACX,OAAO,uBAAuB,CAAC,MAAM,CAAC,CAAC;QACzC,KAAK,OAAO;YACV,OAAO,sBAAsB,CAAC,SAAS,CAAC,CAAC;QAC3C,KAAK,QAAQ;YACX,OAAO,uBAAuB,CAAC,MAAM,CAAC,CAAC;QACzC,KAAK,MAAM,CAAC;QACZ;YACE,OAAO,qBAAqB,CAAC,SAAS,CAAC,CAAC;IAC5C,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tts/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAEtD,MAAM,mBAAmB,GAAoB,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;AAEnF,MAAM,UAAU,kBAAkB,CAAC,MAAoB,EAAE,QAAwB;IAC/E,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;IACnC,MAAM,EAAE,GAAG,QAAQ,IAAI,SAAS,CAAC,QAAQ,IAAI,MAAM,CAAC;IAEpD,QAAQ,EAAE,EAAE,CAAC;QACX,KAAK,QAAQ;YACX,OAAO,uBAAuB,CAAC,MAAM,CAAC,CAAC;QACzC,KAAK,OAAO;YACV,OAAO,sBAAsB,CAAC,SAAS,CAAC,CAAC;QAC3C,KAAK,QAAQ;YACX,OAAO,uBAAuB,CAAC,MAAM,CAAC,CAAC;QACzC,KAAK,MAAM;YACT,OAAO,qBAAqB,CAAC,SAAS,CAAC,CAAC;QAC1C;YACE,MAAM,IAAI,KAAK,CACb,oBAAoB,MAAM,CAAC,EAAE,CAAC,SAAS,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACxE,CAAC;IACN,CAAC;AACH,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../src/tts/openai.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAa,WAAW,EAA2C,MAAM,aAAa,CAAC;AAMjH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,YAAY,GAAG,WAAW,CAqCzE"}
1
+ {"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../src/tts/openai.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAa,WAAW,EAA2C,MAAM,aAAa,CAAC;AASjH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,YAAY,GAAG,WAAW,CAsCzE"}
package/lib/tts/openai.js CHANGED
@@ -1,3 +1,5 @@
1
+ /** TTS 请求超时 */
2
+ const TTS_FETCH_TIMEOUT_MS = 30_000;
1
3
  function resolveOpenAiApiKey(config) {
2
4
  return config.tts?.apiKey || config.stt?.apiKey || '';
3
5
  }
@@ -28,6 +30,7 @@ export function createOpenAiTtsProvider(config) {
28
30
  'Content-Type': 'application/json',
29
31
  },
30
32
  body: JSON.stringify(body),
33
+ signal: AbortSignal.timeout(TTS_FETCH_TIMEOUT_MS),
31
34
  });
32
35
  if (!response.ok) {
33
36
  throw new Error(`OpenAI TTS failed: ${response.status} ${response.statusText}`);
@@ -1 +1 @@
1
- {"version":3,"file":"openai.js","sourceRoot":"","sources":["../../src/tts/openai.ts"],"names":[],"mappings":"AAEA,SAAS,mBAAmB,CAAC,MAAoB;IAC/C,OAAO,MAAM,CAAC,GAAG,EAAE,MAAM,IAAI,MAAM,CAAC,GAAG,EAAE,MAAM,IAAI,EAAE,CAAC;AACxD,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,MAAoB;IAC1D,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;IACnC,OAAO;QACL,EAAE,EAAE,QAAQ;QACZ,KAAK,CAAC,UAAU,CAAC,KAAyB;YACxC,MAAM,IAAI,GAAG,CAAC,SAAS,CAAC,IAAI,IAAI,wBAAwB,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YAC7E,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,IAAI,OAAO,CAAC;YACzC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,SAAS,CAAC,KAAK,IAAI,OAAO,CAAC;YACxD,MAAM,MAAM,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;YAC3C,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC;YAEtD,MAAM,IAAI,GAA4B;gBACpC,KAAK;gBACL,KAAK,EAAE,KAAK,CAAC,IAAI;gBACjB,KAAK;gBACL,eAAe,EAAE,MAAM;aACxB,CAAC;YACF,IAAI,KAAK,CAAC,KAAK,IAAI,IAAI;gBAAE,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;iBAC7C,IAAI,SAAS,CAAC,KAAK,IAAI,IAAI;gBAAE,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC;YAE/D,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,kBAAkB,EAAE;gBACtD,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACP,aAAa,EAAE,UAAU,MAAM,EAAE;oBACjC,cAAc,EAAE,kBAAkB;iBACnC;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;aAC3B,CAAC,CAAC;YAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,MAAM,IAAI,KAAK,CAAC,sBAAsB,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;YAClF,CAAC;YAED,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC;YACvD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAC1B,CAAC;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"openai.js","sourceRoot":"","sources":["../../src/tts/openai.ts"],"names":[],"mappings":"AAEA,eAAe;AACf,MAAM,oBAAoB,GAAG,MAAM,CAAC;AAEpC,SAAS,mBAAmB,CAAC,MAAoB;IAC/C,OAAO,MAAM,CAAC,GAAG,EAAE,MAAM,IAAI,MAAM,CAAC,GAAG,EAAE,MAAM,IAAI,EAAE,CAAC;AACxD,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,MAAoB;IAC1D,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;IACnC,OAAO;QACL,EAAE,EAAE,QAAQ;QACZ,KAAK,CAAC,UAAU,CAAC,KAAyB;YACxC,MAAM,IAAI,GAAG,CAAC,SAAS,CAAC,IAAI,IAAI,wBAAwB,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YAC7E,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,IAAI,OAAO,CAAC;YACzC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,SAAS,CAAC,KAAK,IAAI,OAAO,CAAC;YACxD,MAAM,MAAM,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;YAC3C,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC;YAEtD,MAAM,IAAI,GAA4B;gBACpC,KAAK;gBACL,KAAK,EAAE,KAAK,CAAC,IAAI;gBACjB,KAAK;gBACL,eAAe,EAAE,MAAM;aACxB,CAAC;YACF,IAAI,KAAK,CAAC,KAAK,IAAI,IAAI;gBAAE,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;iBAC7C,IAAI,SAAS,CAAC,KAAK,IAAI,IAAI;gBAAE,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC;YAE/D,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,kBAAkB,EAAE;gBACtD,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACP,aAAa,EAAE,UAAU,MAAM,EAAE;oBACjC,cAAc,EAAE,kBAAkB;iBACnC;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;gBAC1B,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,oBAAoB,CAAC;aAClD,CAAC,CAAC;YAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,MAAM,IAAI,KAAK,CAAC,sBAAsB,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;YAClF,CAAC;YAED,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC;YACvD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAC1B,CAAC;KACF,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/speech",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "STT/TTS for Zhin.js (Whisper + edge/openai/azure/custom TTS); optional peer of zhin.js",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -34,7 +34,7 @@
34
34
  },
35
35
  "license": "MIT",
36
36
  "peerDependencies": {
37
- "@zhin.js/core": "1.4.0"
37
+ "@zhin.js/core": "1.4.2"
38
38
  },
39
39
  "peerDependenciesMeta": {
40
40
  "@zhin.js/core": {
@@ -43,7 +43,7 @@
43
43
  },
44
44
  "devDependencies": {
45
45
  "typescript": "^6.0.3",
46
- "@zhin.js/core": "1.4.0"
46
+ "@zhin.js/core": "1.4.2"
47
47
  },
48
48
  "repository": {
49
49
  "type": "git",
package/src/stt/ollama.ts CHANGED
@@ -1,32 +1,17 @@
1
1
  import type { STTConfig } from '../types.js';
2
2
 
3
+ /**
4
+ * Ollama 没有音频转写模型(whisper 不在 ollama 模型库中),
5
+ * 把音频 base64 塞进 chat images 字段的契约不成立,调用必然失败。
6
+ * 因此这里直接给出明确报错,而不是静默发出无效请求。
7
+ */
3
8
  export async function transcribeWithOllama(
4
- audioData: Buffer,
5
- sttConfig: STTConfig,
9
+ _audioData: Buffer,
10
+ _sttConfig: STTConfig,
6
11
  _mimeType: string = 'audio/wav',
7
12
  ): Promise<string> {
8
- const host = sttConfig.host || 'http://localhost:11434';
9
- const model = sttConfig.model || 'whisper';
10
- const base64Audio = audioData.toString('base64');
11
-
12
- const response = await fetch(`${host}/api/chat`, {
13
- method: 'POST',
14
- headers: { 'Content-Type': 'application/json' },
15
- body: JSON.stringify({
16
- model,
17
- messages: [{
18
- role: 'user',
19
- content: '请将这段音频转写为文字,只输出转写的文字内容,不要加任何说明。',
20
- images: [base64Audio],
21
- }],
22
- stream: false,
23
- }),
24
- });
25
-
26
- if (!response.ok) {
27
- throw new Error(`Ollama STT failed: ${response.status} ${response.statusText}`);
28
- }
29
-
30
- const data = await response.json() as { message?: { content?: string } };
31
- return data.message?.content?.trim() || '';
13
+ throw new Error(
14
+ 'Ollama STT 不可用:ollama 不支持音频转写(无 whisper 音频模型)。' +
15
+ '请将 speech.stt.provider 配置为 "openai"(OpenAI 兼容的 whisper 接口)。',
16
+ );
32
17
  }
package/src/stt/openai.ts CHANGED
@@ -1,9 +1,30 @@
1
- import { randomBytes } from 'node:crypto';
2
- import { tmpdir } from 'node:os';
3
- import { join } from 'node:path';
4
- import { unlink, writeFile } from 'node:fs/promises';
5
1
  import type { STTConfig } from '../types.js';
6
2
 
3
+ /** STT 请求超时(转写大音频可能较慢) */
4
+ const STT_FETCH_TIMEOUT_MS = 60_000;
5
+
6
+ /** mimeType → 文件扩展名映射(OpenAI whisper 按扩展名识别格式) */
7
+ const MIME_TO_EXT: Record<string, string> = {
8
+ 'audio/wav': 'wav',
9
+ 'audio/x-wav': 'wav',
10
+ 'audio/wave': 'wav',
11
+ 'audio/mpeg': 'mp3',
12
+ 'audio/mp3': 'mp3',
13
+ 'audio/ogg': 'ogg',
14
+ 'audio/webm': 'webm',
15
+ 'audio/amr': 'amr',
16
+ 'audio/silk': 'silk',
17
+ 'audio/mp4': 'm4a',
18
+ 'audio/m4a': 'm4a',
19
+ 'audio/x-m4a': 'm4a',
20
+ 'audio/flac': 'flac',
21
+ };
22
+
23
+ export function extFromMimeType(mimeType: string): string {
24
+ const base = mimeType.split(';')[0].trim().toLowerCase();
25
+ return MIME_TO_EXT[base] ?? 'wav';
26
+ }
27
+
7
28
  export async function transcribeWithOpenAI(
8
29
  audioData: Buffer,
9
30
  sttConfig: STTConfig,
@@ -13,30 +34,24 @@ export async function transcribeWithOpenAI(
13
34
  const model = sttConfig.model || 'whisper-1';
14
35
  const apiKey = sttConfig.apiKey || '';
15
36
 
16
- const tmpFile = join(tmpdir(), `zhin-stt-${randomBytes(8).toString('hex')}.wav`);
17
- await writeFile(tmpFile, audioData);
18
-
19
- try {
20
- const ext = mimeType.includes('mp3') ? 'mp3' : 'wav';
21
- const formData = new FormData();
22
- const blob = new Blob([new Uint8Array(audioData)], { type: mimeType });
23
- formData.append('file', blob, `audio.${ext}`);
24
- formData.append('model', model);
25
- formData.append('language', 'zh');
26
-
27
- const response = await fetch(`${host}/v1/audio/transcriptions`, {
28
- method: 'POST',
29
- headers: { Authorization: `Bearer ${apiKey}` },
30
- body: formData,
31
- });
32
-
33
- if (!response.ok) {
34
- throw new Error(`OpenAI STT failed: ${response.status} ${response.statusText}`);
35
- }
36
-
37
- const data = await response.json() as { text?: string };
38
- return data.text?.trim() || '';
39
- } finally {
40
- await unlink(tmpFile).catch(() => {});
37
+ const ext = extFromMimeType(mimeType);
38
+ const formData = new FormData();
39
+ const blob = new Blob([new Uint8Array(audioData)], { type: mimeType });
40
+ formData.append('file', blob, `audio.${ext}`);
41
+ formData.append('model', model);
42
+ formData.append('language', 'zh');
43
+
44
+ const response = await fetch(`${host}/v1/audio/transcriptions`, {
45
+ method: 'POST',
46
+ headers: { Authorization: `Bearer ${apiKey}` },
47
+ body: formData,
48
+ signal: AbortSignal.timeout(STT_FETCH_TIMEOUT_MS),
49
+ });
50
+
51
+ if (!response.ok) {
52
+ throw new Error(`OpenAI STT failed: ${response.status} ${response.statusText}`);
41
53
  }
54
+
55
+ const data = await response.json() as { text?: string };
56
+ return data.text?.trim() || '';
42
57
  }
package/src/tts/azure.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  import type { TTSConfig, TtsProvider, TtsSynthesizeInput, TtsSynthesizeResult } from '../types.js';
2
2
 
3
+ /** TTS 请求超时 */
4
+ const TTS_FETCH_TIMEOUT_MS = 30_000;
5
+
3
6
  /** Azure Cognitive Services text-to-speech REST (output format: audio-16khz-128kbitrate-mono-mp3) */
4
7
  export function createAzureTtsProvider(config: TTSConfig): TtsProvider {
5
8
  return {
@@ -20,6 +23,7 @@ export function createAzureTtsProvider(config: TTSConfig): TtsProvider {
20
23
  'X-Microsoft-OutputFormat': 'audio-16khz-128kbitrate-mono-mp3',
21
24
  },
22
25
  body: ssml,
26
+ signal: AbortSignal.timeout(TTS_FETCH_TIMEOUT_MS),
23
27
  });
24
28
 
25
29
  if (!response.ok) {
package/src/tts/custom.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  import type { SpeechConfig, TTSConfig, TtsProvider, TtsSynthesizeInput, TtsSynthesizeResult } from '../types.js';
2
2
 
3
+ /** TTS 请求超时 */
4
+ const TTS_FETCH_TIMEOUT_MS = 30_000;
5
+
3
6
  function resolveOpenAiApiKey(config: SpeechConfig): string {
4
7
  return config.tts?.apiKey || config.stt?.apiKey || '';
5
8
  }
@@ -37,6 +40,7 @@ export function createCustomTtsProvider(config: SpeechConfig): TtsProvider {
37
40
  method: 'POST',
38
41
  headers,
39
42
  body: JSON.stringify(body),
43
+ signal: AbortSignal.timeout(TTS_FETCH_TIMEOUT_MS),
40
44
  });
41
45
 
42
46
  if (!response.ok) {
package/src/tts/index.ts CHANGED
@@ -4,6 +4,8 @@ import { createCustomTtsProvider } from './custom.js';
4
4
  import { createEdgeTtsProvider } from './edge.js';
5
5
  import { createOpenAiTtsProvider } from './openai.js';
6
6
 
7
+ const VALID_TTS_PROVIDERS: TtsProviderId[] = ['edge', 'openai', 'azure', 'custom'];
8
+
7
9
  export function resolveTtsProvider(config: SpeechConfig, override?: TtsProviderId): TtsProvider {
8
10
  const ttsConfig = config.tts || {};
9
11
  const id = override || ttsConfig.provider || 'edge';
@@ -16,7 +18,10 @@ export function resolveTtsProvider(config: SpeechConfig, override?: TtsProviderI
16
18
  case 'custom':
17
19
  return createCustomTtsProvider(config);
18
20
  case 'edge':
19
- default:
20
21
  return createEdgeTtsProvider(ttsConfig);
22
+ default:
23
+ throw new Error(
24
+ `未知 TTS provider: ${String(id)}。合法值: ${VALID_TTS_PROVIDERS.join(', ')}`,
25
+ );
21
26
  }
22
27
  }
package/src/tts/openai.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  import type { SpeechConfig, TTSConfig, TtsProvider, TtsSynthesizeInput, TtsSynthesizeResult } from '../types.js';
2
2
 
3
+ /** TTS 请求超时 */
4
+ const TTS_FETCH_TIMEOUT_MS = 30_000;
5
+
3
6
  function resolveOpenAiApiKey(config: SpeechConfig): string {
4
7
  return config.tts?.apiKey || config.stt?.apiKey || '';
5
8
  }
@@ -31,6 +34,7 @@ export function createOpenAiTtsProvider(config: SpeechConfig): TtsProvider {
31
34
  'Content-Type': 'application/json',
32
35
  },
33
36
  body: JSON.stringify(body),
37
+ signal: AbortSignal.timeout(TTS_FETCH_TIMEOUT_MS),
34
38
  });
35
39
 
36
40
  if (!response.ok) {