feat: queue messages
This commit is contained in:
1 parent
e62649f940
commit
62750b8980
5 files changed
+225
-4
No files matched your search
@@ -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);
|
||||
|
||||
@@ -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)}
|
||||
/>
|
||||
|
||||
<!-- Queued messages display -->
|
||||
{#if messageQueue.length > 0}
|
||||
<div
|
||||
class="mb-1 mx-2 py-0.5 px-1.5 rounded-2xl bg-gray-900/60 border border-gray-800/50 overflow-hidden"
|
||||
>
|
||||
{#each messageQueue as queuedMessage (queuedMessage.id)}
|
||||
<QueuedMessageItem
|
||||
id={queuedMessage.id}
|
||||
content={queuedMessage.prompt}
|
||||
onSendNow={onQueueSendNow}
|
||||
onEdit={onQueueEdit}
|
||||
onDelete={onQueueDelete}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div
|
||||
id="message-input-container"
|
||||
class="flex-1 flex flex-col relative w-full shadow-lg rounded-3xl border {$temporaryChatEnabled
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
<script lang="ts">
|
||||
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;
|
||||
</script>
|
||||
|
||||
<div class="flex items-center gap-2 px-2 py-1.5">
|
||||
<!-- Arrow forward icon -->
|
||||
<div class="shrink-0 text-gray-500">
|
||||
<ArrowForward className="size-3.5" />
|
||||
</div>
|
||||
|
||||
<!-- Message content -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-sm text-gray-300 truncate">{content}</p>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="flex items-center gap-1 shrink-0">
|
||||
<!-- Send immediately -->
|
||||
<Tooltip content={$i18n.t('Send now')}>
|
||||
<button
|
||||
type="button"
|
||||
class="p-1 text-gray-500 hover:text-gray-300 transition-colors"
|
||||
on:click={() => onSendNow(id)}
|
||||
aria-label={$i18n.t('Send now')}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="size-3.5"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 10.5 12 3m0 0 7.5 7.5M12 3v18" />
|
||||
</svg>
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
||||
<!-- Edit -->
|
||||
<Tooltip content={$i18n.t('Edit')}>
|
||||
<button
|
||||
type="button"
|
||||
class="p-1 text-gray-500 hover:text-gray-300 transition-colors"
|
||||
on:click={() => onEdit(id)}
|
||||
aria-label={$i18n.t('Edit')}
|
||||
>
|
||||
<EditPencil className="size-3.5" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
||||
<!-- Delete -->
|
||||
<Tooltip content={$i18n.t('Delete')}>
|
||||
<button
|
||||
type="button"
|
||||
class="p-1 text-gray-500 hover:text-gray-300 transition-colors"
|
||||
on:click={() => onDelete(id)}
|
||||
aria-label={$i18n.t('Delete')}
|
||||
>
|
||||
<GarbageBin className="size-3.5" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
Reference in new issue
Block a user