@yh-ui/hooks 0.1.12 → 0.1.15

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.d.ts 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.mjs CHANGED
@@ -592,6 +592,37 @@ const qwenParser = (raw) => {
592
592
  }
593
593
  return text || null;
594
594
  };
595
+ const claudeParser = (raw) => {
596
+ const lines = raw.split("\n");
597
+ let text = "";
598
+ for (const line of lines) {
599
+ if (!line.startsWith("data: ")) continue;
600
+ const data = line.slice(6).trim();
601
+ try {
602
+ const json = JSON.parse(data);
603
+ if (json?.type === "content_block_delta" && json?.delta?.text) {
604
+ text += json.delta.text;
605
+ }
606
+ } catch {
607
+ }
608
+ }
609
+ return text || null;
610
+ };
611
+ const geminiParser = (raw) => {
612
+ const lines = raw.split("\n");
613
+ let text = "";
614
+ for (const line of lines) {
615
+ const content = line.startsWith("data: ") ? line.slice(6).trim() : line.trim();
616
+ if (!content) continue;
617
+ try {
618
+ const json = JSON.parse(content);
619
+ const part = json?.candidates?.[0]?.content?.parts?.[0]?.text;
620
+ if (part) text += part;
621
+ } catch {
622
+ }
623
+ }
624
+ return text || null;
625
+ };
595
626
  const plainTextParser = (raw) => raw || null;
596
627
  class TypewriterThrottle {
597
628
  queue = [];
@@ -715,7 +746,14 @@ function useAiStream(options) {
715
746
  fetchStream,
716
747
  stop,
717
748
  // 暴露解析器供测试/自定义使用
718
- parsers: { openaiParser, ernieParser, qwenParser, plainTextParser }
749
+ parsers: {
750
+ openaiParser,
751
+ ernieParser,
752
+ qwenParser,
753
+ claudeParser,
754
+ geminiParser,
755
+ plainTextParser
756
+ }
719
757
  };
720
758
  }
721
759
 
@@ -1141,4 +1179,248 @@ function useAiConversations(options = {}) {
1141
1179
  };
1142
1180
  }
1143
1181
 
1144
- 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 };
1182
+ function useAiRequest(options) {
1183
+ const loading = ref(false);
1184
+ const data = ref("");
1185
+ const error = ref(null);
1186
+ const send = async (query, ...args) => {
1187
+ loading.value = true;
1188
+ error.value = null;
1189
+ try {
1190
+ const result = await options.request(query, ...args);
1191
+ data.value = result;
1192
+ options.onSuccess?.(result);
1193
+ return result;
1194
+ } catch (e) {
1195
+ const err = e instanceof Error ? e : new Error(String(e));
1196
+ error.value = err;
1197
+ options.onError?.(err);
1198
+ throw err;
1199
+ } finally {
1200
+ loading.value = false;
1201
+ }
1202
+ };
1203
+ return {
1204
+ loading,
1205
+ data,
1206
+ error,
1207
+ send
1208
+ };
1209
+ }
1210
+
1211
+ function useAiVoice(options = {}) {
1212
+ const {
1213
+ language = "zh-CN",
1214
+ interimResults = true,
1215
+ continuous = false,
1216
+ vad = true,
1217
+ vadThreshold = 2e3,
1218
+ volumeThreshold = 0.05,
1219
+ waveCount = 20,
1220
+ useSTT = true
1221
+ } = options;
1222
+ const isRecording = ref(false);
1223
+ const transcript = ref("");
1224
+ const interimTranscript = ref("");
1225
+ const volume = ref(0);
1226
+ const amplitudes = ref(new Array(waveCount).fill(5));
1227
+ const audioBlob = ref(null);
1228
+ const recognition = shallowRef(null);
1229
+ const audioContext = shallowRef(null);
1230
+ const analyser = shallowRef(null);
1231
+ const stream = shallowRef(null);
1232
+ const mediaRecorder = shallowRef(null);
1233
+ let chunks = [];
1234
+ let animationId = null;
1235
+ let silenceStart = null;
1236
+ const _window = typeof window !== "undefined" ? window : null;
1237
+ const SpeechRecognition = _window?.SpeechRecognition || _window?.webkitSpeechRecognition;
1238
+ const sttSupported = !!SpeechRecognition;
1239
+ const initMediaRecorder = (mediaStream) => {
1240
+ chunks = [];
1241
+ const recorder = new MediaRecorder(mediaStream);
1242
+ recorder.ondataavailable = (e) => {
1243
+ if (e.data.size > 0) chunks.push(e.data);
1244
+ };
1245
+ recorder.onstop = () => {
1246
+ audioBlob.value = new Blob(chunks, { type: "audio/webm" });
1247
+ if (isRecording.value === false && chunks.length > 0) {
1248
+ options.onStop?.(transcript.value, audioBlob.value);
1249
+ }
1250
+ };
1251
+ mediaRecorder.value = recorder;
1252
+ };
1253
+ const initRecognition = () => {
1254
+ if (!sttSupported || !useSTT) return;
1255
+ const recognitionInstance = new SpeechRecognition();
1256
+ recognitionInstance.lang = language;
1257
+ recognitionInstance.interimResults = interimResults;
1258
+ recognitionInstance.continuous = continuous;
1259
+ recognitionInstance.onresult = (event) => {
1260
+ let currentInterim = "";
1261
+ for (let i = event.resultIndex; i < event.results.length; ++i) {
1262
+ if (event.results[i].isFinal) {
1263
+ transcript.value += event.results[i][0].transcript;
1264
+ options.onResult?.(transcript.value);
1265
+ } else {
1266
+ currentInterim += event.results[i][0].transcript;
1267
+ }
1268
+ }
1269
+ interimTranscript.value = currentInterim;
1270
+ options.onPartialResult?.(currentInterim);
1271
+ };
1272
+ recognitionInstance.onerror = (event) => {
1273
+ if (event.error !== "no-speech" && event.error !== "aborted") {
1274
+ options.onError?.(event);
1275
+ }
1276
+ };
1277
+ recognition.value = recognitionInstance;
1278
+ };
1279
+ const initAudioAnalyzer = async (mediaStream) => {
1280
+ try {
1281
+ const AudioCtx = window.AudioContext || window.webkitAudioContext;
1282
+ audioContext.value = new AudioCtx();
1283
+ if (audioContext.value.state === "suspended") {
1284
+ await audioContext.value.resume();
1285
+ }
1286
+ analyser.value = audioContext.value.createAnalyser();
1287
+ analyser.value.fftSize = 256;
1288
+ const source = audioContext.value.createMediaStreamSource(mediaStream);
1289
+ source.connect(analyser.value);
1290
+ const bufferLength = analyser.value.frequencyBinCount;
1291
+ const dataArray = new Uint8Array(bufferLength);
1292
+ const process = () => {
1293
+ if (!isRecording.value) {
1294
+ amplitudes.value = new Array(waveCount).fill(5);
1295
+ volume.value = 0;
1296
+ return;
1297
+ }
1298
+ animationId = requestAnimationFrame(process);
1299
+ analyser.value.getByteFrequencyData(dataArray);
1300
+ let total = 0;
1301
+ for (let i = 0; i < bufferLength; i++) total += dataArray[i];
1302
+ const avg = total / bufferLength;
1303
+ volume.value = Math.min(100, avg / 128 * 100);
1304
+ const step = Math.floor(bufferLength / waveCount);
1305
+ const newAmps = [];
1306
+ for (let i = 0; i < waveCount; i++) {
1307
+ const val = dataArray[i * step];
1308
+ newAmps.push(6 + val / 255 * 34);
1309
+ }
1310
+ amplitudes.value = newAmps;
1311
+ if (vad) {
1312
+ const normalizedVol = avg / 255;
1313
+ if (normalizedVol < volumeThreshold) {
1314
+ if (silenceStart === null) silenceStart = Date.now();
1315
+ else if (Date.now() - silenceStart > vadThreshold) {
1316
+ stop();
1317
+ }
1318
+ } else {
1319
+ silenceStart = null;
1320
+ }
1321
+ }
1322
+ };
1323
+ process();
1324
+ } catch (err) {
1325
+ options.onError?.(err);
1326
+ }
1327
+ };
1328
+ const start = async () => {
1329
+ if (isRecording.value) return;
1330
+ try {
1331
+ transcript.value = "";
1332
+ interimTranscript.value = "";
1333
+ audioBlob.value = null;
1334
+ silenceStart = null;
1335
+ stream.value = await navigator.mediaDevices.getUserMedia({ audio: true });
1336
+ isRecording.value = true;
1337
+ initMediaRecorder(stream.value);
1338
+ initRecognition();
1339
+ await initAudioAnalyzer(stream.value);
1340
+ mediaRecorder.value?.start(1e3);
1341
+ recognition.value?.start();
1342
+ options.onStart?.();
1343
+ } catch (err) {
1344
+ isRecording.value = false;
1345
+ if (stream.value) {
1346
+ stream.value.getTracks().forEach((t) => t.stop());
1347
+ stream.value = null;
1348
+ }
1349
+ console.error("[yh-ui/hooks] useAiVoice start failed:", err);
1350
+ options.onError?.(err);
1351
+ }
1352
+ };
1353
+ const stop = () => {
1354
+ if (!isRecording.value) return;
1355
+ isRecording.value = false;
1356
+ if (stream.value) {
1357
+ stream.value.getTracks().forEach((track) => track.stop());
1358
+ stream.value = null;
1359
+ }
1360
+ if (recognition.value) {
1361
+ try {
1362
+ recognition.value.stop();
1363
+ } catch {
1364
+ }
1365
+ }
1366
+ if (mediaRecorder.value && mediaRecorder.value.state !== "inactive") {
1367
+ try {
1368
+ mediaRecorder.value.stop();
1369
+ } catch {
1370
+ }
1371
+ }
1372
+ cleanup();
1373
+ };
1374
+ const cancel = () => {
1375
+ if (!isRecording.value) return;
1376
+ isRecording.value = false;
1377
+ if (stream.value) {
1378
+ stream.value.getTracks().forEach((track) => track.stop());
1379
+ stream.value = null;
1380
+ }
1381
+ if (recognition.value) {
1382
+ try {
1383
+ recognition.value.abort();
1384
+ } catch {
1385
+ }
1386
+ }
1387
+ if (mediaRecorder.value && mediaRecorder.value.state !== "inactive") {
1388
+ try {
1389
+ mediaRecorder.value.stop();
1390
+ } catch {
1391
+ }
1392
+ }
1393
+ cleanup();
1394
+ };
1395
+ const cleanup = () => {
1396
+ if (animationId) {
1397
+ cancelAnimationFrame(animationId);
1398
+ animationId = null;
1399
+ }
1400
+ if (audioContext.value && audioContext.value.state !== "closed") {
1401
+ audioContext.value.close().catch((_err) => {
1402
+ });
1403
+ audioContext.value = null;
1404
+ }
1405
+ amplitudes.value = new Array(waveCount).fill(5);
1406
+ volume.value = 0;
1407
+ };
1408
+ onUnmounted(() => {
1409
+ if (isRecording.value) stop();
1410
+ else cleanup();
1411
+ });
1412
+ return {
1413
+ isRecording,
1414
+ transcript,
1415
+ interimTranscript,
1416
+ amplitudes,
1417
+ volume,
1418
+ audioBlob,
1419
+ start,
1420
+ stop,
1421
+ cancel,
1422
+ sttSupported
1423
+ };
1424
+ }
1425
+
1426
+ 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 };
@@ -35,4 +35,26 @@ Object.keys(_useAiConversations).forEach(function (key) {
35
35
  return _useAiConversations[key];
36
36
  }
37
37
  });
38
+ });
39
+ var _useAiRequest = require("./use-ai-request.cjs");
40
+ Object.keys(_useAiRequest).forEach(function (key) {
41
+ if (key === "default" || key === "__esModule") return;
42
+ if (key in exports && exports[key] === _useAiRequest[key]) return;
43
+ Object.defineProperty(exports, key, {
44
+ enumerable: true,
45
+ get: function () {
46
+ return _useAiRequest[key];
47
+ }
48
+ });
49
+ });
50
+ var _useAiVoice = require("./use-ai-voice.cjs");
51
+ Object.keys(_useAiVoice).forEach(function (key) {
52
+ if (key === "default" || key === "__esModule") return;
53
+ if (key in exports && exports[key] === _useAiVoice[key]) return;
54
+ Object.defineProperty(exports, key, {
55
+ enumerable: true,
56
+ get: function () {
57
+ return _useAiVoice[key];
58
+ }
59
+ });
38
60
  });
@@ -1,3 +1,5 @@
1
1
  export * from './use-ai-chat';
2
2
  export * from './use-ai-stream';
3
3
  export * from './use-ai-conversations';
4
+ export * from './use-ai-request';
5
+ export * from './use-ai-voice';
@@ -1,3 +1,5 @@
1
1
  export * from "./use-ai-chat.mjs";
2
2
  export * from "./use-ai-stream.mjs";
3
3
  export * from "./use-ai-conversations.mjs";
4
+ export * from "./use-ai-request.mjs";
5
+ export * from "./use-ai-voice.mjs";
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.useAiRequest = useAiRequest;
7
+ var _vue = require("vue");
8
+ function useAiRequest(options) {
9
+ const loading = (0, _vue.ref)(false);
10
+ const data = (0, _vue.ref)("");
11
+ const error = (0, _vue.ref)(null);
12
+ const send = async (query, ...args) => {
13
+ loading.value = true;
14
+ error.value = null;
15
+ try {
16
+ const result = await options.request(query, ...args);
17
+ data.value = result;
18
+ options.onSuccess?.(result);
19
+ return result;
20
+ } catch (e) {
21
+ const err = e instanceof Error ? e : new Error(String(e));
22
+ error.value = err;
23
+ options.onError?.(err);
24
+ throw err;
25
+ } finally {
26
+ loading.value = false;
27
+ }
28
+ };
29
+ return {
30
+ loading,
31
+ data,
32
+ error,
33
+ send
34
+ };
35
+ }
@@ -0,0 +1,17 @@
1
+ export interface AiRequestOptions {
2
+ /**
3
+ * 请求函数
4
+ */
5
+ request: (query: string, ...args: unknown[]) => Promise<string> | string;
6
+ onSuccess?: (content: string) => void;
7
+ onError?: (err: Error) => void;
8
+ }
9
+ /**
10
+ * useAiRequest - 简单的 AI 非流式请求 Hook
11
+ */
12
+ export declare function useAiRequest(options: AiRequestOptions): {
13
+ loading: import("vue").Ref<boolean, boolean>;
14
+ data: import("vue").Ref<string, string>;
15
+ error: import("vue").Ref<Error | null, Error | null>;
16
+ send: (query: string, ...args: unknown[]) => Promise<string>;
17
+ };
@@ -0,0 +1,29 @@
1
+ import { ref } from "vue";
2
+ export function useAiRequest(options) {
3
+ const loading = ref(false);
4
+ const data = ref("");
5
+ const error = ref(null);
6
+ const send = async (query, ...args) => {
7
+ loading.value = true;
8
+ error.value = null;
9
+ try {
10
+ const result = await options.request(query, ...args);
11
+ data.value = result;
12
+ options.onSuccess?.(result);
13
+ return result;
14
+ } catch (e) {
15
+ const err = e instanceof Error ? e : new Error(String(e));
16
+ error.value = err;
17
+ options.onError?.(err);
18
+ throw err;
19
+ } finally {
20
+ loading.value = false;
21
+ }
22
+ };
23
+ return {
24
+ loading,
25
+ data,
26
+ error,
27
+ send
28
+ };
29
+ }
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.qwenParser = exports.plainTextParser = exports.openaiParser = exports.ernieParser = void 0;
6
+ exports.qwenParser = exports.plainTextParser = exports.openaiParser = exports.geminiParser = exports.ernieParser = exports.claudeParser = void 0;
7
7
  exports.useAiStream = useAiStream;
8
8
  var _vue = require("vue");
9
9
  const openaiParser = raw => {
@@ -51,6 +51,37 @@ const qwenParser = raw => {
51
51
  return text || null;
52
52
  };
53
53
  exports.qwenParser = qwenParser;
54
+ const claudeParser = raw => {
55
+ const lines = raw.split("\n");
56
+ let text = "";
57
+ for (const line of lines) {
58
+ if (!line.startsWith("data: ")) continue;
59
+ const data = line.slice(6).trim();
60
+ try {
61
+ const json = JSON.parse(data);
62
+ if (json?.type === "content_block_delta" && json?.delta?.text) {
63
+ text += json.delta.text;
64
+ }
65
+ } catch {}
66
+ }
67
+ return text || null;
68
+ };
69
+ exports.claudeParser = claudeParser;
70
+ const geminiParser = raw => {
71
+ const lines = raw.split("\n");
72
+ let text = "";
73
+ for (const line of lines) {
74
+ const content = line.startsWith("data: ") ? line.slice(6).trim() : line.trim();
75
+ if (!content) continue;
76
+ try {
77
+ const json = JSON.parse(content);
78
+ const part = json?.candidates?.[0]?.content?.parts?.[0]?.text;
79
+ if (part) text += part;
80
+ } catch {}
81
+ }
82
+ return text || null;
83
+ };
84
+ exports.geminiParser = geminiParser;
54
85
  const plainTextParser = raw => raw || null;
55
86
  exports.plainTextParser = plainTextParser;
56
87
  class TypewriterThrottle {
@@ -184,6 +215,8 @@ function useAiStream(options) {
184
215
  openaiParser,
185
216
  ernieParser,
186
217
  qwenParser,
218
+ claudeParser,
219
+ geminiParser,
187
220
  plainTextParser
188
221
  }
189
222
  };
@@ -14,6 +14,16 @@ export declare const ernieParser: StreamChunkParser;
14
14
  * data: {"output":{"text":"hello"},"finish_reason":null}
15
15
  */
16
16
  export declare const qwenParser: StreamChunkParser;
17
+ /**
18
+ * Anthropic / Claude 格式解析器
19
+ * data: {"type":"content_block_delta","delta":{"type":"text_delta","text":"hello"}}
20
+ */
21
+ export declare const claudeParser: StreamChunkParser;
22
+ /**
23
+ * Google / Gemini 格式解析器
24
+ * data: {"candidates":[{"content":{"parts":[{"text":"hello"}]}}]}
25
+ */
26
+ export declare const geminiParser: StreamChunkParser;
17
27
  /**
18
28
  * 纯文本流解析器(AsyncGenerator 输出的原始字符串)
19
29
  */
@@ -59,6 +69,8 @@ export declare function useAiStream(options: AiStreamOptions): {
59
69
  openaiParser: StreamChunkParser;
60
70
  ernieParser: StreamChunkParser;
61
71
  qwenParser: StreamChunkParser;
72
+ claudeParser: StreamChunkParser;
73
+ geminiParser: StreamChunkParser;
62
74
  plainTextParser: StreamChunkParser;
63
75
  };
64
76
  };