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