Docs
🎨 ⏐ Customization
Custom Components

Custom Components

reachat is completely customizable. You can change the look and feel of the chat by customizing the theme or by providing your own components. Components can even be overriden using slots (opens in a new tab).

Custom Components Example

Below is example markup that creates a custom session empty message and also provides a custom session list item.

const CustomMessageQuestion: FC<any> = ({ question, files }) => (
  <>
    <span className="text-lg font-semibold text-blue-500">
      This is my question: {question}
    </span>
    <MessageFiles files={files}>
      <CustomMessageFile />
    </MessageFiles>
  </>
);
 
const CustomMessageResponse: FC<any> = ({ response }) => (
  <blockquote className="border-l border-blue-500 pl-2">
    This is the response: {response}
  </blockquote>
);
 
const CustomMessageFile: FC<any> = ({ name, type }) => (
  <Chip size="small" className="rounded-full border border-gray-700">
    {name || type}
  </Chip>
);
 
const CustomMessageSource: FC<any> = ({ title, url, image }) => {
  const { theme } = useContext(ChatContext);
  return (
    <Chip
      size="small"
      className="rounded-full border border-blue-500 border-opacity-50"
      onClick={() => alert('take me to ' + url)}
      start={
        image && (
          <img
            src={image}
            alt={title}
            className={cn(theme.messages.message.sources.source.image)}
          />
        )
      }
    >
      {title || url}
    </Chip>
  );
};
 
const CustomSessionListItem: FC<SessionListItemProps> = ({
  session,
  children,
  ...rest
}) => {
  const [open, setOpen] = useState(false);
  const btnRef = useRef(null);
  return (
    <>
      <ListItem
        {...rest}
        end={
          <IconButton
            ref={btnRef}
            size="small"
            variant="text"
            onClick={e => {
              e.stopPropagation();
              setOpen(true);
            }}
          >
            <MenuIcon />
          </IconButton>
        }
      >
        <span className="truncate">{session.title}</span>
      </ListItem>
      <Menu
        open={open}
        onClose={() => setOpen(false)}
        reference={btnRef}
        appendToBody={false}
      >
        <Card disablePadding>
          <List>
            <ListItem onClick={() => alert('rename')}>Rename</ListItem>
            <ListItem onClick={() => alert('delete')}>Delete</ListItem>
          </List>
        </Card>
      </Menu>
    </>
  );
};

Then in the master component you can leverage them like:

export const CustomComponents = () => {
  return (
    <div
      style={{
        position: 'absolute',
        top: 0,
        left: 0,
        right: 0,
        bottom: 0,
        padding: 20,
        margin: 20,
        background: '#02020F',
        borderRadius: 5
      }}
    >
      <Chat
        sessions={[
          ...fakeSessions,
          ...sessionsWithFiles,
          ...sessionWithSources
        ]}
        activeSessionId="1"
      >
        <SessionsList>
          <NewSessionButton>
            <button className="text-blue-500">New Session</button>
          </NewSessionButton>
          <Divider variant="secondary" />
          <SessionGroups>
            {groups =>
              groups.map(({ heading, sessions }) => (
                <SessionsGroup heading={heading} key={heading}>
                  {sessions.map(s => (
                    <SessionListItem key={s.id} session={s}>
                      <CustomSessionListItem session={s} />
                    </SessionListItem>
                  ))}
                </SessionsGroup>
              ))
            }
          </SessionGroups>
        </SessionsList>
        <SessionMessagePanel>
          <SessionMessagesHeader>
            <CustomMessagesHeader />
          </SessionMessagesHeader>
          <SessionMessages>
            {conversations =>
              conversations.map((conversation, index) => (
                <SessionMessage
                  conversation={conversation}
                  isLast={index === conversations.length - 1}
                  key={conversation.id}
                >
                  <MessageQuestion
                    question={conversation.question}
                    files={conversation.files}
                  >
                    <CustomMessageQuestion />
                  </MessageQuestion>
                  <MessageResponse response={conversation.response}>
                    <CustomMessageResponse />
                  </MessageResponse>
                  <MessageSources sources={conversation.sources}>
                    <CustomMessageSource />
                  </MessageSources>
                  <MessageActions
                    question={conversation.question}
                    response={conversation.response}
                  />
                </SessionMessage>
              ))
            }
          </SessionMessages>
          <ChatInput />
        </SessionMessagePanel>
      </Chat>
    </div>
  );
};

Using SessionContext

The SessionContext hook provides access to the current session state and methods to interact with it. Here's how you can use it:

import { useSessionContext } from 'reachat';
 
const CustomSendButton = () => {
  const { sendMessage } = useSessionContext();
  const handleSend = () => {
    sendMessage('Hello, AI!');
    return <button onClick={handleSend}>Send Custom Message</button>;
  };
};

The SessionContext hook provides the following properties and methods:

export interface SessionContextProps {
  sessions: Session[];
  activeSessionId: string | null;
  theme?: ChatTheme;
  isLoading?: boolean;
  activeSession?: Session | null;
  remarkPlugins?: PluggableList[];
  selectSession?: (sessionId: string) => void;
  deleteSession?: (sessionId: string) => void;
  createSession?: () => void;
}

By using these overrides and the SessionContext hook, you can create a fully customized chat experience while still leveraging the core functionality of reachat.