From 9ed2e39b0803bbc4108cdae720b5a96463ba2891 Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Wed, 27 May 2026 17:13:45 +0300 Subject: [PATCH] feat: refresh queued message controls Queued messages now match the reverted message dock style Added per-message edit and send actions Sending one queued message no longer sends the full queue --- packages/ui/src/components/chat/ChatInput.tsx | 57 +++++++++----- .../components/chat/QueuedMessageChips.tsx | 78 ++++++++++++------- packages/ui/src/lib/i18n/messages/en.ts | 3 + packages/ui/src/lib/i18n/messages/es.ts | 3 + packages/ui/src/lib/i18n/messages/ko.ts | 3 + packages/ui/src/lib/i18n/messages/pl.ts | 3 + packages/ui/src/lib/i18n/messages/pt-BR.ts | 3 + packages/ui/src/lib/i18n/messages/uk.ts | 3 + packages/ui/src/lib/i18n/messages/zh-CN.ts | 3 + packages/ui/src/lib/i18n/messages/zh-TW.ts | 3 + 10 files changed, 114 insertions(+), 45 deletions(-) diff --git a/packages/ui/src/components/chat/ChatInput.tsx b/packages/ui/src/components/chat/ChatInput.tsx index 691edb8b..549cbf07 100644 --- a/packages/ui/src/components/chat/ChatInput.tsx +++ b/packages/ui/src/components/chat/ChatInput.tsx @@ -1332,6 +1332,7 @@ const ChatInputComponent: React.FC = ({ onOpenSettings, scrollTo ); const addToQueue = useMessageQueueStore((state) => state.addToQueue); const clearQueue = useMessageQueueStore((state) => state.clearQueue); + const removeFromQueue = useMessageQueueStore((state) => state.removeFromQueue); // Inline comment drafts const draftCount = useInlineCommentDraftStore( @@ -1579,6 +1580,7 @@ const ChatInputComponent: React.FC = ({ onOpenSettings, scrollTo // Keep a ref to handleSubmit so callbacks don't depend on it. type SubmitOptions = { queuedOnly?: boolean; + queuedMessageId?: string; }; const handleSubmitRef = React.useRef<(options?: SubmitOptions) => Promise>(async () => {}); @@ -1627,6 +1629,10 @@ const ChatInputComponent: React.FC = ({ onOpenSettings, scrollTo }, 0); }, []); + const handleQueuedMessageSend = React.useCallback((messageId: string) => { + void handleSubmitRef.current({ queuedOnly: true, queuedMessageId: messageId }); + }, []); + const handleOpenAgentPanel = React.useCallback(() => { setMobileControlsPanel('agent'); }, []); @@ -1645,15 +1651,25 @@ const ChatInputComponent: React.FC = ({ onOpenSettings, scrollTo const handleSubmit = async (options?: SubmitOptions) => { const queuedOnly = options?.queuedOnly ?? false; + const queuedMessageId = options?.queuedMessageId; const inputSnapshot = getCurrentInputSnapshot(); + const queuedMessagesToSend = queuedMessageId + ? queuedMessages.filter((message) => message.id === queuedMessageId) + : queuedMessages; if (queuedOnly) { - if (!hasQueuedMessages || !currentSessionId) return; + if (queuedMessagesToSend.length === 0 || !currentSessionId) return; } else if ((!inputSnapshot.hasContent && !hasQueuedMessages) || (!currentSessionId && !newSessionDraftOpen)) { return; } - if (!currentProviderId || !currentModelId) { + const capturedSendConfig = queuedOnly ? queuedMessagesToSend[0]?.sendConfig : undefined; + const providerIdToSend = capturedSendConfig?.providerID ?? currentProviderId; + const modelIdToSend = capturedSendConfig?.modelID ?? currentModelId; + const agentNameToSend = capturedSendConfig?.agent ?? currentAgentName; + const variantToSend = capturedSendConfig?.variant ?? currentVariant; + + if (!providerIdToSend || !modelIdToSend) { console.warn('Cannot send message: provider or model not selected'); return; } @@ -1675,8 +1691,8 @@ const ChatInputComponent: React.FC = ({ onOpenSettings, scrollTo const syntheticParts = consumePendingSyntheticParts(); // Process queued messages first - for (let i = 0; i < queuedMessages.length; i++) { - const queuedMsg = queuedMessages[i]; + for (let i = 0; i < queuedMessagesToSend.length; i++) { + const queuedMsg = queuedMessagesToSend[i]; const { sanitizedText, mention } = parseAgentMentions(queuedMsg.content, agents); const { sanitizedText: queuedText, attachments: mentionAttachments } = extractInlineFileMentions(sanitizedText); addMentionedSkills(queuedText); @@ -1715,7 +1731,7 @@ const ChatInputComponent: React.FC = ({ onOpenSettings, scrollTo agentMentionName = mention.name; } - if (queuedMessages.length === 0) { + if (queuedMessagesToSend.length === 0) { // No queue - current input is primary primaryText = messageText; primaryAttachments = [...attachmentsToSend, ...mentionAttachments]; @@ -1735,7 +1751,7 @@ const ChatInputComponent: React.FC = ({ onOpenSettings, scrollTo } if (drafts.length > 0) { - if (queuedMessages.length === 0) { + if (queuedMessagesToSend.length === 0) { primaryText = appendInlineComments(primaryText, drafts); } else if (additionalParts.length > 0) { const lastPart = additionalParts[additionalParts.length - 1]; @@ -1786,7 +1802,9 @@ const ChatInputComponent: React.FC = ({ onOpenSettings, scrollTo if (!primaryText && primaryAttachments.length === 0 && additionalParts.length === 0) return; // Clear queue and input - if (currentSessionId && hasQueuedMessages) { + if (currentSessionId && queuedMessageId) { + removeFromQueue(currentSessionId, queuedMessageId); + } else if (currentSessionId && hasQueuedMessages) { clearQueue(currentSessionId); } if (!queuedOnly) { @@ -1862,13 +1880,13 @@ const ChatInputComponent: React.FC = ({ onOpenSettings, scrollTo const instructionsText = await renderMagicPrompt('session.summary.instructions', { topic_block: topicBlock }); await sendMessage( visibleText, - currentProviderId, - currentModelId, - currentAgentName, + providerIdToSend, + modelIdToSend, + agentNameToSend, [], agentMentionName, [{ text: instructionsText, synthetic: true }], - currentVariant, + variantToSend, inputMode, ); scrollToBottom?.(); @@ -1884,13 +1902,13 @@ const ChatInputComponent: React.FC = ({ onOpenSettings, scrollTo const instructionsText = await renderMagicPrompt('session.review.instructions'); await sendMessage( visibleText, - currentProviderId, - currentModelId, - currentAgentName, + providerIdToSend, + modelIdToSend, + agentNameToSend, [], agentMentionName, [{ text: instructionsText, synthetic: true }], - currentVariant, + variantToSend, inputMode, ); scrollToBottom?.(); @@ -1933,13 +1951,13 @@ const ChatInputComponent: React.FC = ({ onOpenSettings, scrollTo const sendPromise = sendMessage( primaryText, - currentProviderId, - currentModelId, - currentAgentName, + providerIdToSend, + modelIdToSend, + agentNameToSend, primaryAttachments, agentMentionName, additionalParts.length > 0 ? additionalParts : undefined, - currentVariant, + variantToSend, inputMode ); @@ -3772,6 +3790,7 @@ const ChatInputComponent: React.FC = ({ onOpenSettings, scrollTo {hasDrafts && (
diff --git a/packages/ui/src/components/chat/QueuedMessageChips.tsx b/packages/ui/src/components/chat/QueuedMessageChips.tsx index bf85b24b..95a4f3f8 100644 --- a/packages/ui/src/components/chat/QueuedMessageChips.tsx +++ b/packages/ui/src/components/chat/QueuedMessageChips.tsx @@ -4,14 +4,16 @@ import { useSessionUIStore } from '@/sync/session-ui-store'; import { useInputStore } from '@/sync/input-store'; import { useI18n } from '@/lib/i18n'; import { Icon } from "@/components/icon/Icon"; +import { Button } from '@/components/ui/button'; interface QueuedMessageChipProps { message: QueuedMessage; sessionId: string; onEdit: (message: QueuedMessage) => void; + onSend: (message: QueuedMessage) => void; } -const QueuedMessageChip = memo(({ message, sessionId, onEdit }: QueuedMessageChipProps) => { +const QueuedMessageChip = memo(({ message, sessionId, onEdit, onSend }: QueuedMessageChipProps) => { const { t } = useI18n(); const removeFromQueue = useMessageQueueStore((state) => state.removeFromQueue); @@ -29,24 +31,31 @@ const QueuedMessageChip = memo(({ message, sessionId, onEdit }: QueuedMessageChi const attachmentCount = message.attachments?.length ?? 0; return ( -
- +