File size: 3,500 Bytes
c0ddd13
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import { useState, useRef, useEffect } from "react";
import { SendHorizontal, Square } from "lucide-react";
import type { VoiceState } from "../../../hooks/useVoiceSession";
import VoiceMicButton from "./VoiceMicButton";

interface ChatInputProps {
  onSend: (text: string) => void;
  onStop?: () => void;
  isLoading: boolean;
  disabled?: boolean;
  voiceState: VoiceState;
  onVoiceToggle: () => void;
}

export default function ChatInput({
  onSend,
  onStop,
  isLoading,
  disabled,
  voiceState,
  onVoiceToggle,
}: ChatInputProps) {
  const [value, setValue] = useState("");
  const textareaRef = useRef<HTMLTextAreaElement>(null);

  const isVoiceActive = voiceState !== "IDLE" && voiceState !== "ERROR";

  useEffect(() => {
    const el = textareaRef.current;
    if (!el) return;
    el.style.height = "auto";
    el.style.height = Math.min(el.scrollHeight, 50) + "px";
  }, [value]);

  const handleSend = () => {
    const trimmed = value.trim();
    if (!trimmed || isLoading || disabled || isVoiceActive) return;
    onSend(trimmed);
    setValue("");
  };

  const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
    if (e.key === "Enter" && !e.shiftKey) {
      e.preventDefault();
      handleSend();
    }
  };

  const canSend = value.trim().length > 0 && !disabled && !isVoiceActive;

  return (
    <div
      className={`bg-white border rounded-2xl shadow-sm px-4 py-3 transition-all ${
        disabled || isVoiceActive
          ? "opacity-80 border-neutral-200"
          : "border-neutral-200 focus-within:border-brand-green focus-within:shadow-md focus-within:shadow-brand-green/10"
      }`}
    >
      <div className="flex items-end gap-3">
        <VoiceMicButton
          voiceState={voiceState}
          onToggle={onVoiceToggle}
          disabled={isLoading}
        />

        <textarea
          ref={textareaRef}
          value={value}
          onChange={(e) => setValue(e.target.value)}
          onKeyDown={handleKeyDown}
          placeholder={
            isVoiceActive
              ? "Voice session active — speak to interact"
              : "Ask me anything… (Enter to send, Shift+Enter for newline)"
          }
          disabled={isLoading || disabled || isVoiceActive}
          rows={1}
          className="flex-1 resize-none bg-transparent text-sm text-neutral-900 leading-6 placeholder:text-neutral-400 outline-none overflow-y-auto"
          style={{ minHeight: "24px", maxHeight: "50px" }}
        />

        {isLoading ? (
          <button
            onClick={onStop}
            className="w-9 h-9 flex-shrink-0 rounded-xl bg-gradient-to-br from-brand-green-light to-brand-green text-white shadow-md shadow-brand-green/25 hover:brightness-105 flex items-center justify-center transition-all"
            aria-label="Stop"
          >
            <Square className="h-3.5 w-3.5" />
          </button>
        ) : (
          <button
            onClick={handleSend}
            disabled={!canSend}
            className={`w-9 h-9 flex-shrink-0 rounded-xl flex items-center justify-center transition-all ${
              canSend
                ? "bg-gradient-to-br from-brand-green-light to-brand-green text-white shadow-md shadow-brand-green/25 hover:brightness-105"
                : "bg-neutral-100 text-neutral-300 cursor-not-allowed"
            }`}
            aria-label="Send"
          >
            <SendHorizontal className="h-4 w-4" />
          </button>
        )}
      </div>
    </div>
  );
}