UNPKG

@restnfeel/agentc-starter-kit

Version:

한국어 기업용 CMS 모듈 - Task Master AI와 함께 빠르게 웹사이트를 구현할 수 있는 재사용 가능한 컴포넌트 시스템

248 lines (241 loc) 10.2 kB
"use client"; import { jsx, jsxs, Fragment } from 'react/jsx-runtime'; import { useState, useEffect, useCallback } from 'react'; import { FloatingChatButton } from './floating-chat-button.js'; import { MobileFullscreenChat } from '../../../src/components/chatbot/customer/mobile-fullscreen-chat.tsx'; import { I18nProvider } from './i18n-context.js'; // Hook for mobile detection function useIsMobile(breakpoint = 768) { const [isMobile, setIsMobile] = useState(false); useEffect(() => { const checkDevice = () => { const width = window.innerWidth; const userAgent = navigator.userAgent; // Check both screen width and user agent const isSmallScreen = width <= breakpoint; const isMobileDevice = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent); setIsMobile(isSmallScreen || isMobileDevice); }; checkDevice(); window.addEventListener("resize", checkDevice); return () => window.removeEventListener("resize", checkDevice); }, [breakpoint]); return isMobile; } // Hook for chat persistence function useChatPersistence(sessionId, enabled = true) { const storageKey = `mobile-chat-${sessionId}`; const saveMessages = useCallback((messages) => { if (!enabled || typeof window === "undefined") return; try { localStorage.setItem(storageKey, JSON.stringify({ messages, timestamp: Date.now(), })); } catch (error) { console.warn("Failed to save chat messages:", error); } }, [storageKey, enabled]); const loadMessages = useCallback(() => { if (!enabled || typeof window === "undefined") return []; try { const stored = localStorage.getItem(storageKey); if (!stored) return []; const data = JSON.parse(stored); // Check if data is less than 24 hours old const isRecent = Date.now() - data.timestamp < 24 * 60 * 60 * 1000; if (isRecent && Array.isArray(data.messages)) { return data.messages.map((msg) => ({ ...msg, timestamp: new Date(msg.timestamp), })); } } catch (error) { console.warn("Failed to load chat messages:", error); } return []; }, [storageKey, enabled]); const clearMessages = useCallback(() => { if (!enabled || typeof window === "undefined") return; try { localStorage.removeItem(storageKey); } catch (error) { console.warn("Failed to clear chat messages:", error); } }, [storageKey, enabled]); return { saveMessages, loadMessages, clearMessages }; } function MobileChatNavigator({ apiEndpoint = "/api/rag/answer", language = "en", title, placeholder, className = "", disabled = false, showSuggestedQuestions = true, suggestedQuestionsContext, initialMessages = [], onNavigate, onMessage, buttonPosition = "bottom-right", buttonStyle, persistChat = true, sessionId = "default", }) { const [isChatOpen, setIsChatOpen] = useState(false); const [messages, setMessages] = useState([]); const [isTyping, setIsTyping] = useState(false); const isMobile = useIsMobile(); const { saveMessages, loadMessages, clearMessages } = useChatPersistence(sessionId, persistChat); // Load persisted messages on mount useEffect(() => { if (persistChat) { const persistedMessages = loadMessages(); if (persistedMessages.length > 0) { setMessages(persistedMessages); } else if (initialMessages.length > 0) { setMessages(initialMessages); } } else if (initialMessages.length > 0) { setMessages(initialMessages); } }, [persistChat, loadMessages, initialMessages]); // Save messages when they change useEffect(() => { if (persistChat && messages.length > 0) { saveMessages(messages); } }, [messages, saveMessages, persistChat]); const handleFloatingButtonClick = useCallback(() => { if (!isMobile) { // On desktop, don't navigate - this should be handled by desktop modal console.warn("MobileChatNavigator should only be used on mobile devices"); return; } const newState = !isChatOpen; setIsChatOpen(newState); onNavigate === null || onNavigate === void 0 ? void 0 : onNavigate(newState); }, [isChatOpen, isMobile, onNavigate]); const handleBackPress = useCallback(() => { setIsChatOpen(false); onNavigate === null || onNavigate === void 0 ? void 0 : onNavigate(false); }, [onNavigate]); const handleSendMessage = useCallback(async (message) => { if (disabled) return; const userMessage = { id: `user-${Date.now()}`, text: message, isUser: true, timestamp: new Date(), }; setMessages((prev) => [...prev, userMessage]); setIsTyping(true); try { let aiResponse; if (onMessage) { // Use custom message handler aiResponse = await onMessage(message); } else { // Use default API call const response = await fetch(apiEndpoint, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ question: message, sessionId, }), }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); aiResponse = data.answer || data.response || "Sorry, I could not process your request."; } const aiMessage = { id: `ai-${Date.now()}`, text: aiResponse, isUser: false, timestamp: new Date(), }; setMessages((prev) => [...prev, aiMessage]); } catch (error) { console.error("Error sending message:", error); const errorMessage = { id: `error-${Date.now()}`, text: "Sorry, there was an error processing your message. Please try again.", isUser: false, timestamp: new Date(), }; setMessages((prev) => [...prev, errorMessage]); } finally { setIsTyping(false); } }, [disabled, onMessage, apiEndpoint, sessionId]); const handleClearHistory = useCallback(() => { setMessages([]); if (persistChat) { clearMessages(); } }, [persistChat, clearMessages]); // Only render if on mobile if (!isMobile) { return null; } return (jsx(I18nProvider, { defaultLanguage: language, children: jsxs("div", { className: `mobile-chat-navigator ${className}`, children: [!isChatOpen && (jsx(FloatingChatButton, { onClick: handleFloatingButtonClick, position: buttonPosition, className: buttonStyle ? "custom-styled" : "", disabled: disabled })), isChatOpen && (jsx(MobileFullscreenChat, { onSendMessage: handleSendMessage, onBackPress: handleBackPress, messages: messages, isTyping: isTyping, title: title, placeholder: placeholder, disabled: disabled, showSuggestedQuestions: showSuggestedQuestions, suggestedQuestionsContext: suggestedQuestionsContext, headerContent: jsx("button", { onClick: handleClearHistory, className: "clear-history-button", "aria-label": "Clear chat history", children: "Clear" }) })), jsx("style", { children: ` .mobile-chat-navigator { position: relative; } .clear-history-button { background: rgba(255, 255, 255, 0.2); border: 1px solid rgba(255, 255, 255, 0.3); color: white; padding: 8px 16px; border-radius: 20px; font-size: 14px; cursor: pointer; transition: all 0.2s ease; } .clear-history-button:hover { background: rgba(255, 255, 255, 0.3); } .clear-history-button:active { transform: scale(0.95); } .custom-styled { ${buttonStyle ? Object.entries(buttonStyle) .map(([key, value]) => `${key .replace(/([A-Z])/g, "-$1") .toLowerCase()}: ${value};`) .join(" ") : ""} } ` })] }) })); } // Higher-order component for easier integration function withMobileNavigation(WrappedComponent) { return function MobileNavigationWrapper(props) { const { apiEndpoint, language, title, placeholder, disabled, showSuggestedQuestions, suggestedQuestionsContext, initialMessages, onNavigate, onMessage, buttonPosition, buttonStyle, persistChat, sessionId, ...componentProps } = props; const navigatorProps = { apiEndpoint, language, title, placeholder, disabled, showSuggestedQuestions, suggestedQuestionsContext, initialMessages, onNavigate, onMessage, buttonPosition, buttonStyle, persistChat, sessionId, }; return (jsxs(Fragment, { children: [jsx(WrappedComponent, { ...componentProps }), jsx(MobileChatNavigator, { ...navigatorProps })] })); }; } export { MobileChatNavigator, withMobileNavigation }; //# sourceMappingURL=mobile-chat-navigator.js.map