@yh-ui/hooks 0.1.12 → 0.1.16

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 CHANGED
@@ -608,6 +608,37 @@ const qwenParser = (raw) => {
608
608
  }
609
609
  return text || null;
610
610
  };
611
+ const claudeParser = (raw) => {
612
+ const lines = raw.split("\n");
613
+ let text = "";
614
+ for (const line of lines) {
615
+ if (!line.startsWith("data: ")) continue;
616
+ const data = line.slice(6).trim();
617
+ try {
618
+ const json = JSON.parse(data);
619
+ if (json?.type === "content_block_delta" && json?.delta?.text) {
620
+ text += json.delta.text;
621
+ }
622
+ } catch {
623
+ }
624
+ }
625
+ return text || null;
626
+ };
627
+ const geminiParser = (raw) => {
628
+ const lines = raw.split("\n");
629
+ let text = "";
630
+ for (const line of lines) {
631
+ const content = line.startsWith("data: ") ? line.slice(6).trim() : line.trim();
632
+ if (!content) continue;
633
+ try {
634
+ const json = JSON.parse(content);
635
+ const part = json?.candidates?.[0]?.content?.parts?.[0]?.text;
636
+ if (part) text += part;
637
+ } catch {
638
+ }
639
+ }
640
+ return text || null;
641
+ };
611
642
  const plainTextParser = (raw) => raw || null;
612
643
  class TypewriterThrottle {
613
644
  queue = [];
@@ -731,7 +762,14 @@ function useAiStream(options) {
731
762
  fetchStream,
732
763
  stop,
733
764
  // 暴露解析器供测试/自定义使用
734
- parsers: { openaiParser, ernieParser, qwenParser, plainTextParser }
765
+ parsers: {
766
+ openaiParser,
767
+ ernieParser,
768
+ qwenParser,
769
+ claudeParser,
770
+ geminiParser,
771
+ plainTextParser
772
+ }
735
773
  };
736
774
  }
737
775
 
@@ -1157,13 +1195,259 @@ function useAiConversations(options = {}) {
1157
1195
  };
1158
1196
  }
1159
1197
 
1198
+ function useAiRequest(options) {
1199
+ const loading = vue.ref(false);
1200
+ const data = vue.ref("");
1201
+ const error = vue.ref(null);
1202
+ const send = async (query, ...args) => {
1203
+ loading.value = true;
1204
+ error.value = null;
1205
+ try {
1206
+ const result = await options.request(query, ...args);
1207
+ data.value = result;
1208
+ options.onSuccess?.(result);
1209
+ return result;
1210
+ } catch (e) {
1211
+ const err = e instanceof Error ? e : new Error(String(e));
1212
+ error.value = err;
1213
+ options.onError?.(err);
1214
+ throw err;
1215
+ } finally {
1216
+ loading.value = false;
1217
+ }
1218
+ };
1219
+ return {
1220
+ loading,
1221
+ data,
1222
+ error,
1223
+ send
1224
+ };
1225
+ }
1226
+
1227
+ function useAiVoice(options = {}) {
1228
+ const {
1229
+ language = "zh-CN",
1230
+ interimResults = true,
1231
+ continuous = false,
1232
+ vad = true,
1233
+ vadThreshold = 2e3,
1234
+ volumeThreshold = 0.05,
1235
+ waveCount = 20,
1236
+ useSTT = true
1237
+ } = options;
1238
+ const isRecording = vue.ref(false);
1239
+ const transcript = vue.ref("");
1240
+ const interimTranscript = vue.ref("");
1241
+ const volume = vue.ref(0);
1242
+ const amplitudes = vue.ref(new Array(waveCount).fill(5));
1243
+ const audioBlob = vue.ref(null);
1244
+ const recognition = vue.shallowRef(null);
1245
+ const audioContext = vue.shallowRef(null);
1246
+ const analyser = vue.shallowRef(null);
1247
+ const stream = vue.shallowRef(null);
1248
+ const mediaRecorder = vue.shallowRef(null);
1249
+ let chunks = [];
1250
+ let animationId = null;
1251
+ let silenceStart = null;
1252
+ const _window = typeof window !== "undefined" ? window : null;
1253
+ const SpeechRecognition = _window?.SpeechRecognition || _window?.webkitSpeechRecognition;
1254
+ const sttSupported = !!SpeechRecognition;
1255
+ const initMediaRecorder = (mediaStream) => {
1256
+ chunks = [];
1257
+ const recorder = new MediaRecorder(mediaStream);
1258
+ recorder.ondataavailable = (e) => {
1259
+ if (e.data.size > 0) chunks.push(e.data);
1260
+ };
1261
+ recorder.onstop = () => {
1262
+ audioBlob.value = new Blob(chunks, { type: "audio/webm" });
1263
+ if (isRecording.value === false && chunks.length > 0) {
1264
+ options.onStop?.(transcript.value, audioBlob.value);
1265
+ }
1266
+ };
1267
+ mediaRecorder.value = recorder;
1268
+ };
1269
+ const initRecognition = () => {
1270
+ if (!sttSupported || !useSTT) return;
1271
+ const recognitionInstance = new SpeechRecognition();
1272
+ recognitionInstance.lang = language;
1273
+ recognitionInstance.interimResults = interimResults;
1274
+ recognitionInstance.continuous = continuous;
1275
+ recognitionInstance.onresult = (event) => {
1276
+ let currentInterim = "";
1277
+ for (let i = event.resultIndex; i < event.results.length; ++i) {
1278
+ if (event.results[i].isFinal) {
1279
+ transcript.value += event.results[i][0].transcript;
1280
+ options.onResult?.(transcript.value);
1281
+ } else {
1282
+ currentInterim += event.results[i][0].transcript;
1283
+ }
1284
+ }
1285
+ interimTranscript.value = currentInterim;
1286
+ options.onPartialResult?.(currentInterim);
1287
+ };
1288
+ recognitionInstance.onerror = (event) => {
1289
+ if (event.error !== "no-speech" && event.error !== "aborted") {
1290
+ options.onError?.(event);
1291
+ }
1292
+ };
1293
+ recognition.value = recognitionInstance;
1294
+ };
1295
+ const initAudioAnalyzer = async (mediaStream) => {
1296
+ try {
1297
+ const AudioCtx = window.AudioContext || window.webkitAudioContext;
1298
+ audioContext.value = new AudioCtx();
1299
+ if (audioContext.value.state === "suspended") {
1300
+ await audioContext.value.resume();
1301
+ }
1302
+ analyser.value = audioContext.value.createAnalyser();
1303
+ analyser.value.fftSize = 256;
1304
+ const source = audioContext.value.createMediaStreamSource(mediaStream);
1305
+ source.connect(analyser.value);
1306
+ const bufferLength = analyser.value.frequencyBinCount;
1307
+ const dataArray = new Uint8Array(bufferLength);
1308
+ const process = () => {
1309
+ if (!isRecording.value) {
1310
+ amplitudes.value = new Array(waveCount).fill(5);
1311
+ volume.value = 0;
1312
+ return;
1313
+ }
1314
+ animationId = requestAnimationFrame(process);
1315
+ analyser.value.getByteFrequencyData(dataArray);
1316
+ let total = 0;
1317
+ for (let i = 0; i < bufferLength; i++) total += dataArray[i];
1318
+ const avg = total / bufferLength;
1319
+ volume.value = Math.min(100, avg / 128 * 100);
1320
+ const step = Math.floor(bufferLength / waveCount);
1321
+ const newAmps = [];
1322
+ for (let i = 0; i < waveCount; i++) {
1323
+ const val = dataArray[i * step];
1324
+ newAmps.push(6 + val / 255 * 34);
1325
+ }
1326
+ amplitudes.value = newAmps;
1327
+ if (vad) {
1328
+ const normalizedVol = avg / 255;
1329
+ if (normalizedVol < volumeThreshold) {
1330
+ if (silenceStart === null) silenceStart = Date.now();
1331
+ else if (Date.now() - silenceStart > vadThreshold) {
1332
+ stop();
1333
+ }
1334
+ } else {
1335
+ silenceStart = null;
1336
+ }
1337
+ }
1338
+ };
1339
+ process();
1340
+ } catch (err) {
1341
+ options.onError?.(err);
1342
+ }
1343
+ };
1344
+ const start = async () => {
1345
+ if (isRecording.value) return;
1346
+ try {
1347
+ transcript.value = "";
1348
+ interimTranscript.value = "";
1349
+ audioBlob.value = null;
1350
+ silenceStart = null;
1351
+ stream.value = await navigator.mediaDevices.getUserMedia({ audio: true });
1352
+ isRecording.value = true;
1353
+ initMediaRecorder(stream.value);
1354
+ initRecognition();
1355
+ await initAudioAnalyzer(stream.value);
1356
+ mediaRecorder.value?.start(1e3);
1357
+ recognition.value?.start();
1358
+ options.onStart?.();
1359
+ } catch (err) {
1360
+ isRecording.value = false;
1361
+ if (stream.value) {
1362
+ stream.value.getTracks().forEach((t) => t.stop());
1363
+ stream.value = null;
1364
+ }
1365
+ console.error("[yh-ui/hooks] useAiVoice start failed:", err);
1366
+ options.onError?.(err);
1367
+ }
1368
+ };
1369
+ const stop = () => {
1370
+ if (!isRecording.value) return;
1371
+ isRecording.value = false;
1372
+ if (stream.value) {
1373
+ stream.value.getTracks().forEach((track) => track.stop());
1374
+ stream.value = null;
1375
+ }
1376
+ if (recognition.value) {
1377
+ try {
1378
+ recognition.value.stop();
1379
+ } catch {
1380
+ }
1381
+ }
1382
+ if (mediaRecorder.value && mediaRecorder.value.state !== "inactive") {
1383
+ try {
1384
+ mediaRecorder.value.stop();
1385
+ } catch {
1386
+ }
1387
+ }
1388
+ cleanup();
1389
+ };
1390
+ const cancel = () => {
1391
+ if (!isRecording.value) return;
1392
+ isRecording.value = false;
1393
+ if (stream.value) {
1394
+ stream.value.getTracks().forEach((track) => track.stop());
1395
+ stream.value = null;
1396
+ }
1397
+ if (recognition.value) {
1398
+ try {
1399
+ recognition.value.abort();
1400
+ } catch {
1401
+ }
1402
+ }
1403
+ if (mediaRecorder.value && mediaRecorder.value.state !== "inactive") {
1404
+ try {
1405
+ mediaRecorder.value.stop();
1406
+ } catch {
1407
+ }
1408
+ }
1409
+ cleanup();
1410
+ };
1411
+ const cleanup = () => {
1412
+ if (animationId) {
1413
+ cancelAnimationFrame(animationId);
1414
+ animationId = null;
1415
+ }
1416
+ if (audioContext.value && audioContext.value.state !== "closed") {
1417
+ audioContext.value.close().catch((_err) => {
1418
+ });
1419
+ audioContext.value = null;
1420
+ }
1421
+ amplitudes.value = new Array(waveCount).fill(5);
1422
+ volume.value = 0;
1423
+ };
1424
+ vue.onUnmounted(() => {
1425
+ if (isRecording.value) stop();
1426
+ else cleanup();
1427
+ });
1428
+ return {
1429
+ isRecording,
1430
+ transcript,
1431
+ interimTranscript,
1432
+ amplitudes,
1433
+ volume,
1434
+ audioBlob,
1435
+ start,
1436
+ stop,
1437
+ cancel,
1438
+ sttSupported
1439
+ };
1440
+ }
1441
+
1160
1442
  exports.FormContextKey = FormContextKey;
1161
1443
  exports.FormItemContextKey = FormItemContextKey;
1162
1444
  exports.IndexedDBAdapter = IndexedDBAdapter;
1445
+ exports.claudeParser = claudeParser;
1163
1446
  exports.configProviderContextKey = configProviderContextKey;
1164
1447
  exports.createZIndexCounter = createZIndexCounter;
1165
1448
  exports.defaultNamespace = defaultNamespace;
1166
1449
  exports.ernieParser = ernieParser;
1450
+ exports.geminiParser = geminiParser;
1167
1451
  exports.getDayjsLocale = getDayjsLocale;
1168
1452
  exports.getNextZIndex = getNextZIndex;
1169
1453
  exports.idInjectionKey = idInjectionKey;
@@ -1178,7 +1462,9 @@ exports.setDayjsLocaleSync = setDayjsLocaleSync;
1178
1462
  exports.updateDayjsMonths = updateDayjsMonths;
1179
1463
  exports.useAiChat = useAiChat;
1180
1464
  exports.useAiConversations = useAiConversations;
1465
+ exports.useAiRequest = useAiRequest;
1181
1466
  exports.useAiStream = useAiStream;
1467
+ exports.useAiVoice = useAiVoice;
1182
1468
  exports.useCache = useCache;
1183
1469
  exports.useClickOutside = useClickOutside;
1184
1470
  exports.useConfig = useConfig;
package/dist/index.d.cts CHANGED
@@ -268,6 +268,16 @@ declare const ernieParser: StreamChunkParser;
268
268
  * data: {"output":{"text":"hello"},"finish_reason":null}
269
269
  */
270
270
  declare const qwenParser: StreamChunkParser;
271
+ /**
272
+ * Anthropic / Claude 格式解析器
273
+ * data: {"type":"content_block_delta","delta":{"type":"text_delta","text":"hello"}}
274
+ */
275
+ declare const claudeParser: StreamChunkParser;
276
+ /**
277
+ * Google / Gemini 格式解析器
278
+ * data: {"candidates":[{"content":{"parts":[{"text":"hello"}]}}]}
279
+ */
280
+ declare const geminiParser: StreamChunkParser;
271
281
  /**
272
282
  * 纯文本流解析器(AsyncGenerator 输出的原始字符串)
273
283
  */
@@ -313,6 +323,8 @@ declare function useAiStream(options: AiStreamOptions): {
313
323
  openaiParser: StreamChunkParser;
314
324
  ernieParser: StreamChunkParser;
315
325
  qwenParser: StreamChunkParser;
326
+ claudeParser: StreamChunkParser;
327
+ geminiParser: StreamChunkParser;
316
328
  plainTextParser: StreamChunkParser;
317
329
  };
318
330
  };
@@ -581,5 +593,107 @@ declare function useAiConversations(options?: UseAiConversationsOptions): {
581
593
  clear: () => Promise<void>;
582
594
  };
583
595
 
584
- export { FormContextKey, FormItemContextKey, IndexedDBAdapter, configProviderContextKey, createZIndexCounter, defaultNamespace, ernieParser, getDayjsLocale, getNextZIndex, idInjectionKey, localStorageAdapter, namespaceContextKey, openaiParser, plainTextParser, qwenParser, resetZIndex, setDayjsLocale, setDayjsLocaleSync, updateDayjsMonths, useAiChat, useAiConversations, useAiStream, useCache, useClickOutside, useConfig, useEventListener, useFormItem, useGlobalNamespace, useId, useIdInjection, useLocale, useNamespace, useScrollLock, useVirtualScroll, useZIndex, zIndexContextKey, zIndexCounterKey };
585
- export type { AiChatMessage, AiConversation, AiStreamOptions, ConfigProviderContext, ConversationGroup, FormContext, FormItemContext, StorageAdapter, StreamChunkParser, UseAiChatOptions, UseAiConversationsOptions, UseCacheReturn, UseFormItemReturn, UseIdReturn, UseLocaleReturn, UseNamespaceReturn, UseZIndexReturn, VirtualScrollOptions, VirtualScrollReturn };
596
+ interface AiRequestOptions {
597
+ /**
598
+ * 请求函数
599
+ */
600
+ request: (query: string, ...args: unknown[]) => Promise<string> | string;
601
+ onSuccess?: (content: string) => void;
602
+ onError?: (err: Error) => void;
603
+ }
604
+ /**
605
+ * useAiRequest - 简单的 AI 非流式请求 Hook
606
+ */
607
+ declare function useAiRequest(options: AiRequestOptions): {
608
+ loading: vue.Ref<boolean, boolean>;
609
+ data: vue.Ref<string, string>;
610
+ error: vue.Ref<Error | null, Error | null>;
611
+ send: (query: string, ...args: unknown[]) => Promise<string>;
612
+ };
613
+
614
+ interface UseAiVoiceOptions {
615
+ /**
616
+ * 语言代码 (用于 SpeechRecognition)
617
+ * @default 'zh-CN'
618
+ */
619
+ language?: string;
620
+ /**
621
+ * 是否需要临时结果(在说话过程中实时返回)
622
+ * @default true
623
+ */
624
+ interimResults?: boolean;
625
+ /**
626
+ * 是否连续识别
627
+ * @default false
628
+ */
629
+ continuous?: boolean;
630
+ /**
631
+ * 智能静音检测(VAD)
632
+ * 开启后,当检测到长时间无声会自动停止录音
633
+ * @default true
634
+ */
635
+ vad?: boolean;
636
+ /**
637
+ * 静音检测阈值 (ms)
638
+ * @default 2000
639
+ */
640
+ vadThreshold?: number;
641
+ /**
642
+ * 音量变化敏感度 (0-1)
643
+ * @default 0.05
644
+ */
645
+ volumeThreshold?: number;
646
+ /**
647
+ * 返回波形柱的数量(对应 AiVoiceTrigger 的 amplitudes)
648
+ * @default 20
649
+ */
650
+ waveCount?: number;
651
+ /**
652
+ * 是否在开始时自动执行浏览器语音识别 (SpeechRecognition)
653
+ * 如果关闭,则只进行物理音频录制
654
+ * @default true
655
+ */
656
+ useSTT?: boolean;
657
+ /** 回调事件 */
658
+ onStart?: () => void;
659
+ /** 停止回调,包含最终转写文本和录音文件 Blob */
660
+ onStop?: (transcript: string, blob: Blob | null) => void;
661
+ onResult?: (transcript: string) => void;
662
+ onPartialResult?: (transcript: string) => void;
663
+ onError?: (error: unknown) => void;
664
+ }
665
+ interface UseAiVoiceReturn {
666
+ /** 是否正在录音 */
667
+ isRecording: vue.Ref<boolean>;
668
+ /** 最终转写文本 */
669
+ transcript: vue.Ref<string>;
670
+ /** 过程中的临时文本 */
671
+ interimTranscript: vue.Ref<string>;
672
+ /** 实时波形数据 */
673
+ amplitudes: vue.Ref<number[]>;
674
+ /** 实时音量 (0-100) */
675
+ volume: vue.Ref<number>;
676
+ /** 录音文件的 Blob */
677
+ audioBlob: vue.Ref<Blob | null>;
678
+ /** 开始录音 */
679
+ start: () => Promise<void>;
680
+ /** 停止录音 */
681
+ stop: () => void;
682
+ /** 取消并放弃当前结果 */
683
+ cancel: () => void;
684
+ /** 浏览器是否支持 SpeechRecognition (用于显示警告) */
685
+ sttSupported: boolean;
686
+ }
687
+ /**
688
+ * useAiVoice - 专业级 AI 语音交互 Hook
689
+ *
690
+ * 核心能力:
691
+ * 1. 【音频录制】:通过 MediaRecorder 真实录制音频并导出 Blob 文件。
692
+ * 2. 【视觉分析】:通过 Web Audio API 实时输出驱动 AiVoiceTrigger 的波形数组。
693
+ * 3. 【智能 VAD】:多维检测静音状态,支持自动停顿结束。
694
+ * 4. 【语音转写】:内置 Web Speech API 实时转写及临时结果反馈。
695
+ */
696
+ declare function useAiVoice(options?: UseAiVoiceOptions): UseAiVoiceReturn;
697
+
698
+ export { FormContextKey, FormItemContextKey, IndexedDBAdapter, claudeParser, configProviderContextKey, createZIndexCounter, defaultNamespace, ernieParser, geminiParser, getDayjsLocale, getNextZIndex, idInjectionKey, localStorageAdapter, namespaceContextKey, openaiParser, plainTextParser, qwenParser, resetZIndex, setDayjsLocale, setDayjsLocaleSync, updateDayjsMonths, useAiChat, useAiConversations, useAiRequest, useAiStream, useAiVoice, useCache, useClickOutside, useConfig, useEventListener, useFormItem, useGlobalNamespace, useId, useIdInjection, useLocale, useNamespace, useScrollLock, useVirtualScroll, useZIndex, zIndexContextKey, zIndexCounterKey };
699
+ export type { AiChatMessage, AiConversation, AiRequestOptions, AiStreamOptions, ConfigProviderContext, ConversationGroup, FormContext, FormItemContext, StorageAdapter, StreamChunkParser, UseAiChatOptions, UseAiConversationsOptions, UseAiVoiceOptions, UseAiVoiceReturn, UseCacheReturn, UseFormItemReturn, UseIdReturn, UseLocaleReturn, UseNamespaceReturn, UseZIndexReturn, VirtualScrollOptions, VirtualScrollReturn };
package/dist/index.d.mts CHANGED
@@ -268,6 +268,16 @@ declare const ernieParser: StreamChunkParser;
268
268
  * data: {"output":{"text":"hello"},"finish_reason":null}
269
269
  */
270
270
  declare const qwenParser: StreamChunkParser;
271
+ /**
272
+ * Anthropic / Claude 格式解析器
273
+ * data: {"type":"content_block_delta","delta":{"type":"text_delta","text":"hello"}}
274
+ */
275
+ declare const claudeParser: StreamChunkParser;
276
+ /**
277
+ * Google / Gemini 格式解析器
278
+ * data: {"candidates":[{"content":{"parts":[{"text":"hello"}]}}]}
279
+ */
280
+ declare const geminiParser: StreamChunkParser;
271
281
  /**
272
282
  * 纯文本流解析器(AsyncGenerator 输出的原始字符串)
273
283
  */
@@ -313,6 +323,8 @@ declare function useAiStream(options: AiStreamOptions): {
313
323
  openaiParser: StreamChunkParser;
314
324
  ernieParser: StreamChunkParser;
315
325
  qwenParser: StreamChunkParser;
326
+ claudeParser: StreamChunkParser;
327
+ geminiParser: StreamChunkParser;
316
328
  plainTextParser: StreamChunkParser;
317
329
  };
318
330
  };
@@ -581,5 +593,107 @@ declare function useAiConversations(options?: UseAiConversationsOptions): {
581
593
  clear: () => Promise<void>;
582
594
  };
583
595
 
584
- export { FormContextKey, FormItemContextKey, IndexedDBAdapter, configProviderContextKey, createZIndexCounter, defaultNamespace, ernieParser, getDayjsLocale, getNextZIndex, idInjectionKey, localStorageAdapter, namespaceContextKey, openaiParser, plainTextParser, qwenParser, resetZIndex, setDayjsLocale, setDayjsLocaleSync, updateDayjsMonths, useAiChat, useAiConversations, useAiStream, useCache, useClickOutside, useConfig, useEventListener, useFormItem, useGlobalNamespace, useId, useIdInjection, useLocale, useNamespace, useScrollLock, useVirtualScroll, useZIndex, zIndexContextKey, zIndexCounterKey };
585
- export type { AiChatMessage, AiConversation, AiStreamOptions, ConfigProviderContext, ConversationGroup, FormContext, FormItemContext, StorageAdapter, StreamChunkParser, UseAiChatOptions, UseAiConversationsOptions, UseCacheReturn, UseFormItemReturn, UseIdReturn, UseLocaleReturn, UseNamespaceReturn, UseZIndexReturn, VirtualScrollOptions, VirtualScrollReturn };
596
+ interface AiRequestOptions {
597
+ /**
598
+ * 请求函数
599
+ */
600
+ request: (query: string, ...args: unknown[]) => Promise<string> | string;
601
+ onSuccess?: (content: string) => void;
602
+ onError?: (err: Error) => void;
603
+ }
604
+ /**
605
+ * useAiRequest - 简单的 AI 非流式请求 Hook
606
+ */
607
+ declare function useAiRequest(options: AiRequestOptions): {
608
+ loading: vue.Ref<boolean, boolean>;
609
+ data: vue.Ref<string, string>;
610
+ error: vue.Ref<Error | null, Error | null>;
611
+ send: (query: string, ...args: unknown[]) => Promise<string>;
612
+ };
613
+
614
+ interface UseAiVoiceOptions {
615
+ /**
616
+ * 语言代码 (用于 SpeechRecognition)
617
+ * @default 'zh-CN'
618
+ */
619
+ language?: string;
620
+ /**
621
+ * 是否需要临时结果(在说话过程中实时返回)
622
+ * @default true
623
+ */
624
+ interimResults?: boolean;
625
+ /**
626
+ * 是否连续识别
627
+ * @default false
628
+ */
629
+ continuous?: boolean;
630
+ /**
631
+ * 智能静音检测(VAD)
632
+ * 开启后,当检测到长时间无声会自动停止录音
633
+ * @default true
634
+ */
635
+ vad?: boolean;
636
+ /**
637
+ * 静音检测阈值 (ms)
638
+ * @default 2000
639
+ */
640
+ vadThreshold?: number;
641
+ /**
642
+ * 音量变化敏感度 (0-1)
643
+ * @default 0.05
644
+ */
645
+ volumeThreshold?: number;
646
+ /**
647
+ * 返回波形柱的数量(对应 AiVoiceTrigger 的 amplitudes)
648
+ * @default 20
649
+ */
650
+ waveCount?: number;
651
+ /**
652
+ * 是否在开始时自动执行浏览器语音识别 (SpeechRecognition)
653
+ * 如果关闭,则只进行物理音频录制
654
+ * @default true
655
+ */
656
+ useSTT?: boolean;
657
+ /** 回调事件 */
658
+ onStart?: () => void;
659
+ /** 停止回调,包含最终转写文本和录音文件 Blob */
660
+ onStop?: (transcript: string, blob: Blob | null) => void;
661
+ onResult?: (transcript: string) => void;
662
+ onPartialResult?: (transcript: string) => void;
663
+ onError?: (error: unknown) => void;
664
+ }
665
+ interface UseAiVoiceReturn {
666
+ /** 是否正在录音 */
667
+ isRecording: vue.Ref<boolean>;
668
+ /** 最终转写文本 */
669
+ transcript: vue.Ref<string>;
670
+ /** 过程中的临时文本 */
671
+ interimTranscript: vue.Ref<string>;
672
+ /** 实时波形数据 */
673
+ amplitudes: vue.Ref<number[]>;
674
+ /** 实时音量 (0-100) */
675
+ volume: vue.Ref<number>;
676
+ /** 录音文件的 Blob */
677
+ audioBlob: vue.Ref<Blob | null>;
678
+ /** 开始录音 */
679
+ start: () => Promise<void>;
680
+ /** 停止录音 */
681
+ stop: () => void;
682
+ /** 取消并放弃当前结果 */
683
+ cancel: () => void;
684
+ /** 浏览器是否支持 SpeechRecognition (用于显示警告) */
685
+ sttSupported: boolean;
686
+ }
687
+ /**
688
+ * useAiVoice - 专业级 AI 语音交互 Hook
689
+ *
690
+ * 核心能力:
691
+ * 1. 【音频录制】:通过 MediaRecorder 真实录制音频并导出 Blob 文件。
692
+ * 2. 【视觉分析】:通过 Web Audio API 实时输出驱动 AiVoiceTrigger 的波形数组。
693
+ * 3. 【智能 VAD】:多维检测静音状态,支持自动停顿结束。
694
+ * 4. 【语音转写】:内置 Web Speech API 实时转写及临时结果反馈。
695
+ */
696
+ declare function useAiVoice(options?: UseAiVoiceOptions): UseAiVoiceReturn;
697
+
698
+ export { FormContextKey, FormItemContextKey, IndexedDBAdapter, claudeParser, configProviderContextKey, createZIndexCounter, defaultNamespace, ernieParser, geminiParser, getDayjsLocale, getNextZIndex, idInjectionKey, localStorageAdapter, namespaceContextKey, openaiParser, plainTextParser, qwenParser, resetZIndex, setDayjsLocale, setDayjsLocaleSync, updateDayjsMonths, useAiChat, useAiConversations, useAiRequest, useAiStream, useAiVoice, useCache, useClickOutside, useConfig, useEventListener, useFormItem, useGlobalNamespace, useId, useIdInjection, useLocale, useNamespace, useScrollLock, useVirtualScroll, useZIndex, zIndexContextKey, zIndexCounterKey };
699
+ export type { AiChatMessage, AiConversation, AiRequestOptions, AiStreamOptions, ConfigProviderContext, ConversationGroup, FormContext, FormItemContext, StorageAdapter, StreamChunkParser, UseAiChatOptions, UseAiConversationsOptions, UseAiVoiceOptions, UseAiVoiceReturn, UseCacheReturn, UseFormItemReturn, UseIdReturn, UseLocaleReturn, UseNamespaceReturn, UseZIndexReturn, VirtualScrollOptions, VirtualScrollReturn };