view love/epi/src/components/ChatUI/Composer.tsx @ 169:295ac2e5ec00

[MrJuneJune] Created separate target for generating html from md.
author MrJuneJune <me@mrjunejune.com>
date Mon, 19 Jan 2026 17:33:18 -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>
  );
}