@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.css +94 -25
- package/dist/index.css.map +1 -1
- package/dist/index.js +558 -114
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/components/input/AtFilePicker.css +19 -19
- package/src/components/input/ChatInput.css +81 -1
- package/src/components/input/ChatInput.tsx +65 -18
- package/src/components/input/DropdownSelector.css +11 -5
- package/src/hooks/useVoiceInput.ts +454 -0
- package/src/hooks/useVoiceToTextInput.ts +87 -0
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
|
|
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
|
|
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
|
|
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] =
|
|
2815
|
-
const [isFocused, setIsFocused] =
|
|
3229
|
+
const [inputText, setInputText] = useState16(value);
|
|
3230
|
+
const [isFocused, setIsFocused] = useState16(false);
|
|
2816
3231
|
const imageUpload = useImageUpload();
|
|
2817
|
-
const imageInputRef =
|
|
2818
|
-
const inputRef =
|
|
2819
|
-
const containerRef =
|
|
2820
|
-
const atSelectorRef =
|
|
2821
|
-
const [atPickerVisible, setAtPickerVisible] =
|
|
2822
|
-
const replaceRangeRef =
|
|
2823
|
-
const pendingCaretRef =
|
|
2824
|
-
const pendingFocusRef =
|
|
2825
|
-
const prevValueRef =
|
|
2826
|
-
const triggerImageUpload =
|
|
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 =
|
|
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 =
|
|
3261
|
+
const closeAtPicker = useCallback12(() => {
|
|
2847
3262
|
setAtPickerVisible(false);
|
|
2848
3263
|
replaceRangeRef.current = null;
|
|
2849
3264
|
pendingFocusRef.current = true;
|
|
2850
3265
|
}, []);
|
|
2851
|
-
const applyAtPath =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
3306
|
+
const hasContent = useMemo11(() => {
|
|
2892
3307
|
return inputText.trim() || imageUpload.hasImages;
|
|
2893
3308
|
}, [inputText, imageUpload.hasImages]);
|
|
2894
|
-
const
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
)
|
|
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 =
|
|
3652
|
+
const userText = useMemo12(() => {
|
|
3209
3653
|
return parts.filter((p) => p.type === "text").map((p) => p.text).join("");
|
|
3210
3654
|
}, [parts]);
|
|
3211
|
-
const hasContent =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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] =
|
|
3395
|
-
const [isIndexing, setIsIndexing] =
|
|
3396
|
-
const [currentFile, setCurrentFile] =
|
|
3397
|
-
const [progressPercentage, setProgressPercentage] =
|
|
3398
|
-
const [progressIndexed, setProgressIndexed] =
|
|
3399
|
-
const [progressTotal, setProgressTotal] =
|
|
3400
|
-
const [currentStage, setCurrentStage] =
|
|
3401
|
-
const [showDeleteConfirm, setShowDeleteConfirm] =
|
|
3402
|
-
const [indexSize, setIndexSize] =
|
|
3403
|
-
const [lastUpdated, setLastUpdated] =
|
|
3404
|
-
const cancelIndexingRef =
|
|
3405
|
-
const formatSize =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
3451
|
-
const setupProgressListener =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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] =
|
|
3721
|
-
const currentSectionLabel =
|
|
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 =
|
|
4173
|
+
const updateConfig = useCallback14((key, value) => {
|
|
3730
4174
|
onChange({ ...config, [key]: value });
|
|
3731
4175
|
}, [config, onChange]);
|
|
3732
|
-
const handleModeChange =
|
|
4176
|
+
const handleModeChange = useCallback14((value) => {
|
|
3733
4177
|
updateConfig("mode", value);
|
|
3734
4178
|
}, [updateConfig]);
|
|
3735
|
-
const handleOverlayClick =
|
|
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 =
|
|
3800
|
-
const inputRef =
|
|
3801
|
-
const [shouldAutoScroll, setShouldAutoScroll] =
|
|
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] =
|
|
3804
|
-
const [models, setModels] =
|
|
3805
|
-
const [confirmDialog, setConfirmDialog] =
|
|
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 =
|
|
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] =
|
|
3825
|
-
const showToast =
|
|
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
|
-
|
|
4321
|
+
useEffect16(() => {
|
|
3878
4322
|
loadSessions();
|
|
3879
4323
|
}, [loadSessions]);
|
|
3880
|
-
|
|
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 =
|
|
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 =
|
|
4332
|
+
const handleScroll = useCallback15(() => {
|
|
3889
4333
|
setShouldAutoScroll(isNearBottom());
|
|
3890
4334
|
}, [isNearBottom]);
|
|
3891
|
-
const scrollToBottom =
|
|
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
|
-
|
|
4341
|
+
useEffect16(() => {
|
|
3898
4342
|
scrollToBottom();
|
|
3899
4343
|
}, [messages, scrollToBottom]);
|
|
3900
|
-
const prevIsLoadingRef =
|
|
3901
|
-
|
|
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 =
|
|
4351
|
+
const handleSend = useCallback15((text) => {
|
|
3908
4352
|
sendMessage(text);
|
|
3909
4353
|
}, [sendMessage]);
|
|
3910
|
-
const handleAtContext =
|
|
4354
|
+
const handleAtContext = useCallback15(() => {
|
|
3911
4355
|
console.log("@ \u4E0A\u4E0B\u6587");
|
|
3912
4356
|
}, []);
|
|
3913
|
-
const handleQuickAction =
|
|
4357
|
+
const handleQuickAction = useCallback15(
|
|
3914
4358
|
(text) => {
|
|
3915
4359
|
sendMessage(text);
|
|
3916
4360
|
},
|
|
3917
4361
|
[sendMessage]
|
|
3918
4362
|
);
|
|
3919
|
-
const handleResend =
|
|
4363
|
+
const handleResend = useCallback15(
|
|
3920
4364
|
(_index, text) => {
|
|
3921
4365
|
sendMessage(text);
|
|
3922
4366
|
},
|
|
3923
4367
|
[sendMessage]
|
|
3924
4368
|
);
|
|
3925
|
-
const handleClose =
|
|
4369
|
+
const handleClose = useCallback15(() => {
|
|
3926
4370
|
onClose?.();
|
|
3927
4371
|
}, [onClose]);
|
|
3928
|
-
const handleSettings =
|
|
4372
|
+
const handleSettings = useCallback15(() => {
|
|
3929
4373
|
setSettingsPanelVisible(true);
|
|
3930
4374
|
}, []);
|
|
3931
|
-
const handleSaveSettings =
|
|
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 =
|
|
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 =
|
|
4392
|
+
const handleCloseOthers = useCallback15(async () => {
|
|
3949
4393
|
await hideOtherSessions();
|
|
3950
4394
|
}, [hideOtherSessions]);
|
|
3951
|
-
const handleExport =
|
|
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 =
|
|
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 =
|
|
4422
|
+
const handleFeedback = useCallback15(() => {
|
|
3979
4423
|
console.log("\u53CD\u9988");
|
|
3980
4424
|
}, []);
|
|
3981
|
-
const inputContextValue =
|
|
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
|
|
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
|
|
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] =
|
|
4125
|
-
const languageDisplay =
|
|
4568
|
+
const [copied, setCopied] = useState20(false);
|
|
4569
|
+
const languageDisplay = useMemo15(
|
|
4126
4570
|
() => getLanguageDisplayName(block.language),
|
|
4127
4571
|
[block.language]
|
|
4128
4572
|
);
|
|
4129
|
-
const highlightedCode =
|
|
4573
|
+
const highlightedCode = useMemo15(
|
|
4130
4574
|
() => highlightCode(block.content, block.language),
|
|
4131
4575
|
[block.content, block.language]
|
|
4132
4576
|
);
|
|
4133
|
-
const handleCopy =
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
4764
|
+
const Component = useMemo20(() => {
|
|
4321
4765
|
return customRenderers[props.toolName] || DefaultToolResult;
|
|
4322
4766
|
}, [customRenderers, props.toolName]);
|
|
4323
4767
|
return /* @__PURE__ */ jsx37(Component, { ...props });
|