@opengeni/react 0.15.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +110 -10
- package/dist/{chunk-TOJR776I.js → chunk-6XUS5VFM.js} +45 -369
- package/dist/chunk-6XUS5VFM.js.map +1 -0
- package/dist/chunk-CATATY77.js +2393 -0
- package/dist/chunk-CATATY77.js.map +1 -0
- package/dist/chunk-I3BJZIG5.js +439 -0
- package/dist/chunk-I3BJZIG5.js.map +1 -0
- package/dist/chunk-TK7G6XLT.js +18 -0
- package/dist/chunk-TK7G6XLT.js.map +1 -0
- package/dist/chunk-UAHPKLB6.js +2237 -0
- package/dist/chunk-UAHPKLB6.js.map +1 -0
- package/dist/chunk-UQS7OQDE.js +591 -0
- package/dist/chunk-UQS7OQDE.js.map +1 -0
- package/dist/chunk-YDNWMKXO.js +102 -0
- package/dist/chunk-YDNWMKXO.js.map +1 -0
- package/dist/composer-CVm5uoUI.d.ts +585 -0
- package/dist/composer.d.ts +6 -0
- package/dist/composer.js +56 -0
- package/dist/composer.js.map +1 -0
- package/dist/index.d.ts +1288 -1785
- package/dist/index.js +7140 -8817
- package/dist/index.js.map +1 -1
- package/dist/machines.d.ts +412 -3
- package/dist/machines.js +3 -1
- package/dist/noto-sans-arabic-loader-IA2T4X2A.js +21 -0
- package/dist/noto-sans-arabic-loader-IA2T4X2A.js.map +1 -0
- package/dist/noto-sans-jp-loader.generated-HFGLYBR3.js +292 -0
- package/dist/noto-sans-jp-loader.generated-HFGLYBR3.js.map +1 -0
- package/dist/queue-surface-implementation-3UW3MIIR.js +890 -0
- package/dist/queue-surface-implementation-3UW3MIIR.js.map +1 -0
- package/dist/session-Db00TRwl.d.ts +446 -0
- package/dist/session-context-rgrfElMl.d.ts +58 -0
- package/dist/session.d.ts +4 -0
- package/dist/session.js +54 -0
- package/dist/session.js.map +1 -0
- package/dist/use-composer-By1wn3MM.d.ts +99 -0
- package/package.json +20 -4
- package/src/approvals.ts +5 -3
- package/src/client.ts +44 -0
- package/src/components/approval-surface.tsx +198 -0
- package/src/components/chat-composer.tsx +94 -993
- package/src/components/code-editor.tsx +104 -26
- package/src/components/command-palette.tsx +8 -2
- package/src/components/composer-transcription-control.tsx +211 -0
- package/src/components/composer.tsx +1536 -0
- package/src/components/desktop-viewer.tsx +3 -3
- package/src/components/enrollment-consent.tsx +2 -2
- package/src/components/file-browser.tsx +367 -29
- package/src/components/human-input-form.tsx +417 -0
- package/src/components/machine-card.tsx +1 -1
- package/src/components/machines-dashboard.tsx +3 -3
- package/src/components/message-timeline.tsx +205 -25
- package/src/components/model-picker.tsx +12 -3
- package/src/components/pierre-diff.tsx +32 -6
- package/src/components/queue-draft-policy.ts +18 -0
- package/src/components/queue-surface-implementation.tsx +1037 -0
- package/src/components/queue-surface-state.tsx +94 -0
- package/src/components/queue-surface.tsx +42 -559
- package/src/components/sandbox-files.tsx +138 -14
- package/src/components/sandbox-terminal.tsx +8 -2
- package/src/components/sandbox-workspace.tsx +339 -119
- package/src/components/workbench-changes.tsx +84 -33
- package/src/components/workspace-dock.tsx +188 -18
- package/src/composer.ts +60 -0
- package/src/hooks/internal.ts +115 -34
- package/src/hooks/use-composer.ts +385 -104
- package/src/hooks/use-human-input.ts +131 -0
- package/src/hooks/use-machine-chip.ts +2 -2
- package/src/hooks/use-machines.ts +27 -11
- package/src/hooks/use-sandbox-files.ts +310 -62
- package/src/hooks/use-sandbox-git.ts +154 -40
- package/src/hooks/use-sandbox-terminal.ts +28 -6
- package/src/hooks/use-session-capabilities.ts +38 -9
- package/src/hooks/use-session-control.ts +37 -8
- package/src/hooks/use-session-events.ts +522 -48
- package/src/hooks/use-session-mcp-approval-policy.ts +161 -0
- package/src/hooks/use-slash-commands.ts +38 -38
- package/src/hooks/use-transcription.ts +757 -0
- package/src/hooks/use-turn-queue.ts +134 -66
- package/src/hooks/use-windowed-sections.ts +2 -2
- package/src/hooks/use-workspace-capture.ts +146 -40
- package/src/hooks/use-workspace-edit.ts +50 -14
- package/src/hooks/use-workspace-sessions.ts +3 -0
- package/src/human-input.ts +72 -0
- package/src/index.ts +85 -6
- package/src/lib/noto-sans-arabic-loader.ts +21 -0
- package/src/lib/noto-sans-jp-loader.generated.ts +295 -0
- package/src/lib/use-portal-token-style.ts +51 -0
- package/src/lib/use-unicode-fonts.ts +57 -0
- package/src/provider.tsx +4 -69
- package/src/session-context.ts +160 -0
- package/src/session.ts +89 -0
- package/src/timeline/parsers.ts +35 -0
- package/src/timeline/projection.ts +12 -4
- package/src/timeline/tool-renderers.tsx +41 -0
- package/src/timeline/turn-summary.tsx +2 -2
- package/src/timeline/types.ts +10 -9
- package/styles/index.css +56 -0
- package/styles/index.d.ts +2 -0
- package/styles/tokens.css +11 -6
- package/styles/tokens.d.ts +2 -0
- package/dist/chunk-TOJR776I.js.map +0 -1
- package/dist/machines-BpdwuQcD.d.ts +0 -449
|
@@ -1,578 +1,61 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
DragOverlay,
|
|
4
|
-
PointerSensor,
|
|
5
|
-
closestCenter,
|
|
6
|
-
useSensor,
|
|
7
|
-
useSensors,
|
|
8
|
-
type DragEndEvent,
|
|
9
|
-
type DragStartEvent,
|
|
10
|
-
type Modifier,
|
|
11
|
-
} from "@dnd-kit/core";
|
|
12
|
-
import {
|
|
13
|
-
SortableContext,
|
|
14
|
-
arrayMove,
|
|
15
|
-
useSortable,
|
|
16
|
-
verticalListSortingStrategy,
|
|
17
|
-
} from "@dnd-kit/sortable";
|
|
18
|
-
import { CSS } from "@dnd-kit/utilities";
|
|
19
|
-
import type { SessionTurn } from "@opengeni/sdk";
|
|
20
|
-
import {
|
|
21
|
-
ArrowDownToLineIcon,
|
|
22
|
-
ArrowUpToLineIcon,
|
|
23
|
-
ChevronDownIcon,
|
|
24
|
-
EllipsisIcon,
|
|
25
|
-
GripVerticalIcon,
|
|
26
|
-
Loader2Icon,
|
|
27
|
-
PencilIcon,
|
|
28
|
-
RotateCwIcon,
|
|
29
|
-
Trash2Icon,
|
|
30
|
-
ZapIcon,
|
|
31
|
-
} from "lucide-react";
|
|
32
|
-
import { useCallback, useMemo, useState, type KeyboardEvent as ReactKeyboardEvent } from "react";
|
|
1
|
+
import { Loader2Icon } from "lucide-react";
|
|
2
|
+
import { lazy, Suspense, type ComponentType } from "react";
|
|
33
3
|
|
|
34
|
-
import {
|
|
35
|
-
|
|
36
|
-
import
|
|
4
|
+
import type { QueueSurfaceProps } from "./queue-surface-implementation";
|
|
5
|
+
export { requestQueueDraftEdit } from "./queue-draft-policy";
|
|
6
|
+
import { EmptyQueueStateSurface } from "./queue-surface-state";
|
|
37
7
|
|
|
38
|
-
|
|
39
|
-
export type QueueSurfaceProps =
|
|
40
|
-
| {
|
|
41
|
-
queue: UseTurnQueueResult;
|
|
42
|
-
composer: ComposerState;
|
|
43
|
-
readOnly?: false | undefined;
|
|
44
|
-
}
|
|
45
|
-
| {
|
|
46
|
-
queue: UseTurnQueueResult;
|
|
47
|
-
composer?: undefined;
|
|
48
|
-
readOnly: true;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
export function QueueSurface({ queue, composer, readOnly = false }: QueueSurfaceProps) {
|
|
52
|
-
const [open, setOpen] = useState(false);
|
|
53
|
-
const [replaceDraftFor, setReplaceDraftFor] = useState<string | null>(null);
|
|
54
|
-
const [announcement, setAnnouncement] = useState("");
|
|
55
|
-
const [draggedTurnId, setDraggedTurnId] = useState<string | null>(null);
|
|
56
|
-
const [keyboardDrag, setKeyboardDrag] = useState<{
|
|
57
|
-
turnId: string;
|
|
58
|
-
projectedIndex: number;
|
|
59
|
-
} | null>(null);
|
|
60
|
-
const count = queue.queue.length;
|
|
61
|
-
const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 6 } }));
|
|
62
|
-
|
|
63
|
-
const displayedQueue = useMemo(() => {
|
|
64
|
-
if (!keyboardDrag) return queue.queue;
|
|
65
|
-
const oldIndex = queue.queue.findIndex((turn) => turn.id === keyboardDrag.turnId);
|
|
66
|
-
if (oldIndex < 0) return queue.queue;
|
|
67
|
-
return arrayMove(queue.queue, oldIndex, keyboardDrag.projectedIndex);
|
|
68
|
-
}, [keyboardDrag, queue.queue]);
|
|
69
|
-
|
|
70
|
-
const ids = useMemo(() => displayedQueue.map((turn) => turn.id), [displayedQueue]);
|
|
71
|
-
const moveToIndex = useCallback(
|
|
72
|
-
async (turnId: string, nextIndex: number): Promise<void> => {
|
|
73
|
-
const oldIndex = queue.queue.findIndex((turn) => turn.id === turnId);
|
|
74
|
-
if (oldIndex < 0) return;
|
|
75
|
-
const boundedIndex = Math.max(0, Math.min(nextIndex, queue.queue.length - 1));
|
|
76
|
-
if (oldIndex === boundedIndex) return;
|
|
77
|
-
const ordered = arrayMove(queue.queue, oldIndex, boundedIndex);
|
|
78
|
-
const beforeTurnId = ordered[boundedIndex + 1]?.id ?? null;
|
|
79
|
-
const moved = await queue.moveTurn(turnId, beforeTurnId);
|
|
80
|
-
setAnnouncement(
|
|
81
|
-
moved
|
|
82
|
-
? `Queued prompt moved to position ${boundedIndex + 1}.`
|
|
83
|
-
: "The queue changed before that prompt could be moved. Refreshed server order.",
|
|
84
|
-
);
|
|
85
|
-
if (moved) focusQueueTurn(turnId);
|
|
86
|
-
},
|
|
87
|
-
[queue],
|
|
88
|
-
);
|
|
89
|
-
|
|
90
|
-
const onDragEnd = useCallback(
|
|
91
|
-
(event: DragEndEvent) => {
|
|
92
|
-
setDraggedTurnId(null);
|
|
93
|
-
const activeId = String(event.active.id);
|
|
94
|
-
const overId = event.over ? String(event.over.id) : null;
|
|
95
|
-
if (!overId || activeId === overId) {
|
|
96
|
-
setAnnouncement("Queued prompt returned to its original position.");
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
const nextIndex = queue.queue.findIndex((turn) => turn.id === overId);
|
|
100
|
-
if (nextIndex >= 0) void moveToIndex(activeId, nextIndex);
|
|
101
|
-
},
|
|
102
|
-
[moveToIndex, queue.queue],
|
|
103
|
-
);
|
|
104
|
-
|
|
105
|
-
const onDragStart = useCallback(
|
|
106
|
-
(event: DragStartEvent) => {
|
|
107
|
-
setKeyboardDrag(null);
|
|
108
|
-
const turnId = String(event.active.id);
|
|
109
|
-
const position = queue.queue.findIndex((turn) => turn.id === turnId) + 1;
|
|
110
|
-
setDraggedTurnId(turnId);
|
|
111
|
-
setAnnouncement(`Dragging queued prompt ${position} of ${queue.queue.length}.`);
|
|
112
|
-
},
|
|
113
|
-
[queue.queue],
|
|
114
|
-
);
|
|
115
|
-
|
|
116
|
-
const onHandleKeyDown = useCallback(
|
|
117
|
-
(event: ReactKeyboardEvent<HTMLButtonElement>, turnId: string) => {
|
|
118
|
-
const canonicalIndex = queue.queue.findIndex((turn) => turn.id === turnId);
|
|
119
|
-
if (canonicalIndex < 0) return;
|
|
8
|
+
export type { QueueSurfaceProps } from "./queue-surface-implementation";
|
|
120
9
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
setKeyboardDrag({ turnId, projectedIndex: canonicalIndex });
|
|
125
|
-
setAnnouncement(
|
|
126
|
-
`Lifted queued prompt ${canonicalIndex + 1} of ${count}. Use arrow keys to move it, then press Space to drop.`,
|
|
127
|
-
);
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
if (keyboardDrag.turnId !== turnId) return;
|
|
10
|
+
const loadQueueSurface = () => import("./queue-surface-implementation");
|
|
11
|
+
type QueueSurfaceModule = { QueueSurface: ComponentType<QueueSurfaceProps> };
|
|
12
|
+
type QueueSurfaceLoader = () => Promise<QueueSurfaceModule>;
|
|
131
13
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
setKeyboardDrag(null);
|
|
135
|
-
setAnnouncement("Queue reorder cancelled.");
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
if (event.key === " ") {
|
|
139
|
-
event.preventDefault();
|
|
140
|
-
const targetIndex = keyboardDrag.projectedIndex;
|
|
141
|
-
setAnnouncement(`Moving queued prompt to position ${targetIndex + 1}.`);
|
|
142
|
-
void moveToIndex(turnId, targetIndex).finally(() => setKeyboardDrag(null));
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
14
|
+
/** The sole human prompt queue: compact above Goal, Agents, and composer. */
|
|
15
|
+
export const QueueSurface = createQueueSurface(loadQueueSurface);
|
|
145
16
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const projectedIndex = Math.max(
|
|
151
|
-
0,
|
|
152
|
-
Math.min(edge ?? keyboardDrag.projectedIndex + direction, count - 1),
|
|
153
|
-
);
|
|
154
|
-
setKeyboardDrag({ turnId, projectedIndex });
|
|
155
|
-
setAnnouncement(`Queued prompt projected to position ${projectedIndex + 1} of ${count}.`);
|
|
156
|
-
},
|
|
157
|
-
[count, keyboardDrag, moveToIndex, queue.queue],
|
|
158
|
-
);
|
|
17
|
+
/** @internal Deterministic Suspense seam for the QueueSurface regression suite. */
|
|
18
|
+
export function createQueueSurfaceForTest(loadImplementation: QueueSurfaceLoader) {
|
|
19
|
+
return createQueueSurface(loadImplementation);
|
|
20
|
+
}
|
|
159
21
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
expectedDraftRevision: composer.draftRevision,
|
|
165
|
-
replaceDraft,
|
|
166
|
-
});
|
|
167
|
-
if (!restored) return;
|
|
168
|
-
composer.applyDraft(restored);
|
|
169
|
-
setReplaceDraftFor(null);
|
|
170
|
-
setAnnouncement("Queued prompt moved back to the composer for editing.");
|
|
171
|
-
window.requestAnimationFrame(() => {
|
|
172
|
-
const input = document.querySelector<HTMLTextAreaElement>(
|
|
173
|
-
'textarea[aria-label="Message the agent"]',
|
|
174
|
-
);
|
|
175
|
-
input?.scrollIntoView({ block: "nearest", behavior: "smooth" });
|
|
176
|
-
input?.focus();
|
|
177
|
-
});
|
|
178
|
-
},
|
|
179
|
-
[composer, queue, readOnly],
|
|
180
|
-
);
|
|
22
|
+
function createQueueSurface(loadImplementation: QueueSurfaceLoader) {
|
|
23
|
+
const LazyQueueSurface = lazy(async () => ({
|
|
24
|
+
default: (await loadImplementation()).QueueSurface,
|
|
25
|
+
}));
|
|
181
26
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
(composer.draft?.tools.length ?? 0) > 0 ||
|
|
189
|
-
(composer.draft?.sourceTurnId !== null && composer.draft?.sourceTurnId !== undefined);
|
|
190
|
-
if (draftDirty) {
|
|
191
|
-
setReplaceDraftFor(turn.id);
|
|
192
|
-
} else {
|
|
193
|
-
void edit(turn, false);
|
|
194
|
-
}
|
|
195
|
-
},
|
|
196
|
-
[composer, edit, readOnly],
|
|
197
|
-
);
|
|
27
|
+
return function QueueSurfaceBoundary(props: QueueSurfaceProps) {
|
|
28
|
+
const { queue } = props;
|
|
29
|
+
if (queue.queue.length === 0) {
|
|
30
|
+
if (!queue.stoppingPreviousAttempt && !queue.error && !queue.mutationError) return null;
|
|
31
|
+
return <EmptyQueueStateSurface queue={queue} />;
|
|
32
|
+
}
|
|
198
33
|
|
|
199
|
-
|
|
34
|
+
return (
|
|
35
|
+
<Suspense fallback={<QueueSurfaceFallback count={queue.queue.length} />}>
|
|
36
|
+
<LazyQueueSurface {...props} />
|
|
37
|
+
</Suspense>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
}
|
|
200
41
|
|
|
42
|
+
function QueueSurfaceFallback({ count }: { count: number }) {
|
|
201
43
|
return (
|
|
202
44
|
<div
|
|
45
|
+
aria-live="polite"
|
|
203
46
|
className="mx-auto mb-2 w-full max-w-3xl shrink-0 px-4 sm:px-6"
|
|
204
|
-
data-testid="queue-surface"
|
|
47
|
+
data-testid="queue-surface-loading"
|
|
48
|
+
role="status"
|
|
205
49
|
>
|
|
206
50
|
<div className="overflow-hidden rounded-lg border border-border bg-surface/80 shadow-sm">
|
|
207
|
-
<
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
aria-expanded={open}
|
|
212
|
-
>
|
|
213
|
-
<ChevronDownIcon
|
|
214
|
-
className={`size-3.5 shrink-0 text-fg-subtle transition-transform ${open ? "rotate-180" : ""}`}
|
|
51
|
+
<div className="flex items-center gap-2 px-3 py-2 text-xs text-fg-muted pointer-coarse:min-h-[44px]">
|
|
52
|
+
<Loader2Icon
|
|
53
|
+
aria-hidden="true"
|
|
54
|
+
className="size-3.5 shrink-0 animate-spin motion-reduce:animate-none"
|
|
215
55
|
/>
|
|
216
|
-
|
|
217
|
-
{count} queued prompt{count === 1 ? "" : "s"}
|
|
218
|
-
</span>
|
|
219
|
-
{readOnly ? (
|
|
220
|
-
<span className="shrink-0 rounded border border-border px-1.5 py-0.5 text-2xs text-fg-subtle">
|
|
221
|
-
Read-only
|
|
222
|
-
</span>
|
|
223
|
-
) : null}
|
|
224
|
-
{!open && count > 0 ? (
|
|
225
|
-
<span className="min-w-0 flex-1 truncate text-xs text-fg-muted">
|
|
226
|
-
{queue.queue[0]?.prompt}
|
|
227
|
-
</span>
|
|
228
|
-
) : null}
|
|
229
|
-
{queue.loading ? <Loader2Icon className="ml-auto size-3.5 animate-spin" /> : null}
|
|
230
|
-
</button>
|
|
231
|
-
|
|
232
|
-
{open && count > 0 && readOnly ? (
|
|
233
|
-
<ol className="divide-y divide-border border-t border-border" aria-label="Queued prompts">
|
|
234
|
-
{queue.queue.map((turn, index) => (
|
|
235
|
-
<ReadOnlyQueueRow key={turn.id} turn={turn} index={index} />
|
|
236
|
-
))}
|
|
237
|
-
</ol>
|
|
238
|
-
) : null}
|
|
239
|
-
|
|
240
|
-
{open && count > 0 && !readOnly ? (
|
|
241
|
-
<DndContext
|
|
242
|
-
sensors={sensors}
|
|
243
|
-
collisionDetection={closestCenter}
|
|
244
|
-
modifiers={[verticalOnly]}
|
|
245
|
-
onDragStart={onDragStart}
|
|
246
|
-
onDragCancel={() => {
|
|
247
|
-
setDraggedTurnId(null);
|
|
248
|
-
setAnnouncement("Queue reorder cancelled.");
|
|
249
|
-
}}
|
|
250
|
-
onDragEnd={onDragEnd}
|
|
251
|
-
>
|
|
252
|
-
<SortableContext items={ids} strategy={verticalListSortingStrategy}>
|
|
253
|
-
<ol
|
|
254
|
-
className="divide-y divide-border border-t border-border"
|
|
255
|
-
aria-label="Queued prompts"
|
|
256
|
-
>
|
|
257
|
-
{displayedQueue.map((turn, index) => (
|
|
258
|
-
<SortableQueueRow
|
|
259
|
-
key={turn.id}
|
|
260
|
-
turn={turn}
|
|
261
|
-
index={index}
|
|
262
|
-
count={count}
|
|
263
|
-
pending={queue.mutationFor(turn.id)}
|
|
264
|
-
confirmingReplace={replaceDraftFor === turn.id}
|
|
265
|
-
keyboardDragging={keyboardDrag?.turnId === turn.id}
|
|
266
|
-
onHandleKeyDown={(event) => onHandleKeyDown(event, turn.id)}
|
|
267
|
-
onMove={(nextIndex) => void moveToIndex(turn.id, nextIndex)}
|
|
268
|
-
onEdit={() => requestEdit(turn)}
|
|
269
|
-
onConfirmReplace={() => void edit(turn, true)}
|
|
270
|
-
onCancelReplace={() => setReplaceDraftFor(null)}
|
|
271
|
-
onSteer={() => {
|
|
272
|
-
void queue.steerTurn(turn.id).then((steered) => {
|
|
273
|
-
setAnnouncement(
|
|
274
|
-
steered
|
|
275
|
-
? "Queued prompt is now the next direction."
|
|
276
|
-
: "That prompt changed before it could be steered.",
|
|
277
|
-
);
|
|
278
|
-
if (steered) focusAfterQueueRemoval(index);
|
|
279
|
-
});
|
|
280
|
-
}}
|
|
281
|
-
onDelete={() => {
|
|
282
|
-
void queue.removeTurn(turn.id).then((removed) => {
|
|
283
|
-
setAnnouncement(
|
|
284
|
-
removed
|
|
285
|
-
? "Queued prompt deleted."
|
|
286
|
-
: "That prompt changed before it could be deleted.",
|
|
287
|
-
);
|
|
288
|
-
if (removed) focusAfterQueueRemoval(index);
|
|
289
|
-
});
|
|
290
|
-
}}
|
|
291
|
-
/>
|
|
292
|
-
))}
|
|
293
|
-
</ol>
|
|
294
|
-
</SortableContext>
|
|
295
|
-
<DragOverlay modifiers={[verticalOnly]}>
|
|
296
|
-
{draggedTurnId ? (
|
|
297
|
-
<div className="max-w-xl rounded-md border border-brand/40 bg-surface px-3 py-2 text-xs text-fg shadow-lg">
|
|
298
|
-
{queue.queue.find((turn) => turn.id === draggedTurnId)?.prompt}
|
|
299
|
-
</div>
|
|
300
|
-
) : null}
|
|
301
|
-
</DragOverlay>
|
|
302
|
-
</DndContext>
|
|
303
|
-
) : null}
|
|
304
|
-
|
|
305
|
-
{queue.error || queue.mutationError ? (
|
|
306
|
-
<div className="border-t border-border p-2">
|
|
307
|
-
<div
|
|
308
|
-
role="alert"
|
|
309
|
-
className="flex items-center gap-2 rounded-md bg-status-failed/10 px-2 py-1.5 text-xs text-status-failed"
|
|
310
|
-
>
|
|
311
|
-
<span className="min-w-0 flex-1">
|
|
312
|
-
{(queue.mutationError ?? queue.error)?.message}
|
|
313
|
-
</span>
|
|
314
|
-
<button
|
|
315
|
-
type="button"
|
|
316
|
-
onClick={() => {
|
|
317
|
-
queue.clearMutationError();
|
|
318
|
-
void queue.refresh();
|
|
319
|
-
}}
|
|
320
|
-
aria-label="Dismiss queue error and retry"
|
|
321
|
-
title="Retry loading the queue"
|
|
322
|
-
className="inline-flex size-7 items-center justify-center rounded-md outline-none transition-colors hover:bg-surface-3 focus-visible:ring-2 focus-visible:ring-ring/40 pointer-coarse:size-11"
|
|
323
|
-
>
|
|
324
|
-
<RotateCwIcon className="size-3.5" />
|
|
325
|
-
</button>
|
|
326
|
-
</div>
|
|
327
|
-
</div>
|
|
328
|
-
) : null}
|
|
329
|
-
</div>
|
|
330
|
-
<p className="sr-only" aria-live="polite" aria-atomic="true">
|
|
331
|
-
{announcement}
|
|
332
|
-
</p>
|
|
333
|
-
</div>
|
|
334
|
-
);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
function ReadOnlyQueueRow({ turn, index }: { turn: SessionTurn; index: number }) {
|
|
338
|
-
return (
|
|
339
|
-
<li className="flex min-w-0 items-start gap-2 bg-surface px-3 py-2">
|
|
340
|
-
<span className="mt-1 shrink-0 font-mono text-2xs text-fg-subtle">{index + 1}</span>
|
|
341
|
-
<div className="min-w-0 flex-1">
|
|
342
|
-
<p className="whitespace-pre-wrap break-words text-xs leading-5 text-fg">{turn.prompt}</p>
|
|
343
|
-
<div className="mt-1 flex flex-wrap gap-x-2 gap-y-0.5 text-2xs text-fg-subtle">
|
|
344
|
-
{turn.resources.length > 0 ? (
|
|
345
|
-
<span>
|
|
346
|
-
{turn.resources.length} resource{turn.resources.length === 1 ? "" : "s"}
|
|
347
|
-
</span>
|
|
348
|
-
) : null}
|
|
349
|
-
{turn.tools.length > 0 ? (
|
|
350
|
-
<span>
|
|
351
|
-
{turn.tools.length} tool{turn.tools.length === 1 ? "" : "s"}
|
|
352
|
-
</span>
|
|
353
|
-
) : null}
|
|
354
|
-
<span>{turn.model}</span>
|
|
355
|
-
<span>{turn.reasoningEffort}</span>
|
|
56
|
+
Loading {count} queued prompt{count === 1 ? "" : "s"}…
|
|
356
57
|
</div>
|
|
357
58
|
</div>
|
|
358
|
-
</
|
|
359
|
-
);
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
function SortableQueueRow({
|
|
363
|
-
turn,
|
|
364
|
-
index,
|
|
365
|
-
count,
|
|
366
|
-
pending,
|
|
367
|
-
confirmingReplace,
|
|
368
|
-
keyboardDragging,
|
|
369
|
-
onHandleKeyDown,
|
|
370
|
-
onMove,
|
|
371
|
-
onEdit,
|
|
372
|
-
onConfirmReplace,
|
|
373
|
-
onCancelReplace,
|
|
374
|
-
onSteer,
|
|
375
|
-
onDelete,
|
|
376
|
-
}: {
|
|
377
|
-
turn: SessionTurn;
|
|
378
|
-
index: number;
|
|
379
|
-
count: number;
|
|
380
|
-
pending: QueueMutationKind | null;
|
|
381
|
-
confirmingReplace: boolean;
|
|
382
|
-
keyboardDragging: boolean;
|
|
383
|
-
onHandleKeyDown: (event: ReactKeyboardEvent<HTMLButtonElement>) => void;
|
|
384
|
-
onMove: (index: number) => void;
|
|
385
|
-
onEdit: () => void;
|
|
386
|
-
onConfirmReplace: () => void;
|
|
387
|
-
onCancelReplace: () => void;
|
|
388
|
-
onSteer: () => void;
|
|
389
|
-
onDelete: () => void;
|
|
390
|
-
}) {
|
|
391
|
-
const sortable = useSortable({
|
|
392
|
-
id: turn.id,
|
|
393
|
-
disabled: pending !== null || keyboardDragging,
|
|
394
|
-
});
|
|
395
|
-
return (
|
|
396
|
-
<li
|
|
397
|
-
data-queue-turn-id={turn.id}
|
|
398
|
-
ref={sortable.setNodeRef}
|
|
399
|
-
style={{
|
|
400
|
-
transform: CSS.Transform.toString(sortable.transform),
|
|
401
|
-
transition: sortable.transition,
|
|
402
|
-
}}
|
|
403
|
-
className={`bg-surface ${sortable.isDragging || keyboardDragging ? "relative z-10 shadow-lg ring-1 ring-brand/40" : ""}`}
|
|
404
|
-
>
|
|
405
|
-
<div className="flex min-w-0 items-start gap-1.5 px-2 py-2 sm:gap-2 sm:px-3">
|
|
406
|
-
<button
|
|
407
|
-
data-queue-handle
|
|
408
|
-
ref={sortable.setActivatorNodeRef}
|
|
409
|
-
type="button"
|
|
410
|
-
{...sortable.attributes}
|
|
411
|
-
{...sortable.listeners}
|
|
412
|
-
onKeyDown={onHandleKeyDown}
|
|
413
|
-
disabled={pending !== null}
|
|
414
|
-
className="mt-0.5 inline-flex size-7 shrink-0 touch-none items-center justify-center rounded-md text-fg-subtle hover:bg-surface-2 hover:text-fg focus-visible:ring-2 focus-visible:ring-ring/40 pointer-coarse:size-11"
|
|
415
|
-
aria-label={`Reorder queued prompt ${index + 1}`}
|
|
416
|
-
title="Drag to reorder. Press Space, arrows, then Space to drop."
|
|
417
|
-
>
|
|
418
|
-
<GripVerticalIcon className="size-3.5" />
|
|
419
|
-
</button>
|
|
420
|
-
<span className="mt-1 shrink-0 font-mono text-2xs text-fg-subtle">{index + 1}</span>
|
|
421
|
-
<div className="min-w-0 flex-1">
|
|
422
|
-
<p className="whitespace-pre-wrap break-words text-xs leading-5 text-fg">{turn.prompt}</p>
|
|
423
|
-
<div className="mt-1 flex flex-wrap gap-x-2 gap-y-0.5 text-2xs text-fg-subtle">
|
|
424
|
-
{turn.resources.length > 0 ? (
|
|
425
|
-
<span>
|
|
426
|
-
{turn.resources.length} resource{turn.resources.length === 1 ? "" : "s"}
|
|
427
|
-
</span>
|
|
428
|
-
) : null}
|
|
429
|
-
{turn.tools.length > 0 ? (
|
|
430
|
-
<span>
|
|
431
|
-
{turn.tools.length} tool{turn.tools.length === 1 ? "" : "s"}
|
|
432
|
-
</span>
|
|
433
|
-
) : null}
|
|
434
|
-
<span>{turn.model}</span>
|
|
435
|
-
<span>{turn.reasoningEffort}</span>
|
|
436
|
-
</div>
|
|
437
|
-
</div>
|
|
438
|
-
<button
|
|
439
|
-
type="button"
|
|
440
|
-
disabled={pending !== null}
|
|
441
|
-
onClick={onSteer}
|
|
442
|
-
aria-label={`Steer queued prompt ${index + 1}`}
|
|
443
|
-
title="Make this the next direction"
|
|
444
|
-
className="inline-flex h-7 shrink-0 items-center justify-center gap-1 rounded-md px-2 text-xs font-medium outline-none transition-colors hover:bg-surface-2 focus-visible:ring-2 focus-visible:ring-ring/40 disabled:pointer-events-none disabled:opacity-50 pointer-coarse:min-h-11"
|
|
445
|
-
>
|
|
446
|
-
{pending === "steer" ? (
|
|
447
|
-
<Loader2Icon className="size-3.5 animate-spin" />
|
|
448
|
-
) : (
|
|
449
|
-
<ZapIcon className="size-3.5" />
|
|
450
|
-
)}
|
|
451
|
-
<span className="hidden sm:inline">Steer</span>
|
|
452
|
-
</button>
|
|
453
|
-
<button
|
|
454
|
-
type="button"
|
|
455
|
-
disabled={pending !== null}
|
|
456
|
-
onClick={onDelete}
|
|
457
|
-
aria-label={`Delete queued prompt ${index + 1}`}
|
|
458
|
-
title="Delete this queued prompt"
|
|
459
|
-
className="inline-flex size-7 shrink-0 items-center justify-center rounded-md outline-none transition-colors hover:bg-surface-2 hover:text-status-failed focus-visible:ring-2 focus-visible:ring-ring/40 disabled:pointer-events-none disabled:opacity-50 pointer-coarse:size-11"
|
|
460
|
-
>
|
|
461
|
-
{pending === "delete" ? (
|
|
462
|
-
<Loader2Icon className="size-3.5 animate-spin" />
|
|
463
|
-
) : (
|
|
464
|
-
<Trash2Icon className="size-3.5" />
|
|
465
|
-
)}
|
|
466
|
-
</button>
|
|
467
|
-
<DropdownMenu.Root>
|
|
468
|
-
<DropdownMenu.Trigger asChild>
|
|
469
|
-
<button
|
|
470
|
-
type="button"
|
|
471
|
-
disabled={pending !== null}
|
|
472
|
-
aria-label={`More actions for queued prompt ${index + 1}`}
|
|
473
|
-
className="inline-flex size-7 shrink-0 items-center justify-center rounded-md outline-none transition-colors hover:bg-surface-2 focus-visible:ring-2 focus-visible:ring-ring/40 disabled:pointer-events-none disabled:opacity-50 pointer-coarse:size-11"
|
|
474
|
-
>
|
|
475
|
-
{pending && pending !== "steer" && pending !== "delete" ? (
|
|
476
|
-
<Loader2Icon className="size-3.5 animate-spin" />
|
|
477
|
-
) : (
|
|
478
|
-
<EllipsisIcon className="size-3.5" />
|
|
479
|
-
)}
|
|
480
|
-
</button>
|
|
481
|
-
</DropdownMenu.Trigger>
|
|
482
|
-
<DropdownMenu.Portal>
|
|
483
|
-
<DropdownMenu.Content
|
|
484
|
-
align="end"
|
|
485
|
-
sideOffset={4}
|
|
486
|
-
className="z-50 w-48 rounded-md border border-border bg-surface p-1 text-xs text-fg shadow-lg"
|
|
487
|
-
>
|
|
488
|
-
<DropdownMenu.Item
|
|
489
|
-
className="flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 outline-none focus:bg-surface-2"
|
|
490
|
-
onSelect={onEdit}
|
|
491
|
-
>
|
|
492
|
-
<PencilIcon className="size-3.5" /> Edit in composer
|
|
493
|
-
</DropdownMenu.Item>
|
|
494
|
-
<DropdownMenu.Separator className="my-1 h-px bg-border" />
|
|
495
|
-
<DropdownMenu.Item
|
|
496
|
-
className="flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 outline-none focus:bg-surface-2 data-[disabled]:opacity-50"
|
|
497
|
-
disabled={index === 0}
|
|
498
|
-
onSelect={() => onMove(0)}
|
|
499
|
-
>
|
|
500
|
-
<ArrowUpToLineIcon className="size-3.5" /> Move to top
|
|
501
|
-
</DropdownMenu.Item>
|
|
502
|
-
<DropdownMenu.Item
|
|
503
|
-
className="flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 outline-none focus:bg-surface-2 data-[disabled]:opacity-50"
|
|
504
|
-
disabled={index === 0}
|
|
505
|
-
onSelect={() => onMove(index - 1)}
|
|
506
|
-
>
|
|
507
|
-
Move up
|
|
508
|
-
</DropdownMenu.Item>
|
|
509
|
-
<DropdownMenu.Item
|
|
510
|
-
className="flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 outline-none focus:bg-surface-2 data-[disabled]:opacity-50"
|
|
511
|
-
disabled={index === count - 1}
|
|
512
|
-
onSelect={() => onMove(index + 1)}
|
|
513
|
-
>
|
|
514
|
-
Move down
|
|
515
|
-
</DropdownMenu.Item>
|
|
516
|
-
<DropdownMenu.Item
|
|
517
|
-
className="flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 outline-none focus:bg-surface-2 data-[disabled]:opacity-50"
|
|
518
|
-
disabled={index === count - 1}
|
|
519
|
-
onSelect={() => onMove(count - 1)}
|
|
520
|
-
>
|
|
521
|
-
<ArrowDownToLineIcon className="size-3.5" /> Move to bottom
|
|
522
|
-
</DropdownMenu.Item>
|
|
523
|
-
</DropdownMenu.Content>
|
|
524
|
-
</DropdownMenu.Portal>
|
|
525
|
-
</DropdownMenu.Root>
|
|
526
|
-
</div>
|
|
527
|
-
{confirmingReplace ? (
|
|
528
|
-
<div className="mx-3 mb-2 rounded-md border border-status-waiting/30 bg-status-waiting/10 p-2 text-xs text-fg">
|
|
529
|
-
<p>Your composer already has a draft. Replace it with this queued prompt?</p>
|
|
530
|
-
<p className="mt-0.5 text-fg-muted">
|
|
531
|
-
The current draft will be permanently discarded; this queued prompt is preserved until
|
|
532
|
-
you confirm.
|
|
533
|
-
</p>
|
|
534
|
-
<div className="mt-2 flex justify-end gap-1.5">
|
|
535
|
-
<button
|
|
536
|
-
type="button"
|
|
537
|
-
className="rounded-md px-2 py-1 font-medium hover:bg-surface-2 focus-visible:ring-2 focus-visible:ring-ring/40"
|
|
538
|
-
onClick={onCancelReplace}
|
|
539
|
-
>
|
|
540
|
-
Keep current draft
|
|
541
|
-
</button>
|
|
542
|
-
<button
|
|
543
|
-
type="button"
|
|
544
|
-
className="rounded-md bg-brand px-2 py-1 font-medium text-white hover:bg-brand/90 focus-visible:ring-2 focus-visible:ring-ring/40"
|
|
545
|
-
onClick={onConfirmReplace}
|
|
546
|
-
>
|
|
547
|
-
Replace and edit
|
|
548
|
-
</button>
|
|
549
|
-
</div>
|
|
550
|
-
</div>
|
|
551
|
-
) : null}
|
|
552
|
-
</li>
|
|
59
|
+
</div>
|
|
553
60
|
);
|
|
554
61
|
}
|
|
555
|
-
|
|
556
|
-
const verticalOnly: Modifier = ({ transform }) => ({ ...transform, x: 0 });
|
|
557
|
-
|
|
558
|
-
function focusQueueTurn(turnId: string): void {
|
|
559
|
-
window.requestAnimationFrame(() => {
|
|
560
|
-
document
|
|
561
|
-
.querySelector<HTMLElement>(`[data-queue-turn-id="${turnId}"] [data-queue-handle]`)
|
|
562
|
-
?.focus();
|
|
563
|
-
});
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
function focusAfterQueueRemoval(previousIndex: number): void {
|
|
567
|
-
window.requestAnimationFrame(() => {
|
|
568
|
-
const handles = document.querySelectorAll<HTMLElement>("[data-queue-handle]");
|
|
569
|
-
const nearest = handles[Math.min(previousIndex, Math.max(0, handles.length - 1))];
|
|
570
|
-
if (nearest) {
|
|
571
|
-
nearest.focus();
|
|
572
|
-
return;
|
|
573
|
-
}
|
|
574
|
-
document
|
|
575
|
-
.querySelector<HTMLTextAreaElement>('textarea[aria-label="Message the agent"]')
|
|
576
|
-
?.focus();
|
|
577
|
-
});
|
|
578
|
-
}
|