import { useState, useRef, useEffect, useCallback } from "react"; import { cn } from "@/lib/utils"; import { Send, Square, Slash, ChevronUp } from "lucide-react"; import { SLASH_COMMANDS } from "@shared/claw-types"; interface ChatInputProps { onSend: (message: string) => void; onSlashCommand?: (command: string, args: string) => void; isStreaming: boolean; onStop: () => void; disabled?: boolean; placeholder?: string; } export function ChatInput({ onSend, onSlashCommand, isStreaming, onStop, disabled, placeholder, }: ChatInputProps) { const [value, setValue] = useState(""); const [showCommands, setShowCommands] = useState(false); const [selectedCommand, setSelectedCommand] = useState(0); const textareaRef = useRef(null); // Filter commands based on input const filteredCommands = value.startsWith("/") ? SLASH_COMMANDS.filter((cmd) => cmd.name.toLowerCase().startsWith(value.split(" ")[0].toLowerCase()) ) : []; useEffect(() => { setShowCommands(value.startsWith("/") && filteredCommands.length > 0 && !value.includes(" ")); setSelectedCommand(0); }, [value, filteredCommands.length]); // Auto-resize textarea useEffect(() => { const textarea = textareaRef.current; if (textarea) { textarea.style.height = "auto"; textarea.style.height = Math.min(textarea.scrollHeight, 200) + "px"; } }, [value]); const handleSubmit = useCallback(() => { const trimmed = value.trim(); if (!trimmed || disabled) return; if (trimmed.startsWith("/")) { const parts = trimmed.split(" "); const cmd = parts[0]; const args = parts.slice(1).join(" "); if (onSlashCommand) { onSlashCommand(cmd, args); } } else { onSend(trimmed); } setValue(""); }, [value, disabled, onSend, onSlashCommand]); const handleKeyDown = (e: React.KeyboardEvent) => { if (showCommands) { if (e.key === "ArrowDown") { e.preventDefault(); setSelectedCommand((prev) => Math.min(prev + 1, filteredCommands.length - 1)); return; } if (e.key === "ArrowUp") { e.preventDefault(); setSelectedCommand((prev) => Math.max(prev - 1, 0)); return; } if (e.key === "Tab" || e.key === "Enter") { e.preventDefault(); const cmd = filteredCommands[selectedCommand]; if (cmd) { setValue(cmd.name + " "); setShowCommands(false); } return; } if (e.key === "Escape") { setShowCommands(false); return; } } if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); if (isStreaming) return; handleSubmit(); } }; // Group commands by category for display const groupedCommands = filteredCommands.reduce>((acc, cmd) => { const cat = cmd.category; if (!acc[cat]) acc[cat] = []; acc[cat].push(cmd); return acc; }, {}); return (
{/* Slash command autocomplete */} {showCommands && (
{Object.entries(groupedCommands).map(([category, cmds]) => (
{category}
{cmds.map((cmd) => { const globalIdx = filteredCommands.indexOf(cmd); return ( ); })}
))}
)} {/* Input area */}