feat: display queued messages and allow submit while session is processing
This commit is contained in:
parent
1fcd5a7880
commit
4be8d5d1d7
1 changed files with 40 additions and 5 deletions
|
|
@ -11,7 +11,7 @@ import type {
|
||||||
SetStateAction,
|
SetStateAction,
|
||||||
TouchEvent,
|
TouchEvent,
|
||||||
} from 'react';
|
} 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 type { PendingPermissionRequest, PermissionMode, Provider } from '../../types/types';
|
||||||
import CommandMenu from './CommandMenu';
|
import CommandMenu from './CommandMenu';
|
||||||
import ClaudeStatus from './ClaudeStatus';
|
import ClaudeStatus from './ClaudeStatus';
|
||||||
|
|
@ -45,6 +45,11 @@ interface SlashCommand {
|
||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface QueuedMessage {
|
||||||
|
id: string;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface ChatComposerProps {
|
interface ChatComposerProps {
|
||||||
pendingPermissionRequests: PendingPermissionRequest[];
|
pendingPermissionRequests: PendingPermissionRequest[];
|
||||||
handlePermissionDecision: (
|
handlePermissionDecision: (
|
||||||
|
|
@ -101,6 +106,8 @@ interface ChatComposerProps {
|
||||||
placeholder: string;
|
placeholder: string;
|
||||||
isTextareaExpanded: boolean;
|
isTextareaExpanded: boolean;
|
||||||
sendByCtrlEnter?: boolean;
|
sendByCtrlEnter?: boolean;
|
||||||
|
messageQueue?: QueuedMessage[];
|
||||||
|
onRemoveQueued?: (id: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ChatComposer({
|
export default function ChatComposer({
|
||||||
|
|
@ -156,6 +163,8 @@ export default function ChatComposer({
|
||||||
placeholder,
|
placeholder,
|
||||||
isTextareaExpanded,
|
isTextareaExpanded,
|
||||||
sendByCtrlEnter,
|
sendByCtrlEnter,
|
||||||
|
messageQueue = [],
|
||||||
|
onRemoveQueued,
|
||||||
}: ChatComposerProps) {
|
}: ChatComposerProps) {
|
||||||
const { t } = useTranslation('chat');
|
const { t } = useTranslation('chat');
|
||||||
const textareaRect = textareaRef.current?.getBoundingClientRect();
|
const textareaRect = textareaRef.current?.getBoundingClientRect();
|
||||||
|
|
@ -165,12 +174,10 @@ export default function ChatComposer({
|
||||||
bottom: textareaRect ? window.innerHeight - textareaRect.top + 8 : 90,
|
bottom: textareaRect ? window.innerHeight - textareaRect.top + 8 : 90,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Detect if the AskUserQuestion interactive panel is active
|
|
||||||
const hasQuestionPanel = pendingPermissionRequests.some(
|
const hasQuestionPanel = pendingPermissionRequests.some(
|
||||||
(r) => r.toolName === 'AskUserQuestion'
|
(r) => r.toolName === 'AskUserQuestion'
|
||||||
);
|
);
|
||||||
|
|
||||||
// Hide the thinking/status bar while any permission request is pending
|
|
||||||
const hasPendingPermissions = pendingPermissionRequests.length > 0;
|
const hasPendingPermissions = pendingPermissionRequests.length > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -194,6 +201,32 @@ export default function ChatComposer({
|
||||||
</div>
|
</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">
|
{!hasQuestionPanel && <div className="relative mx-auto max-w-4xl">
|
||||||
{isUserScrolledUp && hasMessages && (
|
{isUserScrolledUp && hasMessages && (
|
||||||
<div className="absolute -top-10 left-0 right-0 z-10 flex justify-center">
|
<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'
|
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>
|
</div>
|
||||||
<PromptInputSubmit
|
<PromptInputSubmit
|
||||||
disabled={!input.trim() || isLoading}
|
disabled={!input.trim()}
|
||||||
className="h-10 w-10 sm:h-10 sm:w-10"
|
className="h-10 w-10 sm:h-10 sm:w-10"
|
||||||
onMouseDown={(event) => {
|
onMouseDown={(event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue