Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/web/reactCompiledFiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,5 @@ export const REACT_COMPILED_FILES = [
"apps/web/src/hooks/useLocalStorage.ts",
"apps/web/src/hooks/useTheme.ts",
"apps/web/src/hooks/useSettings.ts",
"apps/web/src/components/chat/QueuedRunsControl.tsx",
] as const;
110 changes: 50 additions & 60 deletions apps/web/src/components/chat/QueuedRunsControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
ListOrderedIcon,
PencilIcon,
} from "lucide-react";
import { useId, useImperativeHandle, useMemo, useRef, useState, type Ref } from "react";
import { useId, useImperativeHandle, useRef, useState, type Ref } from "react";

import { useAssetUrls } from "../../assets/assetUrls";
import { threadEnvironment } from "../../state/threads";
Expand Down Expand Up @@ -47,6 +47,31 @@ export interface QueuedRunsControlHandle {
editLatest: (repeat: boolean) => boolean;
}

type ThreadQueueProjection = Parameters<typeof deriveThreadQueueWorkflowState>[0];

// Separate hooks so these stay cached across the useAssetUrls call, which keys on
// the resources array.
function useQueueWorkflow(projection: ThreadQueueProjection | undefined) {
"use memo";
const workflow = projection ? deriveThreadQueueWorkflowState(projection) : null;
const queuedImageAttachmentIds: string[] = [];
for (const { attachments } of workflow?.queuedRuns ?? []) {
for (const attachment of attachments) {
if (attachment.type === "image") queuedImageAttachmentIds.push(attachment.id);
}
}
const queuedImageAttachmentResources = queuedImageAttachmentIds.map((attachmentId) => ({
_tag: "attachment" as const,
attachmentId,
}));
return { workflow, queuedImageAttachmentIds, queuedImageAttachmentResources };
}

function useAcknowledgedMessageIds(projection: ThreadQueueProjection | undefined) {
"use memo";
return new Set((projection?.messages ?? []).map((message) => message.id));
}

export function QueuedRunsControl({
ref,
...props
Expand Down Expand Up @@ -79,51 +104,25 @@ export function QueuedRunsControl({
// Drags must start from the grip, not from an accidental text-drag on the
// row. The grip arms its run id on pointerdown; dragstart verifies it.
const dragArmedRunIdRef = useRef<RunId | null>(null);
const workflow = useMemo(
() => (projection ? deriveThreadQueueWorkflowState(projection) : null),
[projection],
);
const { workflow, queuedImageAttachmentIds, queuedImageAttachmentResources } =
useQueueWorkflow(projection);
const queued = workflow?.queuedRuns ?? [];
const activeRun = workflow?.activeRun ?? null;
const canReorder = workflow?.canReorder === true;
const queuedImageAttachmentIds = useMemo(() => {
const ids: string[] = [];
for (const { attachments } of workflow?.queuedRuns ?? []) {
for (const attachment of attachments) {
if (attachment.type === "image") ids.push(attachment.id);
}
}
return ids;
}, [workflow]);
const queuedImageAttachmentResources = useMemo(
() =>
queuedImageAttachmentIds.map((attachmentId) => ({
_tag: "attachment" as const,
attachmentId,
})),
[queuedImageAttachmentIds],
);
const queuedImageAttachmentUrls = useAssetUrls(
props.environmentId,
queuedImageAttachmentResources,
);
const queuedImageUrlById = useMemo(
() =>
new Map(
queuedImageAttachmentIds.flatMap((attachmentId, index) => {
const url = queuedImageAttachmentUrls[index];
return url ? [[attachmentId, url] as const] : [];
}),
),
[queuedImageAttachmentIds, queuedImageAttachmentUrls],
const queuedImageUrlById = new Map(
queuedImageAttachmentIds.flatMap((attachmentId, index) => {
const url = queuedImageAttachmentUrls[index];
return url ? [[attachmentId, url] as const] : [];
}),
);
// Once the projection holds the message the optimistic copy is stale no
// matter what happened to its run — keying on still-queued runs alone would
// resurrect a phantom "pending" row after the run is cancelled or started.
const acknowledgedMessageIds = useMemo(
() => new Set((projection?.messages ?? []).map((message) => message.id)),
[projection],
);
const acknowledgedMessageIds = useAcknowledgedMessageIds(projection);
const optimisticQueued = props.optimisticMessages.filter(
(message) => message.inputIntent === "queued_turn" && !acknowledgedMessageIds.has(message.id),
);
Expand Down Expand Up @@ -164,14 +163,11 @@ export function QueuedRunsControl({

const move = async (runId: RunId, beforeRunId: RunId | null) => {
setBusyRunId(runId);
try {
await reorder({
environmentId: props.environmentId,
input: { threadId: props.threadId, runId, beforeRunId },
});
} finally {
setBusyRunId(null);
}
await reorder({
environmentId: props.environmentId,
input: { threadId: props.threadId, runId, beforeRunId },
});
setBusyRunId(null);
};

const completeDrag = (runId: RunId, insertIndex: number | null) => {
Expand All @@ -189,15 +185,12 @@ export function QueuedRunsControl({
if (activeRun === null || !workflow?.canPromoteToSteer || steerInFlightRef.current) return;
steerInFlightRef.current = true;
setBusyRunId(queuedRunId);
try {
await promote({
environmentId: props.environmentId,
input: { threadId: props.threadId, queuedRunId, targetRunId: activeRun.id },
});
} finally {
steerInFlightRef.current = false;
setBusyRunId(null);
}
await promote({
environmentId: props.environmentId,
input: { threadId: props.threadId, queuedRunId, targetRunId: activeRun.id },
});
steerInFlightRef.current = false;
setBusyRunId(null);
};

useImperativeHandle(ref, () => ({
Expand Down Expand Up @@ -229,14 +222,11 @@ export function QueuedRunsControl({

const remove = async (runId: RunId) => {
setBusyRunId(runId);
try {
await cancel({
environmentId: props.environmentId,
input: { threadId: props.threadId, runId },
});
} finally {
setBusyRunId(null);
}
await cancel({
environmentId: props.environmentId,
input: { threadId: props.threadId, runId },
});
setBusyRunId(null);
};

return (
Expand Down
Loading