view love/epi/src/components/ChatUI/Composer.tsx @ 71:75de5903355c

Giagantic changes that update Dowa library to be more align with stb style array and hashmap. Updated Seobeo to be caching on server side instead of file level caching. Deleted bunch of things I don't really use.
author June Park <parkjune1995@gmail.com>
date Sun, 28 Dec 2025 20:34:22 -0800
parents cf9caa4abc3e
children
line wrap: on
line source

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>
  );
}