diff --git a/src/renderer/components/communication/MessageComposer.tsx b/src/renderer/components/communication/MessageComposer.tsx index f0f8cdba..6df2735b 100644 --- a/src/renderer/components/communication/MessageComposer.tsx +++ b/src/renderer/components/communication/MessageComposer.tsx @@ -1,30 +1,17 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react" import { AnimatePresence, motion } from "framer-motion" -import { ArrowBendUpLeft, PaperPlaneTilt, X, ChatCircle, UserCircle, Paperclip, FileText } from "@phosphor-icons/react" +import { ArrowBendUpLeft, X, ChatCircle, UserCircle, Paperclip, FileText } from "@phosphor-icons/react" import type { AgentMessageRecord, AgentRecord, DocumentRecord, MessageChannel } from "@shared/types" import { unwrap } from "../../lib/desktop" import { cmdOrCtrlLabel } from "../../lib/platform" import { getAgentInitials } from "../../lib/formatters" import { useAppStore } from "../../state/store" -import { ActionButton, InlineNotice } from "../ui" +import { InlineNotice } from "../ui" import { useT } from "../../i18n" +import { ComposerShell } from "./composer-shell/ComposerShell" const HUMAN_SENDER_ID = "__board__"; -function SendButton({ sending, disabled, onClick, label }: { sending: boolean; disabled: boolean; onClick: () => void; label: string }) { - return ( - - ) -} - // ─── Attachment Picker ────────────────────────────────────────────────────── interface AttachmentItem { type: string; id: string; title: string } @@ -228,8 +215,7 @@ export function MessageComposer({ agents, companyId, channel, channelTargetId, d textareaRef.current?.focus() }, [mentionState]) - const handleBodyChange = useCallback((e: React.ChangeEvent) => { - const value = e.target.value + const handleBodyChange = useCallback((value: string, e: React.ChangeEvent) => { setBody(value) const cursorPos = e.target.selectionStart @@ -277,47 +263,65 @@ export function MessageComposer({ agents, companyId, channel, channelTargetId, d finally { setSending(false) } }, [body, channel, channelTargetId, companyId, directRecipientId, onSent, replyTo, subject, attachments, threadId, waitStateId, addToast]) - const handleKeyDown = useCallback((e: React.KeyboardEvent) => { - if (mentionState.active) return // let MentionDropdown handle keys - if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) { e.preventDefault(); void handleSend() } - }, [handleSend, mentionState.active]) + const handleKeyDownExtra = useCallback(() => { + return mentionState.active // when mention active, ComposerShell should skip its own Enter handler + }, [mentionState.active]) + + const directMissingNotice = channel === "direct" && !directRecipientId + ?
+ : null + + const replyBanner = ( + + {replyTo ? ( + + + Replying to {agents.find((a) => a.id === replyTo.fromAgentId)?.name ?? "agent"}{replyTo.subject ? `: ${replyTo.subject}` : ""} + + + ) : null} + + ) + + const subjectRow = ( +
+
+ You (Board) + {!replyTo ? setSubject(e.target.value)} placeholder="Subject (optional)" aria-label="Message subject" className="min-w-0 flex-1 rounded-lg border border-border bg-card px-2.5 py-1 text-[11px] text-foreground placeholder:text-muted-foreground" /> : null} +
+ ) + + const attachmentsRow = attachments.length > 0 ? ( +
+ {attachments.map((att) => ( + + + {att.title} + + + ))} +
+ ) : null return (
- {sendError ?
: null} - {channel === "direct" && !directRecipientId ?
: null} - - {replyTo ? ( - - - Replying to {agents.find((a) => a.id === replyTo.fromAgentId)?.name ?? "agent"}{replyTo.subject ? `: ${replyTo.subject}` : ""} - - - ) : null} - -
-
- You (Board) - {!replyTo ? setSubject(e.target.value)} placeholder="Subject (optional)" aria-label="Message subject" className="min-w-0 flex-1 rounded-lg border border-border bg-card px-2.5 py-1 text-[11px] text-foreground placeholder:text-muted-foreground" /> : null} -
- - {attachments.length > 0 && ( -
- {attachments.map((att) => ( - - - {att.title} - - - ))} -
- )} - -
- -