All files / src/components/Chatbot index.tsx

100% Statements 97/97
95% Branches 19/20
100% Functions 5/5
100% Lines 97/97

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 1381x 1x     1x 1x 1x 1x     1x     1x     1x     1x     1x 1x 1x     1x     1x   1x 7x 7x 7x 7x   7x 1x 7x   7x 1x 1x 1x   7x   7x   4x 1x 1x 1x 1x 1x   1x   1x   1x 1x     1x 1x 1x 1x   4x   4x 4x 4x 7x     7x 7x 1x 1x 7x   7x 7x 7x 7x 7x 7x 7x 7x 7x 5x 5x 5x 5x 5x 5x 5x 5x   7x   7x     7x 1x 1x   6x 4x 4x 4x 4x 4x 4x 4x 1x 1x 1x 1x 4x   2x 2x 2x 2x 2x 2x 2x   7x   1x  
import { CopilotPopup, CopilotSidebar } from '@copilotkit/react-ui';
import { useCallback, useEffect } from 'react';
 
// Components
import { TriggerButton } from './TriggerButton';
import { UserMessage } from './UserMessage';
import { AssistantMessage } from './SystemMessage';
import CustomChatHeader from './CustomChatHeader';
 
// Constants
import { COPILOT_LABEL } from '@shared/constants';
 
// Providers
import { ChatMode, useChat } from '@/providers/chatbot';
 
// Hooks
import { useBreakpoint } from '@shared/hooks';
 
// Utils
import { combineClasses } from '@shared/utils';
 
// Styles
import './styles.css';
import './chatbot-sidebar.css';
import '@copilotkit/react-ui/styles.css';
 
// Components
import { InputContainer } from './InputContainer';
 
// Stores
import { useSetSuggestionsLoaded } from '@/stores';
 
const ChatBot = () => {
  const { TITLE, INITIAL_MESSAGE } = COPILOT_LABEL;
  const { isOpen, chatBotMode, toggleVisibility, handleSwitchMode, resetChat } =
    useChat();
  const { isDesktop, isUltraLarge } = useBreakpoint();
 
  const handleClose = useCallback(() => {
    toggleVisibility();
  }, [toggleVisibility]);
 
  const handleReset = () => {
    useSetSuggestionsLoaded(false);
    resetChat();
  };
 
  useEffect(() => {
    // Only handle outside clicks when the chatbot is open and in POPUP mode
    if (!isOpen || chatBotMode !== ChatMode.POPUP) return;
 
    const handleGlobalClick = (e: MouseEvent) => {
      const copilotKitPopup =
        document.querySelector<HTMLDivElement>('.copilotKitPopup');
      const copilotKitWindow = document.querySelector<HTMLDivElement>(
        '.copilotKitPopup .copilotKitWindow',
      );
 
      if (!copilotKitPopup || !copilotKitWindow) return;
 
      const target = e.target as Node;
 
      const clickedInsideDialog = copilotKitWindow.contains(target);
      const clickedInsidePopupRoot = copilotKitPopup.contains(target);
 
      // Close the chatbot if the click is inside the overlay but outside the dialog
      if (clickedInsidePopupRoot && !clickedInsideDialog) {
        handleClose();
      }
    };
 
    document.addEventListener('mousedown', handleGlobalClick);
 
    return () => {
      document.removeEventListener('mousedown', handleGlobalClick);
    };
  }, [isOpen, chatBotMode, handleClose]);
 
  // Auto switch from SIDEBAR to POPUP when < 1536px
  useEffect(() => {
    if (chatBotMode === ChatMode.SIDEBAR && !isUltraLarge) {
      handleSwitchMode();
    }
  }, [chatBotMode, isUltraLarge, handleSwitchMode]);
 
  const commonProps = {
    UserMessage,
    AssistantMessage,
    labels: {
      title: TITLE,
      initial: INITIAL_MESSAGE,
    },
    Input: InputContainer,
    Header: () => (
      <CustomChatHeader
        chatBotMode={chatBotMode}
        onSwitchMode={handleSwitchMode}
        onClose={handleClose}
        isDesktop={isDesktop}
        isUltraLarge={isUltraLarge}
        onReset={handleReset}
      />
    ),
  };
 
  const isPopupMode = chatBotMode === ChatMode.POPUP;
 
  // Only show the chatbot in desktop mode
  if (!isDesktop) {
    return null;
  }
 
  return isPopupMode ? (
    <CopilotPopup
      {...commonProps}
      key={`copilot-popup-${isOpen}`}
      defaultOpen={isOpen}
      className={combineClasses(!isOpen && 'hidden-cb-popup')}
      Button={TriggerButton}
      onSetOpen={(open) => {
        if (open !== isOpen) {
          toggleVisibility(); // Sync the state with the CopilotKit popup
        }
      }}
    />
  ) : (
    <CopilotSidebar
      {...commonProps}
      key={`copilot-sidebar-${isOpen}`}
      className="copilotKitWithUI"
      clickOutsideToClose={false}
      defaultOpen={true}
    />
  );
};
 
export default ChatBot;