aboutsummaryrefslogtreecommitdiffstats
path: root/renderer/src/components/ChatPane.tsx
blob: c2cdfbbd8c48b5dc8b9f7509ba4617e954d578fb (plain)
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
import React, { useState, useRef, useEffect } from "react";
import type { Message } from "../types";

interface ChatPaneProps {
  messages: Message[];
  onSend: (message: string) => void;
  isLoading: boolean;
  disabled: boolean;
  placeholder: string;
  collapsed: boolean;
  chatWidth: number;
  onToggleCollapse: () => void;
  activityStatus?: string | null;
  onCancel?: () => void;
}

export function ChatPane({
  messages,
  onSend,
  isLoading,
  disabled,
  placeholder,
  collapsed,
  chatWidth,
  onToggleCollapse,
  activityStatus,
  onCancel,
}: ChatPaneProps) {
  const [input, setInput] = useState("");
  const messagesEndRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
  }, [messages]);

  const handleSend = () => {
    if (!input.trim() || isLoading || disabled) return;
    onSend(input.trim());
    setInput("");
  };

  return (
    <div
      className="chat-pane"
      style={{ width: collapsed ? 28 : chatWidth }}
    >
      <div className="chat-header">
        {!collapsed && <span>Chat</span>}
        <button className="chat-collapse-btn" onClick={onToggleCollapse}>
          {collapsed ? "⟨" : "⟩"}
        </button>
      </div>

      {!collapsed && (
        <>
          <div className="chat-messages">
            {messages.map((msg) => (
              <div key={msg.id} className={`message ${msg.role}`}>
                <div className="message-content">{msg.content}</div>
              </div>
            ))}
            {isLoading && (
              <div className="message assistant loading">
                <div className="message-content">
                  <span>{activityStatus ?? "Thinking…"}</span>
                  {onCancel && (
                    <button
                      className="loading-cancel-x"
                      onClick={onCancel}
                      title="Cancel (Esc)"
                    >
                      ×
                    </button>
                  )}
                </div>
              </div>
            )}
            <div ref={messagesEndRef} />
          </div>

          <div className="chat-input">
            <input
              type="text"
              value={input}
              onChange={(e) => setInput(e.target.value)}
              onKeyDown={(e) =>
                e.key === "Enter" && !e.shiftKey && handleSend()
              }
              placeholder={placeholder}
              disabled={disabled || isLoading}
            />
            <button
              onClick={handleSend}
              disabled={disabled || isLoading || !input.trim()}
            >
              Send
            </button>
          </div>
        </>
      )}
    </div>
  );
}