diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte index 2e9ca148b..806600235 100644 --- a/src/lib/components/chat/Chat.svelte +++ b/src/lib/components/chat/Chat.svelte @@ -160,6 +160,9 @@ let files = []; let params = {}; + // Message queue for storing messages while generating + let messageQueue: { id: string; prompt: string; files: any[] }[] = []; + $: if (chatIdProp) { navigateHandler(); } @@ -167,10 +170,16 @@ const navigateHandler = async () => { loading = true; + // Save current queue to sessionStorage before navigating away + if (messageQueue.length > 0 && $chatId) { + sessionStorage.setItem(`chat-queue-${$chatId}`, JSON.stringify(messageQueue)); + } + prompt = ''; messageInput?.setText(''); files = []; + messageQueue = []; selectedToolIds = []; selectedFilterIds = []; webSearchEnabled = false; @@ -187,6 +196,30 @@ await tick(); + // Restore queue from sessionStorage + const storedQueueData = sessionStorage.getItem(`chat-queue-${chatIdProp}`); + if (storedQueueData) { + try { + const restoredQueue = JSON.parse(storedQueueData); + + if (restoredQueue.length > 0) { + sessionStorage.removeItem(`chat-queue-${chatIdProp}`); + // Check if there are pending tasks (still generating) + const hasPendingTask = taskIds !== null && taskIds.length > 0; + if (!hasPendingTask) { + // No pending tasks - process the queue + files = restoredQueue.flatMap((m) => m.files); + await tick(); + const combinedPrompt = restoredQueue.map((m) => m.prompt).join('\n\n'); + await submitPrompt(combinedPrompt); + } else { + // Has pending tasks - show as queued (chatCompletedHandler will process) + messageQueue = restoredQueue; + } + } + } catch (e) {} + } + if (storageChatInput) { try { const input = JSON.parse(storageChatInput); @@ -1209,6 +1242,18 @@ } taskIds = null; + + // Process message queue - combine all queued messages and submit at once + if (messageQueue.length > 0) { + const combinedPrompt = messageQueue.map((m) => m.prompt).join('\n\n'); + const combinedFiles = messageQueue.flatMap((m) => m.files); + messageQueue = []; + + // Set the files and submit + files = combinedFiles; + await tick(); + await submitPrompt(combinedPrompt); + } }; const chatActionHandler = async (_chatId, actionId, modelId, responseMessageId, event = null) => { @@ -1606,12 +1651,27 @@ return; } + // Check if there are pending tasks (more reliable than lastMessage.done) + if (taskIds !== null && taskIds.length > 0) { + // Tasks pending - queue the message instead of blocking + const _files = JSON.parse(JSON.stringify(files)); + messageQueue = [ + ...messageQueue, + { + id: uuidv4(), + prompt: userPrompt, + files: _files + } + ]; + // Clear input + messageInput?.setText(''); + prompt = ''; + files = []; + return; + } + if (history?.currentId) { const lastMessage = history.messages[history.currentId]; - if (lastMessage.done != true) { - // Response not done - return; - } if (lastMessage.error && !lastMessage.content) { // Error in response @@ -2568,6 +2628,34 @@ {stopResponse} {createMessagePair} {onUpload} + {messageQueue} + onQueueSendNow={async (id) => { + const item = messageQueue.find((m) => m.id === id); + if (item) { + // Remove from queue + messageQueue = messageQueue.filter((m) => m.id !== id); + // Stop current generation first + await stopResponse(); + await tick(); + // Set files and submit + files = item.files; + await tick(); + await submitPrompt(item.prompt); + } + }} + onQueueEdit={(id) => { + const item = messageQueue.find((m) => m.id === id); + if (item) { + // Remove from queue + messageQueue = messageQueue.filter((m) => m.id !== id); + // Set files and restore prompt to input + files = item.files; + messageInput?.setText(item.prompt); + } + }} + onQueueDelete={(id) => { + messageQueue = messageQueue.filter((m) => m.id !== id); + }} onChange={(data) => { if (!$temporaryChatEnabled) { saveDraft(data, $chatId); diff --git a/src/lib/components/chat/MessageInput.svelte b/src/lib/components/chat/MessageInput.svelte index 103da88f9..5914e7afc 100644 --- a/src/lib/components/chat/MessageInput.svelte +++ b/src/lib/components/chat/MessageInput.svelte @@ -90,6 +90,7 @@ import { goto } from '$app/navigation'; import InputModal from '../common/InputModal.svelte'; import Expand from '../icons/Expand.svelte'; + import QueuedMessageItem from './MessageInput/QueuedMessageItem.svelte'; const i18n = getContext('i18n'); @@ -121,6 +122,11 @@ export let webSearchEnabled = false; export let codeInterpreterEnabled = false; + export let messageQueue: { id: string; prompt: string; files: any[] }[] = []; + export let onQueueSendNow: (id: string) => void = () => {}; + export let onQueueEdit: (id: string) => void = () => {}; + export let onQueueDelete: (id: string) => void = () => {}; + let inputContent = null; let showInputVariablesModal = false; @@ -1122,6 +1128,23 @@ on:click={() => createMessagePair(prompt)} /> + + {#if messageQueue.length > 0} +
+ {#each messageQueue as queuedMessage (queuedMessage.id)} + + {/each} +
+ {/if} +
+ import { getContext } from 'svelte'; + import Tooltip from '$lib/components/common/Tooltip.svelte'; + import GarbageBin from '$lib/components/icons/GarbageBin.svelte'; + import EditPencil from '$lib/components/icons/EditPencil.svelte'; + import ArrowForward from '$lib/components/icons/ArrowForward.svelte'; + + const i18n = getContext('i18n'); + + export let id: string; + export let content: string; + export let onSendNow: (id: string) => void; + export let onEdit: (id: string) => void; + export let onDelete: (id: string) => void; + + +
+ +
+ +
+ + +
+

{content}

+
+ + +
+ + + + + + + + + + + + + + +
+
diff --git a/src/lib/components/icons/ArrowForward.svelte b/src/lib/components/icons/ArrowForward.svelte new file mode 100644 index 000000000..3a64245c9 --- /dev/null +++ b/src/lib/components/icons/ArrowForward.svelte @@ -0,0 +1,16 @@ + + + + + + diff --git a/src/lib/components/icons/EditPencil.svelte b/src/lib/components/icons/EditPencil.svelte new file mode 100644 index 000000000..de951b5ae --- /dev/null +++ b/src/lib/components/icons/EditPencil.svelte @@ -0,0 +1,19 @@ + + + + +