import { useState, useRef, useEffect, useCallback } from "react"; import type { MessageType } from "@/types/MessageType"; import type { User } from "@/types/User"; import { postMessage, getUsers } from "@/api"; import { cn } from "@/lib/utils"; interface Props { channelId: string; replyTo: string | null; onClearReply: () => void; onMessageSent: () => void; } const TYPE_META: Record = { text: { prefix: ">", color: "text-muted-foreground", key: "1" }, code: { prefix: "//", color: "text-[var(--color-msg-code)]", key: "2" }, result: { prefix: "=>", color: "text-[var(--color-msg-result)]", key: "3" }, error: { prefix: "!!", color: "text-[var(--color-msg-error)]", key: "4" }, plan: { prefix: "::", color: "text-[var(--color-msg-plan)]", key: "5" }, }; export function ComposeBox({ channelId, replyTo, onClearReply, onMessageSent, }: Props) { const [content, setContent] = useState(""); const [msgType, setMsgType] = useState("text"); const [sending, setSending] = useState(false); const [focused, setFocused] = useState(false); const [users, setUsers] = useState([]); const [mentionOpen, setMentionOpen] = useState(false); const [mentionQuery, setMentionQuery] = useState(""); const [mentionIdx, setMentionIdx] = useState(0); const inputRef = useRef(null); const meta = TYPE_META[msgType]; // Load users for mention autocomplete useEffect(() => { getUsers().then(setUsers).catch(() => {}); }, []); // Auto-resize textarea useEffect(() => { if (inputRef.current) { inputRef.current.style.height = "0"; inputRef.current.style.height = `${Math.min(inputRef.current.scrollHeight, 120)}px`; } }, [content]); // Auto-focus on mount useEffect(() => { inputRef.current?.focus(); }, []); // Detect @mention typing const checkMention = useCallback((text: string, cursorPos: number) => { const before = text.slice(0, cursorPos); const match = before.match(/@(\w*)$/); if (match) { setMentionOpen(true); setMentionQuery(match[1].toLowerCase()); setMentionIdx(0); } else { setMentionOpen(false); } }, []); const filteredUsers = users.filter((u) => u.username.toLowerCase().includes(mentionQuery) || u.display_name.toLowerCase().includes(mentionQuery) ); function insertMention(username: string) { const textarea = inputRef.current; if (!textarea) return; const pos = textarea.selectionStart; const before = content.slice(0, pos); const after = content.slice(pos); const mentionStart = before.lastIndexOf("@"); const newContent = before.slice(0, mentionStart) + `@${username} ` + after; setContent(newContent); setMentionOpen(false); textarea.focus(); } async function handleSend() { if (!content.trim() || sending) return; setSending(true); try { await postMessage(channelId, { content: content.trim(), type: msgType, reply_to: replyTo ?? undefined, }); setContent(""); setMsgType("text"); onClearReply(); onMessageSent(); } finally { setSending(false); } } function cycleType(direction: 1 | -1) { const types: MessageType[] = ["text", "code", "result", "error", "plan"]; const idx = types.indexOf(msgType); const next = (idx + direction + types.length) % types.length; setMsgType(types[next]); } return (
{/* Reply chip */} {replyTo && (
^ #{replyTo.slice(0, 8)}
)} {/* Mention autocomplete */} {mentionOpen && filteredUsers.length > 0 && (
{filteredUsers.map((u, i) => ( ))}
)} {/* Floating input container */}
{/* Textarea */}