mirror of
https://github.com/unslothai/unsloth.git
synced 2026-08-25 16:52:26 +00:00
queue long pastes while a response is running
`canQueueCurrentPrompt` rejects any composer holding an attachment, so a paste that used to queue as inline text started failing with "Only text prompts can be queued". Pasting a log while a response streams is exactly when queueing earns its keep, and the user was left waiting for the run or pouring the paste back into the composer by hand. The queue carries strings, and a pasted attachment is a string the composer parked in a chip, so `queuePastedTextPrompt` reads those files and folds them into the queued prompt after whatever was typed. Composers holding a real attachment still take the existing refusal. `canQueueCurrentPrompt` keeps its exact conditions; the shared ones move into `composerAcceptsQueueing` so the pasted-text path cannot drift from them. The started callback clears only when the text and the attachment ids are still the ones that were queued, matching the text-only guard it sits beside. Manual check: start a response, paste 3000 characters, press enter. The chip clears and the prompt queues with the pasted text; it sends when the run ends. Repeat with an image attached and the refusal toast still appears.
This commit is contained in:
parent
d8e8162e1d
commit
16d1b72271
1 changed files with 74 additions and 3 deletions
|
|
@ -36,6 +36,7 @@ import { TerminalToolUI } from "@/components/assistant-ui/tool-ui-terminal";
|
|||
import { WebSearchToolUI } from "@/components/assistant-ui/tool-ui-web-search";
|
||||
import { ChatDictationBar } from "@/components/assistant-ui/chat-dictation-bar";
|
||||
import {
|
||||
isPastedTextFile,
|
||||
pasteClipboardFiles,
|
||||
pasteLongTextAsFile,
|
||||
isStudioDictationAvailable,
|
||||
|
|
@ -2165,6 +2166,13 @@ const Composer: FC<{
|
|||
(attachment) => attachment.status.type === "running",
|
||||
),
|
||||
);
|
||||
const attachmentsAreAllPastedText = useAuiState(
|
||||
({ composer }) =>
|
||||
composer.attachments.length > 0 &&
|
||||
composer.attachments.every((attachment) =>
|
||||
isPastedTextFile((attachment as { file?: File }).file),
|
||||
),
|
||||
);
|
||||
const hasPendingAudio = useChatRuntimeStore((s) =>
|
||||
Boolean(s.pendingAudioName),
|
||||
);
|
||||
|
|
@ -2510,9 +2518,7 @@ const Composer: FC<{
|
|||
);
|
||||
const hasSendableContent =
|
||||
composerText.trim().length > 0 || hasAttachments || hasPendingAudio;
|
||||
const canQueueCurrentPrompt =
|
||||
composerText.trim().length > 0 &&
|
||||
!hasAttachments &&
|
||||
const composerAcceptsQueueing =
|
||||
!hasPendingAudio &&
|
||||
!isComposing &&
|
||||
!hasPendingAttachments &&
|
||||
|
|
@ -2520,6 +2526,12 @@ const Composer: FC<{
|
|||
!hasMaterializingAudioAttachments &&
|
||||
!disabled &&
|
||||
!overlay;
|
||||
const canQueueCurrentPrompt =
|
||||
composerText.trim().length > 0 && !hasAttachments && composerAcceptsQueueing;
|
||||
// A long paste is text the composer parked in a chip, so it queues like the
|
||||
// same text did before it attached, rather than being refused as a file.
|
||||
const canQueuePastedTextPrompt =
|
||||
attachmentsAreAllPastedText && composerAcceptsQueueing;
|
||||
|
||||
// Per-thread draft autosave: restore on mount, then mirror composer text
|
||||
// into localStorage (debounced) so a half-typed message survives a
|
||||
|
|
@ -2983,6 +2995,57 @@ const Composer: FC<{
|
|||
[createPromptQueueTarget, referenceThreadId],
|
||||
);
|
||||
|
||||
// The queue carries text, and a long paste is text the composer parked in a
|
||||
// chip, so fold it back in rather than refusing to queue it as a file.
|
||||
const queuePastedTextPrompt = useCallback(
|
||||
(waitForCurrentRun: boolean): boolean => {
|
||||
const composer = aui.composer();
|
||||
const attachments = composer.getState().attachments;
|
||||
const files: File[] = [];
|
||||
for (const attachment of attachments) {
|
||||
const file = (attachment as { file?: File }).file;
|
||||
if (file === undefined || !isPastedTextFile(file)) return false;
|
||||
files.push(file);
|
||||
}
|
||||
if (files.length === 0) return false;
|
||||
|
||||
const attachmentIds = attachments.map((attachment) => attachment.id);
|
||||
const textAtQueue = composer.getState().text.trim();
|
||||
void Promise.all(files.map((file) => file.text()))
|
||||
.then((texts) => {
|
||||
const queuedPrompt = [textAtQueue, ...texts]
|
||||
.filter((part) => part.trim().length > 0)
|
||||
.join("\n\n");
|
||||
if (queuedPrompt.length === 0) return;
|
||||
startHydratedPromptQueue([queuedPrompt], waitForCurrentRun, () => {
|
||||
const state = composer.getState();
|
||||
// Only clear the composer this prompt was queued from.
|
||||
if (
|
||||
state.text.trim() !== textAtQueue ||
|
||||
state.attachments.length !== attachmentIds.length ||
|
||||
!state.attachments.every(
|
||||
(attachment, index) => attachment.id === attachmentIds[index],
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
void composer.clearAttachments();
|
||||
flushResourcesSync(() => {
|
||||
composer.setText("");
|
||||
});
|
||||
clearStoredDraft();
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Could not queue the pasted text.", {
|
||||
description: "Show it in the text field, then send it again.",
|
||||
});
|
||||
});
|
||||
return true;
|
||||
},
|
||||
[aui, clearStoredDraft, startHydratedPromptQueue],
|
||||
);
|
||||
|
||||
const dismissWaitToast = useCallback(() => {
|
||||
if (waitToastRef.current !== null) {
|
||||
toast.dismiss(waitToastRef.current);
|
||||
|
|
@ -3326,6 +3389,12 @@ const Composer: FC<{
|
|||
return;
|
||||
}
|
||||
if (!canQueueCurrentPrompt) {
|
||||
if (
|
||||
canQueuePastedTextPrompt &&
|
||||
queuePastedTextPrompt(liveThreadIsRunning || livePreStreamRunActive)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (overlay || hasAttachments || hasPendingAudio) {
|
||||
toast.error(
|
||||
liveThreadIsRunning
|
||||
|
|
@ -3417,6 +3486,8 @@ const Composer: FC<{
|
|||
[
|
||||
aui,
|
||||
canQueueCurrentPrompt,
|
||||
canQueuePastedTextPrompt,
|
||||
queuePastedTextPrompt,
|
||||
clearStoredDraft,
|
||||
closeOverlay,
|
||||
composerText,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue