File size: 5,080 Bytes
0971cc4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import React, { useEffect, useRef } from "react";

import Image from "next/image";

import OllamaLogo from "../../../public/ollama.png";
import CodeDisplayBlock from "../code-display-block";
import { Message } from "ai";

interface ChatListProps {
  messages: Message[];
  isLoading: boolean;
}

const MessageToolbar = () => (
  <div className="mt-1 flex gap-3 empty:hidden juice:flex-row-reverse">
    <div className="text-gray-400 flex self-end lg:self-center items-center justify-center lg:justify-start mt-0 -ml-1 h-7 gap-[2px] invisible">
      <span>Regenerate</span>
      <span>Edit</span>
    </div>
  </div>
);

export default function ChatList({ messages, isLoading }: ChatListProps) {
  const bottomRef = useRef<HTMLDivElement>(null);

  const scrollToBottom = () => {
    bottomRef.current?.scrollIntoView({ behavior: "auto", block: "end" });
  };

  useEffect(() => {
    if (isLoading) {
      return;
    }
    // if user scrolls up, disable auto-scroll
    scrollToBottom();
  }, [messages, isLoading]);

  if (messages.length === 0) {
    return (
      <div className="w-full h-full flex justify-center items-center">
        <div className="flex flex-col gap-4 items-center">
          <Image
            src={OllamaLogo}
            alt="AI"
            width={60}
            height={60}
            className="h-20 w-14 object-contain dark:invert"
          />
          <p className="text-center text-xl text-muted-foreground">
            How can I help you today?
          </p>
        </div>
      </div>
    );
  }

  return (
    <div
      id="scroller"
      className="w-[800px] overflow-y-scroll overflow-x-hidden h-full justify-center m-auto"
    >
      <div className="px-4 py-2 justify-center text-base md:gap-6 m-auto">
        {messages
          .filter((message) => message.role !== "system")
          .map((message, index) => (
            <div
              key={index}
              className="flex flex-1 text-base mx-auto gap-3 juice:gap-4 juice:md:gap-6 md:px-5 lg:px-1 xl:px-5 md:max-w-3xl lg:max-w-[40rem] xl:max-w-[48rem]"
            >
              <div className="flex flex-1 text-base mx-auto gap-3 juice:gap-4 juice:md:gap-6 md:px-5 lg:px-1 xl:px-5 md:max-w-3xl lg:max-w-[40rem] xl:max-w-[48rem]">
                <div className="flex-shrink-0 flex flex-col relative items-end">
                  <div className="pt-0.5">
                    <div className="gizmo-shadow-stroke flex h-6 w-6 items-center justify-center overflow-hidden rounded-full">
                      {message.role === "user" ? (
                        <div className="dark:invert h-full w-full bg-black" />
                      ) : (
                        <Image
                          src={OllamaLogo}
                          alt="AI"
                          className="object-contain dark:invert aspect-square h-full w-full"
                        />
                      )}
                    </div>
                  </div>
                </div>
                {message.role === "user" && (
                  <div className="relative flex w-full min-w-0 flex-col">
                    <div className="font-semibold pb-2">You</div>
                    <div className="flex-col gap-1 md:gap-3">
                      {message.content}
                    </div>
                    <MessageToolbar />
                  </div>
                )}
                {message.role === "assistant" && (
                  <div className="relative flex w-full min-w-0 flex-col">
                    <div className="font-semibold pb-2">Assistant</div>
                    <div className="flex-col gap-1 md:gap-3">
                      <span className="whitespace-pre-wrap">
                        {/* Check if the message content contains a code block */}
                        {message.content.split("```").map((part, index) => {
                          if (index % 2 === 0) {
                            return (
                              <React.Fragment key={index}>
                                {part}
                              </React.Fragment>
                            );
                          } else {
                            return (
                              <CodeDisplayBlock
                                key={index}
                                code={part.trim()}
                                lang=""
                              />
                            );
                          }
                        })}
                        {isLoading &&
                          messages.indexOf(message) === messages.length - 1 && (
                            <span className="animate-pulse" aria-label="Typing">
                              ...
                            </span>
                          )}
                      </span>
                    </div>
                    <MessageToolbar />
                  </div>
                )}
              </div>
            </div>
          ))}
      </div>
      <div id="anchor" ref={bottomRef}></div>
    </div>
  );
}