Previously a message composed while the AI agent was streaming a reply was silently dropped (the composer early-returned on isStreaming). Now such messages are queued FIFO and sent automatically once the current turn finishes cleanly. - chat-input: submit() enqueues while streaming (via new onQueue prop) and sends otherwise; during streaming show a queue Send button (when text is present) alongside the Stop button; the textarea stays usable. - chat-thread: per-conversation queue in local state (mirrored in a ref); flush the next message in onFinish ONLY on a clean finish - ai@6 useChat fires onFinish from a finally on Stop/disconnect/error too, where the queue must be preserved. Pending messages render as removable chips above the composer. Queue is cleared on chat switch (parent remount) and survives in-place new-chat id adoption. - queue-helpers: pure FIFO helpers (enqueue/dequeue/removeQueuedById) + tests. - i18n: add en-US/ru-RU keys (Queue message, Remove queued message, Send when the agent finishes). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
35 lines
1013 B
TypeScript
35 lines
1013 B
TypeScript
// Pure FIFO helpers for the AI-chat "send while the agent is busy" queue.
|
|
// Kept side-effect free so they can be unit-tested without React.
|
|
|
|
export interface QueuedMessage {
|
|
id: string;
|
|
text: string;
|
|
}
|
|
|
|
/** Append a message to the end of the queue (returns a new array). */
|
|
export function enqueueMessage(
|
|
queue: QueuedMessage[],
|
|
message: QueuedMessage,
|
|
): QueuedMessage[] {
|
|
return [...queue, message];
|
|
}
|
|
|
|
/** Split the queue into its first item (`head`) and the remainder (`rest`).
|
|
* `head` is null when the queue is empty. Does not mutate the input. */
|
|
export function dequeue(queue: QueuedMessage[]): {
|
|
head: QueuedMessage | null;
|
|
rest: QueuedMessage[];
|
|
} {
|
|
if (queue.length === 0) return { head: null, rest: [] };
|
|
const [head, ...rest] = queue;
|
|
return { head, rest };
|
|
}
|
|
|
|
/** Remove the queued message with the given id (returns a new array). */
|
|
export function removeQueuedById(
|
|
queue: QueuedMessage[],
|
|
id: string,
|
|
): QueuedMessage[] {
|
|
return queue.filter((m) => m.id !== id);
|
|
}
|