@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 +287 -1
- package/dist/index.d.cts +116 -2
- package/dist/index.d.mts +116 -2
- package/dist/index.d.ts +116 -2
- package/dist/index.mjs +284 -2
- package/dist/use-ai/index.cjs +22 -0
- package/dist/use-ai/index.d.ts +2 -0
- package/dist/use-ai/index.mjs +2 -0
- package/dist/use-ai/use-ai-request.cjs +35 -0
- package/dist/use-ai/use-ai-request.d.ts +17 -0
- package/dist/use-ai/use-ai-request.mjs +29 -0
- package/dist/use-ai/use-ai-stream.cjs +34 -1
- package/dist/use-ai/use-ai-stream.d.ts +12 -0
- package/dist/use-ai/use-ai-stream.mjs +39 -1
- package/dist/use-ai/use-ai-voice.cjs +218 -0
- package/dist/use-ai/use-ai-voice.d.ts +83 -0
- package/dist/use-ai/use-ai-voice.mjs +215 -0
- package/package.json +3 -3
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: {
|
|
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
|
-
|
|
585
|
-
|
|
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
|
-
|
|
585
|
-
|
|
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 };
|