comparison love/epi/src/components/ChatUI/Composer.tsx @ 38:cf9caa4abc3e

[Love] FE and BE. Can chat and render images. Also created MCP for powerpoint generations.
author MrJuneJune <me@mrjunejune.com>
date Mon, 01 Dec 2025 20:35:56 -0800
parents
children
comparison
equal deleted inserted replaced
37:fb9bcd3145cb 38:cf9caa4abc3e
1 import { useEffect, useRef } from 'react';
2 import type { ReactNode } from 'react';
3 import { SendHorizonal, Square } from 'lucide-react';
4 import { useComposer } from '@/hooks/useComposer';
5 import { useChat } from '@/hooks/useChat';
6 import { broadcastDone } from '@/hooks/useChatWebsocket';
7 import { apiUrl } from '@/utils';
8 import { currentChatId } from '@/atoms/chatAtoms';
9 import { useAtom } from 'jotai';
10 import { useNavigate } from '@tanstack/react-router';
11
12 interface ComposerProps {
13 chatId: string;
14 }
15
16 export function Composer({ chatId }: ComposerProps): ReactNode {
17 const { text, setText, isSending, isDisabled, setSending } = useComposer();
18 const [_, setCurrentChatId] = useAtom(currentChatId);
19 const { sendUserMessage } = useChat(chatId);
20
21 const textareaRef = useRef<HTMLTextAreaElement>(null);
22
23 // Auto-resize
24 useEffect(() => {
25 const el = textareaRef.current;
26 if (el) {
27 el.style.height = 'auto';
28 el.style.height = `${el.scrollHeight}px`;
29 }
30 }, [text]);
31 const navigate = useNavigate();
32
33 const handleSubmit = async () => {
34 console.log('before');
35 // Stoping the calls
36 if (isSending) {
37 console.log('called');
38 broadcastDone(chatId);
39 return;
40 }
41
42 // Don't send if it is diabled or no text
43 if (isDisabled || !text.trim()) return;
44
45 let newChatId: string = '';
46 if (!chatId || chatId === 'new') {
47 const res = await fetch(apiUrl('/chats'), { method: 'POST' });
48 const data = await res.json();
49 await navigate(
50 { to: '/chat/$chatId', params: { chatId: data.id } },
51 );
52 console.log('problem: 1');
53 setCurrentChatId(data.id);
54 newChatId = data.id;
55 }
56
57 const message = text.trim();
58 setSending();
59 setText('');
60 sendUserMessage(message, newChatId);
61 };
62
63 const handleKeyDown = (e: React.KeyboardEvent) => {
64 if (e.key === 'Enter' && !e.shiftKey) {
65 e.preventDefault();
66 handleSubmit();
67 }
68 };
69
70 return (
71 <div className="border-t border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900">
72 <div className="max-w-4xl mx-auto p-4">
73 <div className="flex items-end gap-3 bg-gray-50 dark:bg-gray-800/50 rounded-2xl px-4 py-3 shadow-sm ring-1 ring-gray-200 dark:ring-gray-700 focus-within:ring-2 focus-within:ring-blue-500">
74 <textarea
75 ref={textareaRef}
76 value={text}
77 onChange={(e) => setText(e.target.value)}
78 onKeyDown={handleKeyDown}
79 placeholder="Send a message..."
80 rows={1}
81 className="flex-1 resize-none bg-transparent outline-none text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400 text-base leading-6 overflow-hidden max-h-96"
82 />
83
84 <button
85 onClick={handleSubmit}
86 disabled={isDisabled}
87 className={`w-10 h-10 rounded-full flex items-center justify-center transition-all ${
88 isDisabled
89 ? 'bg-gray-300 dark:bg-gray-700 text-gray-500'
90 : isSending
91 ? 'bg-red-500 hover:bg-red-600 text-white'
92 : 'bg-blue-500 hover:bg-blue-600 text-white'
93 }`}
94 >
95 {isSending ? <Square className="w-5 h-5" /> : <SendHorizonal className="w-5 h-5" />}
96 </button>
97 </div>
98 </div>
99 </div>
100 );
101 }