Mercurial
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/love/epi/src/components/ChatUI/Composer.tsx Mon Dec 01 20:35:56 2025 -0800 @@ -0,0 +1,101 @@ +import { useEffect, useRef } from 'react'; +import type { ReactNode } from 'react'; +import { SendHorizonal, Square } from 'lucide-react'; +import { useComposer } from '@/hooks/useComposer'; +import { useChat } from '@/hooks/useChat'; +import { broadcastDone } from '@/hooks/useChatWebsocket'; +import { apiUrl } from '@/utils'; +import { currentChatId } from '@/atoms/chatAtoms'; +import { useAtom } from 'jotai'; +import { useNavigate } from '@tanstack/react-router'; + +interface ComposerProps { + chatId: string; +} + +export function Composer({ chatId }: ComposerProps): ReactNode { + const { text, setText, isSending, isDisabled, setSending } = useComposer(); + const [_, setCurrentChatId] = useAtom(currentChatId); + const { sendUserMessage } = useChat(chatId); + + const textareaRef = useRef<HTMLTextAreaElement>(null); + + // Auto-resize + useEffect(() => { + const el = textareaRef.current; + if (el) { + el.style.height = 'auto'; + el.style.height = `${el.scrollHeight}px`; + } + }, [text]); + const navigate = useNavigate(); + + const handleSubmit = async () => { + console.log('before'); + // Stoping the calls + if (isSending) { + console.log('called'); + broadcastDone(chatId); + return; + } + + // Don't send if it is diabled or no text + if (isDisabled || !text.trim()) return; + + let newChatId: string = ''; + if (!chatId || chatId === 'new') { + const res = await fetch(apiUrl('/chats'), { method: 'POST' }); + const data = await res.json(); + await navigate( + { to: '/chat/$chatId', params: { chatId: data.id } }, + ); + console.log('problem: 1'); + setCurrentChatId(data.id); + newChatId = data.id; + } + + const message = text.trim(); + setSending(); + setText(''); + sendUserMessage(message, newChatId); + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + handleSubmit(); + } + }; + + return ( + <div className="border-t border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900"> + <div className="max-w-4xl mx-auto p-4"> + <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"> + <textarea + ref={textareaRef} + value={text} + onChange={(e) => setText(e.target.value)} + onKeyDown={handleKeyDown} + placeholder="Send a message..." + rows={1} + 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" + /> + + <button + onClick={handleSubmit} + disabled={isDisabled} + className={`w-10 h-10 rounded-full flex items-center justify-center transition-all ${ + isDisabled + ? 'bg-gray-300 dark:bg-gray-700 text-gray-500' + : isSending + ? 'bg-red-500 hover:bg-red-600 text-white' + : 'bg-blue-500 hover:bg-blue-600 text-white' + }`} + > + {isSending ? <Square className="w-5 h-5" /> : <SendHorizonal className="w-5 h-5" />} + </button> + </div> + </div> + </div> + ); +}