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

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,421 @@ 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
+ var asrWarmupDone = false;
2816
+ async function setupAudioWorkletCapture(opts) {
2817
+ const { audioContext, source, chunkSize, onChunk } = opts;
2818
+ const workletCode = `
2819
+ class PcmCaptureProcessor extends AudioWorkletProcessor {
2820
+ constructor(options) {
2821
+ super();
2822
+ const size = (options && options.processorOptions && options.processorOptions.chunkSize) || 4096;
2823
+ this._chunkSize = size;
2824
+ this._buf = new Float32Array(size);
2825
+ this._offset = 0;
2826
+ }
2827
+ process(inputs, outputs) {
2828
+ const input = inputs && inputs[0] && inputs[0][0];
2829
+ const output = outputs && outputs[0] && outputs[0][0];
2830
+ if (!input) return true;
2831
+ if (output) output.set(input);
2832
+
2833
+ let i = 0;
2834
+ while (i < input.length) {
2835
+ const remain = this._chunkSize - this._offset;
2836
+ const take = Math.min(remain, input.length - i);
2837
+ this._buf.set(input.subarray(i, i + take), this._offset);
2838
+ this._offset += take;
2839
+ i += take;
2840
+ if (this._offset >= this._chunkSize) {
2841
+ const out = this._buf;
2842
+ this.port.postMessage(out, [out.buffer]);
2843
+ this._buf = new Float32Array(this._chunkSize);
2844
+ this._offset = 0;
2845
+ }
2846
+ }
2847
+ return true;
2848
+ }
2849
+ }
2850
+ registerProcessor('pcm-capture', PcmCaptureProcessor);
2851
+ `;
2852
+ const blob = new Blob([workletCode], { type: "text/javascript" });
2853
+ const url = URL.createObjectURL(blob);
2854
+ await audioContext.audioWorklet.addModule(url);
2855
+ URL.revokeObjectURL(url);
2856
+ const workletNode = new AudioWorkletNode(audioContext, "pcm-capture", {
2857
+ numberOfInputs: 1,
2858
+ numberOfOutputs: 1,
2859
+ channelCount: 1,
2860
+ processorOptions: { chunkSize }
2861
+ });
2862
+ const silentGain = audioContext.createGain();
2863
+ silentGain.gain.value = 0;
2864
+ const onMessage = (event) => {
2865
+ const data = event.data;
2866
+ if (data instanceof Float32Array) {
2867
+ onChunk(data);
2868
+ return;
2869
+ }
2870
+ if (data instanceof ArrayBuffer) {
2871
+ onChunk(new Float32Array(data));
2872
+ }
2873
+ };
2874
+ workletNode.port.addEventListener("message", onMessage);
2875
+ workletNode.port.start();
2876
+ source.connect(workletNode);
2877
+ workletNode.connect(silentGain);
2878
+ silentGain.connect(audioContext.destination);
2879
+ return {
2880
+ cleanup: () => {
2881
+ try {
2882
+ workletNode.port.removeEventListener("message", onMessage);
2883
+ } catch {
2884
+ }
2885
+ try {
2886
+ workletNode.disconnect();
2887
+ } catch {
2888
+ }
2889
+ try {
2890
+ silentGain.disconnect();
2891
+ } catch {
2892
+ }
2893
+ }
2894
+ };
2895
+ }
2896
+ function useVoiceInput(adapter, config = {}) {
2897
+ const { sampleRate = 16e3, sendInterval = 200, enablePunc = true, enableItn = true } = config;
2898
+ useEffect10(() => {
2899
+ if (adapter && !asrWarmupDone && typeof adapter.asrWarmup === "function") {
2900
+ asrWarmupDone = true;
2901
+ const timer = setTimeout(() => {
2902
+ adapter.asrWarmup?.().catch(() => {
2903
+ });
2904
+ }, 800);
2905
+ return () => clearTimeout(timer);
2906
+ }
2907
+ }, [adapter]);
2908
+ const [status, setStatus] = useState15("idle");
2909
+ const [currentText, setCurrentText] = useState15("");
2910
+ const [finalText, setFinalText] = useState15("");
2911
+ const [error, setError] = useState15(null);
2912
+ const statusRef = useRef7("idle");
2913
+ const currentTextRef = useRef7("");
2914
+ const finalTextRef = useRef7("");
2915
+ const mediaStreamRef = useRef7(null);
2916
+ const audioContextRef = useRef7(null);
2917
+ const workletCleanupRef = useRef7(null);
2918
+ const sourceRef = useRef7(null);
2919
+ const audioBufferRef = useRef7([]);
2920
+ const sendTimerRef = useRef7(null);
2921
+ const cleanupFnsRef = useRef7([]);
2922
+ const isRecording = useMemo9(() => status === "connecting" || status === "recording", [status]);
2923
+ useEffect10(() => {
2924
+ statusRef.current = status;
2925
+ }, [status]);
2926
+ useEffect10(() => {
2927
+ currentTextRef.current = currentText;
2928
+ }, [currentText]);
2929
+ useEffect10(() => {
2930
+ finalTextRef.current = finalText;
2931
+ }, [finalText]);
2932
+ const setStatusSafe = useCallback10((next) => {
2933
+ statusRef.current = next;
2934
+ setStatus(next);
2935
+ }, []);
2936
+ const cleanup = useCallback10(() => {
2937
+ if (sendTimerRef.current) {
2938
+ clearInterval(sendTimerRef.current);
2939
+ sendTimerRef.current = null;
2940
+ }
2941
+ if (workletCleanupRef.current) {
2942
+ workletCleanupRef.current();
2943
+ workletCleanupRef.current = null;
2944
+ }
2945
+ if (sourceRef.current) {
2946
+ try {
2947
+ sourceRef.current.disconnect();
2948
+ } catch {
2949
+ }
2950
+ sourceRef.current = null;
2951
+ }
2952
+ if (audioContextRef.current) {
2953
+ audioContextRef.current.close().catch(() => {
2954
+ });
2955
+ audioContextRef.current = null;
2956
+ }
2957
+ if (mediaStreamRef.current) {
2958
+ mediaStreamRef.current.getTracks().forEach((t) => t.stop());
2959
+ mediaStreamRef.current = null;
2960
+ }
2961
+ cleanupFnsRef.current.forEach((fn) => fn());
2962
+ cleanupFnsRef.current = [];
2963
+ audioBufferRef.current = [];
2964
+ }, []);
2965
+ const sendAudioChunk = useCallback10(async () => {
2966
+ if (!adapter?.asrSendAudio) return;
2967
+ const buf = audioBufferRef.current;
2968
+ if (!buf.length) return;
2969
+ const totalLength = buf.reduce((sum, arr) => sum + arr.length, 0);
2970
+ const merged = new Float32Array(totalLength);
2971
+ let offset = 0;
2972
+ for (const chunk of buf) {
2973
+ merged.set(chunk, offset);
2974
+ offset += chunk.length;
2975
+ }
2976
+ audioBufferRef.current = [];
2977
+ const pcmData = float32ToInt16(merged);
2978
+ const result = await adapter.asrSendAudio(pcmData.buffer);
2979
+ if (!result.success) {
2980
+ console.error("[VoiceInput] \u53D1\u9001\u97F3\u9891\u5931\u8D25:", result.error);
2981
+ }
2982
+ }, [adapter]);
2983
+ const start = useCallback10(async () => {
2984
+ if (statusRef.current === "connecting" || statusRef.current === "recording") return;
2985
+ if (!adapter) {
2986
+ setError("Adapter \u672A\u521D\u59CB\u5316");
2987
+ setStatusSafe("error");
2988
+ return;
2989
+ }
2990
+ if (!adapter.asrStart) {
2991
+ setError("\u8BED\u97F3\u8BC6\u522B\u529F\u80FD\u4E0D\u53EF\u7528");
2992
+ setStatusSafe("error");
2993
+ return;
2994
+ }
2995
+ setError(null);
2996
+ setCurrentText("");
2997
+ setFinalText("");
2998
+ setStatusSafe("connecting");
2999
+ try {
3000
+ mediaStreamRef.current = await navigator.mediaDevices.getUserMedia({
3001
+ audio: {
3002
+ channelCount: 1,
3003
+ sampleRate: { ideal: sampleRate },
3004
+ echoCancellation: true,
3005
+ noiseSuppression: true
3006
+ }
3007
+ });
3008
+ audioContextRef.current = new AudioContext({ sampleRate });
3009
+ const actualSampleRate = audioContextRef.current.sampleRate;
3010
+ if (adapter.onAsrResult) {
3011
+ const off2 = adapter.onAsrResult((data) => {
3012
+ const text = data.result.result?.text || "";
3013
+ setCurrentText(text);
3014
+ if (data.isLast) setFinalText(text);
3015
+ });
3016
+ cleanupFnsRef.current.push(off2);
3017
+ }
3018
+ if (adapter.onAsrError) {
3019
+ const off2 = adapter.onAsrError((err) => {
3020
+ console.error("[VoiceInput] ASR \u9519\u8BEF:", err.message);
3021
+ setError(err.message);
3022
+ setStatusSafe("error");
3023
+ });
3024
+ cleanupFnsRef.current.push(off2);
3025
+ }
3026
+ if (adapter.onAsrClosed) {
3027
+ const off2 = adapter.onAsrClosed(() => {
3028
+ if (statusRef.current === "recording") {
3029
+ setStatusSafe("idle");
3030
+ }
3031
+ });
3032
+ cleanupFnsRef.current.push(off2);
3033
+ }
3034
+ const ctx = audioContextRef.current;
3035
+ const stream = mediaStreamRef.current;
3036
+ if (!ctx || !stream) throw new Error("AudioContext \u6216 MediaStream \u521D\u59CB\u5316\u5931\u8D25");
3037
+ const startResult = await adapter.asrStart({
3038
+ format: "pcm",
3039
+ sampleRate,
3040
+ enablePunc,
3041
+ enableItn,
3042
+ showUtterances: true
3043
+ });
3044
+ if (!startResult.success) {
3045
+ throw new Error(startResult.error || "ASR \u542F\u52A8\u5931\u8D25");
3046
+ }
3047
+ sourceRef.current = ctx.createMediaStreamSource(stream);
3048
+ if (typeof AudioWorkletNode === "undefined" || !ctx.audioWorklet) {
3049
+ throw new Error("\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301 AudioWorkletNode\uFF08\u65E0\u6CD5\u542F\u52A8\u8BED\u97F3\u5F55\u5236\uFF09");
3050
+ }
3051
+ const { cleanup: off } = await setupAudioWorkletCapture({
3052
+ audioContext: ctx,
3053
+ source: sourceRef.current,
3054
+ chunkSize: 4096,
3055
+ onChunk: (chunk) => {
3056
+ if (statusRef.current !== "recording") return;
3057
+ const resampled = resample(chunk, actualSampleRate, sampleRate);
3058
+ audioBufferRef.current.push(new Float32Array(resampled));
3059
+ }
3060
+ });
3061
+ workletCleanupRef.current = off;
3062
+ sendTimerRef.current = setInterval(() => {
3063
+ sendAudioChunk().catch(() => {
3064
+ });
3065
+ }, sendInterval);
3066
+ setStatusSafe("recording");
3067
+ } catch (e) {
3068
+ const msg = e instanceof Error ? e.message : String(e);
3069
+ setError(msg);
3070
+ setStatusSafe("error");
3071
+ cleanup();
3072
+ }
3073
+ }, [adapter, cleanup, enableItn, enablePunc, sampleRate, sendAudioChunk, sendInterval, setStatusSafe]);
3074
+ const stop = useCallback10(async () => {
3075
+ if (statusRef.current === "connecting") {
3076
+ if (adapter?.asrStop) {
3077
+ adapter.asrStop().catch(() => {
3078
+ });
3079
+ }
3080
+ cleanup();
3081
+ setCurrentText("");
3082
+ setFinalText("");
3083
+ setError(null);
3084
+ setStatusSafe("idle");
3085
+ return "";
3086
+ }
3087
+ if (statusRef.current !== "recording") return finalTextRef.current || currentTextRef.current;
3088
+ setStatusSafe("processing");
3089
+ try {
3090
+ await sendAudioChunk();
3091
+ if (adapter?.asrFinish) {
3092
+ await adapter.asrFinish();
3093
+ }
3094
+ await new Promise((resolve) => {
3095
+ const startAt = Date.now();
3096
+ const timer = setInterval(() => {
3097
+ if (Date.now() - startAt > 3e3) {
3098
+ clearInterval(timer);
3099
+ resolve();
3100
+ return;
3101
+ }
3102
+ if (finalTextRef.current) {
3103
+ clearInterval(timer);
3104
+ resolve();
3105
+ }
3106
+ }, 100);
3107
+ });
3108
+ } catch (e) {
3109
+ const msg = e instanceof Error ? e.message : String(e);
3110
+ setError(msg);
3111
+ } finally {
3112
+ cleanup();
3113
+ setStatusSafe("idle");
3114
+ }
3115
+ return finalTextRef.current || currentTextRef.current;
3116
+ }, [adapter, cleanup, sendAudioChunk, setStatusSafe]);
3117
+ const cancel = useCallback10(() => {
3118
+ if (adapter?.asrStop) {
3119
+ adapter.asrStop().catch(() => {
3120
+ });
3121
+ }
3122
+ cleanup();
3123
+ setCurrentText("");
3124
+ setFinalText("");
3125
+ setError(null);
3126
+ setStatusSafe("idle");
3127
+ }, [adapter, cleanup, setStatusSafe]);
3128
+ useEffect10(() => {
3129
+ return () => cancel();
3130
+ }, [cancel]);
3131
+ return {
3132
+ status,
3133
+ isRecording,
3134
+ currentText,
3135
+ finalText,
3136
+ error,
3137
+ start,
3138
+ stop,
3139
+ cancel
3140
+ };
3141
+ }
3142
+
3143
+ // src/hooks/useVoiceToTextInput.ts
3144
+ function useVoiceToTextInput(opts) {
3145
+ const { adapter, inputText, setInputText, hasImages, isLoading } = opts;
3146
+ const voiceInput = useVoiceInput(adapter);
3147
+ const prefixRef = useRef8("");
3148
+ const isVoiceActive = useMemo10(
3149
+ () => voiceInput.status === "connecting" || voiceInput.status === "recording",
3150
+ [voiceInput.status]
3151
+ );
3152
+ useEffect11(() => {
3153
+ if (!isVoiceActive) return;
3154
+ const prefix = prefixRef.current;
3155
+ const t = voiceInput.currentText;
3156
+ const next = prefix ? t ? `${prefix} ${t}` : prefix : t;
3157
+ setInputText(next);
3158
+ }, [isVoiceActive, setInputText, voiceInput.currentText]);
3159
+ const toggleVoice = useCallback11(async () => {
3160
+ if (isLoading) return;
3161
+ if (!adapter) return;
3162
+ if (voiceInput.status === "connecting") {
3163
+ voiceInput.cancel();
3164
+ setInputText(prefixRef.current);
3165
+ prefixRef.current = "";
3166
+ return;
3167
+ }
3168
+ if (voiceInput.status === "recording") {
3169
+ await voiceInput.stop();
3170
+ prefixRef.current = "";
3171
+ return;
3172
+ }
3173
+ prefixRef.current = inputText.trim();
3174
+ await voiceInput.start();
3175
+ }, [adapter, inputText, isLoading, setInputText, voiceInput]);
3176
+ const sendDisabled = useMemo10(() => {
3177
+ if (isLoading) return false;
3178
+ if (isVoiceActive) return true;
3179
+ return !inputText.trim() && !hasImages;
3180
+ }, [hasImages, inputText, isLoading, isVoiceActive]);
3181
+ const handleKeyDownForVoice = useCallback11(
3182
+ (e) => {
3183
+ if (!isVoiceActive) return false;
3184
+ if (e.key === "Enter" && !e.shiftKey) {
3185
+ e.preventDefault();
3186
+ toggleVoice().catch(() => {
3187
+ });
3188
+ return true;
3189
+ }
3190
+ return false;
3191
+ },
3192
+ [isVoiceActive, toggleVoice]
3193
+ );
3194
+ return {
3195
+ voiceInput,
3196
+ isVoiceActive,
3197
+ toggleVoice,
3198
+ sendDisabled,
3199
+ handleKeyDownForVoice
3200
+ };
3201
+ }
3202
+
2788
3203
  // src/components/input/ChatInput.tsx
2789
3204
  import { jsx as jsx24, jsxs as jsxs17 } from "react/jsx-runtime";
2790
3205
  var MODE_OPTIONS = [
@@ -2811,22 +3226,22 @@ var ChatInput = forwardRef7(
2811
3226
  const isMessageVariant = variant === "message";
2812
3227
  const inputContext = useChatInputContext();
2813
3228
  const adapter = inputContext?.adapter;
2814
- const [inputText, setInputText] = useState15(value);
2815
- const [isFocused, setIsFocused] = useState15(false);
3229
+ const [inputText, setInputText] = useState16(value);
3230
+ const [isFocused, setIsFocused] = useState16(false);
2816
3231
  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(() => {
3232
+ const imageInputRef = useRef9(null);
3233
+ const inputRef = useRef9(null);
3234
+ const containerRef = useRef9(null);
3235
+ const atSelectorRef = useRef9(null);
3236
+ const [atPickerVisible, setAtPickerVisible] = useState16(false);
3237
+ const replaceRangeRef = useRef9(null);
3238
+ const pendingCaretRef = useRef9(null);
3239
+ const pendingFocusRef = useRef9(false);
3240
+ const prevValueRef = useRef9(value);
3241
+ const triggerImageUpload = useCallback12(() => {
2827
3242
  imageInputRef.current?.click();
2828
3243
  }, []);
2829
- const groupedModelOptions = useMemo9(() => {
3244
+ const groupedModelOptions = useMemo11(() => {
2830
3245
  const groups = {};
2831
3246
  models.forEach((m) => {
2832
3247
  const groupName = m.group;
@@ -2843,12 +3258,12 @@ var ChatInput = forwardRef7(
2843
3258
  });
2844
3259
  return groups;
2845
3260
  }, [models]);
2846
- const closeAtPicker = useCallback10(() => {
3261
+ const closeAtPicker = useCallback12(() => {
2847
3262
  setAtPickerVisible(false);
2848
3263
  replaceRangeRef.current = null;
2849
3264
  pendingFocusRef.current = true;
2850
3265
  }, []);
2851
- const applyAtPath = useCallback10(
3266
+ const applyAtPath = useCallback12(
2852
3267
  (path) => {
2853
3268
  const el = inputRef.current;
2854
3269
  const current = el?.value ?? inputText;
@@ -2876,22 +3291,37 @@ var ChatInput = forwardRef7(
2876
3291
  },
2877
3292
  [closeAtPicker, inputText]
2878
3293
  );
2879
- const toggleAtPicker = useCallback10(() => {
3294
+ const toggleAtPicker = useCallback12(() => {
2880
3295
  if (!adapter) return;
2881
3296
  if (!atPickerVisible) {
2882
3297
  replaceRangeRef.current = null;
2883
3298
  }
2884
3299
  setAtPickerVisible((prev) => !prev);
2885
3300
  }, [adapter, atPickerVisible]);
2886
- useEffect10(() => {
3301
+ useEffect12(() => {
2887
3302
  setInputText(value);
2888
3303
  }, [value]);
2889
3304
  const showToolbar = !isMessageVariant || isFocused;
2890
3305
  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(() => {
3306
+ const hasContent = useMemo11(() => {
2892
3307
  return inputText.trim() || imageUpload.hasImages;
2893
3308
  }, [inputText, imageUpload.hasImages]);
2894
- const adjustTextareaHeight = useCallback10(() => {
3309
+ const voiceCtl = useVoiceToTextInput({
3310
+ adapter,
3311
+ inputText,
3312
+ setInputText,
3313
+ hasImages: imageUpload.hasImages,
3314
+ isLoading
3315
+ });
3316
+ const voiceInput = voiceCtl.voiceInput;
3317
+ const toggleVoiceInput = useCallback12(async () => {
3318
+ await voiceCtl.toggleVoice();
3319
+ if (voiceInput.status === "recording" || voiceInput.status === "connecting") {
3320
+ pendingFocusRef.current = true;
3321
+ pendingCaretRef.current = null;
3322
+ }
3323
+ }, [voiceCtl, voiceInput.status]);
3324
+ const adjustTextareaHeight = useCallback12(() => {
2895
3325
  if (inputRef.current) {
2896
3326
  inputRef.current.style.height = "auto";
2897
3327
  const scrollHeight = inputRef.current.scrollHeight;
@@ -2953,7 +3383,7 @@ var ChatInput = forwardRef7(
2953
3383
  }),
2954
3384
  [inputText, imageUpload]
2955
3385
  );
2956
- const handleSendOrCancel = useCallback10(() => {
3386
+ const handleSendOrCancel = useCallback12(() => {
2957
3387
  if (isLoading) {
2958
3388
  onCancel?.();
2959
3389
  return;
@@ -2974,16 +3404,17 @@ var ChatInput = forwardRef7(
2974
3404
  setIsFocused(false);
2975
3405
  }
2976
3406
  }, [isLoading, inputText, imageUpload, isMessageVariant, onCancel, onSend]);
2977
- const handleKeyDown = useCallback10(
3407
+ const handleKeyDown = useCallback12(
2978
3408
  (e) => {
3409
+ if (voiceCtl.handleKeyDownForVoice(e)) return;
2979
3410
  if (e.key === "Enter" && !e.shiftKey) {
2980
3411
  e.preventDefault();
2981
3412
  handleSendOrCancel();
2982
3413
  }
2983
3414
  },
2984
- [handleSendOrCancel]
3415
+ [handleSendOrCancel, voiceCtl]
2985
3416
  );
2986
- useEffect10(() => {
3417
+ useEffect12(() => {
2987
3418
  const handleClickOutside = (event) => {
2988
3419
  const target = event.target;
2989
3420
  if (!target.closest(".at-picker-wrapper")) {
@@ -3007,7 +3438,7 @@ var ChatInput = forwardRef7(
3007
3438
  "div",
3008
3439
  {
3009
3440
  ref: containerRef,
3010
- className: `input-container${isFocused ? " focused" : ""}${imageUpload.isDragOver ? " drag-over" : ""}`.trim(),
3441
+ className: `input-container${isFocused ? " focused" : ""}${imageUpload.isDragOver ? " drag-over" : ""}${voiceInput.status === "connecting" ? " connecting" : ""}${voiceInput.isRecording ? " recording" : ""}`.trim(),
3011
3442
  children: [
3012
3443
  imageUpload.hasImages && /* @__PURE__ */ jsx24("div", { className: "images-preview", children: imageUpload.images.map((img, i) => /* @__PURE__ */ jsxs17("div", { className: "image-preview-item", children: [
3013
3444
  /* @__PURE__ */ jsx24(
@@ -3050,6 +3481,7 @@ var ChatInput = forwardRef7(
3050
3481
  placeholder,
3051
3482
  rows: 1,
3052
3483
  className: "input-field chat-scrollbar",
3484
+ readOnly: voiceInput.isRecording,
3053
3485
  spellCheck: false,
3054
3486
  autoCorrect: "off",
3055
3487
  autoComplete: "off",
@@ -3148,15 +3580,27 @@ var ChatInput = forwardRef7(
3148
3580
  ]
3149
3581
  }
3150
3582
  ),
3151
- hasContent || isLoading ? /* @__PURE__ */ jsx24(
3583
+ /* @__PURE__ */ jsx24(
3584
+ "button",
3585
+ {
3586
+ className: `voice-btn${voiceInput.status === "connecting" ? " connecting" : ""}${voiceInput.isRecording ? " recording" : ""}`,
3587
+ title: voiceInput.status === "connecting" ? "\u6B63\u5728\u8FDE\u63A5\uFF0C\u70B9\u51FB\u53D6\u6D88" : voiceInput.isRecording ? "\u70B9\u51FB\u505C\u6B62" : "\u70B9\u51FB\u5F55\u97F3",
3588
+ onClick: () => toggleVoiceInput().catch(() => {
3589
+ }),
3590
+ disabled: isLoading || !adapter,
3591
+ 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 })
3592
+ }
3593
+ ),
3594
+ /* @__PURE__ */ jsx24(
3152
3595
  "button",
3153
3596
  {
3154
3597
  className: `send-btn${isLoading ? " loading" : ""}`,
3155
- title: isLoading ? "\u505C\u6B62" : isMessageVariant ? "\u91CD\u65B0\u53D1\u9001" : "\u53D1\u9001",
3598
+ 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
3599
  onClick: handleSendOrCancel,
3600
+ disabled: !hasContent && !isLoading || voiceInput.isRecording || voiceInput.status === "connecting",
3157
3601
  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
3602
  }
3159
- ) : /* @__PURE__ */ jsx24("button", { className: "icon-btn", title: "\u8BED\u97F3\u8F93\u5165", children: /* @__PURE__ */ jsx24(Icon17, { icon: "lucide:mic", width: 18 }) })
3603
+ )
3160
3604
  ] })
3161
3605
  ] })
3162
3606
  ]
@@ -3205,15 +3649,15 @@ var MessageBubble = ({
3205
3649
  const isUser = role === "user";
3206
3650
  const formattedTime = formatTime2(timestamp);
3207
3651
  const inputContext = useChatInputContext();
3208
- const userText = useMemo10(() => {
3652
+ const userText = useMemo12(() => {
3209
3653
  return parts.filter((p) => p.type === "text").map((p) => p.text).join("");
3210
3654
  }, [parts]);
3211
- const hasContent = useMemo10(() => {
3655
+ const hasContent = useMemo12(() => {
3212
3656
  return parts.some(
3213
3657
  (p) => p.type === "text" && p.text || p.type === "tool_result" || p.type === "thinking" || p.type === "search"
3214
3658
  );
3215
3659
  }, [parts]);
3216
- const loadingState = useMemo10(() => {
3660
+ const loadingState = useMemo12(() => {
3217
3661
  if (!loading) {
3218
3662
  return { type: "none" };
3219
3663
  }
@@ -3301,7 +3745,7 @@ var MessageBubble = ({
3301
3745
  };
3302
3746
 
3303
3747
  // src/components/common/ConfirmDialog.tsx
3304
- import { useEffect as useEffect11 } from "react";
3748
+ import { useEffect as useEffect13 } from "react";
3305
3749
  import { createPortal as createPortal3 } from "react-dom";
3306
3750
  import { Icon as Icon19 } from "@iconify/react";
3307
3751
  import { jsx as jsx26, jsxs as jsxs19 } from "react/jsx-runtime";
@@ -3315,7 +3759,7 @@ var ConfirmDialog = ({
3315
3759
  onConfirm,
3316
3760
  onCancel
3317
3761
  }) => {
3318
- useEffect11(() => {
3762
+ useEffect13(() => {
3319
3763
  const handleKeyDown = (e) => {
3320
3764
  if (e.key === "Escape" && visible) {
3321
3765
  onCancel?.();
@@ -3324,7 +3768,7 @@ var ConfirmDialog = ({
3324
3768
  document.addEventListener("keydown", handleKeyDown);
3325
3769
  return () => document.removeEventListener("keydown", handleKeyDown);
3326
3770
  }, [visible, onCancel]);
3327
- useEffect11(() => {
3771
+ useEffect13(() => {
3328
3772
  if (visible) {
3329
3773
  document.body.style.overflow = "hidden";
3330
3774
  } else {
@@ -3357,7 +3801,7 @@ var ConfirmDialog = ({
3357
3801
  };
3358
3802
 
3359
3803
  // src/components/common/Toast.tsx
3360
- import { useEffect as useEffect12 } from "react";
3804
+ import { useEffect as useEffect14 } from "react";
3361
3805
  import { createPortal as createPortal4 } from "react-dom";
3362
3806
  import { jsx as jsx27 } from "react/jsx-runtime";
3363
3807
  var Toast = ({
@@ -3367,7 +3811,7 @@ var Toast = ({
3367
3811
  duration = 2e3,
3368
3812
  onClose
3369
3813
  }) => {
3370
- useEffect12(() => {
3814
+ useEffect14(() => {
3371
3815
  if (visible && duration > 0) {
3372
3816
  const timer = setTimeout(() => {
3373
3817
  onClose();
@@ -3383,32 +3827,32 @@ var Toast = ({
3383
3827
  };
3384
3828
 
3385
3829
  // src/components/common/SettingsPanel.tsx
3386
- import { useState as useState17, useCallback as useCallback12, useMemo as useMemo11 } from "react";
3830
+ import { useState as useState18, useCallback as useCallback14, useMemo as useMemo13 } from "react";
3387
3831
  import { Icon as Icon21 } from "@iconify/react";
3388
3832
 
3389
3833
  // src/components/common/IndexingSettings.tsx
3390
- import { useState as useState16, useEffect as useEffect13, useCallback as useCallback11, useRef as useRef8 } from "react";
3834
+ import { useState as useState17, useEffect as useEffect15, useCallback as useCallback13, useRef as useRef10 } from "react";
3391
3835
  import { Icon as Icon20 } from "@iconify/react";
3392
3836
  import { jsx as jsx28, jsxs as jsxs20 } from "react/jsx-runtime";
3393
3837
  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) => {
3838
+ const [indexedFiles, setIndexedFiles] = useState17(0);
3839
+ const [isIndexing, setIsIndexing] = useState17(false);
3840
+ const [currentFile, setCurrentFile] = useState17(null);
3841
+ const [progressPercentage, setProgressPercentage] = useState17(100);
3842
+ const [progressIndexed, setProgressIndexed] = useState17(0);
3843
+ const [progressTotal, setProgressTotal] = useState17(0);
3844
+ const [currentStage, setCurrentStage] = useState17(null);
3845
+ const [showDeleteConfirm, setShowDeleteConfirm] = useState17(false);
3846
+ const [indexSize, setIndexSize] = useState17("0 B");
3847
+ const [lastUpdated, setLastUpdated] = useState17(null);
3848
+ const cancelIndexingRef = useRef10(false);
3849
+ const formatSize = useCallback13((bytes) => {
3406
3850
  if (bytes < 1024) return `${bytes} B`;
3407
3851
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
3408
3852
  if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
3409
3853
  return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
3410
3854
  }, []);
3411
- const formatDate = useCallback11((date) => {
3855
+ const formatDate = useCallback13((date) => {
3412
3856
  const now = /* @__PURE__ */ new Date();
3413
3857
  const diff = now.getTime() - date.getTime();
3414
3858
  const days = Math.floor(diff / (1e3 * 60 * 60 * 24));
@@ -3426,7 +3870,7 @@ function IndexingSettings() {
3426
3870
  if (days < 365) return `${Math.floor(days / 30)} \u4E2A\u6708\u524D`;
3427
3871
  return date.toLocaleDateString("zh-CN", { year: "numeric", month: "short", day: "numeric" });
3428
3872
  }, []);
3429
- const fetchIndexStats = useCallback11(async () => {
3873
+ const fetchIndexStats = useCallback13(async () => {
3430
3874
  try {
3431
3875
  const bridge = window.aiChatBridge;
3432
3876
  if (bridge?.getIndexStats) {
@@ -3447,8 +3891,8 @@ function IndexingSettings() {
3447
3891
  setLastUpdated(null);
3448
3892
  }
3449
3893
  }, [formatSize, formatDate]);
3450
- const progressCleanupRef = useRef8(null);
3451
- const setupProgressListener = useCallback11(() => {
3894
+ const progressCleanupRef = useRef10(null);
3895
+ const setupProgressListener = useCallback13(() => {
3452
3896
  if (progressCleanupRef.current) {
3453
3897
  progressCleanupRef.current();
3454
3898
  progressCleanupRef.current = null;
@@ -3493,7 +3937,7 @@ function IndexingSettings() {
3493
3937
  }
3494
3938
  });
3495
3939
  }, [fetchIndexStats]);
3496
- const checkIndexStatus = useCallback11(async () => {
3940
+ const checkIndexStatus = useCallback13(async () => {
3497
3941
  const bridge = window.aiChatBridge;
3498
3942
  if (!bridge?.getIndexStatus) {
3499
3943
  return;
@@ -3519,7 +3963,7 @@ function IndexingSettings() {
3519
3963
  console.error("\u68C0\u67E5\u7D22\u5F15\u72B6\u6001\u5931\u8D25:", error);
3520
3964
  }
3521
3965
  }, []);
3522
- const handleSync = useCallback11(async () => {
3966
+ const handleSync = useCallback13(async () => {
3523
3967
  if (isIndexing) return;
3524
3968
  const bridge = window.aiChatBridge;
3525
3969
  if (!bridge?.syncIndex) {
@@ -3546,7 +3990,7 @@ function IndexingSettings() {
3546
3990
  setCurrentFile(null);
3547
3991
  }
3548
3992
  }, [isIndexing, setupProgressListener]);
3549
- const handleCancelIndex = useCallback11(async () => {
3993
+ const handleCancelIndex = useCallback13(async () => {
3550
3994
  cancelIndexingRef.current = true;
3551
3995
  setIsIndexing(false);
3552
3996
  setCurrentFile(null);
@@ -3559,7 +4003,7 @@ function IndexingSettings() {
3559
4003
  }
3560
4004
  }
3561
4005
  }, []);
3562
- const handleDeleteIndex = useCallback11(async () => {
4006
+ const handleDeleteIndex = useCallback13(async () => {
3563
4007
  if (isIndexing) return;
3564
4008
  setShowDeleteConfirm(false);
3565
4009
  const bridge = window.aiChatBridge;
@@ -3574,7 +4018,7 @@ function IndexingSettings() {
3574
4018
  console.error("\u5220\u9664\u7D22\u5F15\u5931\u8D25:", error);
3575
4019
  }
3576
4020
  }, [isIndexing, fetchIndexStats]);
3577
- useEffect13(() => {
4021
+ useEffect15(() => {
3578
4022
  const init = async () => {
3579
4023
  const bridge = window.aiChatBridge;
3580
4024
  if (bridge?.registerIndexListener) {
@@ -3590,7 +4034,7 @@ function IndexingSettings() {
3590
4034
  };
3591
4035
  init();
3592
4036
  }, [fetchIndexStats, setupProgressListener, checkIndexStatus]);
3593
- useEffect13(() => {
4037
+ useEffect15(() => {
3594
4038
  return () => {
3595
4039
  const bridge = window.aiChatBridge;
3596
4040
  if (bridge?.unregisterIndexListener) {
@@ -3717,8 +4161,8 @@ var sections = [
3717
4161
  { id: "indexing", label: "\u7D22\u5F15\u4E0E\u6587\u6863", icon: "lucide:database" }
3718
4162
  ];
3719
4163
  function SettingsPanel({ visible, config, onClose, onChange }) {
3720
- const [currentSection, setCurrentSection] = useState17("agent");
3721
- const currentSectionLabel = useMemo11(
4164
+ const [currentSection, setCurrentSection] = useState18("agent");
4165
+ const currentSectionLabel = useMemo13(
3722
4166
  () => sections.find((s) => s.id === currentSection)?.label ?? "",
3723
4167
  [currentSection]
3724
4168
  );
@@ -3726,13 +4170,13 @@ function SettingsPanel({ visible, config, onClose, onChange }) {
3726
4170
  { value: "run-everything", label: "\u8FD0\u884C\u6240\u6709\u5185\u5BB9\uFF08\u81EA\u52A8\u6267\u884C\uFF09" },
3727
4171
  { value: "manual", label: "\u624B\u52A8\u6279\u51C6\uFF08\u6BCF\u6B21\u6267\u884C\u524D\u8BE2\u95EE\uFF09" }
3728
4172
  ];
3729
- const updateConfig = useCallback12((key, value) => {
4173
+ const updateConfig = useCallback14((key, value) => {
3730
4174
  onChange({ ...config, [key]: value });
3731
4175
  }, [config, onChange]);
3732
- const handleModeChange = useCallback12((value) => {
4176
+ const handleModeChange = useCallback14((value) => {
3733
4177
  updateConfig("mode", value);
3734
4178
  }, [updateConfig]);
3735
- const handleOverlayClick = useCallback12((e) => {
4179
+ const handleOverlayClick = useCallback14((e) => {
3736
4180
  if (e.target === e.currentTarget) {
3737
4181
  onClose();
3738
4182
  }
@@ -3796,13 +4240,13 @@ var ChatPanel = forwardRef8(({
3796
4240
  toolRenderers = {},
3797
4241
  stepsExpandedType = "auto"
3798
4242
  }, ref) => {
3799
- const messagesRef = useRef9(null);
3800
- const inputRef = useRef9(null);
3801
- const [shouldAutoScroll, setShouldAutoScroll] = useState18(true);
4243
+ const messagesRef = useRef11(null);
4244
+ const inputRef = useRef11(null);
4245
+ const [shouldAutoScroll, setShouldAutoScroll] = useState19(true);
3802
4246
  const SCROLL_THRESHOLD = 100;
3803
- const [settingsPanelVisible, setSettingsPanelVisible] = useState18(false);
3804
- const [models, setModels] = useState18(propModels || []);
3805
- const [confirmDialog, setConfirmDialog] = useState18({
4247
+ const [settingsPanelVisible, setSettingsPanelVisible] = useState19(false);
4248
+ const [models, setModels] = useState19(propModels || []);
4249
+ const [confirmDialog, setConfirmDialog] = useState19({
3806
4250
  visible: false,
3807
4251
  title: "\u786E\u8BA4",
3808
4252
  message: "",
@@ -3811,7 +4255,7 @@ var ChatPanel = forwardRef8(({
3811
4255
  onConfirm: () => {
3812
4256
  }
3813
4257
  });
3814
- const showConfirm = useCallback13((options) => {
4258
+ const showConfirm = useCallback15((options) => {
3815
4259
  setConfirmDialog({
3816
4260
  visible: true,
3817
4261
  title: options.title || "\u786E\u8BA4",
@@ -3821,8 +4265,8 @@ var ChatPanel = forwardRef8(({
3821
4265
  onConfirm: options.onConfirm
3822
4266
  });
3823
4267
  }, []);
3824
- const [toast, setToast] = useState18({ visible: false, message: "", type: "info" });
3825
- const showToast = useCallback13((message, type = "info") => {
4268
+ const [toast, setToast] = useState19({ visible: false, message: "", type: "info" });
4269
+ const showToast = useCallback15((message, type = "info") => {
3826
4270
  setToast({ visible: true, message, type });
3827
4271
  }, []);
3828
4272
  const {
@@ -3874,61 +4318,61 @@ var ChatPanel = forwardRef8(({
3874
4318
  },
3875
4319
  setCwd: setWorkingDirectory
3876
4320
  }), [sendMessage, setWorkingDirectory]);
3877
- useEffect14(() => {
4321
+ useEffect16(() => {
3878
4322
  loadSessions();
3879
4323
  }, [loadSessions]);
3880
- useEffect14(() => {
4324
+ useEffect16(() => {
3881
4325
  adapter.getModels().then(setModels).catch((err) => console.warn("\u83B7\u53D6\u6A21\u578B\u5217\u8868\u5931\u8D25:", err));
3882
4326
  }, [adapter]);
3883
- const isNearBottom = useCallback13(() => {
4327
+ const isNearBottom = useCallback15(() => {
3884
4328
  if (!messagesRef.current) return true;
3885
4329
  const { scrollTop, scrollHeight, clientHeight } = messagesRef.current;
3886
4330
  return scrollHeight - scrollTop - clientHeight < SCROLL_THRESHOLD;
3887
4331
  }, []);
3888
- const handleScroll = useCallback13(() => {
4332
+ const handleScroll = useCallback15(() => {
3889
4333
  setShouldAutoScroll(isNearBottom());
3890
4334
  }, [isNearBottom]);
3891
- const scrollToBottom = useCallback13((force = false) => {
4335
+ const scrollToBottom = useCallback15((force = false) => {
3892
4336
  if (messagesRef.current && (force || shouldAutoScroll)) {
3893
4337
  messagesRef.current.scrollTop = messagesRef.current.scrollHeight;
3894
4338
  setShouldAutoScroll(true);
3895
4339
  }
3896
4340
  }, [shouldAutoScroll]);
3897
- useEffect14(() => {
4341
+ useEffect16(() => {
3898
4342
  scrollToBottom();
3899
4343
  }, [messages, scrollToBottom]);
3900
- const prevIsLoadingRef = useRef9(isLoading);
3901
- useEffect14(() => {
4344
+ const prevIsLoadingRef = useRef11(isLoading);
4345
+ useEffect16(() => {
3902
4346
  if (isLoading && !prevIsLoadingRef.current) {
3903
4347
  scrollToBottom(true);
3904
4348
  }
3905
4349
  prevIsLoadingRef.current = isLoading;
3906
4350
  }, [isLoading, scrollToBottom]);
3907
- const handleSend = useCallback13((text) => {
4351
+ const handleSend = useCallback15((text) => {
3908
4352
  sendMessage(text);
3909
4353
  }, [sendMessage]);
3910
- const handleAtContext = useCallback13(() => {
4354
+ const handleAtContext = useCallback15(() => {
3911
4355
  console.log("@ \u4E0A\u4E0B\u6587");
3912
4356
  }, []);
3913
- const handleQuickAction = useCallback13(
4357
+ const handleQuickAction = useCallback15(
3914
4358
  (text) => {
3915
4359
  sendMessage(text);
3916
4360
  },
3917
4361
  [sendMessage]
3918
4362
  );
3919
- const handleResend = useCallback13(
4363
+ const handleResend = useCallback15(
3920
4364
  (_index, text) => {
3921
4365
  sendMessage(text);
3922
4366
  },
3923
4367
  [sendMessage]
3924
4368
  );
3925
- const handleClose = useCallback13(() => {
4369
+ const handleClose = useCallback15(() => {
3926
4370
  onClose?.();
3927
4371
  }, [onClose]);
3928
- const handleSettings = useCallback13(() => {
4372
+ const handleSettings = useCallback15(() => {
3929
4373
  setSettingsPanelVisible(true);
3930
4374
  }, []);
3931
- const handleSaveSettings = useCallback13(async (config) => {
4375
+ const handleSaveSettings = useCallback15(async (config) => {
3932
4376
  try {
3933
4377
  await saveAutoRunConfig(config);
3934
4378
  } catch (error) {
@@ -3936,7 +4380,7 @@ var ChatPanel = forwardRef8(({
3936
4380
  showToast("\u4FDD\u5B58\u8BBE\u7F6E\u5931\u8D25", "error");
3937
4381
  }
3938
4382
  }, [saveAutoRunConfig, showToast]);
3939
- const handleClearAll = useCallback13(() => {
4383
+ const handleClearAll = useCallback15(() => {
3940
4384
  showConfirm({
3941
4385
  title: "\u6E05\u7A7A\u6240\u6709\u5BF9\u8BDD",
3942
4386
  message: "\u786E\u5B9A\u8981\u6E05\u7A7A\u6240\u6709\u5BF9\u8BDD\u5417\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002",
@@ -3945,10 +4389,10 @@ var ChatPanel = forwardRef8(({
3945
4389
  onConfirm: () => clearAllSessions()
3946
4390
  });
3947
4391
  }, [showConfirm, clearAllSessions]);
3948
- const handleCloseOthers = useCallback13(async () => {
4392
+ const handleCloseOthers = useCallback15(async () => {
3949
4393
  await hideOtherSessions();
3950
4394
  }, [hideOtherSessions]);
3951
- const handleExport = useCallback13(() => {
4395
+ const handleExport = useCallback15(() => {
3952
4396
  const data = exportCurrentSession();
3953
4397
  if (!data) {
3954
4398
  showToast("\u5F53\u524D\u4F1A\u8BDD\u6CA1\u6709\u5185\u5BB9\u53EF\u5BFC\u51FA", "warning");
@@ -3966,7 +4410,7 @@ var ChatPanel = forwardRef8(({
3966
4410
  document.body.removeChild(a);
3967
4411
  URL.revokeObjectURL(url);
3968
4412
  }, [exportCurrentSession, sessions, currentSessionId, showToast]);
3969
- const handleCopyId = useCallback13(async () => {
4413
+ const handleCopyId = useCallback15(async () => {
3970
4414
  if (!currentSessionId) return;
3971
4415
  try {
3972
4416
  await navigator.clipboard.writeText(currentSessionId);
@@ -3975,10 +4419,10 @@ var ChatPanel = forwardRef8(({
3975
4419
  console.error("\u590D\u5236\u5931\u8D25:", error);
3976
4420
  }
3977
4421
  }, [currentSessionId, showToast]);
3978
- const handleFeedback = useCallback13(() => {
4422
+ const handleFeedback = useCallback15(() => {
3979
4423
  console.log("\u53CD\u9988");
3980
4424
  }, []);
3981
- const inputContextValue = useMemo12(
4425
+ const inputContextValue = useMemo14(
3982
4426
  () => ({
3983
4427
  mode,
3984
4428
  model,
@@ -4106,7 +4550,7 @@ var ChatPanel = forwardRef8(({
4106
4550
  ChatPanel.displayName = "ChatPanel";
4107
4551
 
4108
4552
  // src/components/message/ContentRenderer.tsx
4109
- import { useMemo as useMemo14, useContext as useContext3 } from "react";
4553
+ import { useMemo as useMemo16, useContext as useContext3 } from "react";
4110
4554
  import { parseContent } from "@huyooo/ai-chat-shared";
4111
4555
 
4112
4556
  // src/components/message/blocks/TextBlock.tsx
@@ -4116,21 +4560,21 @@ var TextBlock = ({ block }) => {
4116
4560
  };
4117
4561
 
4118
4562
  // src/components/message/blocks/CodeBlock.tsx
4119
- import { useState as useState19, useCallback as useCallback14, useMemo as useMemo13 } from "react";
4563
+ import { useState as useState20, useCallback as useCallback16, useMemo as useMemo15 } from "react";
4120
4564
  import { Icon as Icon23 } from "@iconify/react";
4121
4565
  import { highlightCode, getLanguageDisplayName } from "@huyooo/ai-chat-shared";
4122
4566
  import { jsx as jsx32, jsxs as jsxs23 } from "react/jsx-runtime";
4123
4567
  var CodeBlock = ({ block, onCopy }) => {
4124
- const [copied, setCopied] = useState19(false);
4125
- const languageDisplay = useMemo13(
4568
+ const [copied, setCopied] = useState20(false);
4569
+ const languageDisplay = useMemo15(
4126
4570
  () => getLanguageDisplayName(block.language),
4127
4571
  [block.language]
4128
4572
  );
4129
- const highlightedCode = useMemo13(
4573
+ const highlightedCode = useMemo15(
4130
4574
  () => highlightCode(block.content, block.language),
4131
4575
  [block.content, block.language]
4132
4576
  );
4133
- const handleCopy = useCallback14(async () => {
4577
+ const handleCopy = useCallback16(async () => {
4134
4578
  try {
4135
4579
  await navigator.clipboard.writeText(block.content);
4136
4580
  setCopied(true);
@@ -4157,7 +4601,7 @@ var ContentRenderer = ({
4157
4601
  onCodeCopy
4158
4602
  }) => {
4159
4603
  const customRenderers = useContext3(BlockRenderersContext);
4160
- const blocks = useMemo14(() => {
4604
+ const blocks = useMemo16(() => {
4161
4605
  if (preBlocks?.length) {
4162
4606
  return preBlocks;
4163
4607
  }
@@ -4181,13 +4625,13 @@ var ContentRenderer = ({
4181
4625
  };
4182
4626
 
4183
4627
  // src/components/message/ToolResultRenderer.tsx
4184
- import { useContext as useContext4, useMemo as useMemo18 } from "react";
4628
+ import { useContext as useContext4, useMemo as useMemo20 } from "react";
4185
4629
 
4186
4630
  // src/components/message/tool-results/DefaultToolResult.tsx
4187
- import { useMemo as useMemo15 } from "react";
4631
+ import { useMemo as useMemo17 } from "react";
4188
4632
  import { jsx as jsx34 } from "react/jsx-runtime";
4189
4633
  var DefaultToolResult = ({ toolResult }) => {
4190
- const formattedResult = useMemo15(() => {
4634
+ const formattedResult = useMemo17(() => {
4191
4635
  if (typeof toolResult === "string") {
4192
4636
  return toolResult;
4193
4637
  }
@@ -4201,11 +4645,11 @@ var DefaultToolResult = ({ toolResult }) => {
4201
4645
  };
4202
4646
 
4203
4647
  // src/components/message/tool-results/WeatherCard.tsx
4204
- import { useMemo as useMemo16 } from "react";
4648
+ import { useMemo as useMemo18 } from "react";
4205
4649
  import { Icon as Icon24 } from "@iconify/react";
4206
4650
  import { jsx as jsx35, jsxs as jsxs24 } from "react/jsx-runtime";
4207
4651
  var WeatherCard = ({ toolResult }) => {
4208
- const weather = useMemo16(() => {
4652
+ const weather = useMemo18(() => {
4209
4653
  if (typeof toolResult === "object" && toolResult !== null) {
4210
4654
  return toolResult;
4211
4655
  }
@@ -4254,11 +4698,11 @@ var WeatherCard = ({ toolResult }) => {
4254
4698
  };
4255
4699
 
4256
4700
  // src/components/message/tool-results/SearchResults.tsx
4257
- import { useMemo as useMemo17, useCallback as useCallback15 } from "react";
4701
+ import { useMemo as useMemo19, useCallback as useCallback17 } from "react";
4258
4702
  import { Icon as Icon25 } from "@iconify/react";
4259
4703
  import { jsx as jsx36, jsxs as jsxs25 } from "react/jsx-runtime";
4260
4704
  var SearchResults = ({ toolResult }) => {
4261
- const results = useMemo17(() => {
4705
+ const results = useMemo19(() => {
4262
4706
  if (Array.isArray(toolResult)) {
4263
4707
  return toolResult;
4264
4708
  }
@@ -4267,14 +4711,14 @@ var SearchResults = ({ toolResult }) => {
4267
4711
  }
4268
4712
  return [];
4269
4713
  }, [toolResult]);
4270
- const getDomain = useCallback15((url) => {
4714
+ const getDomain = useCallback17((url) => {
4271
4715
  try {
4272
4716
  return new URL(url).hostname;
4273
4717
  } catch {
4274
4718
  return url;
4275
4719
  }
4276
4720
  }, []);
4277
- const openExternal = useCallback15((url) => {
4721
+ const openExternal = useCallback17((url) => {
4278
4722
  const bridge = window.aiChatBridge;
4279
4723
  if (bridge?.openExternal) {
4280
4724
  bridge.openExternal(url);
@@ -4317,7 +4761,7 @@ var SearchResults = ({ toolResult }) => {
4317
4761
  import { jsx as jsx37 } from "react/jsx-runtime";
4318
4762
  var ToolResultRenderer = (props) => {
4319
4763
  const customRenderers = useContext4(ToolRenderersContext);
4320
- const Component = useMemo18(() => {
4764
+ const Component = useMemo20(() => {
4321
4765
  return customRenderers[props.toolName] || DefaultToolResult;
4322
4766
  }, [customRenderers, props.toolName]);
4323
4767
  return /* @__PURE__ */ jsx37(Component, { ...props });