@h-ai/ai 0.1.0-alpha9 → 1.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +647 -0
- package/README.ja.md +647 -0
- package/README.md +477 -1693
- package/dist/ai-audio-types-4R5yUAVl.d.ts +614 -0
- package/dist/ai-audio-ws-protocol-nfhO0tOo.d.ts +7605 -0
- package/dist/browser.d.ts +8 -5
- package/dist/browser.js +3 -2
- package/dist/chunk-GMQFIWND.js +254 -0
- package/dist/chunk-GMQFIWND.js.map +1 -0
- package/dist/chunk-HEVXR252.js +965 -0
- package/dist/chunk-HEVXR252.js.map +1 -0
- package/dist/chunk-HYMP57AC.js +118 -0
- package/dist/chunk-HYMP57AC.js.map +1 -0
- package/dist/client/index.d.ts +63 -231
- package/dist/client/index.js +2 -1
- package/dist/index.d.ts +48 -15
- package/dist/index.js +6715 -1701
- package/dist/index.js.map +1 -1
- package/package.json +43 -21
- package/dist/ai-reasoning-types-CGUgkLF7.d.ts +0 -2823
- package/dist/ai-types-CRCjluXI.d.ts +0 -1095
- package/dist/api/index.d.ts +0 -628
- package/dist/api/index.js +0 -243
- package/dist/api/index.js.map +0 -1
- package/dist/chunk-6PPXWQ7I.js +0 -462
- package/dist/chunk-6PPXWQ7I.js.map +0 -1
- package/dist/chunk-ABP7RKIP.js +0 -311
- package/dist/chunk-ABP7RKIP.js.map +0 -1
|
@@ -0,0 +1,614 @@
|
|
|
1
|
+
import { HaiResult } from '@h-ai/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @h-ai/ai — Audio 子功能公共类型
|
|
5
|
+
*
|
|
6
|
+
* 仅定义语音识别(ASR)与语音合成(TTS)的对外业务类型。
|
|
7
|
+
* 不暴露 WebSocket、SSE、厂商事件、Session/Commit、二进制帧等传输细节,
|
|
8
|
+
* 也不暴露内部 Provider 接口与厂商报文结构。
|
|
9
|
+
* @module audio/ai-audio-types
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 音频编码格式
|
|
14
|
+
*
|
|
15
|
+
* - `pcm16` — 16bit 小端裸 PCM(必须提供 `sampleRate`)
|
|
16
|
+
* - `wav` — WAV 容器(自描述采样率,可不传 `sampleRate`)
|
|
17
|
+
* - `mp3` — MP3 容器
|
|
18
|
+
* - `opus` — Opus 编码
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* import type { AudioFormat } from '@h-ai/ai'
|
|
23
|
+
* function requiresSampleRate(format: AudioFormat) {
|
|
24
|
+
* return format === 'pcm16'
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
type AudioFormat = 'pcm16' | 'wav' | 'mp3' | 'opus';
|
|
29
|
+
/**
|
|
30
|
+
* 完整音频内容(一次性输入/输出)
|
|
31
|
+
*
|
|
32
|
+
* `data` 统一使用二进制字节;Node.js 中的 `Buffer` 可直接作为 `Uint8Array` 传入。
|
|
33
|
+
* Provider 内部会按厂商要求转换为 Base64 / Multipart / 二进制帧,调用方无需感知。
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* import type { AudioContent } from '@h-ai/ai'
|
|
38
|
+
* const silence: AudioContent = { data: new Uint8Array(320), format: 'pcm16', sampleRate: 16000, channels: 1 }
|
|
39
|
+
* const durationMs = silence.data.byteLength / (16000 * 2) * 1000
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
interface AudioContent {
|
|
43
|
+
/** 音频二进制数据 */
|
|
44
|
+
data: Uint8Array;
|
|
45
|
+
/** 音频编码格式 */
|
|
46
|
+
format: AudioFormat;
|
|
47
|
+
/** 采样率(Hz);`pcm16` 等裸音频必填,`wav` / `mp3` 等自描述格式可省略 */
|
|
48
|
+
sampleRate?: number;
|
|
49
|
+
/** 声道数(默认单声道) */
|
|
50
|
+
channels?: 1 | 2;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* 可直接作为浏览器音频响应传输的负载
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```ts
|
|
57
|
+
* import type { PlayableAudio } from '@h-ai/ai'
|
|
58
|
+
* function source(audio: PlayableAudio) {
|
|
59
|
+
* return 'data:' + audio.mimeType + ';base64,' + audio.audioBase64
|
|
60
|
+
* }
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
interface PlayableAudio {
|
|
64
|
+
/** Base64 编码的音频字节 */
|
|
65
|
+
audioBase64: string;
|
|
66
|
+
/** 浏览器可播放的容器格式 */
|
|
67
|
+
format: 'wav' | 'mp3';
|
|
68
|
+
/** 浏览器播放所需的 MIME 类型 */
|
|
69
|
+
mimeType: 'audio/wav' | 'audio/mpeg';
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* 实时音频输入流(持续到达的音频分片)
|
|
73
|
+
*
|
|
74
|
+
* 仅表达「音频分片持续到达」这一业务语义,不包含 WebSocket、Session、Commit 等传输概念。
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```ts
|
|
78
|
+
* import type { AudioInputStream } from '@h-ai/ai'
|
|
79
|
+
* async function* chunks() { yield new Uint8Array(320) }
|
|
80
|
+
* const input: AudioInputStream = { chunks: chunks(), format: 'pcm16', sampleRate: 16000 }
|
|
81
|
+
* for await (const chunk of input.chunks) {
|
|
82
|
+
* const byteLength = chunk.byteLength
|
|
83
|
+
* }
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
interface AudioInputStream {
|
|
87
|
+
/** 持续到达的音频分片 */
|
|
88
|
+
chunks: AsyncIterable<Uint8Array>;
|
|
89
|
+
/** 音频编码格式 */
|
|
90
|
+
format: AudioFormat;
|
|
91
|
+
/** 采样率(Hz) */
|
|
92
|
+
sampleRate: number;
|
|
93
|
+
/** 声道数(默认单声道) */
|
|
94
|
+
channels?: 1 | 2;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* ASR 时间戳粒度
|
|
98
|
+
*
|
|
99
|
+
* - `segment` — 语义段 / 句级时间轴
|
|
100
|
+
* - `word` — 词级时间轴
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```ts
|
|
104
|
+
* import type { TranscriptionOptions, TranscriptionTimestampGranularity } from '@h-ai/ai'
|
|
105
|
+
* const granularity: TranscriptionTimestampGranularity = 'word'
|
|
106
|
+
* const options: TranscriptionOptions = { timestampGranularities: [granularity], strictCapabilities: true }
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
type TranscriptionTimestampGranularity = 'segment' | 'word';
|
|
110
|
+
/**
|
|
111
|
+
* 单个 ASR 词级识别结果
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```ts
|
|
115
|
+
* import type { TranscriptionWord } from '@h-ai/ai'
|
|
116
|
+
* const word: TranscriptionWord = { text: '你好', startMs: 120, endMs: 400 }
|
|
117
|
+
* const durationMs = word.endMs - word.startMs
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
interface TranscriptionWord {
|
|
121
|
+
/** 识别出的文字、单词或 Token */
|
|
122
|
+
text: string;
|
|
123
|
+
/** 起始时间(毫秒) */
|
|
124
|
+
startMs: number;
|
|
125
|
+
/** 结束时间(毫秒) */
|
|
126
|
+
endMs: number;
|
|
127
|
+
/** 置信度(仅 Provider 能提供明确概率语义时返回) */
|
|
128
|
+
confidence?: number;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* ASR 分段结果
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* ```ts
|
|
135
|
+
* import type { TranscriptionSegment } from '@h-ai/ai'
|
|
136
|
+
* const segment: TranscriptionSegment = { id: 'segment-1', text: '你好', startMs: 0, endMs: 400, words: [{ text: '你好', startMs: 120, endMs: 400 }] }
|
|
137
|
+
* const caption = { text: segment.text, startMs: segment.startMs, endMs: segment.endMs }
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
interface TranscriptionSegment {
|
|
141
|
+
/** Provider 返回或 Framework 生成的 Segment ID */
|
|
142
|
+
id?: string;
|
|
143
|
+
/** 当前 Segment 完整文本 */
|
|
144
|
+
text: string;
|
|
145
|
+
/** Segment 起始位置(毫秒) */
|
|
146
|
+
startMs: number;
|
|
147
|
+
/** Segment 结束位置(毫秒) */
|
|
148
|
+
endMs: number;
|
|
149
|
+
/** 当前 Segment 下的词级时间轴 */
|
|
150
|
+
words?: TranscriptionWord[];
|
|
151
|
+
/** 为后续 Speaker Diarization 预留的说话人 ID */
|
|
152
|
+
speakerId?: string;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* ASR 公共请求选项
|
|
156
|
+
*
|
|
157
|
+
* 完整识别与流式识别共用;所有高级能力字段均为可选,不支持的平台会忽略或(在
|
|
158
|
+
* `strictCapabilities` 为真时)提前失败。
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* ```ts
|
|
162
|
+
* import { ai } from '@h-ai/ai'
|
|
163
|
+
* import type { AudioContent, TranscriptionOptions } from '@h-ai/ai'
|
|
164
|
+
* async function transcribe(audio: AudioContent) {
|
|
165
|
+
* const options: TranscriptionOptions = { language: 'zh', contextHints: ['hai-framework'], timestampGranularities: ['word'], strictCapabilities: true }
|
|
166
|
+
* const result = await ai.audio.transcribe({ ...options, audio })
|
|
167
|
+
* if (!result.success) return result
|
|
168
|
+
* return result.data.segments
|
|
169
|
+
* }
|
|
170
|
+
* ```
|
|
171
|
+
*/
|
|
172
|
+
interface TranscriptionOptions {
|
|
173
|
+
/** 语言提示(如 `zh` / `en`;不传时由模型自动检测) */
|
|
174
|
+
language?: string;
|
|
175
|
+
/**
|
|
176
|
+
* 领域提示词 / 热词(如角色名、专有名词、当前主题关键词)
|
|
177
|
+
*
|
|
178
|
+
* Provider 按能力映射为热词表 / phrase list / vocabulary / 提示词;不支持的平台会忽略。
|
|
179
|
+
*/
|
|
180
|
+
contextHints?: string[];
|
|
181
|
+
/** 请求返回的时间戳粒度(`segment` / `word`) */
|
|
182
|
+
timestampGranularities?: TranscriptionTimestampGranularity[];
|
|
183
|
+
/** 是否启用模型 / 服务端 VAD */
|
|
184
|
+
vad?: boolean;
|
|
185
|
+
/** 是否严格要求高级能力全部被支持(true 时不支持则提前失败,false 为 best effort) */
|
|
186
|
+
strictCapabilities?: boolean;
|
|
187
|
+
/** 模型 ID(不传时使用配置中的默认识别模型) */
|
|
188
|
+
model?: string;
|
|
189
|
+
/** 取消信号 */
|
|
190
|
+
signal?: AbortSignal;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* 完整语音识别请求
|
|
194
|
+
*
|
|
195
|
+
* @example
|
|
196
|
+
* ```ts
|
|
197
|
+
* import { ai } from '@h-ai/ai'
|
|
198
|
+
* import type { AudioContent, TranscriptionRequest } from '@h-ai/ai'
|
|
199
|
+
* async function transcribe(audio: AudioContent) {
|
|
200
|
+
* const request: TranscriptionRequest = { audio, language: 'zh' }
|
|
201
|
+
* const result = await ai.audio.transcribe(request)
|
|
202
|
+
* if (!result.success) return result
|
|
203
|
+
* return result.data.text
|
|
204
|
+
* }
|
|
205
|
+
* ```
|
|
206
|
+
*/
|
|
207
|
+
interface TranscriptionRequest extends TranscriptionOptions {
|
|
208
|
+
/** 待识别的完整音频 */
|
|
209
|
+
audio: AudioContent;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* 流式语音识别请求(支持完整音频或持续音频输入)
|
|
213
|
+
*
|
|
214
|
+
* @example
|
|
215
|
+
* ```ts
|
|
216
|
+
* import { ai } from '@h-ai/ai'
|
|
217
|
+
* import type { AudioInputStream, TranscriptionStreamRequest } from '@h-ai/ai'
|
|
218
|
+
* async function transcribe(audio: AudioInputStream) {
|
|
219
|
+
* const request: TranscriptionStreamRequest = { audio, language: 'zh' }
|
|
220
|
+
* // 流式失败通过异步迭代抛出;调用边界应处理异常和取消。
|
|
221
|
+
* for await (const event of ai.audio.transcribeStream(request)) {
|
|
222
|
+
* if (event.type === 'transcript' && event.final) return event.text
|
|
223
|
+
* }
|
|
224
|
+
* }
|
|
225
|
+
* ```
|
|
226
|
+
*/
|
|
227
|
+
interface TranscriptionStreamRequest extends TranscriptionOptions {
|
|
228
|
+
/** 完整音频,或持续到达的音频输入流 */
|
|
229
|
+
audio: AudioContent | AudioInputStream;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* 完整语音识别结果
|
|
233
|
+
*
|
|
234
|
+
* @example
|
|
235
|
+
* ```ts
|
|
236
|
+
* import type { TranscriptionResult } from '@h-ai/ai'
|
|
237
|
+
* function captions(result: TranscriptionResult) {
|
|
238
|
+
* return (result.segments ?? []).map(segment => ({ text: segment.text, startMs: segment.startMs, endMs: segment.endMs }))
|
|
239
|
+
* }
|
|
240
|
+
* ```
|
|
241
|
+
*/
|
|
242
|
+
interface TranscriptionResult {
|
|
243
|
+
/** 识别文本 */
|
|
244
|
+
text: string;
|
|
245
|
+
/** 实际检测或使用的语言 */
|
|
246
|
+
language?: string;
|
|
247
|
+
/** 输入音频总时长(毫秒) */
|
|
248
|
+
durationMs?: number;
|
|
249
|
+
/**
|
|
250
|
+
* 结构化识别时间轴
|
|
251
|
+
*
|
|
252
|
+
* Word Timestamp 统一存储在 `segments[].words`,不在顶层重复。
|
|
253
|
+
*/
|
|
254
|
+
segments?: TranscriptionSegment[];
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* 流式语音识别领域事件
|
|
258
|
+
*
|
|
259
|
+
* 统一的语音领域事件(非厂商协议)。支持服务端 VAD 的平台(如 Qwen 实时识别)会在检测到
|
|
260
|
+
* 语音起止时额外产出 `speech_started` / `speech_stopped`,使调用方可在「开始说话」的瞬间做出
|
|
261
|
+
* 反应(如取消当前上游生成),而无需自行运行 VAD;不产出 VAD 事件的平台(如豆包)仅产出
|
|
262
|
+
* `transcript`,此时是否需要 VAD 由调用方自行决定。
|
|
263
|
+
*
|
|
264
|
+
* `transcript.text` 表示当前语句的完整识别文本(非字符增量),实时 ASR 会修订前一次临时结果,
|
|
265
|
+
* 调用方可直接用 `text` 覆盖当前临时文本。
|
|
266
|
+
*
|
|
267
|
+
* @example
|
|
268
|
+
* ```ts
|
|
269
|
+
* import type { TranscriptionEvent } from '@h-ai/ai'
|
|
270
|
+
* function apply(event: TranscriptionEvent, previous: string) {
|
|
271
|
+
* return event.type === 'transcript' ? event.text : previous // text 是当前完整文本,应覆盖而非追加。
|
|
272
|
+
* }
|
|
273
|
+
* ```
|
|
274
|
+
*/
|
|
275
|
+
type TranscriptionEvent = {
|
|
276
|
+
type: 'speech_started';
|
|
277
|
+
} | {
|
|
278
|
+
type: 'transcript';
|
|
279
|
+
text: string;
|
|
280
|
+
final: boolean;
|
|
281
|
+
startMs?: number;
|
|
282
|
+
endMs?: number;
|
|
283
|
+
words?: TranscriptionWord[];
|
|
284
|
+
} | {
|
|
285
|
+
type: 'speech_stopped';
|
|
286
|
+
};
|
|
287
|
+
/**
|
|
288
|
+
* TTS 通用参考音频
|
|
289
|
+
*
|
|
290
|
+
* 不绑定任何具体 TTS 模型。既可表达「谁在说话」(说话人参考),也可表达「怎么说」(风格参考),
|
|
291
|
+
* 由 `SynthesisOptions.speakerReference` / `styleReference` 决定用途。
|
|
292
|
+
*
|
|
293
|
+
* @example
|
|
294
|
+
* ```ts
|
|
295
|
+
* import type { AudioContent, AudioReference } from '@h-ai/ai'
|
|
296
|
+
* function reference(audio: AudioContent): AudioReference {
|
|
297
|
+
* return { audio, transcript: '你好,欢迎使用', language: 'zh' }
|
|
298
|
+
* }
|
|
299
|
+
* ```
|
|
300
|
+
*/
|
|
301
|
+
interface AudioReference {
|
|
302
|
+
/** 参考音频 */
|
|
303
|
+
audio: AudioContent;
|
|
304
|
+
/** 参考音频对应文本(仅需要参考 Transcript 的 Provider 使用) */
|
|
305
|
+
transcript?: string;
|
|
306
|
+
/** 参考音频语言 */
|
|
307
|
+
language?: string;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* TTS 公共请求选项
|
|
311
|
+
*
|
|
312
|
+
* 完整合成与流式合成共用;所有高级能力字段均为可选,不支持的平台会忽略或(在
|
|
313
|
+
* `strictCapabilities` 为真时)提前失败。
|
|
314
|
+
*
|
|
315
|
+
* @example
|
|
316
|
+
* ```ts
|
|
317
|
+
* import { ai } from '@h-ai/ai'
|
|
318
|
+
* import type { SynthesisOptions } from '@h-ai/ai'
|
|
319
|
+
* async function speak() {
|
|
320
|
+
* const options: SynthesisOptions = { language: 'zh', format: 'mp3', speed: 1.1 }
|
|
321
|
+
* const result = await ai.audio.synthesize({ ...options, text: '欢迎使用' })
|
|
322
|
+
* if (!result.success) return result
|
|
323
|
+
* return result.data
|
|
324
|
+
* }
|
|
325
|
+
* ```
|
|
326
|
+
*/
|
|
327
|
+
interface SynthesisOptions {
|
|
328
|
+
/** 目标语言 */
|
|
329
|
+
language?: string;
|
|
330
|
+
/** 音色(厂商音色名,不传时使用模型默认音色) */
|
|
331
|
+
voice?: string;
|
|
332
|
+
/** 说话人 / 音色参考(表达「谁在说话」) */
|
|
333
|
+
speakerReference?: AudioReference;
|
|
334
|
+
/** 风格、情绪、韵律参考(表达「怎么说」) */
|
|
335
|
+
styleReference?: AudioReference;
|
|
336
|
+
/** 风格参考影响强度,统一为 `[0, 1]` */
|
|
337
|
+
styleStrength?: number;
|
|
338
|
+
/**
|
|
339
|
+
* 自然语言风格指令(如语速、情绪、角色语气)
|
|
340
|
+
*
|
|
341
|
+
* Provider 按能力映射(如 MiMo 放入 user 消息、Qwen instructions);不支持的平台会忽略。
|
|
342
|
+
*/
|
|
343
|
+
instruction?: string;
|
|
344
|
+
/**
|
|
345
|
+
* 语速倍数
|
|
346
|
+
*
|
|
347
|
+
* `1.0` 正常、`>1` 更快、`<1` 更慢。
|
|
348
|
+
*/
|
|
349
|
+
speed?: number;
|
|
350
|
+
/** 输出音频格式(不传时使用模型默认格式) */
|
|
351
|
+
format?: AudioFormat;
|
|
352
|
+
/** 输出采样率(Hz) */
|
|
353
|
+
sampleRate?: number;
|
|
354
|
+
/** 模型 ID(不传时使用配置中的默认合成模型) */
|
|
355
|
+
model?: string;
|
|
356
|
+
/** 是否严格要求高级能力全部被支持(true 时不支持则提前失败,false 为 best effort) */
|
|
357
|
+
strictCapabilities?: boolean;
|
|
358
|
+
/** 取消信号 */
|
|
359
|
+
signal?: AbortSignal;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* 完整语音合成请求
|
|
363
|
+
*
|
|
364
|
+
* @example
|
|
365
|
+
* ```ts
|
|
366
|
+
* import { ai } from '@h-ai/ai'
|
|
367
|
+
* import type { SynthesisRequest } from '@h-ai/ai'
|
|
368
|
+
* async function speak() {
|
|
369
|
+
* const request: SynthesisRequest = { text: '欢迎使用', format: 'mp3' }
|
|
370
|
+
* const result = await ai.audio.synthesize(request)
|
|
371
|
+
* if (!result.success) return result
|
|
372
|
+
* return result.data.data
|
|
373
|
+
* }
|
|
374
|
+
* ```
|
|
375
|
+
*/
|
|
376
|
+
interface SynthesisRequest extends SynthesisOptions {
|
|
377
|
+
/** 待合成文本 */
|
|
378
|
+
text: string;
|
|
379
|
+
/**
|
|
380
|
+
* 最终音频目标时长(毫秒)
|
|
381
|
+
*
|
|
382
|
+
* 表达 Framework 业务目标,不对应某个模型私有参数;由支持的 Provider / Model Service 闭环逼近。
|
|
383
|
+
*/
|
|
384
|
+
targetDurationMs?: number;
|
|
385
|
+
/** 目标时长允许误差(毫秒) */
|
|
386
|
+
durationToleranceMs?: number;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* 带稳定 ID 的合成文本段
|
|
390
|
+
*
|
|
391
|
+
* @example
|
|
392
|
+
* ```ts
|
|
393
|
+
* import type { SynthesisTextSegment } from '@h-ai/ai'
|
|
394
|
+
* const segment: SynthesisTextSegment = { id: 'turn-1-segment-1', text: '欢迎使用。' }
|
|
395
|
+
* const playbackKey = segment.id // 以同一稳定 ID 关联后续音频和播放确认。
|
|
396
|
+
* ```
|
|
397
|
+
*/
|
|
398
|
+
interface SynthesisTextSegment {
|
|
399
|
+
/** 调用方分配的稳定 ID,用于关联文本、音频与播放完成状态 */
|
|
400
|
+
id: string;
|
|
401
|
+
/** 本段待合成文本 */
|
|
402
|
+
text: string;
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* 流式语音合成请求(支持单段或持续文本段输入)
|
|
406
|
+
*
|
|
407
|
+
* @example
|
|
408
|
+
* ```ts
|
|
409
|
+
* import { ai } from '@h-ai/ai'
|
|
410
|
+
* import type { SynthesisStreamRequest } from '@h-ai/ai'
|
|
411
|
+
* const request: SynthesisStreamRequest = { text: { id: 'segment-1', text: '欢迎使用。' }, format: 'mp3' }
|
|
412
|
+
* for await (const event of ai.audio.synthesizeStream(request)) {
|
|
413
|
+
* if (event.type === 'segment_done') {
|
|
414
|
+
* const generatedSegmentId = event.segmentId // 生成完成仍不等于终端已播放。
|
|
415
|
+
* }
|
|
416
|
+
* }
|
|
417
|
+
* ```
|
|
418
|
+
*/
|
|
419
|
+
interface SynthesisStreamRequest extends SynthesisOptions {
|
|
420
|
+
/** 单个文本段,或持续到达的文本段流 */
|
|
421
|
+
text: SynthesisTextSegment | AsyncIterable<SynthesisTextSegment>;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* 完整语音合成结果
|
|
425
|
+
*
|
|
426
|
+
* @example
|
|
427
|
+
* ```ts
|
|
428
|
+
* import { serializePlayableAudio } from '@h-ai/ai'
|
|
429
|
+
* import type { SynthesisResult } from '@h-ai/ai'
|
|
430
|
+
* function playable(result: SynthesisResult) {
|
|
431
|
+
* const serialized = serializePlayableAudio(result)
|
|
432
|
+
* if (!serialized.success) return serialized
|
|
433
|
+
* return serialized.data.mimeType
|
|
434
|
+
* }
|
|
435
|
+
* ```
|
|
436
|
+
*/
|
|
437
|
+
interface SynthesisResult extends AudioContent {
|
|
438
|
+
/** 实际输出音频时长(毫秒) */
|
|
439
|
+
durationMs?: number;
|
|
440
|
+
/** 通用生成元数据 */
|
|
441
|
+
metadata?: {
|
|
442
|
+
/** 是否满足调用方指定的时长容差(无法判断时保持 undefined) */
|
|
443
|
+
durationMatched?: boolean;
|
|
444
|
+
/** Provider 最终使用的 Framework speed */
|
|
445
|
+
speed?: number;
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* 流式语音合成领域事件
|
|
450
|
+
*
|
|
451
|
+
* 每个文本段严格按 `segment_started → audio* → segment_done` 顺序产出,调用方可在
|
|
452
|
+
* 对应音频真正播放完成后提交该段文本,不需要按字节数反推文本边界。
|
|
453
|
+
*
|
|
454
|
+
* `segment_started` 携带服务端解析 Provider 后的**真实输出音频参数**(format / sampleRate / channels),
|
|
455
|
+
* 调用方据此正确解码、设置 PCM 播放参数、选择保存扩展名,而非根据请求参数猜测。
|
|
456
|
+
*
|
|
457
|
+
* @example
|
|
458
|
+
* ```ts
|
|
459
|
+
* import type { SynthesisEvent } from '@h-ai/ai'
|
|
460
|
+
* function audioBytes(event: SynthesisEvent) {
|
|
461
|
+
* if (event.type === 'audio') return { segmentId: event.segmentId, bytes: event.data }
|
|
462
|
+
* if (event.type === 'segment_started') return { segmentId: event.segmentId, format: event.format }
|
|
463
|
+
* return { generatedSegmentId: event.segmentId }
|
|
464
|
+
* }
|
|
465
|
+
* ```
|
|
466
|
+
*/
|
|
467
|
+
type SynthesisEvent = {
|
|
468
|
+
type: 'segment_started';
|
|
469
|
+
segmentId: string;
|
|
470
|
+
text: string;
|
|
471
|
+
format: AudioFormat;
|
|
472
|
+
sampleRate?: number;
|
|
473
|
+
channels?: 1 | 2;
|
|
474
|
+
} | {
|
|
475
|
+
type: 'audio';
|
|
476
|
+
segmentId: string;
|
|
477
|
+
data: Uint8Array;
|
|
478
|
+
} | {
|
|
479
|
+
type: 'segment_done';
|
|
480
|
+
segmentId: string;
|
|
481
|
+
};
|
|
482
|
+
/**
|
|
483
|
+
* 语音模型的实时能力声明
|
|
484
|
+
*
|
|
485
|
+
* 由 `ai.audio.getCapabilities({ operation, model })` 返回,供实时会话在启动前校验:不同平台对「持续音频输入 /
|
|
486
|
+
* 服务端 VAD / 增量文本输入 / 流式输出」的原生支持不同,同一方法签名在不同平台下的实时语义并不一致。
|
|
487
|
+
* 调用方应据此选择模型或调整策略(如实时 ASR 要求 `realtimeAudioInput` 与 `speechBoundaryEvents`,
|
|
488
|
+
* 实时 TTS 要求 `streamingAudioOutput`)。
|
|
489
|
+
*
|
|
490
|
+
* @example
|
|
491
|
+
* ```ts
|
|
492
|
+
* import { ai } from '@h-ai/ai'
|
|
493
|
+
* import type { AudioModelCapabilities } from '@h-ai/ai'
|
|
494
|
+
* function supportsRealtime(capabilities: AudioModelCapabilities) {
|
|
495
|
+
* return Boolean(capabilities.transcribe?.realtimeAudioInput && capabilities.transcribe.speechBoundaryEvents)
|
|
496
|
+
* }
|
|
497
|
+
* const result = ai.audio.getCapabilities({ operation: 'transcribe' })
|
|
498
|
+
* if (result.success) {
|
|
499
|
+
* const realtime = supportsRealtime(result.data)
|
|
500
|
+
* }
|
|
501
|
+
* ```
|
|
502
|
+
*/
|
|
503
|
+
interface AudioModelCapabilities {
|
|
504
|
+
/** 语音识别能力;模型未声明识别操作时不返回 */
|
|
505
|
+
transcribe?: {
|
|
506
|
+
/** 是否支持 ASR */
|
|
507
|
+
supported: boolean;
|
|
508
|
+
/** 是否原生支持持续实时音频输入 */
|
|
509
|
+
realtimeAudioInput: boolean;
|
|
510
|
+
/** 是否产生语音边界事件(speech_started / speech_stopped) */
|
|
511
|
+
speechBoundaryEvents: boolean;
|
|
512
|
+
/** 是否原生输出增量 Transcript */
|
|
513
|
+
streamingTranscriptOutput: boolean;
|
|
514
|
+
/** 是否接受 Language Hint */
|
|
515
|
+
languageHint?: boolean;
|
|
516
|
+
/** 是否支持自动语言检测 */
|
|
517
|
+
languageDetection?: boolean;
|
|
518
|
+
/** 是否支持 Segment Timestamp */
|
|
519
|
+
segmentTimestamps?: boolean;
|
|
520
|
+
/** 是否支持 Word Timestamp */
|
|
521
|
+
wordTimestamps?: boolean;
|
|
522
|
+
/** 是否支持 Context Hints */
|
|
523
|
+
contextHints?: boolean;
|
|
524
|
+
/** 是否支持 VAD */
|
|
525
|
+
vad?: boolean;
|
|
526
|
+
/** 是否支持 Speaker Diarization */
|
|
527
|
+
speakerDiarization?: boolean;
|
|
528
|
+
};
|
|
529
|
+
/** 语音合成能力;模型未声明合成操作时不返回 */
|
|
530
|
+
synthesize?: {
|
|
531
|
+
/** 是否支持 TTS */
|
|
532
|
+
supported: boolean;
|
|
533
|
+
/** 是否原生支持增量文本输入 */
|
|
534
|
+
incrementalTextInput: boolean;
|
|
535
|
+
/** 是否原生支持流式音频输出 */
|
|
536
|
+
streamingAudioOutput: boolean;
|
|
537
|
+
/** 是否支持指定目标语言 */
|
|
538
|
+
languageSelection?: boolean;
|
|
539
|
+
/** 是否支持预置 Voice */
|
|
540
|
+
presetVoice?: boolean;
|
|
541
|
+
/** 是否支持 Speaker Reference */
|
|
542
|
+
speakerReference?: boolean;
|
|
543
|
+
/** 是否必须提供 Speaker Reference */
|
|
544
|
+
speakerReferenceRequired?: boolean;
|
|
545
|
+
/** 是否支持 Style Reference */
|
|
546
|
+
styleReference?: boolean;
|
|
547
|
+
/** 是否支持通用自然语言 Instruction */
|
|
548
|
+
instruction?: boolean;
|
|
549
|
+
/** 是否支持 Framework Speed */
|
|
550
|
+
speedControl?: boolean;
|
|
551
|
+
/** 是否支持 Framework Target Duration */
|
|
552
|
+
targetDuration?: boolean;
|
|
553
|
+
/** 已知支持语言 */
|
|
554
|
+
supportedLanguages?: string[];
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* 查询语音模型能力的参数
|
|
559
|
+
*
|
|
560
|
+
* @example
|
|
561
|
+
* ```ts
|
|
562
|
+
* import { ai } from '@h-ai/ai'
|
|
563
|
+
* import type { AudioCapabilitiesRequest } from '@h-ai/ai'
|
|
564
|
+
* const request: AudioCapabilitiesRequest = { operation: 'synthesize' }
|
|
565
|
+
* const result = ai.audio.getCapabilities(request)
|
|
566
|
+
* if (result.success) {
|
|
567
|
+
* const streaming = result.data.synthesize?.streamingAudioOutput ?? false
|
|
568
|
+
* }
|
|
569
|
+
* ```
|
|
570
|
+
*/
|
|
571
|
+
interface AudioCapabilitiesRequest {
|
|
572
|
+
/** 要查询的操作,决定默认模型和返回的能力分支 */
|
|
573
|
+
operation: 'transcribe' | 'synthesize';
|
|
574
|
+
/** 模型 ID;不传时使用该操作配置的默认模型 */
|
|
575
|
+
model?: string;
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Audio 操作接口(通过 `ai.audio` 访问)
|
|
579
|
+
*
|
|
580
|
+
* 提供语音识别与语音合成的完整与流式能力。普通方法返回 `HaiResult`;
|
|
581
|
+
* 流式方法返回 `AsyncIterable`,迭代期间发生的连接/协议/上游错误会终止异步迭代(抛出异常)。
|
|
582
|
+
*
|
|
583
|
+
* @example
|
|
584
|
+
* ```ts
|
|
585
|
+
* import { ai, serializePlayableAudio } from '@h-ai/ai'
|
|
586
|
+
* async function speak() {
|
|
587
|
+
* // ai 已由应用初始化;非流式方法逐层检查 HaiResult。
|
|
588
|
+
* const result = await ai.audio.synthesize({ text: '欢迎使用', format: 'mp3' })
|
|
589
|
+
* if (!result.success) return result
|
|
590
|
+
* const playable = serializePlayableAudio(result.data)
|
|
591
|
+
* if (!playable.success) return playable
|
|
592
|
+
* return playable.data
|
|
593
|
+
* }
|
|
594
|
+
* ```
|
|
595
|
+
*/
|
|
596
|
+
interface AudioOperations {
|
|
597
|
+
/** 将完整音频识别为完整文本 */
|
|
598
|
+
transcribe: (request: TranscriptionRequest) => Promise<HaiResult<TranscriptionResult>>;
|
|
599
|
+
/** 持续输入音频或增量返回识别文本(含语音起止领域事件) */
|
|
600
|
+
transcribeStream: (request: TranscriptionStreamRequest) => AsyncIterable<TranscriptionEvent>;
|
|
601
|
+
/** 将完整文本合成为完整音频 */
|
|
602
|
+
synthesize: (request: SynthesisRequest) => Promise<HaiResult<SynthesisResult>>;
|
|
603
|
+
/** 持续输入文本段并按段输出结构化音频事件 */
|
|
604
|
+
synthesizeStream: (request: SynthesisStreamRequest) => AsyncIterable<SynthesisEvent>;
|
|
605
|
+
/**
|
|
606
|
+
* 查询指定模型的实时能力声明
|
|
607
|
+
*
|
|
608
|
+
* @param request - 操作类型与可选模型 ID
|
|
609
|
+
* @returns 该模型对应操作的能力;模型不存在或操作不匹配时返回失败结果
|
|
610
|
+
*/
|
|
611
|
+
getCapabilities: (request: AudioCapabilitiesRequest) => HaiResult<AudioModelCapabilities>;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
export type { AudioCapabilitiesRequest as A, PlayableAudio as P, SynthesisEvent as S, TranscriptionEvent as T, AudioContent as a, AudioFormat as b, AudioInputStream as c, AudioModelCapabilities as d, AudioOperations as e, AudioReference as f, SynthesisOptions as g, SynthesisRequest as h, SynthesisResult as i, SynthesisStreamRequest as j, SynthesisTextSegment as k, TranscriptionOptions as l, TranscriptionRequest as m, TranscriptionResult as n, TranscriptionSegment as o, TranscriptionStreamRequest as p, TranscriptionTimestampGranularity as q, TranscriptionWord as r };
|