import { DndContext, DragOverlay, PointerSensor, closestCenter, useSensor, useSensors, type DragEndEvent, type DragStartEvent, type Modifier, } from "@dnd-kit/core"; import { SortableContext, arrayMove, useSortable, verticalListSortingStrategy, } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; import type { SessionPendingInputPreview, SessionTurn, TimelineAnnotation } from "@opengeni/sdk"; import { modelDisplayName } from "@opengeni/sdk/model-display"; import { ArrowDownToLineIcon, ArrowUpToLineIcon, ChevronDownIcon, EllipsisIcon, GripVerticalIcon, Loader2Icon, PencilIcon, Trash2Icon, ZapIcon, } from "lucide-react"; import { useCallback, useId, useMemo, useState, type KeyboardEvent as ReactKeyboardEvent, } from "react"; import { DropdownMenu } from "radix-ui"; import { containedPortal } from "../ui/portal-container"; import { MENU_ITEM_CLASS, MENU_SEPARATOR_CLASS, MENU_SURFACE_CLASS } from "../lib/menu-styles"; import type { ComposerState } from "../hooks/use-composer"; import type { QueueMutationKind, UseTurnQueueResult } from "../hooks/use-turn-queue"; import { type PortalTokenStyle, usePortalTokenSource, usePortalTokenStyle, } from "../lib/use-portal-token-style"; import { requestQueueDraftEdit } from "./queue-draft-policy"; import { projectPendingQueueMove, QUEUE_COLLAPSED_PREVIEW_CHARACTERS, QUEUE_REPLACE_DRAFT_COPY, QUEUE_ROW_PREVIEW_CHARACTERS, queueContentPreview, queueMovePlan, queuePromptPreview, queueTurnPreview, } from "../queue-presentation-model"; import { QUEUE_ITEM_CONTENT_UNAVAILABLE, queueItemContent } from "./queue-item-content"; import { QueueErrorAlert, QueueStoppingStatus } from "./queue-surface-state"; import { TimelineAnnotationsChip } from "./timeline-annotations"; import { useOpenGeniText, type OpenGeniText } from "../messages/runtime"; const DropdownMenuContainedPortal = containedPortal(DropdownMenu.Portal); // Queued prompts can be long; menu rows wrap instead of truncating. const QUEUE_MENU_ITEM_CLASS = `${MENU_ITEM_CLASS} whitespace-normal break-words`; /** The sole pending-input surface: compact above Goal, Agents, and composer. */ type QueueSurfaceCommonProps = { /** Focus the composer owned by this queue after checkout/removal. */ onRequestComposerFocus?: (() => void) | undefined; }; export type QueueSurfaceProps = | (QueueSurfaceCommonProps & { queue: UseTurnQueueResult; composer: ComposerState; readOnly?: false | undefined; }) | (QueueSurfaceCommonProps & { queue: UseTurnQueueResult; composer?: undefined; readOnly: true; }); /** @internal Pure policy seam for queue/composer embedding tests. */ export function queueComposerCheckoutEnabled( composer: ComposerState | undefined, readOnly: boolean, ): boolean { return !readOnly && composer !== undefined && composer.draftPersistence !== "disabled"; } export function QueueSurface({ queue, composer, readOnly = false, onRequestComposerFocus, }: QueueSurfaceProps) { const t = useOpenGeniText(); const [open, setOpen] = useState(false); const [replaceDraftFor, setReplaceDraftFor] = useState(null); const [announcement, setAnnouncement] = useState(""); const [draggedTurnId, setDraggedTurnId] = useState(null); const [pendingMove, setPendingMove] = useState<{ baseVersion: number | null; turnIds: string[]; } | null>(null); const surface = usePortalTokenSource(); const surfaceRef = surface.currentRef; const portalTokenStyle = usePortalTokenStyle(surface.source); const [keyboardDrag, setKeyboardDrag] = useState<{ turnId: string; projectedIndex: number; } | null>(null); const count = queue.queue.length; const pendingInputCount = queue.pendingInputs.length; const totalCount = count + pendingInputCount; const attachedInputIds = queue.pendingInputAttachment?.inputIds ?? []; const attachedInputs = queue.pendingInputs.filter((input) => attachedInputIds.includes(input.id)); const standaloneInputs = queue.pendingInputs.filter( (input) => !attachedInputIds.includes(input.id), ); const canEditInComposer = queueComposerCheckoutEnabled(composer, readOnly); const collapsedPreview = useMemo(() => { const firstTurn = queue.queue[0]; return firstTurn ? queueTurnPreview(t, firstTurn, QUEUE_COLLAPSED_PREVIEW_CHARACTERS) : queuePromptPreview( t, queue.pendingInputs[0]?.summary ?? "", QUEUE_COLLAPSED_PREVIEW_CHARACTERS, ); }, [queue.pendingInputs, queue.queue, t]); const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 6 } })); const displayedQueue = useMemo(() => { if (keyboardDrag) { const oldIndex = queue.queue.findIndex((turn) => turn.id === keyboardDrag.turnId); if (oldIndex < 0) return queue.queue; return arrayMove(queue.queue, oldIndex, keyboardDrag.projectedIndex); } // Never let an optimistic ordering projection omit or invent a row. Any // concurrent authoritative membership change wins immediately. return projectPendingQueueMove(queue.queue, pendingMove, queue.snapshot); }, [keyboardDrag, pendingMove, queue.queue, queue.snapshot]); const ids = useMemo(() => displayedQueue.map((turn) => turn.id), [displayedQueue]); const moveToIndex = useCallback( async (turnId: string, nextIndex: number): Promise => { if (queue.mutating || pendingMove) return; const plan = queueMovePlan(queue.queue, turnId, nextIndex); if (!plan) return; const { beforeTurnId, index: boundedIndex } = plan; setPendingMove({ baseVersion: queue.snapshot?.version ?? null, turnIds: plan.turnIds, }); setAnnouncement( t("queueSurfaceImplementation.savingQueuedPromptAtPosition", { boundedIndex: boundedIndex + 1, }), ); try { const moved = await queue.moveTurn(turnId, beforeTurnId); setAnnouncement( moved ? t("queueSurfaceImplementation.queuedPromptMovedToPosition", { boundedIndex: boundedIndex + 1, }) : t("queueSurfaceImplementation.theQueueChangedBeforeThat"), ); if (moved) focusQueueTurn(surfaceRef.current, turnId); } finally { setPendingMove(null); } }, [pendingMove, queue, surfaceRef, t], ); const onDragEnd = useCallback( (event: DragEndEvent) => { setDraggedTurnId(null); const activeId = String(event.active.id); const overId = event.over ? String(event.over.id) : null; if (!overId || activeId === overId) { setAnnouncement(t("queueSurfaceImplementation.queuedPromptReturnedToIts")); return; } const nextIndex = queue.queue.findIndex((turn) => turn.id === overId); if (nextIndex >= 0) void moveToIndex(activeId, nextIndex); }, [moveToIndex, queue.queue, t], ); const onDragStart = useCallback( (event: DragStartEvent) => { setKeyboardDrag(null); const turnId = String(event.active.id); const position = queue.queue.findIndex((turn) => turn.id === turnId) + 1; setDraggedTurnId(turnId); setAnnouncement( t("queueSurfaceImplementation.draggingQueuedPromptPositionOf", { position, length: queue.queue.length, }), ); }, [queue.queue, t], ); const onHandleKeyDown = useCallback( (event: ReactKeyboardEvent, turnId: string) => { const canonicalIndex = queue.queue.findIndex((turn) => turn.id === turnId); if (canonicalIndex < 0) return; if (!keyboardDrag) { if (event.key !== " ") return; event.preventDefault(); setKeyboardDrag({ turnId, projectedIndex: canonicalIndex }); setAnnouncement( t("queueSurfaceImplementation.liftedQueuedPromptCanonicalindexOf", { canonicalIndex: canonicalIndex + 1, countValue: count, }), ); return; } if (keyboardDrag.turnId !== turnId) return; if (event.key === "Escape") { event.preventDefault(); setKeyboardDrag(null); setAnnouncement(t("queueSurfaceImplementation.queueReorderCancelled")); return; } if (event.key === " ") { event.preventDefault(); const targetIndex = keyboardDrag.projectedIndex; setAnnouncement( t("queueSurfaceImplementation.movingQueuedPromptToPosition", { targetIndex: targetIndex + 1, }), ); void moveToIndex(turnId, targetIndex).finally(() => setKeyboardDrag(null)); return; } const direction = event.key === "ArrowUp" ? -1 : event.key === "ArrowDown" ? 1 : 0; const edge = event.key === "Home" ? 0 : event.key === "End" ? count - 1 : null; if (direction === 0 && edge === null) return; event.preventDefault(); const projectedIndex = Math.max( 0, Math.min(edge ?? keyboardDrag.projectedIndex + direction, count - 1), ); setKeyboardDrag({ turnId, projectedIndex }); setAnnouncement( t("queueSurfaceImplementation.queuedPromptProjectedToPosition", { projectedIndex: projectedIndex + 1, countValue: count, }), ); }, [count, keyboardDrag, moveToIndex, queue.queue, t], ); const edit = useCallback( async (turn: SessionTurn, replaceDraft: boolean) => { if (!composer || !canEditInComposer) return; setAnnouncement(t("queueSurfaceImplementation.movingQueuedPromptToThe")); const restored = await queue.editTurn(turn.id, { expectedDraftRevision: composer.draftRevision, replaceDraft, }); if (!restored) { setAnnouncement(t("queueSurfaceImplementation.thatPromptChangedBeforeIt")); return; } composer.applyDraft(restored); setReplaceDraftFor(null); setAnnouncement(t("queueSurfaceImplementation.queuedPromptMovedBackTo")); window.requestAnimationFrame(() => { if (onRequestComposerFocus) { onRequestComposerFocus(); return; } const input = document.querySelector( 'textarea[aria-label="Message the agent"]', ); input?.scrollIntoView({ block: "nearest", behavior: "smooth" }); input?.focus(); }); }, [canEditInComposer, composer, onRequestComposerFocus, queue, t], ); const requestEdit = useCallback( (turn: SessionTurn) => { if (!composer || !canEditInComposer) return; requestQueueDraftEdit( composer, () => setReplaceDraftFor(turn.id), () => void edit(turn, false), ); }, [canEditInComposer, composer, edit], ); if (totalCount === 0 && !queue.stoppingPreviousAttempt && !queue.error && !queue.mutationError) return null; return (
{queue.stoppingPreviousAttempt ? : null} {open && standaloneInputs.length > 0 ? ( ) : null} {open && count > 0 && readOnly ? (
    {queue.queue.map((turn, index) => ( setAnnouncement( t( expanded ? "queueSurfaceImplementation.fullContentForQueuedPromptShown" : "queueSurfaceImplementation.fullContentForQueuedPromptHidden", { index: index + 1 }, ), ) } /> ))}
) : null} {open && count > 0 && !readOnly ? ( { setDraggedTurnId(null); setAnnouncement(t("queueSurfaceImplementation.queueReorderCancelled")); }} onDragEnd={onDragEnd} >
    {displayedQueue.map((turn, index) => ( onHandleKeyDown(event, turn.id)} onMove={(nextIndex) => void moveToIndex(turn.id, nextIndex)} onEdit={canEditInComposer ? () => requestEdit(turn) : undefined} onConfirmReplace={() => void edit(turn, true)} onCancelReplace={() => setReplaceDraftFor(null)} onSteer={() => { setAnnouncement(t("queueSurfaceImplementation.requestingThisQueuedPromptAs")); void queue.steerTurn(turn.id).then((steered) => { setAnnouncement( steered ? t("queueSurfaceImplementation.queuedPromptIsNowThe") : t("queueSurfaceImplementation.thatPromptChangedBeforeIt2"), ); if (steered) { focusAfterQueueRemoval(surfaceRef.current, index, onRequestComposerFocus); } }); }} onDelete={() => { setAnnouncement(t("queueSurfaceImplementation.deletingQueuedPrompt")); void queue.removeTurn(turn.id).then((removed) => { setAnnouncement( removed ? t("queueSurfaceImplementation.queuedPromptDeleted") : t("queueSurfaceImplementation.thatPromptChangedBeforeIt3"), ); if (removed) { focusAfterQueueRemoval(surfaceRef.current, index, onRequestComposerFocus); } }); }} onDisclosureChange={(expanded) => setAnnouncement( t( expanded ? "queueSurfaceImplementation.fullContentForQueuedPromptShown" : "queueSurfaceImplementation.fullContentForQueuedPromptHidden", { index: index + 1 }, ), ) } /> ))}
{draggedTurnId ? (
) : null}
) : null} {queue.error || queue.mutationError ? ( ) : null}

{announcement}

); } function PendingMachineInputs({ inputs, attached = false, }: { inputs: SessionPendingInputPreview[]; attached?: boolean; }) { const t = useOpenGeniText(); return (

{attached ? t("queueSurfaceImplementation.updatesWillJoinThisPrompt", { count: inputs.length }) : t("queueSurfaceImplementation.incomingUpdatesWaitingToRun")}

    {inputs.map((input) => (
  1. {input.kind.replace("_terminal", "").replaceAll("_", " ")} {" "} · {input.sourceId} {input.summary ? ` — ${input.summary}` : ""}
  2. ))}
); } function QueuePrompt({ prompt, annotations, index, onDisclosureChange, }: { prompt: string; annotations: readonly TimelineAnnotation[]; index: number; onDisclosureChange: (expanded: boolean) => void; }) { const t = useOpenGeniText(); const [expanded, setExpanded] = useState(false); const fullContentId = useId(); const content = queueItemContent(prompt, annotations.length); const preview = useMemo( () => queueContentPreview(t, prompt, annotations.length, QUEUE_ROW_PREVIEW_CHARACTERS), [annotations.length, prompt, t], ); return (
{prompt ? (
) : null} {annotations.length > 0 ? ( ) : null} {content === "unavailable" ? (

{QUEUE_ITEM_CONTENT_UNAVAILABLE(t)}

) : null} {prompt ? ( ) : null} {expanded && prompt ? (
          {prompt}
        
) : null}
); } function ReadOnlyQueueRow({ turn, index, attachedInputs, onDisclosureChange, }: { turn: SessionTurn; index: number; attachedInputs: SessionPendingInputPreview[]; onDisclosureChange: (expanded: boolean) => void; }) { const t = useOpenGeniText(); return (
  • {index + 1}
    {attachedInputs.length > 0 ? ( ) : null}
    {turn.resources.length > 0 ? ( {t("queueSurfaceImplementation.resources", { count: turn.resources.length })} ) : null} {turn.tools.length > 0 ? ( {t("queueSurfaceImplementation.tools", { count: turn.tools.length })} ) : null} {modelDisplayName(turn.model)} {turn.reasoningEffort}
  • ); } function SortableQueueRow({ turn, index, count, attachedInputs, pending, reorderDisabled, confirmingReplace, keyboardDragging, portalTokenStyle, onHandleKeyDown, onMove, onEdit, onConfirmReplace, onCancelReplace, onSteer, onDelete, onDisclosureChange, }: { turn: SessionTurn; index: number; count: number; attachedInputs: SessionPendingInputPreview[]; pending: QueueMutationKind | null; reorderDisabled: boolean; confirmingReplace: boolean; keyboardDragging: boolean; portalTokenStyle: PortalTokenStyle; onHandleKeyDown: (event: ReactKeyboardEvent) => void; onMove: (index: number) => void; onEdit?: (() => void) | undefined; onConfirmReplace: () => void; onCancelReplace: () => void; onSteer: () => void; onDelete: () => void; onDisclosureChange: (expanded: boolean) => void; }) { const t = useOpenGeniText(); const sortable = useSortable({ id: turn.id, disabled: reorderDisabled || keyboardDragging, }); return (
  • {index + 1}
    {attachedInputs.length > 0 ? ( ) : null}
    {turn.resources.length > 0 ? ( {t("queueSurfaceImplementation.resources", { count: turn.resources.length })} ) : null} {turn.tools.length > 0 ? ( {t("queueSurfaceImplementation.tools", { count: turn.tools.length })} ) : null} {modelDisplayName(turn.model)} {turn.reasoningEffort}
    {pending ? (
    ) : null}
    {onEdit ? ( <> {t("queueSurfaceImplementation.editInComposer")} ) : null} onMove(0)} > {t("queueSurfaceImplementation.moveToTop")} onMove(index - 1)} > {t("queueSurfaceImplementation.moveUp")} onMove(index + 1)} > {t("queueSurfaceImplementation.moveDown")} onMove(count - 1)} > {t("queueSurfaceImplementation.moveToBottom")}
    {confirmingReplace ? (

    {QUEUE_REPLACE_DRAFT_COPY(t).title}

    {QUEUE_REPLACE_DRAFT_COPY(t).detail}

    ) : null}
  • ); } function queueMutationPendingLabel(t: OpenGeniText, kind: QueueMutationKind): string { switch (kind) { case "move": return t("queueSurfaceImplementation.savingNewPosition"); case "edit": return t("queueSurfaceImplementation.movingToComposer"); case "steer": return t("queueSurfaceImplementation.changingDirection"); case "delete": return t("queueSurfaceImplementation.deleting"); } } const verticalOnly: Modifier = ({ transform }) => ({ ...transform, x: 0 }); function focusQueueTurn(surface: HTMLElement | null, turnId: string): void { window.requestAnimationFrame(() => { surface ?.querySelector(`[data-queue-turn-id="${turnId}"] [data-queue-handle]`) ?.focus(); }); } function focusAfterQueueRemoval( surface: HTMLElement | null, previousIndex: number, onRequestComposerFocus?: (() => void) | undefined, ): void { window.requestAnimationFrame(() => { const handles = surface?.querySelectorAll("[data-queue-handle]") ?? []; const nearest = handles[Math.min(previousIndex, Math.max(0, handles.length - 1))]; if (nearest) { nearest.focus(); return; } if (onRequestComposerFocus) { onRequestComposerFocus(); return; } document .querySelector('textarea[aria-label="Message the agent"]') ?.focus(); }); }