feat: display queued messages and allow submit while session is processing

This commit is contained in:
Zac Gaetano 2026-05-30 10:09:29 -04:00
parent 1fcd5a7880
commit 4be8d5d1d7

View file

@ -11,7 +11,7 @@ import type {
SetStateAction,
TouchEvent,
} from 'react';
import { ImageIcon, MessageSquareIcon, XIcon, ArrowDownIcon } from 'lucide-react';
import { ImageIcon, MessageSquareIcon, XIcon, ArrowDownIcon, ClockIcon } from 'lucide-react';
import type { PendingPermissionRequest, PermissionMode, Provider } from '../../types/types';
import CommandMenu from './CommandMenu';
import ClaudeStatus from './ClaudeStatus';
@ -45,6 +45,11 @@ interface SlashCommand {
[key: string]: unknown;
}
interface QueuedMessage {
id: string;
text: string;
}
interface ChatComposerProps {
pendingPermissionRequests: PendingPermissionRequest[];
handlePermissionDecision: (
@ -101,6 +106,8 @@ interface ChatComposerProps {
placeholder: string;
isTextareaExpanded: boolean;
sendByCtrlEnter?: boolean;
messageQueue?: QueuedMessage[];
onRemoveQueued?: (id: string) => void;
}
export default function ChatComposer({
@ -156,6 +163,8 @@ export default function ChatComposer({
placeholder,
isTextareaExpanded,
sendByCtrlEnter,
messageQueue = [],
onRemoveQueued,
}: ChatComposerProps) {
const { t } = useTranslation('chat');
const textareaRect = textareaRef.current?.getBoundingClientRect();
@ -165,12 +174,10 @@ export default function ChatComposer({
bottom: textareaRect ? window.innerHeight - textareaRect.top + 8 : 90,
};
// Detect if the AskUserQuestion interactive panel is active
const hasQuestionPanel = pendingPermissionRequests.some(
(r) => r.toolName === 'AskUserQuestion'
);
// Hide the thinking/status bar while any permission request is pending
const hasPendingPermissions = pendingPermissionRequests.length > 0;
return (
@ -194,6 +201,32 @@ export default function ChatComposer({
</div>
)}
{/* Queued messages — shown when user sends while session is processing */}
{messageQueue.length > 0 && (
<div className="mx-auto mb-2 max-w-4xl space-y-1.5">
{messageQueue.map((q) => (
<div
key={q.id}
className="flex items-center gap-2 rounded-lg border border-border/50 bg-muted/40 px-3 py-1.5 text-sm text-muted-foreground"
>
<ClockIcon className="h-3.5 w-3.5 flex-shrink-0 text-amber-500" />
<span className="flex-1 truncate">{q.text}</span>
<span className="text-[10px] uppercase tracking-wide text-muted-foreground/60">Queued</span>
{onRemoveQueued && (
<button
type="button"
onClick={() => onRemoveQueued(q.id)}
className="rounded p-0.5 hover:bg-accent"
aria-label="Remove queued message"
>
<XIcon className="h-3 w-3" />
</button>
)}
</div>
))}
</div>
)}
{!hasQuestionPanel && <div className="relative mx-auto max-w-4xl">
{isUserScrolledUp && hasMessages && (
<div className="absolute -top-10 left-0 right-0 z-10 flex justify-center">
@ -396,10 +429,12 @@ export default function ChatComposer({
input.trim() ? 'opacity-0' : 'opacity-100'
}`}
>
{sendByCtrlEnter ? t('input.hintText.ctrlEnter') : t('input.hintText.enter')}
{isLoading
? t('input.hintText.queue', { defaultValue: 'Enter to queue' })
: sendByCtrlEnter ? t('input.hintText.ctrlEnter') : t('input.hintText.enter')}
</div>
<PromptInputSubmit
disabled={!input.trim() || isLoading}
disabled={!input.trim()}
className="h-10 w-10 sm:h-10 sm:w-10"
onMouseDown={(event) => {
event.preventDefault();