@usecrow/ui 0.1.57 → 0.1.59

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.cjs CHANGED
@@ -937,18 +937,21 @@ function useConversations({ productId, apiUrl = "" }) {
937
937
  const [isLoadingHistory, setIsLoadingHistory] = React3.useState(false);
938
938
  const loadConversations = React3.useCallback(async () => {
939
939
  const token = window.__crow_identity_token;
940
- if (!token) return;
940
+ if (!token) return [];
941
941
  try {
942
942
  const res = await fetch(
943
943
  `${apiUrl}/api/chat/conversations?product_id=${productId}&identity_token=${encodeURIComponent(token)}`
944
944
  );
945
945
  if (res.ok) {
946
946
  const data = await res.json();
947
- setConversations(data.conversations || []);
947
+ const convs = data.conversations || [];
948
+ setConversations(convs);
949
+ return convs;
948
950
  }
949
951
  } catch (error) {
950
952
  console.error("[Crow] Failed to load conversations:", error);
951
953
  }
954
+ return [];
952
955
  }, [apiUrl, productId]);
953
956
  const loadConversationHistory = React3.useCallback(
954
957
  async (conversationId) => {
@@ -1769,6 +1772,186 @@ function usePreviewCopilotStyles(previewStyles) {
1769
1772
  styles: mergeCopilotStyles(void 0, previewStyles)
1770
1773
  };
1771
1774
  }
1775
+ function useTTSOutput({
1776
+ backendUrl,
1777
+ voiceId = "YTpq7expH9539ERJ"
1778
+ }) {
1779
+ const [isSpeaking, setIsSpeaking] = React3.useState(false);
1780
+ const [error, setError] = React3.useState(null);
1781
+ const wsRef = React3.useRef(null);
1782
+ const audioContextRef = React3.useRef(null);
1783
+ const nextTimeRef = React3.useRef(0);
1784
+ const streamCompleteRef = React3.useRef(false);
1785
+ const completionCheckIntervalRef = React3.useRef(null);
1786
+ const cleanupAudioContext = React3.useCallback(() => {
1787
+ setIsSpeaking(false);
1788
+ if (audioContextRef.current && audioContextRef.current.state !== "closed") {
1789
+ audioContextRef.current.close();
1790
+ audioContextRef.current = null;
1791
+ }
1792
+ if (completionCheckIntervalRef.current) {
1793
+ clearInterval(completionCheckIntervalRef.current);
1794
+ completionCheckIntervalRef.current = null;
1795
+ }
1796
+ }, []);
1797
+ const closeWebSocket = React3.useCallback(() => {
1798
+ if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
1799
+ try {
1800
+ wsRef.current.send(JSON.stringify({ type: "stop" }));
1801
+ wsRef.current.close();
1802
+ } catch (e) {
1803
+ }
1804
+ }
1805
+ wsRef.current = null;
1806
+ }, []);
1807
+ const cleanupTTS = React3.useCallback(() => {
1808
+ setIsSpeaking(false);
1809
+ setError(null);
1810
+ closeWebSocket();
1811
+ cleanupAudioContext();
1812
+ }, [closeWebSocket, cleanupAudioContext]);
1813
+ const waitForAudioComplete = React3.useCallback(() => {
1814
+ if (completionCheckIntervalRef.current) {
1815
+ clearInterval(completionCheckIntervalRef.current);
1816
+ }
1817
+ completionCheckIntervalRef.current = setInterval(() => {
1818
+ if (!audioContextRef.current) {
1819
+ if (completionCheckIntervalRef.current) {
1820
+ clearInterval(completionCheckIntervalRef.current);
1821
+ completionCheckIntervalRef.current = null;
1822
+ }
1823
+ return;
1824
+ }
1825
+ const now = audioContextRef.current.currentTime;
1826
+ if (now >= nextTimeRef.current) {
1827
+ if (completionCheckIntervalRef.current) {
1828
+ clearInterval(completionCheckIntervalRef.current);
1829
+ completionCheckIntervalRef.current = null;
1830
+ }
1831
+ cleanupAudioContext();
1832
+ }
1833
+ }, 100);
1834
+ }, [cleanupAudioContext]);
1835
+ const playAudioChunk = React3.useCallback((base64Audio) => {
1836
+ if (!audioContextRef.current || audioContextRef.current.state === "closed") {
1837
+ console.error("TTS: AudioContext not available");
1838
+ return;
1839
+ }
1840
+ try {
1841
+ const binary = atob(base64Audio);
1842
+ const bytes = new Uint8Array(binary.length);
1843
+ for (let i = 0; i < binary.length; i++) {
1844
+ bytes[i] = binary.charCodeAt(i);
1845
+ }
1846
+ const pcm16 = new Int16Array(bytes.buffer);
1847
+ const float32 = new Float32Array(pcm16.length);
1848
+ for (let i = 0; i < pcm16.length; i++) {
1849
+ float32[i] = pcm16[i] / 32768;
1850
+ }
1851
+ const buffer = audioContextRef.current.createBuffer(1, float32.length, 48e3);
1852
+ buffer.getChannelData(0).set(float32);
1853
+ const source = audioContextRef.current.createBufferSource();
1854
+ source.buffer = buffer;
1855
+ source.connect(audioContextRef.current.destination);
1856
+ const now = audioContextRef.current.currentTime;
1857
+ if (nextTimeRef.current < now) {
1858
+ nextTimeRef.current = now;
1859
+ }
1860
+ source.start(nextTimeRef.current);
1861
+ nextTimeRef.current += buffer.duration;
1862
+ } catch (err) {
1863
+ console.error("TTS: Error playing audio chunk:", err);
1864
+ setError(err instanceof Error ? err.message : "Failed to play audio chunk");
1865
+ }
1866
+ }, []);
1867
+ const speak = React3.useCallback(
1868
+ (text) => {
1869
+ console.log("[TTS Hook] speak called with:", text.substring(0, 50), "backendUrl:", backendUrl);
1870
+ if (!text.trim()) {
1871
+ console.log("[TTS Hook] No text to speak");
1872
+ setError("No text to speak");
1873
+ return;
1874
+ }
1875
+ if (isSpeaking || wsRef.current) {
1876
+ console.log("[TTS Hook] Already playing");
1877
+ setError("Already playing, stop first");
1878
+ return;
1879
+ }
1880
+ setError(null);
1881
+ nextTimeRef.current = 0;
1882
+ streamCompleteRef.current = false;
1883
+ try {
1884
+ audioContextRef.current = new (window.AudioContext || window.webkitAudioContext)({
1885
+ sampleRate: 48e3
1886
+ });
1887
+ const url = backendUrl.startsWith("http") ? backendUrl.replace(/^http/, "ws") : backendUrl;
1888
+ const wsUrl = `${url}/api/tts/stream`;
1889
+ console.log("[TTS Hook] Connecting to:", wsUrl);
1890
+ const ws = new WebSocket(wsUrl);
1891
+ wsRef.current = ws;
1892
+ ws.onopen = () => {
1893
+ ws.send(
1894
+ JSON.stringify({
1895
+ type: "setup",
1896
+ voice_id: voiceId,
1897
+ output_format: "pcm"
1898
+ })
1899
+ );
1900
+ };
1901
+ ws.onmessage = (event) => {
1902
+ const msg = JSON.parse(event.data);
1903
+ if (msg.type === "ready") {
1904
+ ws.send(JSON.stringify({ type: "text", text }));
1905
+ ws.send(JSON.stringify({ type: "end_of_stream" }));
1906
+ } else if (msg.type === "audio") {
1907
+ playAudioChunk(msg.audio);
1908
+ } else if (msg.type === "done") {
1909
+ streamCompleteRef.current = true;
1910
+ closeWebSocket();
1911
+ waitForAudioComplete();
1912
+ } else if (msg.type === "error") {
1913
+ setError(msg.message || "TTS error");
1914
+ cleanupTTS();
1915
+ }
1916
+ };
1917
+ ws.onerror = () => {
1918
+ setError("WebSocket error");
1919
+ cleanupTTS();
1920
+ };
1921
+ ws.onclose = () => {
1922
+ wsRef.current = null;
1923
+ };
1924
+ setIsSpeaking(true);
1925
+ } catch (err) {
1926
+ setError(err instanceof Error ? err.message : "Failed to start TTS");
1927
+ cleanupTTS();
1928
+ }
1929
+ },
1930
+ [
1931
+ isSpeaking,
1932
+ backendUrl,
1933
+ voiceId,
1934
+ playAudioChunk,
1935
+ closeWebSocket,
1936
+ waitForAudioComplete,
1937
+ cleanupTTS
1938
+ ]
1939
+ );
1940
+ const stop = React3.useCallback(() => {
1941
+ cleanupTTS();
1942
+ }, [cleanupTTS]);
1943
+ React3.useEffect(() => {
1944
+ return () => {
1945
+ cleanupTTS();
1946
+ };
1947
+ }, [cleanupTTS]);
1948
+ return {
1949
+ speak,
1950
+ stop,
1951
+ isSpeaking,
1952
+ error
1953
+ };
1954
+ }
1772
1955
  var WidgetStyleContext = React3.createContext(null);
1773
1956
  function WidgetStyleProvider({
1774
1957
  children,
@@ -2756,80 +2939,176 @@ var ModelSelector = ({
2756
2939
  ] }, provider)) })
2757
2940
  ] });
2758
2941
  };
2759
- var getSpeechRecognition = () => {
2760
- if (typeof window === "undefined") return null;
2761
- return window.SpeechRecognition || window.webkitSpeechRecognition || null;
2942
+ var isMediaRecorderSupported = () => {
2943
+ if (typeof window === "undefined") return false;
2944
+ return !!(navigator.mediaDevices && typeof navigator.mediaDevices.getUserMedia === "function" && (window.AudioContext || window.webkitAudioContext));
2762
2945
  };
2763
- function useVoiceInput(options = {}) {
2764
- const { lang, silenceTimeoutMs } = options;
2765
- const [supported] = React3.useState(() => getSpeechRecognition() !== null);
2946
+ function useVoiceInput(options) {
2947
+ const { backendUrl, silenceTimeoutMs } = options;
2948
+ const [supported] = React3.useState(() => isMediaRecorderSupported());
2766
2949
  const [isRecording, setIsRecording] = React3.useState(false);
2767
2950
  const [transcript, setTranscript] = React3.useState("");
2768
- const recognitionRef = React3.useRef(null);
2951
+ const [error, setError] = React3.useState(null);
2952
+ const wsRef = React3.useRef(null);
2953
+ const streamRef = React3.useRef(null);
2954
+ const audioContextRef = React3.useRef(null);
2955
+ const processorRef = React3.useRef(null);
2769
2956
  const silenceTimerRef = React3.useRef(null);
2770
- const finalTranscriptRef = React3.useRef("");
2957
+ const transcriptRef = React3.useRef("");
2958
+ const interimRef = React3.useRef("");
2959
+ const isRecordingRef = React3.useRef(false);
2771
2960
  const clearSilenceTimer = React3.useCallback(() => {
2772
2961
  if (silenceTimerRef.current) {
2773
2962
  clearTimeout(silenceTimerRef.current);
2774
2963
  silenceTimerRef.current = null;
2775
2964
  }
2776
2965
  }, []);
2777
- const stop = React3.useCallback(() => {
2966
+ const cleanup = React3.useCallback(() => {
2778
2967
  clearSilenceTimer();
2779
- if (recognitionRef.current) {
2780
- recognitionRef.current.stop();
2968
+ isRecordingRef.current = false;
2969
+ if (interimRef.current) {
2970
+ transcriptRef.current += interimRef.current + " ";
2971
+ setTranscript(transcriptRef.current.trim());
2972
+ interimRef.current = "";
2973
+ }
2974
+ if (wsRef.current) {
2975
+ try {
2976
+ if (wsRef.current.readyState === WebSocket.OPEN) {
2977
+ wsRef.current.send(JSON.stringify({ type: "stop" }));
2978
+ }
2979
+ wsRef.current.close();
2980
+ } catch (e) {
2981
+ }
2982
+ wsRef.current = null;
2983
+ }
2984
+ if (processorRef.current) {
2985
+ processorRef.current.disconnect();
2986
+ processorRef.current = null;
2987
+ }
2988
+ if (audioContextRef.current) {
2989
+ audioContextRef.current.close();
2990
+ audioContextRef.current = null;
2781
2991
  }
2992
+ if (streamRef.current) {
2993
+ streamRef.current.getTracks().forEach((track) => track.stop());
2994
+ streamRef.current = null;
2995
+ }
2996
+ setIsRecording(false);
2782
2997
  }, [clearSilenceTimer]);
2998
+ const stop = React3.useCallback(() => {
2999
+ cleanup();
3000
+ }, [cleanup]);
2783
3001
  const clear = React3.useCallback(() => {
2784
3002
  setTranscript("");
2785
- finalTranscriptRef.current = "";
3003
+ transcriptRef.current = "";
3004
+ setError(null);
2786
3005
  }, []);
2787
- const start = React3.useCallback(() => {
2788
- const SpeechRecognition = getSpeechRecognition();
2789
- if (!SpeechRecognition) return;
2790
- if (recognitionRef.current) {
2791
- recognitionRef.current.abort();
2792
- }
2793
- finalTranscriptRef.current = "";
2794
- setTranscript("");
2795
- const recognition = new SpeechRecognition();
2796
- recognition.continuous = true;
2797
- recognition.interimResults = true;
2798
- recognition.lang = lang || navigator.language || "en-US";
2799
- recognition.onresult = (event) => {
2800
- let interim = "";
2801
- let final = "";
2802
- for (let i = 0; i < event.results.length; i++) {
2803
- const result = event.results[i];
2804
- if (result.isFinal) {
2805
- final += result[0].transcript;
2806
- } else {
2807
- interim += result[0].transcript;
2808
- }
3006
+ const startAudioCapture = React3.useCallback(() => {
3007
+ if (!streamRef.current || !wsRef.current) return;
3008
+ audioContextRef.current = new (window.AudioContext || window.webkitAudioContext)({ sampleRate: 24e3 });
3009
+ const source = audioContextRef.current.createMediaStreamSource(
3010
+ streamRef.current
3011
+ );
3012
+ processorRef.current = audioContextRef.current.createScriptProcessor(
3013
+ 4096,
3014
+ 1,
3015
+ 1
3016
+ );
3017
+ processorRef.current.onaudioprocess = (event) => {
3018
+ if (!isRecordingRef.current || !wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) {
3019
+ return;
2809
3020
  }
2810
- finalTranscriptRef.current = final;
2811
- setTranscript(final + interim);
2812
- if (silenceTimeoutMs) {
2813
- clearSilenceTimer();
2814
- silenceTimerRef.current = setTimeout(() => {
2815
- stop();
2816
- }, silenceTimeoutMs);
3021
+ const inputData = event.inputBuffer.getChannelData(0);
3022
+ const pcm16 = new Int16Array(inputData.length);
3023
+ for (let i = 0; i < inputData.length; i++) {
3024
+ const s = Math.max(-1, Math.min(1, inputData[i]));
3025
+ pcm16[i] = s < 0 ? s * 32768 : s * 32767;
2817
3026
  }
2818
- };
2819
- recognition.onerror = (event) => {
2820
- if (event.error !== "aborted") {
2821
- console.warn("[Crow Voice] Speech recognition error:", event.error);
3027
+ const bytes = new Uint8Array(pcm16.buffer);
3028
+ let binary = "";
3029
+ for (let i = 0; i < bytes.length; i++) {
3030
+ binary += String.fromCharCode(bytes[i]);
2822
3031
  }
2823
- setIsRecording(false);
2824
- };
2825
- recognition.onend = () => {
2826
- setIsRecording(false);
2827
- recognitionRef.current = null;
3032
+ wsRef.current.send(
3033
+ JSON.stringify({ type: "audio", data: btoa(binary) })
3034
+ );
2828
3035
  };
2829
- recognitionRef.current = recognition;
2830
- recognition.start();
2831
- setIsRecording(true);
2832
- }, [lang, silenceTimeoutMs, clearSilenceTimer, stop]);
3036
+ source.connect(processorRef.current);
3037
+ processorRef.current.connect(audioContextRef.current.destination);
3038
+ }, []);
3039
+ const start = React3.useCallback(async () => {
3040
+ if (!supported) {
3041
+ setError("Audio recording not supported in this browser");
3042
+ return;
3043
+ }
3044
+ setError(null);
3045
+ transcriptRef.current = "";
3046
+ setTranscript("");
3047
+ try {
3048
+ streamRef.current = await navigator.mediaDevices.getUserMedia({
3049
+ audio: {
3050
+ echoCancellation: true,
3051
+ noiseSuppression: true,
3052
+ sampleRate: 24e3
3053
+ }
3054
+ });
3055
+ const wsProtocol = backendUrl.startsWith("https") ? "wss" : "ws";
3056
+ const wsHost = backendUrl.replace(/^https?:\/\//, "");
3057
+ const wsUrl = `${wsProtocol}://${wsHost}/api/stt/stream`;
3058
+ wsRef.current = new WebSocket(wsUrl);
3059
+ wsRef.current.onopen = () => {
3060
+ wsRef.current?.send(JSON.stringify({ type: "setup" }));
3061
+ };
3062
+ wsRef.current.onmessage = (event) => {
3063
+ const msg = JSON.parse(event.data);
3064
+ if (msg.type === "ready") {
3065
+ startAudioCapture();
3066
+ isRecordingRef.current = true;
3067
+ setIsRecording(true);
3068
+ } else if (msg.type === "transcript") {
3069
+ if (msg.is_final && msg.text) {
3070
+ transcriptRef.current += msg.text + " ";
3071
+ interimRef.current = "";
3072
+ setTranscript(transcriptRef.current.trim());
3073
+ if (silenceTimeoutMs) {
3074
+ clearSilenceTimer();
3075
+ silenceTimerRef.current = setTimeout(() => {
3076
+ stop();
3077
+ }, silenceTimeoutMs);
3078
+ }
3079
+ } else if (!msg.is_final && msg.text) {
3080
+ interimRef.current = msg.text;
3081
+ setTranscript((transcriptRef.current + msg.text).trim());
3082
+ }
3083
+ } else if (msg.type === "error") {
3084
+ setError(msg.message || "STT error");
3085
+ cleanup();
3086
+ }
3087
+ };
3088
+ wsRef.current.onerror = () => {
3089
+ setError("WebSocket connection error");
3090
+ cleanup();
3091
+ };
3092
+ wsRef.current.onclose = () => {
3093
+ if (isRecordingRef.current) {
3094
+ cleanup();
3095
+ }
3096
+ };
3097
+ } catch (err) {
3098
+ setError(
3099
+ err instanceof Error ? err.message : "Failed to start recording"
3100
+ );
3101
+ cleanup();
3102
+ }
3103
+ }, [
3104
+ supported,
3105
+ backendUrl,
3106
+ startAudioCapture,
3107
+ silenceTimeoutMs,
3108
+ clearSilenceTimer,
3109
+ stop,
3110
+ cleanup
3111
+ ]);
2833
3112
  const toggle = React3.useCallback(() => {
2834
3113
  if (isRecording) {
2835
3114
  stop();
@@ -2839,13 +3118,19 @@ function useVoiceInput(options = {}) {
2839
3118
  }, [isRecording, start, stop]);
2840
3119
  React3.useEffect(() => {
2841
3120
  return () => {
2842
- clearSilenceTimer();
2843
- if (recognitionRef.current) {
2844
- recognitionRef.current.abort();
2845
- }
3121
+ cleanup();
2846
3122
  };
2847
- }, [clearSilenceTimer]);
2848
- return { supported, isRecording, transcript, start, stop, toggle, clear };
3123
+ }, [cleanup]);
3124
+ return {
3125
+ supported,
3126
+ isRecording,
3127
+ transcript,
3128
+ error,
3129
+ start,
3130
+ stop,
3131
+ toggle,
3132
+ clear
3133
+ };
2849
3134
  }
2850
3135
  var Textarea = React3__default.default.forwardRef(
2851
3136
  ({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
@@ -3039,11 +3324,23 @@ var PromptInputBox = React3__default.default.forwardRef(
3039
3324
  selectedModel = "gpt-4o",
3040
3325
  onModelChange,
3041
3326
  availableModels = [],
3042
- highlighted = false
3327
+ highlighted = false,
3328
+ backendUrl = "",
3329
+ triggerVoiceRecording = 0
3043
3330
  }, ref) => {
3044
3331
  const [input, setInput] = React3__default.default.useState("");
3045
3332
  const promptBoxRef = React3__default.default.useRef(null);
3046
- const voice = useVoiceInput();
3333
+ const voice = useVoiceInput({ backendUrl, silenceTimeoutMs: 1500 });
3334
+ const lastTriggerRef = React3__default.default.useRef(0);
3335
+ const voiceRef = React3__default.default.useRef(voice);
3336
+ voiceRef.current = voice;
3337
+ React3__default.default.useEffect(() => {
3338
+ if (triggerVoiceRecording > 0 && triggerVoiceRecording !== lastTriggerRef.current) {
3339
+ console.log("[Voice] Auto-starting recording from trigger");
3340
+ voiceRef.current.start();
3341
+ }
3342
+ lastTriggerRef.current = triggerVoiceRecording;
3343
+ }, [triggerVoiceRecording]);
3047
3344
  React3__default.default.useEffect(() => {
3048
3345
  if (voice.isRecording && voice.transcript) {
3049
3346
  setInput(voice.transcript);
@@ -3052,11 +3349,16 @@ var PromptInputBox = React3__default.default.forwardRef(
3052
3349
  const wasRecordingRef = React3__default.default.useRef(false);
3053
3350
  React3__default.default.useEffect(() => {
3054
3351
  if (wasRecordingRef.current && !voice.isRecording && voice.transcript) {
3055
- setInput(voice.transcript);
3352
+ const messageToSend = voice.transcript.trim();
3353
+ if (messageToSend) {
3354
+ console.log("[Voice] Auto-sending:", messageToSend);
3355
+ onSend(messageToSend);
3356
+ setInput("");
3357
+ }
3056
3358
  voice.clear();
3057
3359
  }
3058
3360
  wasRecordingRef.current = voice.isRecording;
3059
- }, [voice.isRecording, voice.transcript, voice.clear]);
3361
+ }, [voice.isRecording, voice.transcript, voice.clear, onSend]);
3060
3362
  const handleSubmit = () => {
3061
3363
  if (input.trim()) {
3062
3364
  if (voice.isRecording) {
@@ -3667,6 +3969,25 @@ function CrowWidget({
3667
3969
  setShouldRestoreHistory(true);
3668
3970
  }
3669
3971
  });
3972
+ const tts = useTTSOutput({ backendUrl: apiUrl });
3973
+ const ttsRef = React3.useRef(tts);
3974
+ ttsRef.current = tts;
3975
+ const wasLoadingRef = React3.useRef(false);
3976
+ React3.useEffect(() => {
3977
+ console.log("[Crow TTS] isLoading changed:", chat.isLoading, "wasLoading:", wasLoadingRef.current);
3978
+ if (wasLoadingRef.current && !chat.isLoading) {
3979
+ const lastMessage = [...chat.messages].reverse().find((m) => m.isBot);
3980
+ console.log("[Crow TTS] Last bot message:", lastMessage?.content?.substring(0, 50));
3981
+ if (lastMessage?.content) {
3982
+ const textToSpeak = lastMessage.content.replace(/\*\*/g, "").replace(/\*/g, "").replace(/`[^`]+`/g, "").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").trim();
3983
+ if (textToSpeak) {
3984
+ console.log("[Crow TTS] Speaking:", textToSpeak.substring(0, 50));
3985
+ ttsRef.current.speak(textToSpeak);
3986
+ }
3987
+ }
3988
+ }
3989
+ wasLoadingRef.current = chat.isLoading;
3990
+ }, [chat.isLoading, chat.messages]);
3670
3991
  React3.useEffect(() => {
3671
3992
  if (initialSuggestions.length > 0 && chat.suggestedActions.length === 0) {
3672
3993
  chat.setSuggestedActions(initialSuggestions);
@@ -3706,7 +4027,15 @@ function CrowWidget({
3706
4027
  const { executeClientTool } = useCrowAPI({
3707
4028
  onIdentified: async () => {
3708
4029
  setIsVerifiedUser(true);
3709
- await conversations.loadConversations();
4030
+ const convs = await conversations.loadConversations();
4031
+ if (convs.length > 0) {
4032
+ const mostRecent = convs[0];
4033
+ const historyMessages = await conversations.loadConversationHistory(mostRecent.id);
4034
+ if (historyMessages.length > 0) {
4035
+ chat.loadMessages(historyMessages);
4036
+ chat.setConversationId(mostRecent.id);
4037
+ }
4038
+ }
3710
4039
  },
3711
4040
  onReset: () => {
3712
4041
  setIsVerifiedUser(false);
@@ -4061,7 +4390,8 @@ function CrowWidget({
4061
4390
  isLoading: chat.isLoading,
4062
4391
  showStopButton: isBrowserUseActive || !!askUserResolver || !!pendingConfirmation,
4063
4392
  highlighted: !!askUserResolver,
4064
- className: "crow-backdrop-blur-md"
4393
+ className: "crow-backdrop-blur-md",
4394
+ backendUrl: apiUrl
4065
4395
  }
4066
4396
  )
4067
4397
  ] })