File size: 4,649 Bytes
baa4c21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import type { Message } from 'ai';
import React, { Fragment } from 'react';
import { classNames } from '~/utils/classNames';
import { AssistantMessage } from './AssistantMessage';
import { UserMessage } from './UserMessage';
import { useLocation } from '@remix-run/react';
import { db, chatId } from '~/lib/persistence/useChatHistory';
import { forkChat } from '~/lib/persistence/db';
import { toast } from 'react-toastify';
import WithTooltip from '~/components/ui/Tooltip';

interface MessagesProps {
  id?: string;
  className?: string;
  isStreaming?: boolean;
  messages?: Message[];
}

export const Messages = React.forwardRef<HTMLDivElement, MessagesProps>((props: MessagesProps, ref) => {
  const { id, isStreaming = false, messages = [] } = props;
  const location = useLocation();

  const handleRewind = (messageId: string) => {
    const searchParams = new URLSearchParams(location.search);
    searchParams.set('rewindTo', messageId);
    window.location.search = searchParams.toString();
  };

  const handleFork = async (messageId: string) => {
    try {
      if (!db || !chatId.get()) {
        toast.error('Chat persistence is not available');
        return;
      }

      const urlId = await forkChat(db, chatId.get()!, messageId);
      window.location.href = `/chat/${urlId}`;
    } catch (error) {
      toast.error('Failed to fork chat: ' + (error as Error).message);
    }
  };

  return (
    <div id={id} ref={ref} className={props.className}>

      {messages.length > 0

        ? messages.map((message, index) => {

            const { role, content, id: messageId, annotations } = message;

            const isUserMessage = role === 'user';

            const isFirst = index === 0;

            const isLast = index === messages.length - 1;

            const isHidden = annotations?.includes('hidden');



            if (isHidden) {

              return <Fragment key={index} />;

            }



            return (

              <div

                key={index}

                className={classNames('flex gap-4 p-6 w-full rounded-[calc(0.75rem-1px)]', {

                  'bg-bolt-elements-messages-background': isUserMessage || !isStreaming || (isStreaming && !isLast),

                  'bg-gradient-to-b from-bolt-elements-messages-background from-30% to-transparent':

                    isStreaming && isLast,

                  'mt-4': !isFirst,

                })}

              >

                {isUserMessage && (

                  <div className="flex items-center justify-center w-[34px] h-[34px] overflow-hidden bg-white text-gray-600 rounded-full shrink-0 self-start">

                    <div className="i-ph:user-fill text-xl"></div>

                  </div>

                )}

                <div className="grid grid-col-1 w-full">

                  {isUserMessage ? (

                    <UserMessage content={content} />

                  ) : (

                    <AssistantMessage content={content} annotations={message.annotations} />

                  )}

                </div>

                {!isUserMessage && (

                  <div className="flex gap-2 flex-col lg:flex-row">

                    {messageId && (

                      <WithTooltip tooltip="Revert to this message">

                        <button

                          onClick={() => handleRewind(messageId)}

                          key="i-ph:arrow-u-up-left"

                          className={classNames(

                            'i-ph:arrow-u-up-left',

                            'text-xl text-bolt-elements-textSecondary hover:text-bolt-elements-textPrimary transition-colors',

                          )}

                        />

                      </WithTooltip>

                    )}



                    <WithTooltip tooltip="Fork chat from this message">

                      <button

                        onClick={() => handleFork(messageId)}

                        key="i-ph:git-fork"

                        className={classNames(

                          'i-ph:git-fork',

                          'text-xl text-bolt-elements-textSecondary hover:text-bolt-elements-textPrimary transition-colors',

                        )}

                      />

                    </WithTooltip>

                  </div>

                )}

              </div>

            );

          })

        : null}

      {isStreaming && (

        <div className="text-center w-full text-bolt-elements-textSecondary i-svg-spinners:3-dots-fade text-4xl mt-4"></div>

      )}

    </div>
  );
});