import { useState, type KeyboardEvent } from "react"; import Row from "@moduletrace/twui/layout/Row"; import Stack from "@moduletrace/twui/layout/Stack"; import Textarea from "@moduletrace/twui/form/Textarea"; import type { BotMessageWithAttachments, BotWithLastMessage, } from "@/src/types"; import sendBotMessage from "../(functions)/send-bot-message"; import useComposerAttachments from "../(hooks)/use-composer-attachments"; import useComposerDraft from "../(hooks)/use-composer-draft"; import ComposerAttachButton from "./composer-attach-button"; import ComposerAttachments from "./composer-attachments"; import ComposerSendButton from "./composer-send-button"; type Props = { bot: BotWithLastMessage; /** * The workspace the conversation is in, if any. */ workspace_id?: number | null; onSent: (params: { message: BotMessageWithAttachments }) => void; }; /** * Enter sends, Shift+Enter adds a line. Grows to five lines, then scrolls. * Its top rule lines up with the rail's user-row rule; picked files sit * just above it. Files can also be pasted or dropped in. Unsent text is * kept per conversation across reloads, with its uploaded files. */ export default function Composer({ bot, workspace_id, onSent }: Props) { const { text, setText } = useComposerDraft({ bot_id: bot.id, workspace_id }); const [sending, setSending] = useState(false); const [error, setError] = useState(); const files = useComposerAttachments({ bot_id: bot.id, workspace_id }); const can_send = Boolean(text.trim() || files.media_ids.length) && !sending && !files.uploading; async function send() { if (!can_send || !bot.id) return; setSending(true); setError(undefined); const res = await sendBotMessage({ bot_id: bot.id, workspace_id, text, media_ids: files.media_ids, }); setSending(false); if (!res.success || !res.singleRes) { setError(res.msg || "Message not sent"); return; } setText(""); files.clear(); onSent({ message: res.singleRes }); } function handleKeyDown( e: KeyboardEvent, ) { if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) { e.preventDefault(); send(); } } return ( { if (e.dataTransfer.types.includes("Files")) e.preventDefault(); }} onDrop={(e) => { if (!e.dataTransfer.files.length) return; e.preventDefault(); files.add({ files: [...e.dataTransfer.files] }); }} >