"use client"; import { useState, useRef, useCallback } from "react"; import { Send, Square, Paperclip } from "lucide-react"; import { useChatStore } from "@/lib/store"; import { streamChat } from "@/lib/api"; import { cn } from "@/lib/utils"; import { MAX_MESSAGE_LENGTH } from "@/lib/constants"; interface ChatInputProps { conversationId: string; } export function ChatInput({ conversationId }: ChatInputProps) { const [input, setInput] = useState(""); const [isStreaming, setIsStreaming] = useState(false); const textareaRef = useRef(null); const abortRef = useRef(null); const { conversations, settings, addMessage, updateMessage } = useChatStore(); const conversation = conversations.find((c) => c.id === conversationId); const handleSubmit = useCallback(async () => { const text = input.trim(); if (!text || isStreaming) return; setInput(""); setIsStreaming(true); // Add user message addMessage(conversationId, { role: "user", content: text, status: "complete", }); // Add placeholder assistant message const assistantId = addMessage(conversationId, { role: "assistant", content: "", status: "streaming", }); const controller = new AbortController(); abortRef.current = controller; const messages = [ ...(conversation?.messages ?? []).map((m) => ({ role: m.role, content: m.content, })), { role: "user" as const, content: text }, ]; let fullText = ""; try { for await (const chunk of streamChat(messages, settings.model, controller.signal)) { if (chunk.type === "text" && chunk.content) { fullText += chunk.content; updateMessage(conversationId, assistantId, { content: fullText, status: "streaming", }); } else if (chunk.type === "done") { break; } else if (chunk.type === "error") { updateMessage(conversationId, assistantId, { content: chunk.error ?? "An error occurred", status: "error", }); return; } } updateMessage(conversationId, assistantId, { status: "complete" }); } catch (err) { if ((err as Error).name !== "AbortError") { updateMessage(conversationId, assistantId, { content: "Request failed. Please try again.", status: "error", }); } else { updateMessage(conversationId, assistantId, { status: "complete" }); } } finally { setIsStreaming(false); abortRef.current = null; } }, [input, isStreaming, conversationId, conversation, settings.model, addMessage, updateMessage]); const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); handleSubmit(); } }; const handleStop = () => { abortRef.current?.abort(); }; const adjustHeight = () => { const el = textareaRef.current; if (!el) return; el.style.height = "auto"; el.style.height = `${Math.min(el.scrollHeight, 200)}px`; }; return (