@huyooo/ai-chat-frontend-react 0.2.11 → 0.2.12

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.js CHANGED
@@ -842,7 +842,7 @@ var RenderersProvider = ({
842
842
  };
843
843
 
844
844
  // src/components/ChatPanel.tsx
845
- import { useEffect as useEffect14, useRef as useRef9, useCallback as useCallback13, useMemo as useMemo12, useState as useState18, forwardRef as forwardRef8, useImperativeHandle as useImperativeHandle8 } from "react";
845
+ import { useEffect as useEffect16, useRef as useRef11, useCallback as useCallback15, useMemo as useMemo14, useState as useState19, forwardRef as forwardRef8, useImperativeHandle as useImperativeHandle8 } from "react";
846
846
  import { Icon as Icon22 } from "@iconify/react";
847
847
 
848
848
  // src/components/header/ChatHeader.tsx
@@ -1192,7 +1192,7 @@ var WelcomeMessage = ({ config: propsConfig, onQuickAction }) => {
1192
1192
  };
1193
1193
 
1194
1194
  // src/components/message/MessageBubble.tsx
1195
- import { useMemo as useMemo10 } from "react";
1195
+ import { useMemo as useMemo12 } from "react";
1196
1196
  import { Icon as Icon18 } from "@iconify/react";
1197
1197
 
1198
1198
  // src/components/message/parts/CollapsibleCard.tsx
@@ -1918,7 +1918,7 @@ var PartsRenderer = ({
1918
1918
  };
1919
1919
 
1920
1920
  // src/components/input/ChatInput.tsx
1921
- import { useState as useState15, useRef as useRef7, useCallback as useCallback10, useEffect as useEffect10, forwardRef as forwardRef7, useImperativeHandle as useImperativeHandle7, useLayoutEffect, useMemo as useMemo9 } from "react";
1921
+ import { useState as useState16, useRef as useRef9, useCallback as useCallback12, useEffect as useEffect12, forwardRef as forwardRef7, useImperativeHandle as useImperativeHandle7, useLayoutEffect, useMemo as useMemo11 } from "react";
1922
1922
  import { Icon as Icon17 } from "@iconify/react";
1923
1923
 
1924
1924
  // src/components/input/AtFilePicker.tsx
@@ -2785,6 +2785,410 @@ function useImageUpload(options = {}) {
2785
2785
  };
2786
2786
  }
2787
2787
 
2788
+ // src/hooks/useVoiceToTextInput.ts
2789
+ import { useCallback as useCallback11, useEffect as useEffect11, useMemo as useMemo10, useRef as useRef8 } from "react";
2790
+
2791
+ // src/hooks/useVoiceInput.ts
2792
+ import { useCallback as useCallback10, useEffect as useEffect10, useMemo as useMemo9, useRef as useRef7, useState as useState15 } from "react";
2793
+ function float32ToInt16(float32Array) {
2794
+ const int16Array = new Int16Array(float32Array.length);
2795
+ for (let i = 0; i < float32Array.length; i++) {
2796
+ const s = Math.max(-1, Math.min(1, float32Array[i]));
2797
+ int16Array[i] = s < 0 ? s * 32768 : s * 32767;
2798
+ }
2799
+ return int16Array;
2800
+ }
2801
+ function resample(audioData, fromSampleRate, toSampleRate) {
2802
+ if (fromSampleRate === toSampleRate) return audioData;
2803
+ const ratio = fromSampleRate / toSampleRate;
2804
+ const newLength = Math.round(audioData.length / ratio);
2805
+ const result = new Float32Array(newLength);
2806
+ for (let i = 0; i < newLength; i++) {
2807
+ const srcIndex = i * ratio;
2808
+ const srcIndexFloor = Math.floor(srcIndex);
2809
+ const srcIndexCeil = Math.min(srcIndexFloor + 1, audioData.length - 1);
2810
+ const t = srcIndex - srcIndexFloor;
2811
+ result[i] = audioData[srcIndexFloor] * (1 - t) + audioData[srcIndexCeil] * t;
2812
+ }
2813
+ return result;
2814
+ }
2815
+ async function setupAudioWorkletCapture(opts) {
2816
+ const { audioContext, source, chunkSize, onChunk } = opts;
2817
+ const workletCode = `
2818
+ class PcmCaptureProcessor extends AudioWorkletProcessor {
2819
+ constructor(options) {
2820
+ super();
2821
+ const size = (options && options.processorOptions && options.processorOptions.chunkSize) || 4096;
2822
+ this._chunkSize = size;
2823
+ this._buf = new Float32Array(size);
2824
+ this._offset = 0;
2825
+ }
2826
+ process(inputs, outputs) {
2827
+ const input = inputs && inputs[0] && inputs[0][0];
2828
+ const output = outputs && outputs[0] && outputs[0][0];
2829
+ if (!input) return true;
2830
+ if (output) output.set(input);
2831
+
2832
+ let i = 0;
2833
+ while (i < input.length) {
2834
+ const remain = this._chunkSize - this._offset;
2835
+ const take = Math.min(remain, input.length - i);
2836
+ this._buf.set(input.subarray(i, i + take), this._offset);
2837
+ this._offset += take;
2838
+ i += take;
2839
+ if (this._offset >= this._chunkSize) {
2840
+ const out = this._buf;
2841
+ this.port.postMessage(out, [out.buffer]);
2842
+ this._buf = new Float32Array(this._chunkSize);
2843
+ this._offset = 0;
2844
+ }
2845
+ }
2846
+ return true;
2847
+ }
2848
+ }
2849
+ registerProcessor('pcm-capture', PcmCaptureProcessor);
2850
+ `;
2851
+ const blob = new Blob([workletCode], { type: "text/javascript" });
2852
+ const url = URL.createObjectURL(blob);
2853
+ await audioContext.audioWorklet.addModule(url);
2854
+ URL.revokeObjectURL(url);
2855
+ const workletNode = new AudioWorkletNode(audioContext, "pcm-capture", {
2856
+ numberOfInputs: 1,
2857
+ numberOfOutputs: 1,
2858
+ channelCount: 1,
2859
+ processorOptions: { chunkSize }
2860
+ });
2861
+ const silentGain = audioContext.createGain();
2862
+ silentGain.gain.value = 0;
2863
+ const onMessage = (event) => {
2864
+ const data = event.data;
2865
+ if (data instanceof Float32Array) {
2866
+ onChunk(data);
2867
+ return;
2868
+ }
2869
+ if (data instanceof ArrayBuffer) {
2870
+ onChunk(new Float32Array(data));
2871
+ }
2872
+ };
2873
+ workletNode.port.addEventListener("message", onMessage);
2874
+ workletNode.port.start();
2875
+ source.connect(workletNode);
2876
+ workletNode.connect(silentGain);
2877
+ silentGain.connect(audioContext.destination);
2878
+ return {
2879
+ cleanup: () => {
2880
+ try {
2881
+ workletNode.port.removeEventListener("message", onMessage);
2882
+ } catch {
2883
+ }
2884
+ try {
2885
+ workletNode.disconnect();
2886
+ } catch {
2887
+ }
2888
+ try {
2889
+ silentGain.disconnect();
2890
+ } catch {
2891
+ }
2892
+ }
2893
+ };
2894
+ }
2895
+ function useVoiceInput(adapter, config = {}) {
2896
+ const { sampleRate = 16e3, sendInterval = 200, enablePunc = true, enableItn = true } = config;
2897
+ const [status, setStatus] = useState15("idle");
2898
+ const [currentText, setCurrentText] = useState15("");
2899
+ const [finalText, setFinalText] = useState15("");
2900
+ const [error, setError] = useState15(null);
2901
+ const statusRef = useRef7("idle");
2902
+ const currentTextRef = useRef7("");
2903
+ const finalTextRef = useRef7("");
2904
+ const mediaStreamRef = useRef7(null);
2905
+ const audioContextRef = useRef7(null);
2906
+ const workletCleanupRef = useRef7(null);
2907
+ const sourceRef = useRef7(null);
2908
+ const audioBufferRef = useRef7([]);
2909
+ const sendTimerRef = useRef7(null);
2910
+ const cleanupFnsRef = useRef7([]);
2911
+ const isRecording = useMemo9(() => status === "connecting" || status === "recording", [status]);
2912
+ useEffect10(() => {
2913
+ statusRef.current = status;
2914
+ }, [status]);
2915
+ useEffect10(() => {
2916
+ currentTextRef.current = currentText;
2917
+ }, [currentText]);
2918
+ useEffect10(() => {
2919
+ finalTextRef.current = finalText;
2920
+ }, [finalText]);
2921
+ const setStatusSafe = useCallback10((next) => {
2922
+ statusRef.current = next;
2923
+ setStatus(next);
2924
+ }, []);
2925
+ const cleanup = useCallback10(() => {
2926
+ if (sendTimerRef.current) {
2927
+ clearInterval(sendTimerRef.current);
2928
+ sendTimerRef.current = null;
2929
+ }
2930
+ if (workletCleanupRef.current) {
2931
+ workletCleanupRef.current();
2932
+ workletCleanupRef.current = null;
2933
+ }
2934
+ if (sourceRef.current) {
2935
+ try {
2936
+ sourceRef.current.disconnect();
2937
+ } catch {
2938
+ }
2939
+ sourceRef.current = null;
2940
+ }
2941
+ if (audioContextRef.current) {
2942
+ audioContextRef.current.close().catch(() => {
2943
+ });
2944
+ audioContextRef.current = null;
2945
+ }
2946
+ if (mediaStreamRef.current) {
2947
+ mediaStreamRef.current.getTracks().forEach((t) => t.stop());
2948
+ mediaStreamRef.current = null;
2949
+ }
2950
+ cleanupFnsRef.current.forEach((fn) => fn());
2951
+ cleanupFnsRef.current = [];
2952
+ audioBufferRef.current = [];
2953
+ }, []);
2954
+ const sendAudioChunk = useCallback10(async () => {
2955
+ if (!adapter?.asrSendAudio) return;
2956
+ const buf = audioBufferRef.current;
2957
+ if (!buf.length) return;
2958
+ const totalLength = buf.reduce((sum, arr) => sum + arr.length, 0);
2959
+ const merged = new Float32Array(totalLength);
2960
+ let offset = 0;
2961
+ for (const chunk of buf) {
2962
+ merged.set(chunk, offset);
2963
+ offset += chunk.length;
2964
+ }
2965
+ audioBufferRef.current = [];
2966
+ const pcmData = float32ToInt16(merged);
2967
+ const result = await adapter.asrSendAudio(pcmData.buffer);
2968
+ if (!result.success) {
2969
+ console.error("[VoiceInput] \u53D1\u9001\u97F3\u9891\u5931\u8D25:", result.error);
2970
+ }
2971
+ }, [adapter]);
2972
+ const start = useCallback10(async () => {
2973
+ if (statusRef.current === "connecting" || statusRef.current === "recording") return;
2974
+ if (!adapter) {
2975
+ setError("Adapter \u672A\u521D\u59CB\u5316");
2976
+ setStatusSafe("error");
2977
+ return;
2978
+ }
2979
+ if (!adapter.asrStart) {
2980
+ setError("\u8BED\u97F3\u8BC6\u522B\u529F\u80FD\u4E0D\u53EF\u7528");
2981
+ setStatusSafe("error");
2982
+ return;
2983
+ }
2984
+ setError(null);
2985
+ setCurrentText("");
2986
+ setFinalText("");
2987
+ setStatusSafe("connecting");
2988
+ try {
2989
+ mediaStreamRef.current = await navigator.mediaDevices.getUserMedia({
2990
+ audio: {
2991
+ channelCount: 1,
2992
+ sampleRate: { ideal: sampleRate },
2993
+ echoCancellation: true,
2994
+ noiseSuppression: true
2995
+ }
2996
+ });
2997
+ audioContextRef.current = new AudioContext({ sampleRate });
2998
+ const actualSampleRate = audioContextRef.current.sampleRate;
2999
+ if (adapter.onAsrResult) {
3000
+ const off2 = adapter.onAsrResult((data) => {
3001
+ const text = data.result.result?.text || "";
3002
+ setCurrentText(text);
3003
+ if (data.isLast) setFinalText(text);
3004
+ });
3005
+ cleanupFnsRef.current.push(off2);
3006
+ }
3007
+ if (adapter.onAsrError) {
3008
+ const off2 = adapter.onAsrError((err) => {
3009
+ console.error("[VoiceInput] ASR \u9519\u8BEF:", err.message);
3010
+ setError(err.message);
3011
+ setStatusSafe("error");
3012
+ });
3013
+ cleanupFnsRef.current.push(off2);
3014
+ }
3015
+ if (adapter.onAsrClosed) {
3016
+ const off2 = adapter.onAsrClosed(() => {
3017
+ if (statusRef.current === "recording") {
3018
+ setStatusSafe("idle");
3019
+ }
3020
+ });
3021
+ cleanupFnsRef.current.push(off2);
3022
+ }
3023
+ const ctx = audioContextRef.current;
3024
+ const stream = mediaStreamRef.current;
3025
+ if (!ctx || !stream) throw new Error("AudioContext \u6216 MediaStream \u521D\u59CB\u5316\u5931\u8D25");
3026
+ const startResult = await adapter.asrStart({
3027
+ format: "pcm",
3028
+ sampleRate,
3029
+ enablePunc,
3030
+ enableItn,
3031
+ showUtterances: true
3032
+ });
3033
+ if (!startResult.success) {
3034
+ throw new Error(startResult.error || "ASR \u542F\u52A8\u5931\u8D25");
3035
+ }
3036
+ sourceRef.current = ctx.createMediaStreamSource(stream);
3037
+ if (typeof AudioWorkletNode === "undefined" || !ctx.audioWorklet) {
3038
+ throw new Error("\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301 AudioWorkletNode\uFF08\u65E0\u6CD5\u542F\u52A8\u8BED\u97F3\u5F55\u5236\uFF09");
3039
+ }
3040
+ const { cleanup: off } = await setupAudioWorkletCapture({
3041
+ audioContext: ctx,
3042
+ source: sourceRef.current,
3043
+ chunkSize: 4096,
3044
+ onChunk: (chunk) => {
3045
+ if (statusRef.current !== "recording") return;
3046
+ const resampled = resample(chunk, actualSampleRate, sampleRate);
3047
+ audioBufferRef.current.push(new Float32Array(resampled));
3048
+ }
3049
+ });
3050
+ workletCleanupRef.current = off;
3051
+ sendTimerRef.current = setInterval(() => {
3052
+ sendAudioChunk().catch(() => {
3053
+ });
3054
+ }, sendInterval);
3055
+ setStatusSafe("recording");
3056
+ } catch (e) {
3057
+ const msg = e instanceof Error ? e.message : String(e);
3058
+ setError(msg);
3059
+ setStatusSafe("error");
3060
+ cleanup();
3061
+ }
3062
+ }, [adapter, cleanup, enableItn, enablePunc, sampleRate, sendAudioChunk, sendInterval, setStatusSafe]);
3063
+ const stop = useCallback10(async () => {
3064
+ if (statusRef.current === "connecting") {
3065
+ if (adapter?.asrStop) {
3066
+ adapter.asrStop().catch(() => {
3067
+ });
3068
+ }
3069
+ cleanup();
3070
+ setCurrentText("");
3071
+ setFinalText("");
3072
+ setError(null);
3073
+ setStatusSafe("idle");
3074
+ return "";
3075
+ }
3076
+ if (statusRef.current !== "recording") return finalTextRef.current || currentTextRef.current;
3077
+ setStatusSafe("processing");
3078
+ try {
3079
+ await sendAudioChunk();
3080
+ if (adapter?.asrFinish) {
3081
+ await adapter.asrFinish();
3082
+ }
3083
+ await new Promise((resolve) => {
3084
+ const startAt = Date.now();
3085
+ const timer = setInterval(() => {
3086
+ if (Date.now() - startAt > 3e3) {
3087
+ clearInterval(timer);
3088
+ resolve();
3089
+ return;
3090
+ }
3091
+ if (finalTextRef.current) {
3092
+ clearInterval(timer);
3093
+ resolve();
3094
+ }
3095
+ }, 100);
3096
+ });
3097
+ } catch (e) {
3098
+ const msg = e instanceof Error ? e.message : String(e);
3099
+ setError(msg);
3100
+ } finally {
3101
+ cleanup();
3102
+ setStatusSafe("idle");
3103
+ }
3104
+ return finalTextRef.current || currentTextRef.current;
3105
+ }, [adapter, cleanup, sendAudioChunk, setStatusSafe]);
3106
+ const cancel = useCallback10(() => {
3107
+ if (adapter?.asrStop) {
3108
+ adapter.asrStop().catch(() => {
3109
+ });
3110
+ }
3111
+ cleanup();
3112
+ setCurrentText("");
3113
+ setFinalText("");
3114
+ setError(null);
3115
+ setStatusSafe("idle");
3116
+ }, [adapter, cleanup, setStatusSafe]);
3117
+ useEffect10(() => {
3118
+ return () => cancel();
3119
+ }, [cancel]);
3120
+ return {
3121
+ status,
3122
+ isRecording,
3123
+ currentText,
3124
+ finalText,
3125
+ error,
3126
+ start,
3127
+ stop,
3128
+ cancel
3129
+ };
3130
+ }
3131
+
3132
+ // src/hooks/useVoiceToTextInput.ts
3133
+ function useVoiceToTextInput(opts) {
3134
+ const { adapter, inputText, setInputText, hasImages, isLoading } = opts;
3135
+ const voiceInput = useVoiceInput(adapter);
3136
+ const prefixRef = useRef8("");
3137
+ const isVoiceActive = useMemo10(
3138
+ () => voiceInput.status === "connecting" || voiceInput.status === "recording",
3139
+ [voiceInput.status]
3140
+ );
3141
+ useEffect11(() => {
3142
+ if (!isVoiceActive) return;
3143
+ const prefix = prefixRef.current;
3144
+ const t = voiceInput.currentText;
3145
+ const next = prefix ? t ? `${prefix} ${t}` : prefix : t;
3146
+ setInputText(next);
3147
+ }, [isVoiceActive, setInputText, voiceInput.currentText]);
3148
+ const toggleVoice = useCallback11(async () => {
3149
+ if (isLoading) return;
3150
+ if (!adapter) return;
3151
+ if (voiceInput.status === "connecting") {
3152
+ voiceInput.cancel();
3153
+ setInputText(prefixRef.current);
3154
+ prefixRef.current = "";
3155
+ return;
3156
+ }
3157
+ if (voiceInput.status === "recording") {
3158
+ await voiceInput.stop();
3159
+ prefixRef.current = "";
3160
+ return;
3161
+ }
3162
+ prefixRef.current = inputText.trim();
3163
+ await voiceInput.start();
3164
+ }, [adapter, inputText, isLoading, setInputText, voiceInput]);
3165
+ const sendDisabled = useMemo10(() => {
3166
+ if (isLoading) return false;
3167
+ if (isVoiceActive) return true;
3168
+ return !inputText.trim() && !hasImages;
3169
+ }, [hasImages, inputText, isLoading, isVoiceActive]);
3170
+ const handleKeyDownForVoice = useCallback11(
3171
+ (e) => {
3172
+ if (!isVoiceActive) return false;
3173
+ if (e.key === "Enter" && !e.shiftKey) {
3174
+ e.preventDefault();
3175
+ toggleVoice().catch(() => {
3176
+ });
3177
+ return true;
3178
+ }
3179
+ return false;
3180
+ },
3181
+ [isVoiceActive, toggleVoice]
3182
+ );
3183
+ return {
3184
+ voiceInput,
3185
+ isVoiceActive,
3186
+ toggleVoice,
3187
+ sendDisabled,
3188
+ handleKeyDownForVoice
3189
+ };
3190
+ }
3191
+
2788
3192
  // src/components/input/ChatInput.tsx
2789
3193
  import { jsx as jsx24, jsxs as jsxs17 } from "react/jsx-runtime";
2790
3194
  var MODE_OPTIONS = [
@@ -2811,22 +3215,22 @@ var ChatInput = forwardRef7(
2811
3215
  const isMessageVariant = variant === "message";
2812
3216
  const inputContext = useChatInputContext();
2813
3217
  const adapter = inputContext?.adapter;
2814
- const [inputText, setInputText] = useState15(value);
2815
- const [isFocused, setIsFocused] = useState15(false);
3218
+ const [inputText, setInputText] = useState16(value);
3219
+ const [isFocused, setIsFocused] = useState16(false);
2816
3220
  const imageUpload = useImageUpload();
2817
- const imageInputRef = useRef7(null);
2818
- const inputRef = useRef7(null);
2819
- const containerRef = useRef7(null);
2820
- const atSelectorRef = useRef7(null);
2821
- const [atPickerVisible, setAtPickerVisible] = useState15(false);
2822
- const replaceRangeRef = useRef7(null);
2823
- const pendingCaretRef = useRef7(null);
2824
- const pendingFocusRef = useRef7(false);
2825
- const prevValueRef = useRef7(value);
2826
- const triggerImageUpload = useCallback10(() => {
3221
+ const imageInputRef = useRef9(null);
3222
+ const inputRef = useRef9(null);
3223
+ const containerRef = useRef9(null);
3224
+ const atSelectorRef = useRef9(null);
3225
+ const [atPickerVisible, setAtPickerVisible] = useState16(false);
3226
+ const replaceRangeRef = useRef9(null);
3227
+ const pendingCaretRef = useRef9(null);
3228
+ const pendingFocusRef = useRef9(false);
3229
+ const prevValueRef = useRef9(value);
3230
+ const triggerImageUpload = useCallback12(() => {
2827
3231
  imageInputRef.current?.click();
2828
3232
  }, []);
2829
- const groupedModelOptions = useMemo9(() => {
3233
+ const groupedModelOptions = useMemo11(() => {
2830
3234
  const groups = {};
2831
3235
  models.forEach((m) => {
2832
3236
  const groupName = m.group;
@@ -2843,12 +3247,12 @@ var ChatInput = forwardRef7(
2843
3247
  });
2844
3248
  return groups;
2845
3249
  }, [models]);
2846
- const closeAtPicker = useCallback10(() => {
3250
+ const closeAtPicker = useCallback12(() => {
2847
3251
  setAtPickerVisible(false);
2848
3252
  replaceRangeRef.current = null;
2849
3253
  pendingFocusRef.current = true;
2850
3254
  }, []);
2851
- const applyAtPath = useCallback10(
3255
+ const applyAtPath = useCallback12(
2852
3256
  (path) => {
2853
3257
  const el = inputRef.current;
2854
3258
  const current = el?.value ?? inputText;
@@ -2876,22 +3280,37 @@ var ChatInput = forwardRef7(
2876
3280
  },
2877
3281
  [closeAtPicker, inputText]
2878
3282
  );
2879
- const toggleAtPicker = useCallback10(() => {
3283
+ const toggleAtPicker = useCallback12(() => {
2880
3284
  if (!adapter) return;
2881
3285
  if (!atPickerVisible) {
2882
3286
  replaceRangeRef.current = null;
2883
3287
  }
2884
3288
  setAtPickerVisible((prev) => !prev);
2885
3289
  }, [adapter, atPickerVisible]);
2886
- useEffect10(() => {
3290
+ useEffect12(() => {
2887
3291
  setInputText(value);
2888
3292
  }, [value]);
2889
3293
  const showToolbar = !isMessageVariant || isFocused;
2890
3294
  const placeholder = mode === "ask" ? "\u6709\u4EC0\u4E48\u95EE\u9898\u60F3\u95EE\u6211\uFF1F" : "\u63CF\u8FF0\u4EFB\u52A1\uFF0C@ \u6DFB\u52A0\u4E0A\u4E0B\u6587";
2891
- const hasContent = useMemo9(() => {
3295
+ const hasContent = useMemo11(() => {
2892
3296
  return inputText.trim() || imageUpload.hasImages;
2893
3297
  }, [inputText, imageUpload.hasImages]);
2894
- const adjustTextareaHeight = useCallback10(() => {
3298
+ const voiceCtl = useVoiceToTextInput({
3299
+ adapter,
3300
+ inputText,
3301
+ setInputText,
3302
+ hasImages: imageUpload.hasImages,
3303
+ isLoading
3304
+ });
3305
+ const voiceInput = voiceCtl.voiceInput;
3306
+ const toggleVoiceInput = useCallback12(async () => {
3307
+ await voiceCtl.toggleVoice();
3308
+ if (voiceInput.status === "recording" || voiceInput.status === "connecting") {
3309
+ pendingFocusRef.current = true;
3310
+ pendingCaretRef.current = null;
3311
+ }
3312
+ }, [voiceCtl, voiceInput.status]);
3313
+ const adjustTextareaHeight = useCallback12(() => {
2895
3314
  if (inputRef.current) {
2896
3315
  inputRef.current.style.height = "auto";
2897
3316
  const scrollHeight = inputRef.current.scrollHeight;
@@ -2953,7 +3372,7 @@ var ChatInput = forwardRef7(
2953
3372
  }),
2954
3373
  [inputText, imageUpload]
2955
3374
  );
2956
- const handleSendOrCancel = useCallback10(() => {
3375
+ const handleSendOrCancel = useCallback12(() => {
2957
3376
  if (isLoading) {
2958
3377
  onCancel?.();
2959
3378
  return;
@@ -2974,16 +3393,17 @@ var ChatInput = forwardRef7(
2974
3393
  setIsFocused(false);
2975
3394
  }
2976
3395
  }, [isLoading, inputText, imageUpload, isMessageVariant, onCancel, onSend]);
2977
- const handleKeyDown = useCallback10(
3396
+ const handleKeyDown = useCallback12(
2978
3397
  (e) => {
3398
+ if (voiceCtl.handleKeyDownForVoice(e)) return;
2979
3399
  if (e.key === "Enter" && !e.shiftKey) {
2980
3400
  e.preventDefault();
2981
3401
  handleSendOrCancel();
2982
3402
  }
2983
3403
  },
2984
- [handleSendOrCancel]
3404
+ [handleSendOrCancel, voiceCtl]
2985
3405
  );
2986
- useEffect10(() => {
3406
+ useEffect12(() => {
2987
3407
  const handleClickOutside = (event) => {
2988
3408
  const target = event.target;
2989
3409
  if (!target.closest(".at-picker-wrapper")) {
@@ -3007,7 +3427,7 @@ var ChatInput = forwardRef7(
3007
3427
  "div",
3008
3428
  {
3009
3429
  ref: containerRef,
3010
- className: `input-container${isFocused ? " focused" : ""}${imageUpload.isDragOver ? " drag-over" : ""}`.trim(),
3430
+ className: `input-container${isFocused ? " focused" : ""}${imageUpload.isDragOver ? " drag-over" : ""}${voiceInput.status === "connecting" ? " connecting" : ""}${voiceInput.isRecording ? " recording" : ""}`.trim(),
3011
3431
  children: [
3012
3432
  imageUpload.hasImages && /* @__PURE__ */ jsx24("div", { className: "images-preview", children: imageUpload.images.map((img, i) => /* @__PURE__ */ jsxs17("div", { className: "image-preview-item", children: [
3013
3433
  /* @__PURE__ */ jsx24(
@@ -3050,6 +3470,7 @@ var ChatInput = forwardRef7(
3050
3470
  placeholder,
3051
3471
  rows: 1,
3052
3472
  className: "input-field chat-scrollbar",
3473
+ readOnly: voiceInput.isRecording,
3053
3474
  spellCheck: false,
3054
3475
  autoCorrect: "off",
3055
3476
  autoComplete: "off",
@@ -3148,15 +3569,27 @@ var ChatInput = forwardRef7(
3148
3569
  ]
3149
3570
  }
3150
3571
  ),
3151
- hasContent || isLoading ? /* @__PURE__ */ jsx24(
3572
+ /* @__PURE__ */ jsx24(
3573
+ "button",
3574
+ {
3575
+ className: `voice-btn${voiceInput.status === "connecting" ? " connecting" : ""}${voiceInput.isRecording ? " recording" : ""}`,
3576
+ title: voiceInput.status === "connecting" ? "\u6B63\u5728\u8FDE\u63A5\uFF0C\u70B9\u51FB\u53D6\u6D88" : voiceInput.isRecording ? "\u70B9\u51FB\u505C\u6B62" : "\u70B9\u51FB\u5F55\u97F3",
3577
+ onClick: () => toggleVoiceInput().catch(() => {
3578
+ }),
3579
+ disabled: isLoading || !adapter,
3580
+ children: voiceInput.status === "connecting" ? /* @__PURE__ */ jsx24(Icon17, { icon: "lucide:loader-2", width: 18, className: "spin" }) : voiceInput.isRecording ? /* @__PURE__ */ jsx24(Icon17, { icon: "streamline-flex:button-pause-circle-remix", width: 18 }) : /* @__PURE__ */ jsx24(Icon17, { icon: "lucide:mic", width: 18 })
3581
+ }
3582
+ ),
3583
+ /* @__PURE__ */ jsx24(
3152
3584
  "button",
3153
3585
  {
3154
3586
  className: `send-btn${isLoading ? " loading" : ""}`,
3155
- title: isLoading ? "\u505C\u6B62" : isMessageVariant ? "\u91CD\u65B0\u53D1\u9001" : "\u53D1\u9001",
3587
+ title: isLoading ? "\u505C\u6B62" : voiceInput.status === "connecting" ? "\u8BED\u97F3\u8FDE\u63A5\u4E2D\uFF0C\u5148\u53D6\u6D88/\u505C\u6B62" : voiceInput.isRecording ? "\u5F55\u97F3\u4E2D\uFF0C\u5148\u505C\u6B62" : isMessageVariant ? "\u91CD\u65B0\u53D1\u9001" : "\u53D1\u9001",
3156
3588
  onClick: handleSendOrCancel,
3589
+ disabled: !hasContent && !isLoading || voiceInput.isRecording || voiceInput.status === "connecting",
3157
3590
  children: isLoading ? /* @__PURE__ */ jsx24(Icon17, { icon: "streamline-flex:button-pause-circle-remix", width: 18 }) : /* @__PURE__ */ jsx24(Icon17, { icon: "streamline-flex:mail-send-email-message-circle-remix", width: 18 })
3158
3591
  }
3159
- ) : /* @__PURE__ */ jsx24("button", { className: "icon-btn", title: "\u8BED\u97F3\u8F93\u5165", children: /* @__PURE__ */ jsx24(Icon17, { icon: "lucide:mic", width: 18 }) })
3592
+ )
3160
3593
  ] })
3161
3594
  ] })
3162
3595
  ]
@@ -3205,15 +3638,15 @@ var MessageBubble = ({
3205
3638
  const isUser = role === "user";
3206
3639
  const formattedTime = formatTime2(timestamp);
3207
3640
  const inputContext = useChatInputContext();
3208
- const userText = useMemo10(() => {
3641
+ const userText = useMemo12(() => {
3209
3642
  return parts.filter((p) => p.type === "text").map((p) => p.text).join("");
3210
3643
  }, [parts]);
3211
- const hasContent = useMemo10(() => {
3644
+ const hasContent = useMemo12(() => {
3212
3645
  return parts.some(
3213
3646
  (p) => p.type === "text" && p.text || p.type === "tool_result" || p.type === "thinking" || p.type === "search"
3214
3647
  );
3215
3648
  }, [parts]);
3216
- const loadingState = useMemo10(() => {
3649
+ const loadingState = useMemo12(() => {
3217
3650
  if (!loading) {
3218
3651
  return { type: "none" };
3219
3652
  }
@@ -3301,7 +3734,7 @@ var MessageBubble = ({
3301
3734
  };
3302
3735
 
3303
3736
  // src/components/common/ConfirmDialog.tsx
3304
- import { useEffect as useEffect11 } from "react";
3737
+ import { useEffect as useEffect13 } from "react";
3305
3738
  import { createPortal as createPortal3 } from "react-dom";
3306
3739
  import { Icon as Icon19 } from "@iconify/react";
3307
3740
  import { jsx as jsx26, jsxs as jsxs19 } from "react/jsx-runtime";
@@ -3315,7 +3748,7 @@ var ConfirmDialog = ({
3315
3748
  onConfirm,
3316
3749
  onCancel
3317
3750
  }) => {
3318
- useEffect11(() => {
3751
+ useEffect13(() => {
3319
3752
  const handleKeyDown = (e) => {
3320
3753
  if (e.key === "Escape" && visible) {
3321
3754
  onCancel?.();
@@ -3324,7 +3757,7 @@ var ConfirmDialog = ({
3324
3757
  document.addEventListener("keydown", handleKeyDown);
3325
3758
  return () => document.removeEventListener("keydown", handleKeyDown);
3326
3759
  }, [visible, onCancel]);
3327
- useEffect11(() => {
3760
+ useEffect13(() => {
3328
3761
  if (visible) {
3329
3762
  document.body.style.overflow = "hidden";
3330
3763
  } else {
@@ -3357,7 +3790,7 @@ var ConfirmDialog = ({
3357
3790
  };
3358
3791
 
3359
3792
  // src/components/common/Toast.tsx
3360
- import { useEffect as useEffect12 } from "react";
3793
+ import { useEffect as useEffect14 } from "react";
3361
3794
  import { createPortal as createPortal4 } from "react-dom";
3362
3795
  import { jsx as jsx27 } from "react/jsx-runtime";
3363
3796
  var Toast = ({
@@ -3367,7 +3800,7 @@ var Toast = ({
3367
3800
  duration = 2e3,
3368
3801
  onClose
3369
3802
  }) => {
3370
- useEffect12(() => {
3803
+ useEffect14(() => {
3371
3804
  if (visible && duration > 0) {
3372
3805
  const timer = setTimeout(() => {
3373
3806
  onClose();
@@ -3383,32 +3816,32 @@ var Toast = ({
3383
3816
  };
3384
3817
 
3385
3818
  // src/components/common/SettingsPanel.tsx
3386
- import { useState as useState17, useCallback as useCallback12, useMemo as useMemo11 } from "react";
3819
+ import { useState as useState18, useCallback as useCallback14, useMemo as useMemo13 } from "react";
3387
3820
  import { Icon as Icon21 } from "@iconify/react";
3388
3821
 
3389
3822
  // src/components/common/IndexingSettings.tsx
3390
- import { useState as useState16, useEffect as useEffect13, useCallback as useCallback11, useRef as useRef8 } from "react";
3823
+ import { useState as useState17, useEffect as useEffect15, useCallback as useCallback13, useRef as useRef10 } from "react";
3391
3824
  import { Icon as Icon20 } from "@iconify/react";
3392
3825
  import { jsx as jsx28, jsxs as jsxs20 } from "react/jsx-runtime";
3393
3826
  function IndexingSettings() {
3394
- const [indexedFiles, setIndexedFiles] = useState16(0);
3395
- const [isIndexing, setIsIndexing] = useState16(false);
3396
- const [currentFile, setCurrentFile] = useState16(null);
3397
- const [progressPercentage, setProgressPercentage] = useState16(100);
3398
- const [progressIndexed, setProgressIndexed] = useState16(0);
3399
- const [progressTotal, setProgressTotal] = useState16(0);
3400
- const [currentStage, setCurrentStage] = useState16(null);
3401
- const [showDeleteConfirm, setShowDeleteConfirm] = useState16(false);
3402
- const [indexSize, setIndexSize] = useState16("0 B");
3403
- const [lastUpdated, setLastUpdated] = useState16(null);
3404
- const cancelIndexingRef = useRef8(false);
3405
- const formatSize = useCallback11((bytes) => {
3827
+ const [indexedFiles, setIndexedFiles] = useState17(0);
3828
+ const [isIndexing, setIsIndexing] = useState17(false);
3829
+ const [currentFile, setCurrentFile] = useState17(null);
3830
+ const [progressPercentage, setProgressPercentage] = useState17(100);
3831
+ const [progressIndexed, setProgressIndexed] = useState17(0);
3832
+ const [progressTotal, setProgressTotal] = useState17(0);
3833
+ const [currentStage, setCurrentStage] = useState17(null);
3834
+ const [showDeleteConfirm, setShowDeleteConfirm] = useState17(false);
3835
+ const [indexSize, setIndexSize] = useState17("0 B");
3836
+ const [lastUpdated, setLastUpdated] = useState17(null);
3837
+ const cancelIndexingRef = useRef10(false);
3838
+ const formatSize = useCallback13((bytes) => {
3406
3839
  if (bytes < 1024) return `${bytes} B`;
3407
3840
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
3408
3841
  if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
3409
3842
  return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
3410
3843
  }, []);
3411
- const formatDate = useCallback11((date) => {
3844
+ const formatDate = useCallback13((date) => {
3412
3845
  const now = /* @__PURE__ */ new Date();
3413
3846
  const diff = now.getTime() - date.getTime();
3414
3847
  const days = Math.floor(diff / (1e3 * 60 * 60 * 24));
@@ -3426,7 +3859,7 @@ function IndexingSettings() {
3426
3859
  if (days < 365) return `${Math.floor(days / 30)} \u4E2A\u6708\u524D`;
3427
3860
  return date.toLocaleDateString("zh-CN", { year: "numeric", month: "short", day: "numeric" });
3428
3861
  }, []);
3429
- const fetchIndexStats = useCallback11(async () => {
3862
+ const fetchIndexStats = useCallback13(async () => {
3430
3863
  try {
3431
3864
  const bridge = window.aiChatBridge;
3432
3865
  if (bridge?.getIndexStats) {
@@ -3447,8 +3880,8 @@ function IndexingSettings() {
3447
3880
  setLastUpdated(null);
3448
3881
  }
3449
3882
  }, [formatSize, formatDate]);
3450
- const progressCleanupRef = useRef8(null);
3451
- const setupProgressListener = useCallback11(() => {
3883
+ const progressCleanupRef = useRef10(null);
3884
+ const setupProgressListener = useCallback13(() => {
3452
3885
  if (progressCleanupRef.current) {
3453
3886
  progressCleanupRef.current();
3454
3887
  progressCleanupRef.current = null;
@@ -3493,7 +3926,7 @@ function IndexingSettings() {
3493
3926
  }
3494
3927
  });
3495
3928
  }, [fetchIndexStats]);
3496
- const checkIndexStatus = useCallback11(async () => {
3929
+ const checkIndexStatus = useCallback13(async () => {
3497
3930
  const bridge = window.aiChatBridge;
3498
3931
  if (!bridge?.getIndexStatus) {
3499
3932
  return;
@@ -3519,7 +3952,7 @@ function IndexingSettings() {
3519
3952
  console.error("\u68C0\u67E5\u7D22\u5F15\u72B6\u6001\u5931\u8D25:", error);
3520
3953
  }
3521
3954
  }, []);
3522
- const handleSync = useCallback11(async () => {
3955
+ const handleSync = useCallback13(async () => {
3523
3956
  if (isIndexing) return;
3524
3957
  const bridge = window.aiChatBridge;
3525
3958
  if (!bridge?.syncIndex) {
@@ -3546,7 +3979,7 @@ function IndexingSettings() {
3546
3979
  setCurrentFile(null);
3547
3980
  }
3548
3981
  }, [isIndexing, setupProgressListener]);
3549
- const handleCancelIndex = useCallback11(async () => {
3982
+ const handleCancelIndex = useCallback13(async () => {
3550
3983
  cancelIndexingRef.current = true;
3551
3984
  setIsIndexing(false);
3552
3985
  setCurrentFile(null);
@@ -3559,7 +3992,7 @@ function IndexingSettings() {
3559
3992
  }
3560
3993
  }
3561
3994
  }, []);
3562
- const handleDeleteIndex = useCallback11(async () => {
3995
+ const handleDeleteIndex = useCallback13(async () => {
3563
3996
  if (isIndexing) return;
3564
3997
  setShowDeleteConfirm(false);
3565
3998
  const bridge = window.aiChatBridge;
@@ -3574,7 +4007,7 @@ function IndexingSettings() {
3574
4007
  console.error("\u5220\u9664\u7D22\u5F15\u5931\u8D25:", error);
3575
4008
  }
3576
4009
  }, [isIndexing, fetchIndexStats]);
3577
- useEffect13(() => {
4010
+ useEffect15(() => {
3578
4011
  const init = async () => {
3579
4012
  const bridge = window.aiChatBridge;
3580
4013
  if (bridge?.registerIndexListener) {
@@ -3590,7 +4023,7 @@ function IndexingSettings() {
3590
4023
  };
3591
4024
  init();
3592
4025
  }, [fetchIndexStats, setupProgressListener, checkIndexStatus]);
3593
- useEffect13(() => {
4026
+ useEffect15(() => {
3594
4027
  return () => {
3595
4028
  const bridge = window.aiChatBridge;
3596
4029
  if (bridge?.unregisterIndexListener) {
@@ -3717,8 +4150,8 @@ var sections = [
3717
4150
  { id: "indexing", label: "\u7D22\u5F15\u4E0E\u6587\u6863", icon: "lucide:database" }
3718
4151
  ];
3719
4152
  function SettingsPanel({ visible, config, onClose, onChange }) {
3720
- const [currentSection, setCurrentSection] = useState17("agent");
3721
- const currentSectionLabel = useMemo11(
4153
+ const [currentSection, setCurrentSection] = useState18("agent");
4154
+ const currentSectionLabel = useMemo13(
3722
4155
  () => sections.find((s) => s.id === currentSection)?.label ?? "",
3723
4156
  [currentSection]
3724
4157
  );
@@ -3726,13 +4159,13 @@ function SettingsPanel({ visible, config, onClose, onChange }) {
3726
4159
  { value: "run-everything", label: "\u8FD0\u884C\u6240\u6709\u5185\u5BB9\uFF08\u81EA\u52A8\u6267\u884C\uFF09" },
3727
4160
  { value: "manual", label: "\u624B\u52A8\u6279\u51C6\uFF08\u6BCF\u6B21\u6267\u884C\u524D\u8BE2\u95EE\uFF09" }
3728
4161
  ];
3729
- const updateConfig = useCallback12((key, value) => {
4162
+ const updateConfig = useCallback14((key, value) => {
3730
4163
  onChange({ ...config, [key]: value });
3731
4164
  }, [config, onChange]);
3732
- const handleModeChange = useCallback12((value) => {
4165
+ const handleModeChange = useCallback14((value) => {
3733
4166
  updateConfig("mode", value);
3734
4167
  }, [updateConfig]);
3735
- const handleOverlayClick = useCallback12((e) => {
4168
+ const handleOverlayClick = useCallback14((e) => {
3736
4169
  if (e.target === e.currentTarget) {
3737
4170
  onClose();
3738
4171
  }
@@ -3796,13 +4229,13 @@ var ChatPanel = forwardRef8(({
3796
4229
  toolRenderers = {},
3797
4230
  stepsExpandedType = "auto"
3798
4231
  }, ref) => {
3799
- const messagesRef = useRef9(null);
3800
- const inputRef = useRef9(null);
3801
- const [shouldAutoScroll, setShouldAutoScroll] = useState18(true);
4232
+ const messagesRef = useRef11(null);
4233
+ const inputRef = useRef11(null);
4234
+ const [shouldAutoScroll, setShouldAutoScroll] = useState19(true);
3802
4235
  const SCROLL_THRESHOLD = 100;
3803
- const [settingsPanelVisible, setSettingsPanelVisible] = useState18(false);
3804
- const [models, setModels] = useState18(propModels || []);
3805
- const [confirmDialog, setConfirmDialog] = useState18({
4236
+ const [settingsPanelVisible, setSettingsPanelVisible] = useState19(false);
4237
+ const [models, setModels] = useState19(propModels || []);
4238
+ const [confirmDialog, setConfirmDialog] = useState19({
3806
4239
  visible: false,
3807
4240
  title: "\u786E\u8BA4",
3808
4241
  message: "",
@@ -3811,7 +4244,7 @@ var ChatPanel = forwardRef8(({
3811
4244
  onConfirm: () => {
3812
4245
  }
3813
4246
  });
3814
- const showConfirm = useCallback13((options) => {
4247
+ const showConfirm = useCallback15((options) => {
3815
4248
  setConfirmDialog({
3816
4249
  visible: true,
3817
4250
  title: options.title || "\u786E\u8BA4",
@@ -3821,8 +4254,8 @@ var ChatPanel = forwardRef8(({
3821
4254
  onConfirm: options.onConfirm
3822
4255
  });
3823
4256
  }, []);
3824
- const [toast, setToast] = useState18({ visible: false, message: "", type: "info" });
3825
- const showToast = useCallback13((message, type = "info") => {
4257
+ const [toast, setToast] = useState19({ visible: false, message: "", type: "info" });
4258
+ const showToast = useCallback15((message, type = "info") => {
3826
4259
  setToast({ visible: true, message, type });
3827
4260
  }, []);
3828
4261
  const {
@@ -3874,61 +4307,61 @@ var ChatPanel = forwardRef8(({
3874
4307
  },
3875
4308
  setCwd: setWorkingDirectory
3876
4309
  }), [sendMessage, setWorkingDirectory]);
3877
- useEffect14(() => {
4310
+ useEffect16(() => {
3878
4311
  loadSessions();
3879
4312
  }, [loadSessions]);
3880
- useEffect14(() => {
4313
+ useEffect16(() => {
3881
4314
  adapter.getModels().then(setModels).catch((err) => console.warn("\u83B7\u53D6\u6A21\u578B\u5217\u8868\u5931\u8D25:", err));
3882
4315
  }, [adapter]);
3883
- const isNearBottom = useCallback13(() => {
4316
+ const isNearBottom = useCallback15(() => {
3884
4317
  if (!messagesRef.current) return true;
3885
4318
  const { scrollTop, scrollHeight, clientHeight } = messagesRef.current;
3886
4319
  return scrollHeight - scrollTop - clientHeight < SCROLL_THRESHOLD;
3887
4320
  }, []);
3888
- const handleScroll = useCallback13(() => {
4321
+ const handleScroll = useCallback15(() => {
3889
4322
  setShouldAutoScroll(isNearBottom());
3890
4323
  }, [isNearBottom]);
3891
- const scrollToBottom = useCallback13((force = false) => {
4324
+ const scrollToBottom = useCallback15((force = false) => {
3892
4325
  if (messagesRef.current && (force || shouldAutoScroll)) {
3893
4326
  messagesRef.current.scrollTop = messagesRef.current.scrollHeight;
3894
4327
  setShouldAutoScroll(true);
3895
4328
  }
3896
4329
  }, [shouldAutoScroll]);
3897
- useEffect14(() => {
4330
+ useEffect16(() => {
3898
4331
  scrollToBottom();
3899
4332
  }, [messages, scrollToBottom]);
3900
- const prevIsLoadingRef = useRef9(isLoading);
3901
- useEffect14(() => {
4333
+ const prevIsLoadingRef = useRef11(isLoading);
4334
+ useEffect16(() => {
3902
4335
  if (isLoading && !prevIsLoadingRef.current) {
3903
4336
  scrollToBottom(true);
3904
4337
  }
3905
4338
  prevIsLoadingRef.current = isLoading;
3906
4339
  }, [isLoading, scrollToBottom]);
3907
- const handleSend = useCallback13((text) => {
4340
+ const handleSend = useCallback15((text) => {
3908
4341
  sendMessage(text);
3909
4342
  }, [sendMessage]);
3910
- const handleAtContext = useCallback13(() => {
4343
+ const handleAtContext = useCallback15(() => {
3911
4344
  console.log("@ \u4E0A\u4E0B\u6587");
3912
4345
  }, []);
3913
- const handleQuickAction = useCallback13(
4346
+ const handleQuickAction = useCallback15(
3914
4347
  (text) => {
3915
4348
  sendMessage(text);
3916
4349
  },
3917
4350
  [sendMessage]
3918
4351
  );
3919
- const handleResend = useCallback13(
4352
+ const handleResend = useCallback15(
3920
4353
  (_index, text) => {
3921
4354
  sendMessage(text);
3922
4355
  },
3923
4356
  [sendMessage]
3924
4357
  );
3925
- const handleClose = useCallback13(() => {
4358
+ const handleClose = useCallback15(() => {
3926
4359
  onClose?.();
3927
4360
  }, [onClose]);
3928
- const handleSettings = useCallback13(() => {
4361
+ const handleSettings = useCallback15(() => {
3929
4362
  setSettingsPanelVisible(true);
3930
4363
  }, []);
3931
- const handleSaveSettings = useCallback13(async (config) => {
4364
+ const handleSaveSettings = useCallback15(async (config) => {
3932
4365
  try {
3933
4366
  await saveAutoRunConfig(config);
3934
4367
  } catch (error) {
@@ -3936,7 +4369,7 @@ var ChatPanel = forwardRef8(({
3936
4369
  showToast("\u4FDD\u5B58\u8BBE\u7F6E\u5931\u8D25", "error");
3937
4370
  }
3938
4371
  }, [saveAutoRunConfig, showToast]);
3939
- const handleClearAll = useCallback13(() => {
4372
+ const handleClearAll = useCallback15(() => {
3940
4373
  showConfirm({
3941
4374
  title: "\u6E05\u7A7A\u6240\u6709\u5BF9\u8BDD",
3942
4375
  message: "\u786E\u5B9A\u8981\u6E05\u7A7A\u6240\u6709\u5BF9\u8BDD\u5417\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002",
@@ -3945,10 +4378,10 @@ var ChatPanel = forwardRef8(({
3945
4378
  onConfirm: () => clearAllSessions()
3946
4379
  });
3947
4380
  }, [showConfirm, clearAllSessions]);
3948
- const handleCloseOthers = useCallback13(async () => {
4381
+ const handleCloseOthers = useCallback15(async () => {
3949
4382
  await hideOtherSessions();
3950
4383
  }, [hideOtherSessions]);
3951
- const handleExport = useCallback13(() => {
4384
+ const handleExport = useCallback15(() => {
3952
4385
  const data = exportCurrentSession();
3953
4386
  if (!data) {
3954
4387
  showToast("\u5F53\u524D\u4F1A\u8BDD\u6CA1\u6709\u5185\u5BB9\u53EF\u5BFC\u51FA", "warning");
@@ -3966,7 +4399,7 @@ var ChatPanel = forwardRef8(({
3966
4399
  document.body.removeChild(a);
3967
4400
  URL.revokeObjectURL(url);
3968
4401
  }, [exportCurrentSession, sessions, currentSessionId, showToast]);
3969
- const handleCopyId = useCallback13(async () => {
4402
+ const handleCopyId = useCallback15(async () => {
3970
4403
  if (!currentSessionId) return;
3971
4404
  try {
3972
4405
  await navigator.clipboard.writeText(currentSessionId);
@@ -3975,10 +4408,10 @@ var ChatPanel = forwardRef8(({
3975
4408
  console.error("\u590D\u5236\u5931\u8D25:", error);
3976
4409
  }
3977
4410
  }, [currentSessionId, showToast]);
3978
- const handleFeedback = useCallback13(() => {
4411
+ const handleFeedback = useCallback15(() => {
3979
4412
  console.log("\u53CD\u9988");
3980
4413
  }, []);
3981
- const inputContextValue = useMemo12(
4414
+ const inputContextValue = useMemo14(
3982
4415
  () => ({
3983
4416
  mode,
3984
4417
  model,
@@ -4106,7 +4539,7 @@ var ChatPanel = forwardRef8(({
4106
4539
  ChatPanel.displayName = "ChatPanel";
4107
4540
 
4108
4541
  // src/components/message/ContentRenderer.tsx
4109
- import { useMemo as useMemo14, useContext as useContext3 } from "react";
4542
+ import { useMemo as useMemo16, useContext as useContext3 } from "react";
4110
4543
  import { parseContent } from "@huyooo/ai-chat-shared";
4111
4544
 
4112
4545
  // src/components/message/blocks/TextBlock.tsx
@@ -4116,21 +4549,21 @@ var TextBlock = ({ block }) => {
4116
4549
  };
4117
4550
 
4118
4551
  // src/components/message/blocks/CodeBlock.tsx
4119
- import { useState as useState19, useCallback as useCallback14, useMemo as useMemo13 } from "react";
4552
+ import { useState as useState20, useCallback as useCallback16, useMemo as useMemo15 } from "react";
4120
4553
  import { Icon as Icon23 } from "@iconify/react";
4121
4554
  import { highlightCode, getLanguageDisplayName } from "@huyooo/ai-chat-shared";
4122
4555
  import { jsx as jsx32, jsxs as jsxs23 } from "react/jsx-runtime";
4123
4556
  var CodeBlock = ({ block, onCopy }) => {
4124
- const [copied, setCopied] = useState19(false);
4125
- const languageDisplay = useMemo13(
4557
+ const [copied, setCopied] = useState20(false);
4558
+ const languageDisplay = useMemo15(
4126
4559
  () => getLanguageDisplayName(block.language),
4127
4560
  [block.language]
4128
4561
  );
4129
- const highlightedCode = useMemo13(
4562
+ const highlightedCode = useMemo15(
4130
4563
  () => highlightCode(block.content, block.language),
4131
4564
  [block.content, block.language]
4132
4565
  );
4133
- const handleCopy = useCallback14(async () => {
4566
+ const handleCopy = useCallback16(async () => {
4134
4567
  try {
4135
4568
  await navigator.clipboard.writeText(block.content);
4136
4569
  setCopied(true);
@@ -4157,7 +4590,7 @@ var ContentRenderer = ({
4157
4590
  onCodeCopy
4158
4591
  }) => {
4159
4592
  const customRenderers = useContext3(BlockRenderersContext);
4160
- const blocks = useMemo14(() => {
4593
+ const blocks = useMemo16(() => {
4161
4594
  if (preBlocks?.length) {
4162
4595
  return preBlocks;
4163
4596
  }
@@ -4181,13 +4614,13 @@ var ContentRenderer = ({
4181
4614
  };
4182
4615
 
4183
4616
  // src/components/message/ToolResultRenderer.tsx
4184
- import { useContext as useContext4, useMemo as useMemo18 } from "react";
4617
+ import { useContext as useContext4, useMemo as useMemo20 } from "react";
4185
4618
 
4186
4619
  // src/components/message/tool-results/DefaultToolResult.tsx
4187
- import { useMemo as useMemo15 } from "react";
4620
+ import { useMemo as useMemo17 } from "react";
4188
4621
  import { jsx as jsx34 } from "react/jsx-runtime";
4189
4622
  var DefaultToolResult = ({ toolResult }) => {
4190
- const formattedResult = useMemo15(() => {
4623
+ const formattedResult = useMemo17(() => {
4191
4624
  if (typeof toolResult === "string") {
4192
4625
  return toolResult;
4193
4626
  }
@@ -4201,11 +4634,11 @@ var DefaultToolResult = ({ toolResult }) => {
4201
4634
  };
4202
4635
 
4203
4636
  // src/components/message/tool-results/WeatherCard.tsx
4204
- import { useMemo as useMemo16 } from "react";
4637
+ import { useMemo as useMemo18 } from "react";
4205
4638
  import { Icon as Icon24 } from "@iconify/react";
4206
4639
  import { jsx as jsx35, jsxs as jsxs24 } from "react/jsx-runtime";
4207
4640
  var WeatherCard = ({ toolResult }) => {
4208
- const weather = useMemo16(() => {
4641
+ const weather = useMemo18(() => {
4209
4642
  if (typeof toolResult === "object" && toolResult !== null) {
4210
4643
  return toolResult;
4211
4644
  }
@@ -4254,11 +4687,11 @@ var WeatherCard = ({ toolResult }) => {
4254
4687
  };
4255
4688
 
4256
4689
  // src/components/message/tool-results/SearchResults.tsx
4257
- import { useMemo as useMemo17, useCallback as useCallback15 } from "react";
4690
+ import { useMemo as useMemo19, useCallback as useCallback17 } from "react";
4258
4691
  import { Icon as Icon25 } from "@iconify/react";
4259
4692
  import { jsx as jsx36, jsxs as jsxs25 } from "react/jsx-runtime";
4260
4693
  var SearchResults = ({ toolResult }) => {
4261
- const results = useMemo17(() => {
4694
+ const results = useMemo19(() => {
4262
4695
  if (Array.isArray(toolResult)) {
4263
4696
  return toolResult;
4264
4697
  }
@@ -4267,14 +4700,14 @@ var SearchResults = ({ toolResult }) => {
4267
4700
  }
4268
4701
  return [];
4269
4702
  }, [toolResult]);
4270
- const getDomain = useCallback15((url) => {
4703
+ const getDomain = useCallback17((url) => {
4271
4704
  try {
4272
4705
  return new URL(url).hostname;
4273
4706
  } catch {
4274
4707
  return url;
4275
4708
  }
4276
4709
  }, []);
4277
- const openExternal = useCallback15((url) => {
4710
+ const openExternal = useCallback17((url) => {
4278
4711
  const bridge = window.aiChatBridge;
4279
4712
  if (bridge?.openExternal) {
4280
4713
  bridge.openExternal(url);
@@ -4317,7 +4750,7 @@ var SearchResults = ({ toolResult }) => {
4317
4750
  import { jsx as jsx37 } from "react/jsx-runtime";
4318
4751
  var ToolResultRenderer = (props) => {
4319
4752
  const customRenderers = useContext4(ToolRenderersContext);
4320
- const Component = useMemo18(() => {
4753
+ const Component = useMemo20(() => {
4321
4754
  return customRenderers[props.toolName] || DefaultToolResult;
4322
4755
  }, [customRenderers, props.toolName]);
4323
4756
  return /* @__PURE__ */ jsx37(Component, { ...props });