File size: 3,618 Bytes
cd6f98e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import clsx from "clsx";
import { useTranslation } from "next-i18next";
import type { ReactNode } from "react";
import React, { useEffect, useRef, useState } from "react";
import { FaArrowCircleDown, FaCommentDots } from "react-icons/fa";
import { ImSpinner2 } from "react-icons/im";

import type { HeaderProps } from "./MacWindowHeader";
import { MacWindowHeader, messageListId } from "./MacWindowHeader";
import { useAgentStore } from "../../stores";
import Button from "../Button";
import Input from "../Input";
import HideShow from "../motions/HideShow";

interface ChatControls {
  value: string;
  onChange: (string) => void;
  handleChat: () => Promise<void>;
  loading?: boolean;
}

interface ChatWindowProps extends HeaderProps {
  children?: ReactNode;
  setAgentRun?: (name: string, goal: string) => void;
  visibleOnMobile?: boolean;
  chatControls?: ChatControls;
}

const ChatWindow = ({ messages, children, title, chatControls }: ChatWindowProps) => {
  const [t] = useTranslation();
  const [hasUserScrolled, setHasUserScrolled] = useState(false);
  const isThinking = useAgentStore.use.isAgentThinking();
  const isStopped = useAgentStore.use.lifecycle() === "stopped";
  const scrollRef = useRef<HTMLDivElement>(null);

  const handleScroll = (event: React.UIEvent<HTMLDivElement>) => {
    const { scrollTop, scrollHeight, clientHeight } = event.currentTarget;

    // Use has scrolled if we have scrolled up at all from the bottom
    const hasUserScrolled = scrollTop < scrollHeight - clientHeight - 10;
    setHasUserScrolled(hasUserScrolled);
  };

  const handleScrollToBottom = (behaviour: "instant" | "smooth") => {
    if (!scrollRef || !scrollRef.current) return;

    scrollRef.current.scrollTo({
      top: scrollRef.current.scrollHeight,
      behavior: behaviour,
    });
  };

  useEffect(() => {
    if (!hasUserScrolled) {
      handleScrollToBottom("instant");
    }
  });

  return (
    <div
      className={clsx(
        "flex h-full w-full max-w-[inherit] flex-1 flex-col overflow-auto text-slate-12 transition-all duration-500"
      )}
    >
      <HideShow
        showComponent={hasUserScrolled}
        className="absolute bottom-11 right-6 z-40 cursor-pointer sm:bottom-14"
      >
        <FaArrowCircleDown
          onClick={() => handleScrollToBottom("smooth")}
          className="h-6 w-6 animate-bounce md:h-7 md:w-7"
        />
      </HideShow>
      <MacWindowHeader title={title} messages={messages} />
      <div
        className="mb-2 mr-2 flex-1 overflow-auto transition-all duration-500"
        ref={scrollRef}
        onScroll={handleScroll}
        id={messageListId}
      >
        {children}
        <div
          className={clsx(
            isThinking && !isStopped ? "opacity-100" : "opacity-0",
            "mr-2 flex flex-row items-center gap-2 p-2 transition duration-300 sm:mr-4",
            "text-xs sm:text-base"
          )}
        >
          <p>🧠 Thinking</p>
          <ImSpinner2 className="animate-spin" />
        </div>
      </div>
      {chatControls && (
        <div className="mt-auto flex flex-row gap-2 p-2 pt-0 sm:p-4">
          <Input
            small
            placeholder="Chat with your agent..."
            value={chatControls.value}
            onChange={(e) => chatControls?.onChange(e.target.value)}
          />
          <Button
            className="px-1 py-1 sm:px-3 md:py-1"
            onClick={chatControls?.handleChat}
            disabled={chatControls.loading}
          >
            <FaCommentDots />
          </Button>
        </div>
      )}
    </div>
  );
};

export default ChatWindow;