import { Dispatch, SetStateAction, MutableRefObject } from "react"; import { BotData, ChatState } from "~/types/bot"; import { Message, ConversationStep } from "~/types/chat"; import { addBreadcrumb } from "~/utils/sentry"; import { WidgetType } from "~/utils/session-storage"; import { createErrorMessage, createSuggestionMessage, } from "../utils/message-builders"; /** * Creates an error handler for streaming API errors */ export function createStreamErrorHandler( currentAbortController: MutableRefObject, botData: BotData, chatState: ChatState, widgetType: WidgetType, setChatState: Dispatch>, setIsInputDisabled: Dispatch>, setInputState: Dispatch< SetStateAction<"starting" | "generating" | "minimized"> >, setCurrentStep: Dispatch>, setMessages: Dispatch>, onThreadAccessError?: () => void ) { return (error: string) => { if (currentAbortController.current?.signal.aborted) { currentAbortController.current = null; setChatState((prev) => ({ ...prev, isRequestInProgress: false, repliedProduct: null, })); setIsInputDisabled(false); setInputState("starting"); setCurrentStep("ongoing"); setMessages((prev) => prev.filter((msg) => msg.type !== "loading")); return; } // Check if error indicates thread access issue const isThreadAccessError = error.toLowerCase().includes("access denied for thread") || error.toLowerCase().includes("access denied") || error.toLowerCase().includes("thread not found") || error.includes("403") || error.includes("404"); if (isThreadAccessError && onThreadAccessError) { onThreadAccessError(); addBreadcrumb( "Thread access error detected - starting new conversation", "chat.thread_access_error", "warning", { error_message: error, thread_id: chatState.threadId, } ); setMessages((prev) => [ ...prev.filter((msg) => msg.type !== "loading"), createErrorMessage("شروع گفتگوی جدید..."), ]); currentAbortController.current = null; setChatState((prev) => ({ ...prev, isRequestInProgress: false, repliedProduct: null, })); setIsInputDisabled(false); setInputState("starting"); setCurrentStep("ongoing"); return; } // Log streaming API error for debugging console.error("[Mori Widget] Streaming API error:", { errorMessage: error, threadId: chatState.threadId, assistantId: botData?.assistant_id, widgetType: widgetType, timestamp: new Date().toISOString(), }); addBreadcrumb( "Streaming API error callback", "chat.stream_error", "error", { error_message: error, thread_id: chatState.threadId, } ); setMessages((prev) => [ ...prev.filter((msg) => msg.type !== "loading"), createErrorMessage(error), ]); currentAbortController.current = null; setChatState((prev) => ({ ...prev, isRequestInProgress: false, repliedProduct: null, })); setIsInputDisabled(false); setInputState("starting"); setCurrentStep("ongoing"); }; } /** * Creates a completion handler for successful streaming completion */ export function createStreamCompletionHandler( currentAbortController: MutableRefObject, currentBotMessageRef: { current: Message | null }, pendingSuggestionsRef: { current: string[] }, setChatState: Dispatch>, setIsInputDisabled: Dispatch>, setInputState: Dispatch< SetStateAction<"starting" | "generating" | "minimized"> >, setCurrentStep: Dispatch>, setMessages: Dispatch> ) { return () => { if (pendingSuggestionsRef.current.length > 0) { setMessages((prev) => { const filteredMessages = prev.filter( (msg) => msg.type !== "suggestions" && msg.type !== "loading" ); const newMessages = [...filteredMessages]; const currentMessageIndex = newMessages.findIndex( (msg) => msg.id === currentBotMessageRef.current?.id ); const suggestionMessage = createSuggestionMessage( pendingSuggestionsRef.current ); if (currentMessageIndex !== -1) { newMessages.splice(currentMessageIndex + 1, 0, suggestionMessage); } else { newMessages.push(suggestionMessage); } return newMessages; }); } currentAbortController.current = null; setChatState((prev) => ({ ...prev, isRequestInProgress: false, repliedProduct: null, })); setIsInputDisabled(false); setInputState("starting"); setCurrentStep("ongoing"); }; }