fix(web): queue structured prompts
This commit is contained in:
+43
-8
@@ -108,6 +108,7 @@ export function createStructuredConnection(
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true"><path d="m7 5 5 5-5 5M12 10H3"/></svg>
|
||||
</button>
|
||||
<button type="button" class="btn structured-cancel" hidden>Cancel turn</button>
|
||||
<span class="structured-queue" role="status" aria-live="polite" hidden></span>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -127,7 +128,9 @@ export function createStructuredConnection(
|
||||
const commandMenu = el.querySelector(".structured-command-menu") as HTMLDivElement;
|
||||
const input = el.querySelector(".structured-input") as HTMLTextAreaElement;
|
||||
const sendBtn = el.querySelector(".structured-send") as HTMLButtonElement;
|
||||
const sendLabel = sendBtn.querySelector("span") as HTMLSpanElement;
|
||||
const cancelBtn = el.querySelector(".structured-cancel") as HTMLButtonElement;
|
||||
const queueEl = el.querySelector(".structured-queue") as HTMLSpanElement;
|
||||
|
||||
disconnectBtn.addEventListener("click", () => opts.onRequestClose?.(sessionId));
|
||||
// Mobile-only (CSS hides it on desktop, where the sidebar is always in
|
||||
@@ -165,6 +168,8 @@ export function createStructuredConnection(
|
||||
let disposed = false;
|
||||
let optimisticTurnStartedAt: number | null = null;
|
||||
let activityTimer: number | null = null;
|
||||
const promptQueue: string[] = [];
|
||||
let promptRequestPending = false;
|
||||
|
||||
// Within 64px, wheel and touch imprecision should still count as following.
|
||||
const FOLLOW_BOTTOM_THRESHOLD_PX = 64;
|
||||
@@ -529,11 +534,15 @@ export function createStructuredConnection(
|
||||
function renderComposerState(): void {
|
||||
const active = state.turn.responding || state.turn.thinking || awaitingBeginGeneration !== null;
|
||||
const projectedActive = state.turn.responding || state.turn.thinking;
|
||||
input.disabled = active;
|
||||
sendBtn.disabled = active;
|
||||
sendBtn.hidden = projectedActive;
|
||||
input.disabled = false;
|
||||
sendBtn.disabled = false;
|
||||
sendBtn.hidden = false;
|
||||
sendLabel.textContent = active ? "Queue" : "Send";
|
||||
cancelBtn.hidden = !projectedActive;
|
||||
if (active) closeCommandMenu();
|
||||
queueEl.hidden = promptQueue.length === 0;
|
||||
queueEl.textContent = promptQueue.length === 1
|
||||
? "1 message queued"
|
||||
: `${promptQueue.length} messages queued`;
|
||||
renderActivity();
|
||||
}
|
||||
|
||||
@@ -595,6 +604,7 @@ export function createStructuredConnection(
|
||||
}
|
||||
renderConfig();
|
||||
renderComposerState();
|
||||
dispatchQueuedPrompt();
|
||||
}
|
||||
|
||||
function closeConfigMenu(): void {
|
||||
@@ -794,21 +804,20 @@ export function createStructuredConnection(
|
||||
if (!commandMenu.contains(document.activeElement)) closeCommandMenu();
|
||||
}, 0);
|
||||
});
|
||||
composerForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
const text = input.value;
|
||||
if (!text.trim() || input.disabled) return;
|
||||
function dispatchPrompt(text: string): void {
|
||||
const generation = ++submissionGeneration;
|
||||
const submissionSocketGeneration = socketGeneration;
|
||||
awaitingBeginGeneration = generation;
|
||||
optimisticTurnStartedAt = Date.now();
|
||||
acceptedSubmission = null;
|
||||
promptRequestPending = true;
|
||||
input.value = "";
|
||||
autoResize();
|
||||
clearError();
|
||||
renderComposerState();
|
||||
sendStructuredPrompt(sessionId, text).then(
|
||||
() => {
|
||||
promptRequestPending = false;
|
||||
if (
|
||||
disposed
|
||||
|| generation !== submissionGeneration
|
||||
@@ -819,8 +828,11 @@ export function createStructuredConnection(
|
||||
// A replacement socket may have captured idle before the POST claimed
|
||||
// the prompt. Reconnect again so its snapshot cannot predate the 204.
|
||||
if (acceptanceSocketGeneration > submissionSocketGeneration) reconnectForReconciliation();
|
||||
renderComposerState();
|
||||
dispatchQueuedPrompt();
|
||||
},
|
||||
(e: unknown) => {
|
||||
promptRequestPending = false;
|
||||
if (disposed || generation !== submissionGeneration) return;
|
||||
if (awaitingBeginGeneration === generation) awaitingBeginGeneration = null;
|
||||
if (input.value === "") {
|
||||
@@ -831,6 +843,29 @@ export function createStructuredConnection(
|
||||
renderComposerState();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function dispatchQueuedPrompt(): void {
|
||||
const active = state.turn.responding || state.turn.thinking || awaitingBeginGeneration !== null;
|
||||
if (disposed || active || promptRequestPending || promptQueue.length === 0) return;
|
||||
dispatchPrompt(promptQueue.shift()!);
|
||||
}
|
||||
|
||||
composerForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
const text = input.value;
|
||||
if (!text.trim()) return;
|
||||
const active = state.turn.responding || state.turn.thinking || awaitingBeginGeneration !== null;
|
||||
if (active || promptQueue.length > 0) {
|
||||
promptQueue.push(text);
|
||||
input.value = "";
|
||||
autoResize();
|
||||
clearError();
|
||||
renderComposerState();
|
||||
dispatchQueuedPrompt();
|
||||
return;
|
||||
}
|
||||
dispatchPrompt(text);
|
||||
});
|
||||
cancelBtn.addEventListener("click", () => {
|
||||
clearError();
|
||||
|
||||
Reference in New Issue
Block a user