@invergent/agent-chat-react 2.15.3 → 2.15.7
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/dist/index.cjs +1919 -1706
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +46 -9
- package/dist/index.d.ts +46 -9
- package/dist/index.js +1905 -1694
- package/dist/index.js.map +1 -1
- package/package.json +1 -2
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { cn, __publicField } from './chunk-47MF6JUW.js';
|
|
2
2
|
import { createContext, memo, useRef, useEffect, useCallback, useMemo, useState, useLayoutEffect, forwardRef, useId, lazy, useContext, useSyncExternalStore, Fragment as Fragment$1, Suspense, Children } from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
4
|
+
import { ChevronDownIcon, AlertTriangle, ChevronDown, ChevronRight as ChevronRight$1, RefreshCw, CreditCard, File as File$1, FileImage, FileAudio, FileVideo, FileText, FileCode, FileArchive, FileSpreadsheet, PlugZapIcon, ChevronRightIcon, ArrowLeftIcon, Loader2Icon, InboxIcon, Loader2, AlertCircle, Pause, Play, Ban, TargetIcon, CalendarClockIcon, UsersIcon, Pen, Eraser, Type, MousePointer2, Hand, Maximize2, Undo2, Redo2, CheckIcon, CopyIcon, DownloadIcon, Maximize2Icon, GlobeIcon, FolderIcon, MessageSquareIcon, UploadIcon, RefreshCwIcon, AlertCircleIcon, ExternalLinkIcon, ClipboardCheckIcon, ShieldAlertIcon, TimerIcon, CircleDotIcon, ChevronLeftIcon, XIcon, Trash2Icon, SquareIcon, ArrowDownIcon, TrashIcon, ImageIcon, FileTextIcon, Clock, ArrowRight, ListTreeIcon, PenLineIcon, TerminalIcon, SparklesIcon, ClockIcon, PaperclipIcon, CloudIcon, HardDriveIcon, IdCardIcon, PlusIcon, ShrinkIcon, FolderOpenIcon, FileIcon, MinusIcon, SearchIcon, CircleDot, XCircle, CheckCircle2, ZapIcon, ArrowUpIcon, CircleCheckIcon, ThumbsUpIcon, ThumbsDownIcon, BookOpenIcon, GitBranchIcon, ActivityIcon, EyeIcon, LibraryIcon, ListTodoIcon, BotIcon, ListIcon, FileEditIcon, WrenchIcon, FileCodeIcon, CodeIcon, FileArchiveIcon, FileVideoIcon, FileAudioIcon, FileImageIcon, FileChartLineIcon, FileSpreadsheetIcon, XCircleIcon, CheckCircle2Icon, Code, CircleXIcon, SkullIcon, CheckCircleIcon, CircleIcon } from 'lucide-react';
|
|
4
5
|
import { cva } from 'class-variance-authority';
|
|
5
6
|
import { Collapsible as Collapsible$1, Slot, Tooltip as Tooltip$1, Checkbox as Checkbox$1, Label as Label$1, Progress as Progress$1, Tabs as Tabs$1, Dialog as Dialog$1, ScrollArea as ScrollArea$1, Popover as Popover$1 } from 'radix-ui';
|
|
6
|
-
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
7
7
|
import { StickToBottom, useStickToBottomContext } from 'use-stick-to-bottom';
|
|
8
8
|
import { cjk } from '@streamdown/cjk';
|
|
9
9
|
import { code } from '@streamdown/code';
|
|
@@ -32,6 +32,481 @@ function useAgentChatAdapterContext() {
|
|
|
32
32
|
}
|
|
33
33
|
return value;
|
|
34
34
|
}
|
|
35
|
+
var NAMED_KEYS = /* @__PURE__ */ new Set([
|
|
36
|
+
"Enter",
|
|
37
|
+
"Tab",
|
|
38
|
+
"Escape",
|
|
39
|
+
"Backspace",
|
|
40
|
+
"Delete",
|
|
41
|
+
"ArrowUp",
|
|
42
|
+
"ArrowDown",
|
|
43
|
+
"ArrowLeft",
|
|
44
|
+
"ArrowRight",
|
|
45
|
+
"Home",
|
|
46
|
+
"End",
|
|
47
|
+
"PageUp",
|
|
48
|
+
"PageDown"
|
|
49
|
+
]);
|
|
50
|
+
function toWsUrl(src) {
|
|
51
|
+
const url = new URL(src, window.location.href);
|
|
52
|
+
if (url.protocol === "http:") url.protocol = "ws:";
|
|
53
|
+
else if (url.protocol === "https:") url.protocol = "wss:";
|
|
54
|
+
return url.toString();
|
|
55
|
+
}
|
|
56
|
+
function normalizePoint(clientX, clientY, rect, naturalWidth, naturalHeight) {
|
|
57
|
+
let { width, height } = rect;
|
|
58
|
+
let offsetX = 0;
|
|
59
|
+
let offsetY = 0;
|
|
60
|
+
if (naturalWidth > 0 && naturalHeight > 0) {
|
|
61
|
+
const scale = Math.min(rect.width / naturalWidth, rect.height / naturalHeight);
|
|
62
|
+
width = naturalWidth * scale;
|
|
63
|
+
height = naturalHeight * scale;
|
|
64
|
+
offsetX = (rect.width - width) / 2;
|
|
65
|
+
offsetY = (rect.height - height) / 2;
|
|
66
|
+
}
|
|
67
|
+
if (width <= 0 || height <= 0) return null;
|
|
68
|
+
const x = (clientX - rect.left - offsetX) / width;
|
|
69
|
+
const y = (clientY - rect.top - offsetY) / height;
|
|
70
|
+
if (x < 0 || x > 1 || y < 0 || y > 1) return null;
|
|
71
|
+
return { x, y };
|
|
72
|
+
}
|
|
73
|
+
function hostOf(url) {
|
|
74
|
+
try {
|
|
75
|
+
const parsed = new URL(url);
|
|
76
|
+
return { origin: parsed.host, rest: `${parsed.pathname}${parsed.search}` };
|
|
77
|
+
} catch {
|
|
78
|
+
return { origin: url, rest: "" };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
var ICON = "flex size-[26px] shrink-0 items-center justify-center rounded-[5px] text-muted-foreground transition-colors hover:bg-secondary hover:text-foreground disabled:pointer-events-none disabled:opacity-40";
|
|
82
|
+
function BrowserShell({
|
|
83
|
+
src,
|
|
84
|
+
hasControl,
|
|
85
|
+
onToggleControl,
|
|
86
|
+
onClose,
|
|
87
|
+
onMaximize,
|
|
88
|
+
onDisconnect,
|
|
89
|
+
testId = "browser-shell"
|
|
90
|
+
}) {
|
|
91
|
+
const socketRef = useRef(null);
|
|
92
|
+
const frameRef = useRef(null);
|
|
93
|
+
const onDisconnectRef = useRef(onDisconnect);
|
|
94
|
+
onDisconnectRef.current = onDisconnect;
|
|
95
|
+
const objectUrlRef = useRef(null);
|
|
96
|
+
const [state, setState] = useState("connecting");
|
|
97
|
+
const [frame2, setFrame] = useState(null);
|
|
98
|
+
const [tabs, setTabs] = useState([]);
|
|
99
|
+
const [nav, setNav] = useState({ url: "", title: "" });
|
|
100
|
+
const [menuOpen, setMenuOpen] = useState(false);
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
setState("connecting");
|
|
103
|
+
let disposed = false;
|
|
104
|
+
const socket = new WebSocket(toWsUrl(src));
|
|
105
|
+
socketRef.current = socket;
|
|
106
|
+
socket.onopen = () => {
|
|
107
|
+
if (!disposed) setState("connected");
|
|
108
|
+
};
|
|
109
|
+
socket.onerror = () => {
|
|
110
|
+
if (!disposed) setState("disconnected");
|
|
111
|
+
};
|
|
112
|
+
socket.onclose = (event) => {
|
|
113
|
+
if (disposed) return;
|
|
114
|
+
setState("disconnected");
|
|
115
|
+
onDisconnectRef.current?.(event?.wasClean ?? false);
|
|
116
|
+
};
|
|
117
|
+
socket.onmessage = (event) => {
|
|
118
|
+
if (disposed) return;
|
|
119
|
+
const data = event.data;
|
|
120
|
+
if (typeof data !== "string") {
|
|
121
|
+
const next = URL.createObjectURL(
|
|
122
|
+
data instanceof Blob ? data : new Blob([data])
|
|
123
|
+
);
|
|
124
|
+
if (objectUrlRef.current) URL.revokeObjectURL(objectUrlRef.current);
|
|
125
|
+
objectUrlRef.current = next;
|
|
126
|
+
setFrame(next);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
let message;
|
|
130
|
+
try {
|
|
131
|
+
message = JSON.parse(data);
|
|
132
|
+
} catch {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (message.t === "tabs") {
|
|
136
|
+
const next = message.tabs ?? [];
|
|
137
|
+
setTabs(next);
|
|
138
|
+
const active = next.find((tab) => tab.active);
|
|
139
|
+
if (active) setNav({ url: active.url, title: active.title });
|
|
140
|
+
} else if (message.t === "nav") {
|
|
141
|
+
setNav({
|
|
142
|
+
url: String(message.url ?? ""),
|
|
143
|
+
title: String(message.title ?? "")
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
return () => {
|
|
148
|
+
disposed = true;
|
|
149
|
+
socket.onopen = null;
|
|
150
|
+
socket.onmessage = null;
|
|
151
|
+
socket.onclose = null;
|
|
152
|
+
socket.onerror = null;
|
|
153
|
+
socket.close();
|
|
154
|
+
if (objectUrlRef.current) URL.revokeObjectURL(objectUrlRef.current);
|
|
155
|
+
objectUrlRef.current = null;
|
|
156
|
+
socketRef.current = null;
|
|
157
|
+
};
|
|
158
|
+
}, [src]);
|
|
159
|
+
const send = useCallback((message) => {
|
|
160
|
+
const socket = socketRef.current;
|
|
161
|
+
if (!socket || socket.readyState !== WebSocket.OPEN) return;
|
|
162
|
+
socket.send(JSON.stringify(message));
|
|
163
|
+
}, []);
|
|
164
|
+
const command = useCallback(
|
|
165
|
+
(message) => {
|
|
166
|
+
if (!hasControl) return;
|
|
167
|
+
send(message);
|
|
168
|
+
},
|
|
169
|
+
[hasControl, send]
|
|
170
|
+
);
|
|
171
|
+
const pointerToPage = (event) => {
|
|
172
|
+
const image = frameRef.current;
|
|
173
|
+
if (!image) return null;
|
|
174
|
+
return normalizePoint(
|
|
175
|
+
event.clientX,
|
|
176
|
+
event.clientY,
|
|
177
|
+
image.getBoundingClientRect(),
|
|
178
|
+
image.naturalWidth,
|
|
179
|
+
image.naturalHeight
|
|
180
|
+
);
|
|
181
|
+
};
|
|
182
|
+
const handleKeyDown = (event) => {
|
|
183
|
+
if (!hasControl) return;
|
|
184
|
+
if (NAMED_KEYS.has(event.key)) {
|
|
185
|
+
event.preventDefault();
|
|
186
|
+
command({
|
|
187
|
+
t: "key",
|
|
188
|
+
key: event.key,
|
|
189
|
+
mods: {
|
|
190
|
+
shift: event.shiftKey,
|
|
191
|
+
ctrl: event.ctrlKey,
|
|
192
|
+
alt: event.altKey,
|
|
193
|
+
meta: event.metaKey
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (event.key.length === 1 && !event.ctrlKey && !event.metaKey) {
|
|
199
|
+
event.preventDefault();
|
|
200
|
+
command({ t: "type", text: event.key });
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
const { origin, rest } = hostOf(nav.url);
|
|
204
|
+
const showTabs = tabs.length > 1;
|
|
205
|
+
return /* @__PURE__ */ jsxs(
|
|
206
|
+
"div",
|
|
207
|
+
{
|
|
208
|
+
"data-testid": testId,
|
|
209
|
+
className: "flex h-full min-h-0 flex-col bg-background",
|
|
210
|
+
children: [
|
|
211
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-h-11 items-center gap-1.5 border-b border-line bg-card px-2.5", children: [
|
|
212
|
+
/* @__PURE__ */ jsx(
|
|
213
|
+
"button",
|
|
214
|
+
{
|
|
215
|
+
type: "button",
|
|
216
|
+
"aria-label": "Back",
|
|
217
|
+
className: ICON,
|
|
218
|
+
disabled: !hasControl,
|
|
219
|
+
onClick: () => command({ t: "back" }),
|
|
220
|
+
children: /* @__PURE__ */ jsx(ChevronLeft, {})
|
|
221
|
+
}
|
|
222
|
+
),
|
|
223
|
+
/* @__PURE__ */ jsx(
|
|
224
|
+
"button",
|
|
225
|
+
{
|
|
226
|
+
type: "button",
|
|
227
|
+
"aria-label": "Forward",
|
|
228
|
+
className: ICON,
|
|
229
|
+
disabled: !hasControl,
|
|
230
|
+
onClick: () => command({ t: "forward" }),
|
|
231
|
+
children: /* @__PURE__ */ jsx(ChevronRight, {})
|
|
232
|
+
}
|
|
233
|
+
),
|
|
234
|
+
/* @__PURE__ */ jsx(
|
|
235
|
+
"button",
|
|
236
|
+
{
|
|
237
|
+
type: "button",
|
|
238
|
+
"aria-label": "Reload",
|
|
239
|
+
className: ICON,
|
|
240
|
+
disabled: !hasControl,
|
|
241
|
+
onClick: () => command({ t: "reload" }),
|
|
242
|
+
children: /* @__PURE__ */ jsx(Reload, {})
|
|
243
|
+
}
|
|
244
|
+
),
|
|
245
|
+
/* @__PURE__ */ jsx(
|
|
246
|
+
"button",
|
|
247
|
+
{
|
|
248
|
+
type: "button",
|
|
249
|
+
"data-testid": "browser-shell-control",
|
|
250
|
+
"aria-label": hasControl ? "Return control" : "Take control",
|
|
251
|
+
"aria-pressed": hasControl,
|
|
252
|
+
onClick: onToggleControl,
|
|
253
|
+
className: hasControl ? "flex size-[26px] shrink-0 items-center justify-center rounded-[5px] bg-[#F5A524] text-black" : ICON,
|
|
254
|
+
children: /* @__PURE__ */ jsx(Pointer, {})
|
|
255
|
+
}
|
|
256
|
+
),
|
|
257
|
+
/* @__PURE__ */ jsxs(
|
|
258
|
+
"div",
|
|
259
|
+
{
|
|
260
|
+
"data-testid": "browser-shell-url",
|
|
261
|
+
className: "flex h-7 min-w-0 flex-grow items-center gap-1.5 rounded-md border border-line bg-background px-2.5 text-[11px] text-foreground",
|
|
262
|
+
children: [
|
|
263
|
+
/* @__PURE__ */ jsx(
|
|
264
|
+
"span",
|
|
265
|
+
{
|
|
266
|
+
"aria-hidden": "true",
|
|
267
|
+
className: `size-1.5 shrink-0 rounded-full ${state === "connected" ? "bg-emerald-500" : "bg-muted-foreground"}`
|
|
268
|
+
}
|
|
269
|
+
),
|
|
270
|
+
/* @__PURE__ */ jsxs("span", { className: "truncate", children: [
|
|
271
|
+
origin,
|
|
272
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: rest })
|
|
273
|
+
] })
|
|
274
|
+
]
|
|
275
|
+
}
|
|
276
|
+
),
|
|
277
|
+
/* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
278
|
+
/* @__PURE__ */ jsx(
|
|
279
|
+
"button",
|
|
280
|
+
{
|
|
281
|
+
type: "button",
|
|
282
|
+
"aria-label": "More",
|
|
283
|
+
className: ICON,
|
|
284
|
+
onClick: () => setMenuOpen((open) => !open),
|
|
285
|
+
children: /* @__PURE__ */ jsx(Ellipsis, {})
|
|
286
|
+
}
|
|
287
|
+
),
|
|
288
|
+
menuOpen && /* @__PURE__ */ jsxs(
|
|
289
|
+
"div",
|
|
290
|
+
{
|
|
291
|
+
"data-testid": "browser-shell-menu",
|
|
292
|
+
className: "absolute right-0 top-8 z-10 min-w-36 rounded-md border border-line bg-card py-1 text-xs shadow-md",
|
|
293
|
+
children: [
|
|
294
|
+
onMaximize && /* @__PURE__ */ jsx(
|
|
295
|
+
"button",
|
|
296
|
+
{
|
|
297
|
+
type: "button",
|
|
298
|
+
className: "block w-full px-3 py-1.5 text-left hover:bg-secondary",
|
|
299
|
+
onClick: () => {
|
|
300
|
+
setMenuOpen(false);
|
|
301
|
+
onMaximize();
|
|
302
|
+
},
|
|
303
|
+
children: "Maximize"
|
|
304
|
+
}
|
|
305
|
+
),
|
|
306
|
+
onClose && /* @__PURE__ */ jsx(
|
|
307
|
+
"button",
|
|
308
|
+
{
|
|
309
|
+
type: "button",
|
|
310
|
+
className: "block w-full px-3 py-1.5 text-left hover:bg-secondary",
|
|
311
|
+
onClick: () => {
|
|
312
|
+
setMenuOpen(false);
|
|
313
|
+
onClose();
|
|
314
|
+
},
|
|
315
|
+
children: "Close browser"
|
|
316
|
+
}
|
|
317
|
+
)
|
|
318
|
+
]
|
|
319
|
+
}
|
|
320
|
+
)
|
|
321
|
+
] })
|
|
322
|
+
] }),
|
|
323
|
+
showTabs && /* @__PURE__ */ jsx(
|
|
324
|
+
"div",
|
|
325
|
+
{
|
|
326
|
+
"data-testid": "browser-shell-tabs",
|
|
327
|
+
className: "flex min-h-[34px] items-center gap-0.5 border-b border-line bg-card px-1.5",
|
|
328
|
+
children: tabs.map((tab) => (
|
|
329
|
+
// A div, not a button: the close control nests inside, and a
|
|
330
|
+
// button inside a button is invalid markup.
|
|
331
|
+
/* @__PURE__ */ jsxs(
|
|
332
|
+
"div",
|
|
333
|
+
{
|
|
334
|
+
role: "tab",
|
|
335
|
+
tabIndex: 0,
|
|
336
|
+
"aria-selected": tab.active,
|
|
337
|
+
"data-testid": "browser-shell-tab",
|
|
338
|
+
onClick: () => send({ t: "switch_tab", id: tab.id }),
|
|
339
|
+
onKeyDown: (event) => {
|
|
340
|
+
if (event.key === "Enter") send({ t: "switch_tab", id: tab.id });
|
|
341
|
+
},
|
|
342
|
+
className: `flex h-6 max-w-33 cursor-pointer items-center gap-1 truncate rounded px-2 text-[11px] ${tab.active ? "bg-secondary text-foreground" : "text-muted-foreground hover:text-foreground"}`,
|
|
343
|
+
children: [
|
|
344
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: tab.title || tab.url }),
|
|
345
|
+
hasControl && /* @__PURE__ */ jsx(
|
|
346
|
+
"button",
|
|
347
|
+
{
|
|
348
|
+
type: "button",
|
|
349
|
+
"data-testid": "browser-shell-tab-close",
|
|
350
|
+
"aria-label": `Close ${tab.title || tab.url}`,
|
|
351
|
+
onClick: (event) => {
|
|
352
|
+
event.stopPropagation();
|
|
353
|
+
command({ t: "close_tab", id: tab.id });
|
|
354
|
+
},
|
|
355
|
+
className: "flex size-3.5 shrink-0 items-center justify-center rounded-sm text-muted-foreground hover:bg-secondary hover:text-foreground",
|
|
356
|
+
children: /* @__PURE__ */ jsx(XSmall, {})
|
|
357
|
+
}
|
|
358
|
+
)
|
|
359
|
+
]
|
|
360
|
+
},
|
|
361
|
+
tab.id
|
|
362
|
+
)
|
|
363
|
+
))
|
|
364
|
+
}
|
|
365
|
+
),
|
|
366
|
+
/* @__PURE__ */ jsxs(
|
|
367
|
+
"div",
|
|
368
|
+
{
|
|
369
|
+
className: `relative min-h-0 flex-1 bg-black ${hasControl ? "ring-2 ring-inset ring-[#F5A524]" : ""}`,
|
|
370
|
+
tabIndex: hasControl ? 0 : -1,
|
|
371
|
+
onKeyDown: handleKeyDown,
|
|
372
|
+
children: [
|
|
373
|
+
/* @__PURE__ */ jsx(
|
|
374
|
+
"img",
|
|
375
|
+
{
|
|
376
|
+
ref: frameRef,
|
|
377
|
+
"data-testid": "browser-shell-frame",
|
|
378
|
+
alt: "",
|
|
379
|
+
src: frame2 ?? void 0,
|
|
380
|
+
draggable: false,
|
|
381
|
+
className: "h-full w-full object-contain",
|
|
382
|
+
onMouseDown: (event) => {
|
|
383
|
+
const point = pointerToPage(event);
|
|
384
|
+
if (point) command({ t: "click", ...point, count: event.detail || 1 });
|
|
385
|
+
},
|
|
386
|
+
onWheel: (event) => {
|
|
387
|
+
const point = pointerToPage(event);
|
|
388
|
+
if (point) {
|
|
389
|
+
command({
|
|
390
|
+
t: "scroll",
|
|
391
|
+
...point,
|
|
392
|
+
dx: event.deltaX,
|
|
393
|
+
dy: event.deltaY
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
),
|
|
399
|
+
state !== "connected" && /* @__PURE__ */ jsx(
|
|
400
|
+
"div",
|
|
401
|
+
{
|
|
402
|
+
"data-testid": "browser-shell-overlay",
|
|
403
|
+
className: "pointer-events-none absolute inset-0 flex items-center justify-center bg-black/70 text-sm text-muted-foreground",
|
|
404
|
+
children: state === "connecting" ? "Connecting to browser\u2026" : "Browser disconnected."
|
|
405
|
+
}
|
|
406
|
+
),
|
|
407
|
+
hasControl && /* @__PURE__ */ jsxs("div", { className: "pointer-events-none absolute bottom-3 left-1/2 flex -translate-x-1/2 items-center gap-1.5 rounded-full bg-black/85 px-3 py-1 text-[11px] font-medium text-amber-200", children: [
|
|
408
|
+
/* @__PURE__ */ jsx(Pointer, {}),
|
|
409
|
+
"You have control \xB7 click the pointer to return"
|
|
410
|
+
] })
|
|
411
|
+
]
|
|
412
|
+
}
|
|
413
|
+
)
|
|
414
|
+
]
|
|
415
|
+
}
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
var svg = {
|
|
419
|
+
width: 14,
|
|
420
|
+
height: 14,
|
|
421
|
+
viewBox: "0 0 24 24",
|
|
422
|
+
fill: "none",
|
|
423
|
+
stroke: "currentColor",
|
|
424
|
+
strokeWidth: 2,
|
|
425
|
+
strokeLinecap: "round",
|
|
426
|
+
strokeLinejoin: "round",
|
|
427
|
+
"aria-hidden": true
|
|
428
|
+
};
|
|
429
|
+
var ChevronLeft = () => /* @__PURE__ */ jsx("svg", { ...svg, children: /* @__PURE__ */ jsx("path", { d: "m15 18-6-6 6-6" }) });
|
|
430
|
+
var ChevronRight = () => /* @__PURE__ */ jsx("svg", { ...svg, children: /* @__PURE__ */ jsx("path", { d: "m9 18 6-6-6-6" }) });
|
|
431
|
+
var Reload = () => /* @__PURE__ */ jsxs("svg", { ...svg, children: [
|
|
432
|
+
/* @__PURE__ */ jsx("path", { d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" }),
|
|
433
|
+
/* @__PURE__ */ jsx("path", { d: "M3 3v5h5" })
|
|
434
|
+
] });
|
|
435
|
+
var Pointer = () => /* @__PURE__ */ jsxs("svg", { ...svg, children: [
|
|
436
|
+
/* @__PURE__ */ jsx("path", { d: "M12.586 12.586 19 19" }),
|
|
437
|
+
/* @__PURE__ */ jsx("path", { d: "M3.688 3.037a.497.497 0 0 0-.651.651l6.5 15.999a.501.501 0 0 0 .947-.062l1.569-6.083a2 2 0 0 1 1.448-1.479l6.124-1.579a.5.5 0 0 0 .063-.947z" })
|
|
438
|
+
] });
|
|
439
|
+
var XSmall = () => /* @__PURE__ */ jsxs("svg", { ...svg, width: 10, height: 10, children: [
|
|
440
|
+
/* @__PURE__ */ jsx("path", { d: "M18 6 6 18" }),
|
|
441
|
+
/* @__PURE__ */ jsx("path", { d: "m6 6 12 12" })
|
|
442
|
+
] });
|
|
443
|
+
var Ellipsis = () => /* @__PURE__ */ jsxs("svg", { ...svg, children: [
|
|
444
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "1" }),
|
|
445
|
+
/* @__PURE__ */ jsx("circle", { cx: "19", cy: "12", r: "1" }),
|
|
446
|
+
/* @__PURE__ */ jsx("circle", { cx: "5", cy: "12", r: "1" })
|
|
447
|
+
] });
|
|
448
|
+
function useBrowserControl({
|
|
449
|
+
sessionId,
|
|
450
|
+
hasControl,
|
|
451
|
+
adapter,
|
|
452
|
+
onControlAcquired,
|
|
453
|
+
onControlReleased,
|
|
454
|
+
onClose
|
|
455
|
+
}) {
|
|
456
|
+
const [pending, setPending] = useState(false);
|
|
457
|
+
const [confirmOpen, setConfirmOpen] = useState(false);
|
|
458
|
+
const [error, setError] = useState(null);
|
|
459
|
+
async function toggleControl() {
|
|
460
|
+
if (!sessionId || pending) return;
|
|
461
|
+
setPending(true);
|
|
462
|
+
setError(null);
|
|
463
|
+
try {
|
|
464
|
+
if (hasControl) {
|
|
465
|
+
await adapter.releaseBrowserControl(sessionId);
|
|
466
|
+
onControlReleased?.();
|
|
467
|
+
} else {
|
|
468
|
+
await adapter.acquireBrowserControl(sessionId);
|
|
469
|
+
onControlAcquired?.();
|
|
470
|
+
}
|
|
471
|
+
} catch (nextError) {
|
|
472
|
+
setError(nextError.message);
|
|
473
|
+
} finally {
|
|
474
|
+
setPending(false);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
async function handleConfirmClose() {
|
|
478
|
+
setError(null);
|
|
479
|
+
try {
|
|
480
|
+
if (hasControl) {
|
|
481
|
+
try {
|
|
482
|
+
await adapter.releaseBrowserControl(sessionId);
|
|
483
|
+
onControlReleased?.();
|
|
484
|
+
} catch (releaseError) {
|
|
485
|
+
console.error(
|
|
486
|
+
"Failed to release browser control before close",
|
|
487
|
+
releaseError
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
if (adapter.closeBrowserSession) {
|
|
492
|
+
await adapter.closeBrowserSession(sessionId);
|
|
493
|
+
}
|
|
494
|
+
setConfirmOpen(false);
|
|
495
|
+
onClose?.();
|
|
496
|
+
} catch (nextError) {
|
|
497
|
+
setError(nextError.message);
|
|
498
|
+
throw nextError;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return {
|
|
502
|
+
pending,
|
|
503
|
+
error,
|
|
504
|
+
confirmOpen,
|
|
505
|
+
setConfirmOpen,
|
|
506
|
+
toggleControl,
|
|
507
|
+
handleConfirmClose
|
|
508
|
+
};
|
|
509
|
+
}
|
|
35
510
|
var buttonVariants = cva(
|
|
36
511
|
"group/button inline-flex shrink-0 items-center justify-center rounded-none border border-transparent bg-clip-padding text-xs font-semibold tracking-widest whitespace-nowrap uppercase transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/30 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
|
|
37
512
|
{
|
|
@@ -260,369 +735,34 @@ function ConfirmDialog({
|
|
|
260
735
|
}
|
|
261
736
|
);
|
|
262
737
|
}
|
|
263
|
-
function
|
|
738
|
+
function BrowserPane({
|
|
264
739
|
sessionId,
|
|
265
|
-
|
|
266
|
-
adapter,
|
|
267
|
-
onControlAcquired,
|
|
268
|
-
onControlReleased,
|
|
269
|
-
onClose
|
|
270
|
-
}) {
|
|
271
|
-
const [pending, setPending] = useState(false);
|
|
272
|
-
const [confirmOpen, setConfirmOpen] = useState(false);
|
|
273
|
-
const [error, setError] = useState(null);
|
|
274
|
-
async function toggleControl() {
|
|
275
|
-
if (!sessionId || pending) return;
|
|
276
|
-
setPending(true);
|
|
277
|
-
setError(null);
|
|
278
|
-
try {
|
|
279
|
-
if (hasControl) {
|
|
280
|
-
await adapter.releaseBrowserControl(sessionId);
|
|
281
|
-
onControlReleased?.();
|
|
282
|
-
} else {
|
|
283
|
-
await adapter.acquireBrowserControl(sessionId);
|
|
284
|
-
onControlAcquired?.();
|
|
285
|
-
}
|
|
286
|
-
} catch (nextError) {
|
|
287
|
-
setError(nextError.message);
|
|
288
|
-
} finally {
|
|
289
|
-
setPending(false);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
async function handleConfirmClose() {
|
|
293
|
-
setError(null);
|
|
294
|
-
try {
|
|
295
|
-
if (hasControl) {
|
|
296
|
-
try {
|
|
297
|
-
await adapter.releaseBrowserControl(sessionId);
|
|
298
|
-
onControlReleased?.();
|
|
299
|
-
} catch (releaseError) {
|
|
300
|
-
console.error(
|
|
301
|
-
"Failed to release browser control before close",
|
|
302
|
-
releaseError
|
|
303
|
-
);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
if (adapter.closeBrowserSession) {
|
|
307
|
-
await adapter.closeBrowserSession(sessionId);
|
|
308
|
-
}
|
|
309
|
-
setConfirmOpen(false);
|
|
310
|
-
onClose?.();
|
|
311
|
-
} catch (nextError) {
|
|
312
|
-
setError(nextError.message);
|
|
313
|
-
throw nextError;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
return /* @__PURE__ */ jsxs("div", { className: "flex min-h-11 items-center gap-2 border-t border-line bg-card px-3 py-2", children: [
|
|
317
|
-
/* @__PURE__ */ jsxs(
|
|
318
|
-
Button,
|
|
319
|
-
{
|
|
320
|
-
type: "button",
|
|
321
|
-
size: "xs",
|
|
322
|
-
variant: hasControl ? "secondary" : "default",
|
|
323
|
-
disabled: !sessionId || pending,
|
|
324
|
-
onClick: () => void toggleControl(),
|
|
325
|
-
children: [
|
|
326
|
-
hasControl ? /* @__PURE__ */ jsx(RotateCcwIcon, { className: "size-3", "aria-hidden": "true" }) : /* @__PURE__ */ jsx(MousePointer2Icon, { className: "size-3", "aria-hidden": "true" }),
|
|
327
|
-
hasControl ? "Return control" : "Take control"
|
|
328
|
-
]
|
|
329
|
-
}
|
|
330
|
-
),
|
|
331
|
-
onClose && /* @__PURE__ */ jsxs(
|
|
332
|
-
Button,
|
|
333
|
-
{
|
|
334
|
-
type: "button",
|
|
335
|
-
size: "xs",
|
|
336
|
-
variant: "secondary",
|
|
337
|
-
disabled: !sessionId,
|
|
338
|
-
onClick: () => {
|
|
339
|
-
setError(null);
|
|
340
|
-
setConfirmOpen(true);
|
|
341
|
-
},
|
|
342
|
-
children: [
|
|
343
|
-
/* @__PURE__ */ jsx(XIcon, { className: "size-3", "aria-hidden": "true" }),
|
|
344
|
-
"Close"
|
|
345
|
-
]
|
|
346
|
-
}
|
|
347
|
-
),
|
|
348
|
-
/* @__PURE__ */ jsxs("span", { className: "ml-auto flex items-center gap-1.5 text-[11px] text-muted-foreground", children: [
|
|
349
|
-
/* @__PURE__ */ jsx("span", { className: "size-1.5 rounded-full bg-red-500", "aria-hidden": "true" }),
|
|
350
|
-
"Live"
|
|
351
|
-
] }),
|
|
352
|
-
error && /* @__PURE__ */ jsx("span", { className: "max-w-40 truncate text-[11px] text-destructive", children: error }),
|
|
353
|
-
/* @__PURE__ */ jsx(
|
|
354
|
-
ConfirmDialog,
|
|
355
|
-
{
|
|
356
|
-
open: confirmOpen,
|
|
357
|
-
title: "Close browser session?",
|
|
358
|
-
description: adapter.closeBrowserSession ? "This permanently shuts down the browser sandbox for this session. The agent will lose access to the page until it re-opens a browser. This cannot be undone." : "This hides the browser panel. The sandbox stays running so the agent can keep using it.",
|
|
359
|
-
confirmLabel: "Close browser",
|
|
360
|
-
variant: "destructive",
|
|
361
|
-
confirmIcon: /* @__PURE__ */ jsx(XIcon, { className: "size-3.5", "aria-hidden": "true" }),
|
|
362
|
-
onConfirm: handleConfirmClose,
|
|
363
|
-
onCancel: () => setConfirmOpen(false)
|
|
364
|
-
}
|
|
365
|
-
)
|
|
366
|
-
] });
|
|
367
|
-
}
|
|
368
|
-
var MIN_ZOOM = 1;
|
|
369
|
-
var MAX_ZOOM = 3;
|
|
370
|
-
var ZOOM_STEP = 0.5;
|
|
371
|
-
function clampZoom(zoom) {
|
|
372
|
-
const bounded = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, zoom));
|
|
373
|
-
return Math.round(bounded * 100) / 100;
|
|
374
|
-
}
|
|
375
|
-
function toWsUrl(src) {
|
|
376
|
-
const url = new URL(src, window.location.href);
|
|
377
|
-
if (url.protocol === "http:") {
|
|
378
|
-
url.protocol = "ws:";
|
|
379
|
-
} else if (url.protocol === "https:") {
|
|
380
|
-
url.protocol = "wss:";
|
|
381
|
-
}
|
|
382
|
-
return url.toString();
|
|
383
|
-
}
|
|
384
|
-
function BrowserLiveView({
|
|
385
|
-
src,
|
|
386
|
-
testId = "browser-rfb",
|
|
387
|
-
onDisconnect
|
|
388
|
-
}) {
|
|
389
|
-
const containerRef = useRef(null);
|
|
390
|
-
const onDisconnectRef = useRef(onDisconnect);
|
|
391
|
-
onDisconnectRef.current = onDisconnect;
|
|
392
|
-
const [state, setState] = useState("connecting");
|
|
393
|
-
const [zoom, setZoom] = useState(MIN_ZOOM);
|
|
394
|
-
useEffect(() => {
|
|
395
|
-
const container = containerRef.current;
|
|
396
|
-
if (!container) return;
|
|
397
|
-
setState("connecting");
|
|
398
|
-
setZoom(MIN_ZOOM);
|
|
399
|
-
let rfb;
|
|
400
|
-
let disposed = false;
|
|
401
|
-
const handleConnect = () => setState("connected");
|
|
402
|
-
const handleDisconnect = (event) => {
|
|
403
|
-
const detail = event.detail;
|
|
404
|
-
setState("disconnected");
|
|
405
|
-
onDisconnectRef.current?.(detail?.clean ?? false);
|
|
406
|
-
};
|
|
407
|
-
void import('@novnc/novnc').then((mod) => {
|
|
408
|
-
if (disposed || !containerRef.current) return;
|
|
409
|
-
const instance = new mod.default(containerRef.current, toWsUrl(src), {
|
|
410
|
-
wsProtocols: ["binary"]
|
|
411
|
-
});
|
|
412
|
-
instance.viewOnly = false;
|
|
413
|
-
instance.scaleViewport = true;
|
|
414
|
-
instance.addEventListener("connect", handleConnect);
|
|
415
|
-
instance.addEventListener("disconnect", handleDisconnect);
|
|
416
|
-
rfb = instance;
|
|
417
|
-
}).catch(() => {
|
|
418
|
-
if (!disposed) setState("disconnected");
|
|
419
|
-
});
|
|
420
|
-
return () => {
|
|
421
|
-
disposed = true;
|
|
422
|
-
if (rfb) {
|
|
423
|
-
rfb.removeEventListener("connect", handleConnect);
|
|
424
|
-
rfb.removeEventListener("disconnect", handleDisconnect);
|
|
425
|
-
rfb.disconnect();
|
|
426
|
-
}
|
|
427
|
-
};
|
|
428
|
-
}, [src]);
|
|
429
|
-
const keepCanvasFocus = (event) => event.preventDefault();
|
|
430
|
-
const zoomTo = (next) => setZoom((current) => clampZoom(next || current));
|
|
431
|
-
return /* @__PURE__ */ jsxs("div", { className: "relative h-full w-full bg-black", children: [
|
|
432
|
-
/* @__PURE__ */ jsx("div", { className: "absolute inset-0 overflow-auto", children: /* @__PURE__ */ jsx(
|
|
433
|
-
"div",
|
|
434
|
-
{
|
|
435
|
-
ref: containerRef,
|
|
436
|
-
"data-testid": testId,
|
|
437
|
-
style: { width: `${zoom * 100}%`, height: `${zoom * 100}%` }
|
|
438
|
-
}
|
|
439
|
-
) }),
|
|
440
|
-
state === "connected" && /* @__PURE__ */ jsxs(
|
|
441
|
-
"div",
|
|
442
|
-
{
|
|
443
|
-
"data-testid": "browser-rfb-zoom",
|
|
444
|
-
className: "absolute bottom-3 right-3 flex items-center gap-0.5 rounded-md border border-line bg-card/90 p-0.5 text-muted-foreground shadow-sm backdrop-blur",
|
|
445
|
-
children: [
|
|
446
|
-
/* @__PURE__ */ jsx(
|
|
447
|
-
"button",
|
|
448
|
-
{
|
|
449
|
-
type: "button",
|
|
450
|
-
tabIndex: -1,
|
|
451
|
-
"aria-label": "Zoom out",
|
|
452
|
-
disabled: zoom <= MIN_ZOOM,
|
|
453
|
-
onMouseDown: keepCanvasFocus,
|
|
454
|
-
onClick: () => zoomTo(zoom - ZOOM_STEP),
|
|
455
|
-
className: "flex size-6 items-center justify-center rounded text-sm leading-none hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-40",
|
|
456
|
-
children: "\u2212"
|
|
457
|
-
}
|
|
458
|
-
),
|
|
459
|
-
/* @__PURE__ */ jsxs(
|
|
460
|
-
"button",
|
|
461
|
-
{
|
|
462
|
-
type: "button",
|
|
463
|
-
tabIndex: -1,
|
|
464
|
-
"aria-label": "Reset zoom",
|
|
465
|
-
disabled: zoom === MIN_ZOOM,
|
|
466
|
-
onMouseDown: keepCanvasFocus,
|
|
467
|
-
onClick: () => setZoom(MIN_ZOOM),
|
|
468
|
-
className: "min-w-10 rounded px-1 text-center text-xs tabular-nums hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none",
|
|
469
|
-
children: [
|
|
470
|
-
Math.round(zoom * 100),
|
|
471
|
-
"%"
|
|
472
|
-
]
|
|
473
|
-
}
|
|
474
|
-
),
|
|
475
|
-
/* @__PURE__ */ jsx(
|
|
476
|
-
"button",
|
|
477
|
-
{
|
|
478
|
-
type: "button",
|
|
479
|
-
tabIndex: -1,
|
|
480
|
-
"aria-label": "Zoom in",
|
|
481
|
-
disabled: zoom >= MAX_ZOOM,
|
|
482
|
-
onMouseDown: keepCanvasFocus,
|
|
483
|
-
onClick: () => zoomTo(zoom + ZOOM_STEP),
|
|
484
|
-
className: "flex size-6 items-center justify-center rounded text-sm leading-none hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-40",
|
|
485
|
-
children: "+"
|
|
486
|
-
}
|
|
487
|
-
)
|
|
488
|
-
]
|
|
489
|
-
}
|
|
490
|
-
),
|
|
491
|
-
state !== "connected" && /* @__PURE__ */ jsx(
|
|
492
|
-
"div",
|
|
493
|
-
{
|
|
494
|
-
"data-testid": "browser-rfb-overlay",
|
|
495
|
-
className: "pointer-events-none absolute inset-0 flex items-center justify-center bg-black/70 text-sm text-muted-foreground",
|
|
496
|
-
children: state === "connecting" ? "Connecting to browser\u2026" : "Live view disconnected \u2014 take control again to reconnect."
|
|
497
|
-
}
|
|
498
|
-
)
|
|
499
|
-
] });
|
|
500
|
-
}
|
|
501
|
-
function BrowserStatusDot({
|
|
502
|
-
status
|
|
503
|
-
}) {
|
|
504
|
-
return /* @__PURE__ */ jsx(
|
|
505
|
-
"span",
|
|
506
|
-
{
|
|
507
|
-
"aria-hidden": "true",
|
|
508
|
-
className: cn(
|
|
509
|
-
"inline-block size-2 shrink-0 rounded-full",
|
|
510
|
-
status === "live" && "bg-emerald-500",
|
|
511
|
-
status === "user-control" && "bg-amber-500",
|
|
512
|
-
status === "provisioning" && "animate-pulse bg-sky-500",
|
|
513
|
-
status === "closed" && "bg-muted-foreground"
|
|
514
|
-
)
|
|
515
|
-
}
|
|
516
|
-
);
|
|
517
|
-
}
|
|
518
|
-
function TooltipProvider({
|
|
519
|
-
delayDuration = 0,
|
|
520
|
-
...props
|
|
521
|
-
}) {
|
|
522
|
-
return /* @__PURE__ */ jsx(
|
|
523
|
-
Tooltip$1.Provider,
|
|
524
|
-
{
|
|
525
|
-
"data-slot": "tooltip-provider",
|
|
526
|
-
delayDuration,
|
|
527
|
-
...props
|
|
528
|
-
}
|
|
529
|
-
);
|
|
530
|
-
}
|
|
531
|
-
function Tooltip({
|
|
532
|
-
...props
|
|
533
|
-
}) {
|
|
534
|
-
return /* @__PURE__ */ jsx(Tooltip$1.Root, { "data-slot": "tooltip", ...props });
|
|
535
|
-
}
|
|
536
|
-
function TooltipTrigger({
|
|
537
|
-
...props
|
|
538
|
-
}) {
|
|
539
|
-
return /* @__PURE__ */ jsx(Tooltip$1.Trigger, { "data-slot": "tooltip-trigger", ...props });
|
|
540
|
-
}
|
|
541
|
-
function TooltipContent({
|
|
542
|
-
className,
|
|
543
|
-
sideOffset = 0,
|
|
544
|
-
children,
|
|
545
|
-
...props
|
|
546
|
-
}) {
|
|
547
|
-
return /* @__PURE__ */ jsx(Tooltip$1.Portal, { children: /* @__PURE__ */ jsxs(
|
|
548
|
-
Tooltip$1.Content,
|
|
549
|
-
{
|
|
550
|
-
"data-slot": "tooltip-content",
|
|
551
|
-
sideOffset,
|
|
552
|
-
className: cn(
|
|
553
|
-
"z-50 inline-flex w-fit max-w-xs origin-(--radix-tooltip-content-transform-origin) items-center gap-1.5 rounded-none bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-none data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
|
554
|
-
className
|
|
555
|
-
),
|
|
556
|
-
...props,
|
|
557
|
-
children: [
|
|
558
|
-
children,
|
|
559
|
-
/* @__PURE__ */ jsx(Tooltip$1.Arrow, { className: "z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-none bg-foreground fill-foreground" })
|
|
560
|
-
]
|
|
561
|
-
}
|
|
562
|
-
) });
|
|
563
|
-
}
|
|
564
|
-
function BrowserPane({
|
|
565
|
-
sessionId,
|
|
566
|
-
state,
|
|
740
|
+
state,
|
|
567
741
|
adapter,
|
|
568
742
|
onClose
|
|
569
743
|
}) {
|
|
570
744
|
const [fullscreenOpen, setFullscreenOpen] = useState(false);
|
|
571
745
|
const [openFullscreenOnControl, setOpenFullscreenOnControl] = useState(false);
|
|
572
746
|
const [localControlActive, setLocalControlActive] = useState(false);
|
|
573
|
-
const
|
|
574
|
-
const [previewLoading, setPreviewLoading] = useState(false);
|
|
575
|
-
const [previewError, setPreviewError] = useState(null);
|
|
576
|
-
const hasLiveViewAdapter = typeof adapter.browserLiveViewUrl === "function";
|
|
577
|
-
const hasPreviewAdapter = typeof adapter.getBrowserPreviewSnapshot === "function";
|
|
747
|
+
const hasShellAdapter = typeof adapter.browserShellUrl === "function";
|
|
578
748
|
const hasControlAdapter = typeof adapter.acquireBrowserControl === "function" && typeof adapter.releaseBrowserControl === "function";
|
|
579
|
-
const
|
|
580
|
-
if (!
|
|
581
|
-
return adapter.
|
|
582
|
-
}, [adapter,
|
|
749
|
+
const shellUrl = useMemo(() => {
|
|
750
|
+
if (!hasShellAdapter) return "";
|
|
751
|
+
return adapter.browserShellUrl(sessionId);
|
|
752
|
+
}, [adapter, hasShellAdapter, sessionId]);
|
|
583
753
|
const hasLiveView = state.status !== "provisioning" && state.status !== "closed";
|
|
584
|
-
const
|
|
585
|
-
const canUseLiveView = hasLiveView && hasUserControl && Boolean(liveViewUrl);
|
|
586
|
-
const canOpenPreview = hasLiveView && (hasPreviewAdapter || canUseLiveView);
|
|
587
|
-
const canOpenFullscreen = canOpenPreview;
|
|
588
|
-
const refreshPreview = useCallback(
|
|
589
|
-
async (signal) => {
|
|
590
|
-
if (!hasPreviewAdapter || canUseLiveView) return;
|
|
591
|
-
setPreviewLoading(true);
|
|
592
|
-
setPreviewError(null);
|
|
593
|
-
try {
|
|
594
|
-
const snapshot = await adapter.getBrowserPreviewSnapshot?.(sessionId);
|
|
595
|
-
if (signal?.aborted) return;
|
|
596
|
-
setPreviewSnapshot(snapshot ?? null);
|
|
597
|
-
if (!snapshot) {
|
|
598
|
-
setPreviewError("Browser preview is unavailable.");
|
|
599
|
-
}
|
|
600
|
-
} catch (error) {
|
|
601
|
-
if (signal?.aborted) return;
|
|
602
|
-
setPreviewSnapshot(null);
|
|
603
|
-
setPreviewError(
|
|
604
|
-
error instanceof Error ? error.message : "Browser preview failed."
|
|
605
|
-
);
|
|
606
|
-
} finally {
|
|
607
|
-
if (!signal?.aborted) setPreviewLoading(false);
|
|
608
|
-
}
|
|
609
|
-
},
|
|
610
|
-
[adapter, canUseLiveView, hasPreviewAdapter, sessionId]
|
|
611
|
-
);
|
|
754
|
+
const canUseShell = hasLiveView && Boolean(shellUrl);
|
|
612
755
|
useEffect(() => {
|
|
613
756
|
setFullscreenOpen(false);
|
|
614
757
|
setOpenFullscreenOnControl(false);
|
|
615
758
|
setLocalControlActive(false);
|
|
616
|
-
setPreviewSnapshot(null);
|
|
617
|
-
setPreviewLoading(false);
|
|
618
|
-
setPreviewError(null);
|
|
619
759
|
}, [sessionId]);
|
|
620
760
|
useEffect(() => {
|
|
621
761
|
if (!openFullscreenOnControl) return;
|
|
622
|
-
if (!
|
|
762
|
+
if (!canUseShell) return;
|
|
623
763
|
setFullscreenOpen(true);
|
|
624
764
|
setOpenFullscreenOnControl(false);
|
|
625
|
-
}, [
|
|
765
|
+
}, [canUseShell, openFullscreenOnControl]);
|
|
626
766
|
const handleFullscreenOpenChange = useCallback(
|
|
627
767
|
(open) => {
|
|
628
768
|
setFullscreenOpen(open);
|
|
@@ -630,7 +770,7 @@ function BrowserPane({
|
|
|
630
770
|
[]
|
|
631
771
|
);
|
|
632
772
|
useEffect(() => {
|
|
633
|
-
if (!
|
|
773
|
+
if (!localControlActive || !hasControlAdapter) return;
|
|
634
774
|
const handle = window.setInterval(() => {
|
|
635
775
|
void adapter.acquireBrowserControl(sessionId).catch((error) => {
|
|
636
776
|
console.error("Failed to refresh browser control", error);
|
|
@@ -638,30 +778,31 @@ function BrowserPane({
|
|
|
638
778
|
});
|
|
639
779
|
}, 25e3);
|
|
640
780
|
return () => window.clearInterval(handle);
|
|
641
|
-
}, [adapter,
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
781
|
+
}, [adapter, localControlActive, hasControlAdapter, sessionId]);
|
|
782
|
+
const control = useBrowserControl({
|
|
783
|
+
sessionId,
|
|
784
|
+
hasControl: localControlActive,
|
|
785
|
+
adapter,
|
|
786
|
+
onControlAcquired: () => {
|
|
787
|
+
setLocalControlActive(true);
|
|
788
|
+
setOpenFullscreenOnControl(true);
|
|
789
|
+
},
|
|
790
|
+
onControlReleased: () => setLocalControlActive(false),
|
|
791
|
+
onClose
|
|
792
|
+
});
|
|
793
|
+
const shell = (testId) => /* @__PURE__ */ jsx(
|
|
794
|
+
BrowserShell,
|
|
795
|
+
{
|
|
796
|
+
src: shellUrl,
|
|
797
|
+
testId,
|
|
798
|
+
hasControl: localControlActive,
|
|
799
|
+
onToggleControl: hasControlAdapter ? () => void control.toggleControl() : void 0,
|
|
800
|
+
onClose: onClose ? () => control.setConfirmOpen(true) : void 0,
|
|
801
|
+
onMaximize: fullscreenOpen ? void 0 : () => setFullscreenOpen(true),
|
|
802
|
+
onDisconnect: () => setLocalControlActive(false)
|
|
647
803
|
}
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
void refreshPreview(abort.signal);
|
|
651
|
-
const timer = window.setInterval(() => {
|
|
652
|
-
void refreshPreview(abort.signal);
|
|
653
|
-
}, 5e3);
|
|
654
|
-
return () => {
|
|
655
|
-
abort.abort();
|
|
656
|
-
window.clearInterval(timer);
|
|
657
|
-
};
|
|
658
|
-
}, [
|
|
659
|
-
canUseLiveView,
|
|
660
|
-
fullscreenOpen,
|
|
661
|
-
hasLiveView,
|
|
662
|
-
hasPreviewAdapter,
|
|
663
|
-
refreshPreview
|
|
664
|
-
]);
|
|
804
|
+
);
|
|
805
|
+
const placeholder = (message) => /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: message });
|
|
665
806
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
666
807
|
/* @__PURE__ */ jsxs(
|
|
667
808
|
"div",
|
|
@@ -669,56 +810,8 @@ function BrowserPane({
|
|
|
669
810
|
"data-testid": "browser-pane",
|
|
670
811
|
className: "flex h-full min-h-0 flex-col bg-background",
|
|
671
812
|
children: [
|
|
672
|
-
/* @__PURE__ */
|
|
673
|
-
|
|
674
|
-
/* @__PURE__ */ jsx("span", { className: "font-medium", children: "Browser" }),
|
|
675
|
-
/* @__PURE__ */ jsx(BrowserStatusDot, { status: state.status }),
|
|
676
|
-
state.controlOwner && /* @__PURE__ */ jsxs("span", { className: "min-w-0 truncate text-amber-500", children: [
|
|
677
|
-
state.controlOwner,
|
|
678
|
-
" has control"
|
|
679
|
-
] }),
|
|
680
|
-
canOpenFullscreen && /* @__PURE__ */ jsx("div", { className: "ml-auto flex items-center", children: /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
681
|
-
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
682
|
-
"button",
|
|
683
|
-
{
|
|
684
|
-
type: "button",
|
|
685
|
-
"aria-label": "Maximize browser",
|
|
686
|
-
className: "inline-flex size-7 items-center justify-center text-muted-foreground transition-colors hover:bg-secondary hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
687
|
-
onClick: () => setFullscreenOpen(true),
|
|
688
|
-
children: /* @__PURE__ */ jsx(Maximize2Icon, { className: "size-3.5", "aria-hidden": "true" })
|
|
689
|
-
}
|
|
690
|
-
) }),
|
|
691
|
-
/* @__PURE__ */ jsx(TooltipContent, { side: "bottom", children: "Full screen" })
|
|
692
|
-
] }) }) })
|
|
693
|
-
] }),
|
|
694
|
-
/* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 bg-black", children: state.status === "provisioning" ? /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: "Starting browser..." }) : state.status === "closed" ? /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: "Browser closed." }) : canUseLiveView ? /* @__PURE__ */ jsx(
|
|
695
|
-
BrowserLiveView,
|
|
696
|
-
{
|
|
697
|
-
src: liveViewUrl,
|
|
698
|
-
onDisconnect: () => setLocalControlActive(false)
|
|
699
|
-
}
|
|
700
|
-
) : previewSnapshot ? /* @__PURE__ */ jsx(
|
|
701
|
-
BrowserPreviewImage,
|
|
702
|
-
{
|
|
703
|
-
src: previewSnapshot.src,
|
|
704
|
-
fit: "contain",
|
|
705
|
-
testId: "browser-preview-image"
|
|
706
|
-
}
|
|
707
|
-
) : previewLoading ? /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: "Loading browser preview..." }) : previewError ? /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: previewError }) : /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: "Browser preview is unavailable." }) }),
|
|
708
|
-
hasLiveView && hasControlAdapter && /* @__PURE__ */ jsx(
|
|
709
|
-
BrowserControlBar,
|
|
710
|
-
{
|
|
711
|
-
sessionId,
|
|
712
|
-
hasControl: localControlActive,
|
|
713
|
-
adapter,
|
|
714
|
-
onControlAcquired: () => {
|
|
715
|
-
setLocalControlActive(true);
|
|
716
|
-
setOpenFullscreenOnControl(true);
|
|
717
|
-
},
|
|
718
|
-
onControlReleased: () => setLocalControlActive(false),
|
|
719
|
-
onClose
|
|
720
|
-
}
|
|
721
|
-
)
|
|
813
|
+
/* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1", children: state.status === "provisioning" ? placeholder("Starting browser...") : state.status === "closed" ? placeholder("Browser closed.") : canUseShell ? shell() : placeholder("Browser view is unavailable.") }),
|
|
814
|
+
control.error && /* @__PURE__ */ jsx("div", { className: "border-t border-line bg-card px-3 py-1.5 text-[11px] text-destructive", children: control.error })
|
|
722
815
|
]
|
|
723
816
|
}
|
|
724
817
|
),
|
|
@@ -728,50 +821,72 @@ function BrowserPane({
|
|
|
728
821
|
"aria-describedby": void 0,
|
|
729
822
|
className: "flex h-dvh w-screen max-w-none flex-col gap-0 overflow-hidden rounded-none border-0 bg-background p-0 shadow-none ring-0 sm:max-w-none",
|
|
730
823
|
children: [
|
|
731
|
-
/* @__PURE__ */
|
|
732
|
-
|
|
733
|
-
/* @__PURE__ */ jsx(DialogTitle, { className: "text-xs normal-case tracking-normal", children: "Browser" }),
|
|
734
|
-
/* @__PURE__ */ jsx(BrowserStatusDot, { status: state.status }),
|
|
735
|
-
state.controlOwner && /* @__PURE__ */ jsxs("span", { className: "min-w-0 truncate text-xs text-amber-500", children: [
|
|
736
|
-
state.controlOwner,
|
|
737
|
-
" has control"
|
|
738
|
-
] })
|
|
739
|
-
] }),
|
|
740
|
-
/* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 bg-black", children: canUseLiveView ? /* @__PURE__ */ jsx(
|
|
741
|
-
BrowserLiveView,
|
|
742
|
-
{
|
|
743
|
-
src: liveViewUrl,
|
|
744
|
-
testId: "browser-fullscreen-rfb",
|
|
745
|
-
onDisconnect: () => setLocalControlActive(false)
|
|
746
|
-
}
|
|
747
|
-
) : previewSnapshot ? /* @__PURE__ */ jsx(
|
|
748
|
-
BrowserPreviewImage,
|
|
749
|
-
{
|
|
750
|
-
src: previewSnapshot.src,
|
|
751
|
-
fit: "contain",
|
|
752
|
-
testId: "browser-fullscreen-preview-image"
|
|
753
|
-
}
|
|
754
|
-
) : previewLoading ? /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: "Loading browser preview..." }) : previewError ? /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: previewError }) : /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: "Browser preview is unavailable." }) })
|
|
824
|
+
/* @__PURE__ */ jsx(DialogHeader, { className: "sr-only", children: /* @__PURE__ */ jsx(DialogTitle, { children: "Browser" }) }),
|
|
825
|
+
/* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1", children: canUseShell ? shell("browser-fullscreen-shell") : placeholder("Browser view is unavailable.") })
|
|
755
826
|
]
|
|
756
827
|
}
|
|
757
|
-
) })
|
|
828
|
+
) }),
|
|
829
|
+
/* @__PURE__ */ jsx(
|
|
830
|
+
ConfirmDialog,
|
|
831
|
+
{
|
|
832
|
+
open: control.confirmOpen,
|
|
833
|
+
title: "Close browser session?",
|
|
834
|
+
description: adapter.closeBrowserSession ? "This permanently shuts down the browser sandbox for this session. The agent will lose access to the page until it re-opens a browser. This cannot be undone." : "This hides the browser panel. The sandbox stays running so the agent can keep using it.",
|
|
835
|
+
confirmLabel: "Close browser",
|
|
836
|
+
variant: "destructive",
|
|
837
|
+
onConfirm: control.handleConfirmClose,
|
|
838
|
+
onCancel: () => control.setConfirmOpen(false)
|
|
839
|
+
}
|
|
840
|
+
)
|
|
758
841
|
] });
|
|
759
842
|
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
843
|
+
var POLL_MS = 15e3;
|
|
844
|
+
var MAX_FAILURES = 3;
|
|
845
|
+
function useBrowserPreview({
|
|
846
|
+
adapter,
|
|
847
|
+
sessionId,
|
|
848
|
+
enabled
|
|
764
849
|
}) {
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
draggable: false
|
|
850
|
+
const [src, setSrc] = useState(null);
|
|
851
|
+
const adapterRef = useRef(adapter);
|
|
852
|
+
adapterRef.current = adapter;
|
|
853
|
+
useEffect(() => {
|
|
854
|
+
if (!enabled || !sessionId) {
|
|
855
|
+
setSrc(null);
|
|
856
|
+
return;
|
|
773
857
|
}
|
|
774
|
-
|
|
858
|
+
let cancelled = false;
|
|
859
|
+
let failures = 0;
|
|
860
|
+
let timer = 0;
|
|
861
|
+
const refresh = async () => {
|
|
862
|
+
if (document.hidden) return;
|
|
863
|
+
const fetchSnapshot = adapterRef.current.getBrowserPreviewSnapshot;
|
|
864
|
+
if (typeof fetchSnapshot !== "function") return;
|
|
865
|
+
try {
|
|
866
|
+
const snapshot = await fetchSnapshot(sessionId);
|
|
867
|
+
if (cancelled) return;
|
|
868
|
+
if (!snapshot) {
|
|
869
|
+
window.clearInterval(timer);
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
failures = 0;
|
|
873
|
+
if (snapshot.src) setSrc(snapshot.src);
|
|
874
|
+
} catch {
|
|
875
|
+
if (cancelled) return;
|
|
876
|
+
failures += 1;
|
|
877
|
+
if (failures >= MAX_FAILURES) {
|
|
878
|
+
window.clearInterval(timer);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
};
|
|
882
|
+
void refresh();
|
|
883
|
+
timer = window.setInterval(() => void refresh(), POLL_MS);
|
|
884
|
+
return () => {
|
|
885
|
+
cancelled = true;
|
|
886
|
+
window.clearInterval(timer);
|
|
887
|
+
};
|
|
888
|
+
}, [enabled, sessionId]);
|
|
889
|
+
return src;
|
|
775
890
|
}
|
|
776
891
|
var Conversation = ({ className, ...props }) => /* @__PURE__ */ jsx(
|
|
777
892
|
StickToBottom,
|
|
@@ -871,17 +986,63 @@ function ButtonGroup({
|
|
|
871
986
|
}
|
|
872
987
|
);
|
|
873
988
|
}
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
989
|
+
function TooltipProvider({
|
|
990
|
+
delayDuration = 0,
|
|
991
|
+
...props
|
|
992
|
+
}) {
|
|
993
|
+
return /* @__PURE__ */ jsx(
|
|
994
|
+
Tooltip$1.Provider,
|
|
995
|
+
{
|
|
996
|
+
"data-slot": "tooltip-provider",
|
|
997
|
+
delayDuration,
|
|
998
|
+
...props
|
|
999
|
+
}
|
|
1000
|
+
);
|
|
1001
|
+
}
|
|
1002
|
+
function Tooltip({
|
|
1003
|
+
...props
|
|
1004
|
+
}) {
|
|
1005
|
+
return /* @__PURE__ */ jsx(Tooltip$1.Root, { "data-slot": "tooltip", ...props });
|
|
1006
|
+
}
|
|
1007
|
+
function TooltipTrigger({
|
|
1008
|
+
...props
|
|
1009
|
+
}) {
|
|
1010
|
+
return /* @__PURE__ */ jsx(Tooltip$1.Trigger, { "data-slot": "tooltip-trigger", ...props });
|
|
1011
|
+
}
|
|
1012
|
+
function TooltipContent({
|
|
1013
|
+
className,
|
|
1014
|
+
sideOffset = 0,
|
|
1015
|
+
children,
|
|
1016
|
+
...props
|
|
1017
|
+
}) {
|
|
1018
|
+
return /* @__PURE__ */ jsx(Tooltip$1.Portal, { children: /* @__PURE__ */ jsxs(
|
|
1019
|
+
Tooltip$1.Content,
|
|
1020
|
+
{
|
|
1021
|
+
"data-slot": "tooltip-content",
|
|
1022
|
+
sideOffset,
|
|
1023
|
+
className: cn(
|
|
1024
|
+
"z-50 inline-flex w-fit max-w-xs origin-(--radix-tooltip-content-transform-origin) items-center gap-1.5 rounded-none bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-none data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
|
1025
|
+
className
|
|
1026
|
+
),
|
|
1027
|
+
...props,
|
|
1028
|
+
children: [
|
|
1029
|
+
children,
|
|
1030
|
+
/* @__PURE__ */ jsx(Tooltip$1.Arrow, { className: "z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-none bg-foreground fill-foreground" })
|
|
1031
|
+
]
|
|
1032
|
+
}
|
|
1033
|
+
) });
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// src/lib/oauth-popup.ts
|
|
1037
|
+
function openOAuthPopup(url) {
|
|
1038
|
+
const w = 600;
|
|
1039
|
+
const h = 720;
|
|
1040
|
+
const dualLeft = window.screenLeft ?? window.screenX ?? 0;
|
|
1041
|
+
const dualTop = window.screenTop ?? window.screenY ?? 0;
|
|
1042
|
+
const width = window.innerWidth || document.documentElement.clientWidth || w;
|
|
1043
|
+
const height = window.innerHeight || document.documentElement.clientHeight || h;
|
|
1044
|
+
const left = dualLeft + Math.max(0, (width - w) / 2);
|
|
1045
|
+
const top = dualTop + Math.max(0, (height - h) / 2);
|
|
885
1046
|
const features = `popup=yes,width=${w},height=${h},left=${left},top=${top}`;
|
|
886
1047
|
const popup = window.open(url, "composio-oauth", features);
|
|
887
1048
|
if (!popup) {
|
|
@@ -2648,6 +2809,25 @@ function hostname(url) {
|
|
|
2648
2809
|
function truncate2(s, n) {
|
|
2649
2810
|
return s.length > n ? `${s.slice(0, n)}\u2026` : s;
|
|
2650
2811
|
}
|
|
2812
|
+
function KnowledgeBaseToolBlock({ tc }) {
|
|
2813
|
+
const args = parseArgs(tc.args) ?? {};
|
|
2814
|
+
const label = {
|
|
2815
|
+
kb_search_pages: "KB Search",
|
|
2816
|
+
kb_list_pages: "KB Browse",
|
|
2817
|
+
kb_read_page: "KB Read"
|
|
2818
|
+
}[tc.toolName] ?? tc.toolName;
|
|
2819
|
+
let detail = "";
|
|
2820
|
+
if (tc.toolName === "kb_search_pages" && args.query) {
|
|
2821
|
+
detail = `\u201C${truncate2(args.query, 60)}\u201D`;
|
|
2822
|
+
} else if (tc.toolName === "kb_read_page" && args.path) {
|
|
2823
|
+
detail = args.pages ? `${args.path} \xB7 pages ${args.pages}` : args.path;
|
|
2824
|
+
}
|
|
2825
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm py-0.5", children: [
|
|
2826
|
+
/* @__PURE__ */ jsx(LibraryIcon, { className: "size-3.5 text-muted-foreground/60 shrink-0" }),
|
|
2827
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: label }),
|
|
2828
|
+
detail && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/70 truncate text-xs", children: detail })
|
|
2829
|
+
] });
|
|
2830
|
+
}
|
|
2651
2831
|
function VisionAnalyzeBlock({ tc }) {
|
|
2652
2832
|
let image = "";
|
|
2653
2833
|
try {
|
|
@@ -4933,6 +5113,10 @@ function ToolCallBlock({
|
|
|
4933
5113
|
case "web_search":
|
|
4934
5114
|
case "web_crawl":
|
|
4935
5115
|
return /* @__PURE__ */ jsx(WebToolBlock, { tc });
|
|
5116
|
+
case "kb_search_pages":
|
|
5117
|
+
case "kb_list_pages":
|
|
5118
|
+
case "kb_read_page":
|
|
5119
|
+
return /* @__PURE__ */ jsx(KnowledgeBaseToolBlock, { tc });
|
|
4936
5120
|
case "vision_analyze":
|
|
4937
5121
|
return /* @__PURE__ */ jsx(VisionAnalyzeBlock, { tc });
|
|
4938
5122
|
case "read_file":
|
|
@@ -9952,6 +10136,72 @@ function ChatComposerInner({
|
|
|
9952
10136
|
}
|
|
9953
10137
|
);
|
|
9954
10138
|
}
|
|
10139
|
+
function SessionPaneCard({
|
|
10140
|
+
title,
|
|
10141
|
+
subtitle,
|
|
10142
|
+
thumbnail,
|
|
10143
|
+
count,
|
|
10144
|
+
icon: Icon = GlobeIcon,
|
|
10145
|
+
onOpen,
|
|
10146
|
+
testId = "session-pane-card",
|
|
10147
|
+
className,
|
|
10148
|
+
expanded
|
|
10149
|
+
}) {
|
|
10150
|
+
return /* @__PURE__ */ jsxs(
|
|
10151
|
+
"button",
|
|
10152
|
+
{
|
|
10153
|
+
type: "button",
|
|
10154
|
+
"data-testid": testId,
|
|
10155
|
+
"aria-label": `Open ${title}`,
|
|
10156
|
+
"aria-expanded": expanded === void 0 ? void 0 : expanded,
|
|
10157
|
+
onClick: onOpen,
|
|
10158
|
+
className: cn(
|
|
10159
|
+
// No radius here: the caller sets it. Declaring rounded-xl in the base
|
|
10160
|
+
// and cancelling the bottom from a className leaves both rules live,
|
|
10161
|
+
// and which corner wins comes down to stylesheet order rather than
|
|
10162
|
+
// intent -- which is how the stacked cards kept round bottoms.
|
|
10163
|
+
"flex w-full items-center gap-3 border px-3 pt-3.5 pb-5 text-left transition-colors hover:border-t-3 hover:border-r-3 hover:border-l-3 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
10164
|
+
className
|
|
10165
|
+
),
|
|
10166
|
+
children: [
|
|
10167
|
+
thumbnail ? /* @__PURE__ */ jsx(
|
|
10168
|
+
"img",
|
|
10169
|
+
{
|
|
10170
|
+
"data-testid": "session-pane-card-thumb",
|
|
10171
|
+
src: thumbnail,
|
|
10172
|
+
alt: "",
|
|
10173
|
+
className: "h-11 w-16 shrink-0 rounded-md border border-line object-cover object-top"
|
|
10174
|
+
}
|
|
10175
|
+
) : /* @__PURE__ */ jsx(
|
|
10176
|
+
Icon,
|
|
10177
|
+
{
|
|
10178
|
+
"data-testid": "session-pane-card-icon",
|
|
10179
|
+
className: "size-5 shrink-0 text-muted-foreground",
|
|
10180
|
+
"aria-hidden": "true"
|
|
10181
|
+
}
|
|
10182
|
+
),
|
|
10183
|
+
/* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-col", children: [
|
|
10184
|
+
/* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5 text-sm font-medium text-foreground", children: [
|
|
10185
|
+
title,
|
|
10186
|
+
count !== void 0 && count > 0 && /* @__PURE__ */ jsx("span", { className: "rounded-full bg-secondary px-1.5 text-[11px] font-normal text-muted-foreground", children: count })
|
|
10187
|
+
] }),
|
|
10188
|
+
subtitle && /* @__PURE__ */ jsx("span", { className: "truncate text-xs text-muted-foreground", children: subtitle })
|
|
10189
|
+
] }),
|
|
10190
|
+
expanded !== void 0 && /* @__PURE__ */ jsx(
|
|
10191
|
+
ChevronDownIcon,
|
|
10192
|
+
{
|
|
10193
|
+
"data-testid": "session-pane-card-chevron",
|
|
10194
|
+
className: cn(
|
|
10195
|
+
"ml-auto size-4 shrink-0 text-muted-foreground transition-transform",
|
|
10196
|
+
expanded && "rotate-180"
|
|
10197
|
+
),
|
|
10198
|
+
"aria-hidden": "true"
|
|
10199
|
+
}
|
|
10200
|
+
)
|
|
10201
|
+
]
|
|
10202
|
+
}
|
|
10203
|
+
);
|
|
10204
|
+
}
|
|
9955
10205
|
var MAX_LOGOS = 10;
|
|
9956
10206
|
function IntegrationsBand({
|
|
9957
10207
|
agentId,
|
|
@@ -10523,9 +10773,9 @@ function ArtifactTable({ spec }) {
|
|
|
10523
10773
|
}
|
|
10524
10774
|
var DEFAULT_HEIGHT_PX = 480;
|
|
10525
10775
|
var EXPANDED_HEIGHT_PX = 800;
|
|
10526
|
-
var
|
|
10527
|
-
var
|
|
10528
|
-
var
|
|
10776
|
+
var MIN_ZOOM = 0.5;
|
|
10777
|
+
var MAX_ZOOM = 2;
|
|
10778
|
+
var ZOOM_STEP = 0.1;
|
|
10529
10779
|
var SAFETY_STYLE_PREAMBLE = "<style>html,body{max-height:100vh}canvas{max-height:80vh!important}</style>";
|
|
10530
10780
|
function injectSafetyStyle(html) {
|
|
10531
10781
|
const headCloseIdx = html.search(/<\/head\s*>/i);
|
|
@@ -10542,7 +10792,7 @@ function ArtifactHtml({
|
|
|
10542
10792
|
const [zoom, setZoom] = useState(1);
|
|
10543
10793
|
const safeSrcdoc = useMemo(() => injectSafetyStyle(spec.html), [spec.html]);
|
|
10544
10794
|
const viewportHeight = expanded ? EXPANDED_HEIGHT_PX : DEFAULT_HEIGHT_PX;
|
|
10545
|
-
const clamp4 = (v) => Math.round(Math.min(
|
|
10795
|
+
const clamp4 = (v) => Math.round(Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, v)) * 100) / 100;
|
|
10546
10796
|
return /* @__PURE__ */ jsxs(
|
|
10547
10797
|
"div",
|
|
10548
10798
|
{
|
|
@@ -10588,8 +10838,8 @@ function ArtifactHtml({
|
|
|
10588
10838
|
"button",
|
|
10589
10839
|
{
|
|
10590
10840
|
type: "button",
|
|
10591
|
-
onClick: () => setZoom((z) => clamp4(z -
|
|
10592
|
-
disabled: zoom <=
|
|
10841
|
+
onClick: () => setZoom((z) => clamp4(z - ZOOM_STEP)),
|
|
10842
|
+
disabled: zoom <= MIN_ZOOM,
|
|
10593
10843
|
className: "hover:text-foreground disabled:opacity-40 cursor-pointer",
|
|
10594
10844
|
"aria-label": "zoom out",
|
|
10595
10845
|
children: "\u2212"
|
|
@@ -10612,8 +10862,8 @@ function ArtifactHtml({
|
|
|
10612
10862
|
"button",
|
|
10613
10863
|
{
|
|
10614
10864
|
type: "button",
|
|
10615
|
-
onClick: () => setZoom((z) => clamp4(z +
|
|
10616
|
-
disabled: zoom >=
|
|
10865
|
+
onClick: () => setZoom((z) => clamp4(z + ZOOM_STEP)),
|
|
10866
|
+
disabled: zoom >= MAX_ZOOM,
|
|
10617
10867
|
className: "hover:text-foreground disabled:opacity-40",
|
|
10618
10868
|
"aria-label": "zoom in",
|
|
10619
10869
|
children: "+"
|
|
@@ -11035,7 +11285,7 @@ var ErrorMessage = memo(function ErrorMessage2({
|
|
|
11035
11285
|
className: "flex items-center gap-1 self-start text-xs text-muted-foreground hover:text-foreground",
|
|
11036
11286
|
"aria-expanded": detailOpen,
|
|
11037
11287
|
children: [
|
|
11038
|
-
detailOpen ? /* @__PURE__ */ jsx(ChevronDown, { className: "size-3" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "size-3" }),
|
|
11288
|
+
detailOpen ? /* @__PURE__ */ jsx(ChevronDown, { className: "size-3" }) : /* @__PURE__ */ jsx(ChevronRight$1, { className: "size-3" }),
|
|
11039
11289
|
detailOpen ? "Hide details" : "Show details"
|
|
11040
11290
|
]
|
|
11041
11291
|
}
|
|
@@ -11111,745 +11361,294 @@ var InsufficientCreditsCard = memo(function InsufficientCreditsCard2({
|
|
|
11111
11361
|
}
|
|
11112
11362
|
);
|
|
11113
11363
|
});
|
|
11114
|
-
var
|
|
11115
|
-
|
|
11116
|
-
|
|
11117
|
-
|
|
11118
|
-
|
|
11119
|
-
|
|
11120
|
-
|
|
11121
|
-
|
|
11122
|
-
|
|
11123
|
-
|
|
11124
|
-
|
|
11125
|
-
|
|
11126
|
-
|
|
11364
|
+
var _EXTENSION_KIND = {
|
|
11365
|
+
// Documents
|
|
11366
|
+
pdf: { icon: FileTextIcon, typeLabel: "PDF" },
|
|
11367
|
+
doc: { icon: FileTextIcon, typeLabel: "Word document" },
|
|
11368
|
+
docx: { icon: FileTextIcon, typeLabel: "Word document" },
|
|
11369
|
+
odt: { icon: FileTextIcon, typeLabel: "OpenDocument text" },
|
|
11370
|
+
rtf: { icon: FileTextIcon, typeLabel: "Rich text" },
|
|
11371
|
+
txt: { icon: FileTextIcon, typeLabel: "Text" },
|
|
11372
|
+
md: { icon: FileTextIcon, typeLabel: "Markdown" },
|
|
11373
|
+
// Spreadsheets
|
|
11374
|
+
xls: { icon: FileSpreadsheetIcon, typeLabel: "Spreadsheet" },
|
|
11375
|
+
xlsx: { icon: FileSpreadsheetIcon, typeLabel: "Spreadsheet" },
|
|
11376
|
+
ods: { icon: FileSpreadsheetIcon, typeLabel: "OpenDocument spreadsheet" },
|
|
11377
|
+
csv: { icon: FileSpreadsheetIcon, typeLabel: "CSV" },
|
|
11378
|
+
tsv: { icon: FileSpreadsheetIcon, typeLabel: "TSV" },
|
|
11379
|
+
// Presentations / charts
|
|
11380
|
+
ppt: { icon: FileChartLineIcon, typeLabel: "Presentation" },
|
|
11381
|
+
pptx: { icon: FileChartLineIcon, typeLabel: "Presentation" },
|
|
11382
|
+
// Images
|
|
11383
|
+
png: { icon: FileImageIcon, typeLabel: "Image" },
|
|
11384
|
+
jpg: { icon: FileImageIcon, typeLabel: "Image" },
|
|
11385
|
+
jpeg: { icon: FileImageIcon, typeLabel: "Image" },
|
|
11386
|
+
gif: { icon: FileImageIcon, typeLabel: "Image" },
|
|
11387
|
+
svg: { icon: FileImageIcon, typeLabel: "SVG image" },
|
|
11388
|
+
webp: { icon: FileImageIcon, typeLabel: "Image" },
|
|
11389
|
+
bmp: { icon: FileImageIcon, typeLabel: "Image" },
|
|
11390
|
+
// Audio / video
|
|
11391
|
+
mp3: { icon: FileAudioIcon, typeLabel: "Audio" },
|
|
11392
|
+
wav: { icon: FileAudioIcon, typeLabel: "Audio" },
|
|
11393
|
+
ogg: { icon: FileAudioIcon, typeLabel: "Audio" },
|
|
11394
|
+
mp4: { icon: FileVideoIcon, typeLabel: "Video" },
|
|
11395
|
+
mov: { icon: FileVideoIcon, typeLabel: "Video" },
|
|
11396
|
+
webm: { icon: FileVideoIcon, typeLabel: "Video" },
|
|
11397
|
+
// Archives
|
|
11398
|
+
zip: { icon: FileArchiveIcon, typeLabel: "Archive" },
|
|
11399
|
+
tar: { icon: FileArchiveIcon, typeLabel: "Archive" },
|
|
11400
|
+
gz: { icon: FileArchiveIcon, typeLabel: "Archive" },
|
|
11401
|
+
bz2: { icon: FileArchiveIcon, typeLabel: "Archive" },
|
|
11402
|
+
"7z": { icon: FileArchiveIcon, typeLabel: "Archive" },
|
|
11403
|
+
// Code
|
|
11404
|
+
py: { icon: FileCodeIcon, typeLabel: "Python" },
|
|
11405
|
+
js: { icon: FileCodeIcon, typeLabel: "JavaScript" },
|
|
11406
|
+
ts: { icon: FileCodeIcon, typeLabel: "TypeScript" },
|
|
11407
|
+
tsx: { icon: FileCodeIcon, typeLabel: "TypeScript" },
|
|
11408
|
+
jsx: { icon: FileCodeIcon, typeLabel: "JavaScript" },
|
|
11409
|
+
json: { icon: FileCodeIcon, typeLabel: "JSON" },
|
|
11410
|
+
yaml: { icon: FileCodeIcon, typeLabel: "YAML" },
|
|
11411
|
+
yml: { icon: FileCodeIcon, typeLabel: "YAML" },
|
|
11412
|
+
toml: { icon: FileCodeIcon, typeLabel: "TOML" },
|
|
11413
|
+
sh: { icon: CodeIcon, typeLabel: "Shell" },
|
|
11414
|
+
rs: { icon: FileCodeIcon, typeLabel: "Rust" },
|
|
11415
|
+
go: { icon: FileCodeIcon, typeLabel: "Go" },
|
|
11416
|
+
rb: { icon: FileCodeIcon, typeLabel: "Ruby" },
|
|
11417
|
+
java: { icon: FileCodeIcon, typeLabel: "Java" },
|
|
11418
|
+
cpp: { icon: FileCodeIcon, typeLabel: "C++" },
|
|
11419
|
+
c: { icon: FileCodeIcon, typeLabel: "C" },
|
|
11420
|
+
html: { icon: FileCodeIcon, typeLabel: "HTML" },
|
|
11421
|
+
css: { icon: FileCodeIcon, typeLabel: "CSS" },
|
|
11422
|
+
scss: { icon: FileCodeIcon, typeLabel: "Sass" },
|
|
11423
|
+
sql: { icon: FileCodeIcon, typeLabel: "SQL" }
|
|
11424
|
+
};
|
|
11425
|
+
function _extension(path) {
|
|
11426
|
+
const idx = path.lastIndexOf(".");
|
|
11427
|
+
if (idx < 0 || idx === path.length - 1) return "";
|
|
11428
|
+
return path.slice(idx + 1).toLowerCase();
|
|
11429
|
+
}
|
|
11430
|
+
function _fileKindFor(path) {
|
|
11431
|
+
const ext = _extension(path);
|
|
11432
|
+
return _EXTENSION_KIND[ext] ?? { icon: FileIcon, typeLabel: "File" };
|
|
11433
|
+
}
|
|
11434
|
+
function _basename(path) {
|
|
11435
|
+
const idx = path.lastIndexOf("/");
|
|
11436
|
+
return idx >= 0 ? path.slice(idx + 1) : path;
|
|
11437
|
+
}
|
|
11438
|
+
function WorkspaceFileCard({
|
|
11439
|
+
sessionId,
|
|
11440
|
+
path,
|
|
11441
|
+
label
|
|
11127
11442
|
}) {
|
|
11128
|
-
const
|
|
11129
|
-
|
|
11130
|
-
const
|
|
11131
|
-
const
|
|
11132
|
-
const
|
|
11133
|
-
const
|
|
11134
|
-
|
|
11135
|
-
|
|
11136
|
-
|
|
11137
|
-
|
|
11138
|
-
|
|
11139
|
-
|
|
11140
|
-
|
|
11141
|
-
|
|
11142
|
-
|
|
11143
|
-
|
|
11144
|
-
|
|
11145
|
-
|
|
11146
|
-
] })
|
|
11147
|
-
] }),
|
|
11148
|
-
downloadUrl && /* @__PURE__ */ jsx(
|
|
11149
|
-
"a",
|
|
11150
|
-
{
|
|
11151
|
-
href: downloadUrl,
|
|
11152
|
-
download: fileName2,
|
|
11153
|
-
className: "shrink-0 rounded p-0.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
|
11154
|
-
"aria-label": `Download ${fileName2}`,
|
|
11155
|
-
title: "Download",
|
|
11156
|
-
children: /* @__PURE__ */ jsx(DownloadIcon, { className: "size-3.5" })
|
|
11157
|
-
}
|
|
11158
|
-
),
|
|
11159
|
-
onDelete && /* @__PURE__ */ jsx(
|
|
11160
|
-
"button",
|
|
11161
|
-
{
|
|
11162
|
-
type: "button",
|
|
11163
|
-
onClick: onDelete,
|
|
11164
|
-
className: "shrink-0 rounded p-0.5 text-muted-foreground transition-colors hover:bg-destructive/10 hover:text-destructive",
|
|
11165
|
-
"aria-label": `Delete ${fileName2}`,
|
|
11166
|
-
title: "Delete",
|
|
11167
|
-
children: /* @__PURE__ */ jsx(TrashIcon, { className: "size-3.5" })
|
|
11443
|
+
const { adapter, onFileSelect } = useAgentChatAdapterContext();
|
|
11444
|
+
const kind = _fileKindFor(path);
|
|
11445
|
+
const Icon = kind.icon;
|
|
11446
|
+
const downloadUrl = adapter.getWorkspaceDownloadUrl({ sessionId, path });
|
|
11447
|
+
const downloadName = _basename(path) || label;
|
|
11448
|
+
const handleCardClick = useCallback(() => {
|
|
11449
|
+
onFileSelect?.(path);
|
|
11450
|
+
}, [onFileSelect, path]);
|
|
11451
|
+
return /* @__PURE__ */ jsxs(
|
|
11452
|
+
"div",
|
|
11453
|
+
{
|
|
11454
|
+
role: "button",
|
|
11455
|
+
tabIndex: 0,
|
|
11456
|
+
onClick: handleCardClick,
|
|
11457
|
+
onKeyDown: (e) => {
|
|
11458
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
11459
|
+
e.preventDefault();
|
|
11460
|
+
handleCardClick();
|
|
11168
11461
|
}
|
|
11462
|
+
},
|
|
11463
|
+
className: cn(
|
|
11464
|
+
"group/file flex items-center gap-3 rounded-lg border border-border",
|
|
11465
|
+
"bg-muted/20 px-3 py-2 text-left transition-colors",
|
|
11466
|
+
"cursor-pointer hover:bg-muted/40"
|
|
11169
11467
|
),
|
|
11170
|
-
|
|
11171
|
-
"
|
|
11172
|
-
{
|
|
11173
|
-
|
|
11174
|
-
|
|
11175
|
-
|
|
11176
|
-
|
|
11177
|
-
|
|
11178
|
-
|
|
11179
|
-
|
|
11180
|
-
|
|
11181
|
-
|
|
11182
|
-
|
|
11183
|
-
|
|
11184
|
-
|
|
11185
|
-
|
|
11186
|
-
|
|
11187
|
-
|
|
11188
|
-
|
|
11189
|
-
|
|
11190
|
-
|
|
11191
|
-
|
|
11192
|
-
|
|
11193
|
-
|
|
11194
|
-
|
|
11195
|
-
|
|
11196
|
-
|
|
11197
|
-
error && /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 p-3 text-xs text-destructive", children: [
|
|
11198
|
-
/* @__PURE__ */ jsx(AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0" }),
|
|
11199
|
-
/* @__PURE__ */ jsx("span", { children: error })
|
|
11200
|
-
] }),
|
|
11201
|
-
file && isPdf && file.encoding !== "base64" && /* @__PURE__ */ jsx(FileViewerError, { message: "PDF preview requires base64 file content." }),
|
|
11202
|
-
file && isImage && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center p-4", children: /* @__PURE__ */ jsx(
|
|
11203
|
-
"img",
|
|
11204
|
-
{
|
|
11205
|
-
src: `data:${file.mime_type ?? "image/png"};base64,${file.content}`,
|
|
11206
|
-
alt: fileName2,
|
|
11207
|
-
className: "max-h-[60vh] max-w-full rounded object-contain"
|
|
11208
|
-
}
|
|
11209
|
-
) }),
|
|
11210
|
-
file && !isImage && !isPdf && /* @__PURE__ */ jsx("pre", { className: "wrap-break-word whitespace-pre-wrap p-3 text-[11px] leading-relaxed text-foreground", children: /* @__PURE__ */ jsx("code", { "data-language": lang, children: file.content }) })
|
|
11211
|
-
] })
|
|
11212
|
-
] });
|
|
11468
|
+
children: [
|
|
11469
|
+
/* @__PURE__ */ jsx("div", { className: "flex size-10 shrink-0 items-center justify-center rounded-md border border-border bg-background", children: /* @__PURE__ */ jsx(Icon, { className: "size-5 text-muted-foreground", "aria-hidden": true }) }),
|
|
11470
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
11471
|
+
/* @__PURE__ */ jsx("div", { className: "truncate text-sm font-medium text-foreground", children: label }),
|
|
11472
|
+
/* @__PURE__ */ jsx("div", { className: "truncate text-xs text-muted-foreground", children: kind.typeLabel })
|
|
11473
|
+
] }),
|
|
11474
|
+
/* @__PURE__ */ jsxs(
|
|
11475
|
+
"a",
|
|
11476
|
+
{
|
|
11477
|
+
href: downloadUrl,
|
|
11478
|
+
download: downloadName,
|
|
11479
|
+
onClick: (e) => e.stopPropagation(),
|
|
11480
|
+
className: cn(
|
|
11481
|
+
"shrink-0 inline-flex items-center gap-1.5 rounded-md border border-border",
|
|
11482
|
+
"bg-background px-2.5 py-1.5 text-xs font-medium text-foreground",
|
|
11483
|
+
"transition-colors hover:bg-muted"
|
|
11484
|
+
),
|
|
11485
|
+
"aria-label": `Download ${downloadName}`,
|
|
11486
|
+
children: [
|
|
11487
|
+
/* @__PURE__ */ jsx(DownloadIcon, { className: "size-4", "aria-hidden": true }),
|
|
11488
|
+
"Download"
|
|
11489
|
+
]
|
|
11490
|
+
}
|
|
11491
|
+
)
|
|
11492
|
+
]
|
|
11493
|
+
}
|
|
11494
|
+
);
|
|
11213
11495
|
}
|
|
11214
|
-
function
|
|
11215
|
-
|
|
11216
|
-
/* @__PURE__ */ jsx(AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0" }),
|
|
11217
|
-
/* @__PURE__ */ jsx("span", { children: message })
|
|
11218
|
-
] });
|
|
11496
|
+
function scrollToArtifactBlock(artifactId) {
|
|
11497
|
+
document.querySelector(`[data-artifact-anchor="${CSS.escape(artifactId)}"]`)?.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
11219
11498
|
}
|
|
11220
|
-
function
|
|
11221
|
-
|
|
11222
|
-
|
|
11499
|
+
function ArtifactRefCard({
|
|
11500
|
+
artifactId,
|
|
11501
|
+
name,
|
|
11502
|
+
kind
|
|
11223
11503
|
}) {
|
|
11224
|
-
|
|
11225
|
-
|
|
11226
|
-
|
|
11227
|
-
|
|
11228
|
-
|
|
11229
|
-
|
|
11230
|
-
|
|
11231
|
-
|
|
11232
|
-
|
|
11233
|
-
|
|
11234
|
-
|
|
11235
|
-
|
|
11236
|
-
|
|
11237
|
-
|
|
11238
|
-
|
|
11239
|
-
|
|
11240
|
-
|
|
11241
|
-
|
|
11242
|
-
|
|
11243
|
-
|
|
11244
|
-
|
|
11245
|
-
|
|
11246
|
-
|
|
11247
|
-
|
|
11248
|
-
|
|
11249
|
-
|
|
11250
|
-
|
|
11251
|
-
|
|
11252
|
-
|
|
11253
|
-
|
|
11254
|
-
|
|
11255
|
-
|
|
11256
|
-
|
|
11257
|
-
|
|
11258
|
-
|
|
11259
|
-
|
|
11260
|
-
|
|
11261
|
-
|
|
11262
|
-
|
|
11263
|
-
|
|
11264
|
-
|
|
11265
|
-
|
|
11266
|
-
|
|
11267
|
-
|
|
11268
|
-
|
|
11269
|
-
|
|
11270
|
-
|
|
11271
|
-
|
|
11272
|
-
|
|
11273
|
-
|
|
11274
|
-
|
|
11275
|
-
const onPageChanging = (event) => {
|
|
11276
|
-
if (typeof event.pageNumber === "number") {
|
|
11277
|
-
setPageNumber(event.pageNumber);
|
|
11278
|
-
}
|
|
11279
|
-
};
|
|
11280
|
-
const onScaleChanging = (event) => {
|
|
11281
|
-
if (typeof event.scale === "number") {
|
|
11282
|
-
setScale(event.scale);
|
|
11283
|
-
}
|
|
11284
|
-
};
|
|
11285
|
-
eventBus.on("pagesinit", onPagesInit);
|
|
11286
|
-
eventBus.on("pagechanging", onPageChanging);
|
|
11287
|
-
eventBus.on("scalechanging", onScaleChanging);
|
|
11288
|
-
pdfViewerRef.current = pdfViewer;
|
|
11289
|
-
eventBusRef.current = eventBus;
|
|
11290
|
-
findControllerRef.current = findController;
|
|
11291
|
-
setPagesCount(pdf.numPages);
|
|
11292
|
-
pdfViewer.setDocument(pdf);
|
|
11293
|
-
};
|
|
11294
|
-
void renderPdf().catch((nextError) => {
|
|
11295
|
-
if (cancelled) return;
|
|
11296
|
-
if (nextError instanceof Error && nextError.name === "RenderingCancelledException") {
|
|
11297
|
-
return;
|
|
11504
|
+
return /* @__PURE__ */ jsxs(
|
|
11505
|
+
"button",
|
|
11506
|
+
{
|
|
11507
|
+
type: "button",
|
|
11508
|
+
onClick: () => scrollToArtifactBlock(artifactId),
|
|
11509
|
+
className: "flex w-full min-w-0 cursor-pointer items-center gap-2 rounded border border-border bg-background px-3 py-2 text-left text-sm transition-colors hover:bg-muted/60",
|
|
11510
|
+
children: [
|
|
11511
|
+
/* @__PURE__ */ jsx(FileTextIcon, { className: "size-4 shrink-0 text-muted-foreground", "aria-hidden": true }),
|
|
11512
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate font-medium text-foreground", children: name }),
|
|
11513
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: KIND_LABEL[kind] })
|
|
11514
|
+
]
|
|
11515
|
+
}
|
|
11516
|
+
);
|
|
11517
|
+
}
|
|
11518
|
+
function resolveArtifactRef(ref, messages) {
|
|
11519
|
+
for (const msg of messages) {
|
|
11520
|
+
if (msg.role !== "system" || msg.systemKind !== "artifact") continue;
|
|
11521
|
+
const meta = msg.systemMeta ?? {};
|
|
11522
|
+
if (meta.artifact_id !== ref) continue;
|
|
11523
|
+
const kind = meta.kind ?? "markdown";
|
|
11524
|
+
const version = typeof meta.version === "number" ? meta.version : 1;
|
|
11525
|
+
const name = typeof meta.name === "string" ? meta.name : "";
|
|
11526
|
+
return { artifactId: ref, name, kind, version };
|
|
11527
|
+
}
|
|
11528
|
+
return null;
|
|
11529
|
+
}
|
|
11530
|
+
function TurnSummaryCard({
|
|
11531
|
+
summary,
|
|
11532
|
+
sessionId,
|
|
11533
|
+
messages,
|
|
11534
|
+
onFileSelect,
|
|
11535
|
+
onCommandSelect
|
|
11536
|
+
}) {
|
|
11537
|
+
const hasRecap = summary.recap.trim().length > 0;
|
|
11538
|
+
const hasArtifacts = summary.artifacts.length > 0;
|
|
11539
|
+
if (!hasArtifacts) return null;
|
|
11540
|
+
return /* @__PURE__ */ jsxs("div", { className: "mt-3 rounded border border-border bg-muted/50 px-3 py-2 text-sm", children: [
|
|
11541
|
+
/* @__PURE__ */ jsx("div", { className: "mb-2 text-xs font-semibold uppercase tracking-wide", children: "Summary" }),
|
|
11542
|
+
hasRecap && /* @__PURE__ */ jsx("p", { className: "mb-2 whitespace-pre-wrap text-foreground", children: summary.recap }),
|
|
11543
|
+
hasArtifacts && /* @__PURE__ */ jsx("div", { className: "space-y-2", children: summary.artifacts.map((artifact, i) => {
|
|
11544
|
+
const key = `${artifact.kind}:${artifact.ref}:${i}`;
|
|
11545
|
+
if (artifact.kind === "file" && sessionId) {
|
|
11546
|
+
return /* @__PURE__ */ jsx(
|
|
11547
|
+
WorkspaceFileCard,
|
|
11548
|
+
{
|
|
11549
|
+
sessionId,
|
|
11550
|
+
path: artifact.ref,
|
|
11551
|
+
label: artifact.label
|
|
11552
|
+
},
|
|
11553
|
+
key
|
|
11554
|
+
);
|
|
11298
11555
|
}
|
|
11299
|
-
|
|
11300
|
-
|
|
11301
|
-
|
|
11302
|
-
|
|
11303
|
-
|
|
11304
|
-
|
|
11305
|
-
|
|
11306
|
-
|
|
11307
|
-
|
|
11308
|
-
|
|
11309
|
-
|
|
11310
|
-
|
|
11311
|
-
const setViewerPage = (nextPage) => {
|
|
11312
|
-
const viewer = pdfViewerRef.current;
|
|
11313
|
-
if (!viewer) return;
|
|
11314
|
-
const clampedPage = Math.min(
|
|
11315
|
-
Math.max(1, nextPage),
|
|
11316
|
-
Math.max(1, viewer.pagesCount)
|
|
11317
|
-
);
|
|
11318
|
-
viewer.currentPageNumber = clampedPage;
|
|
11319
|
-
setPageNumber(clampedPage);
|
|
11320
|
-
};
|
|
11321
|
-
const setViewerScale = (nextScale) => {
|
|
11322
|
-
const viewer = pdfViewerRef.current;
|
|
11323
|
-
if (!viewer) return;
|
|
11324
|
-
const clampedScale = Math.min(3, Math.max(0.5, nextScale));
|
|
11325
|
-
viewer.currentScale = clampedScale;
|
|
11326
|
-
setScale(clampedScale);
|
|
11327
|
-
};
|
|
11328
|
-
const runFind = (findPrevious = false) => {
|
|
11329
|
-
const eventBus = eventBusRef.current;
|
|
11330
|
-
if (!eventBus || !query.trim()) return;
|
|
11331
|
-
eventBus.dispatch("find", {
|
|
11332
|
-
source: eventBus,
|
|
11333
|
-
type: "again",
|
|
11334
|
-
query,
|
|
11335
|
-
phraseSearch: true,
|
|
11336
|
-
caseSensitive: false,
|
|
11337
|
-
entireWord: false,
|
|
11338
|
-
highlightAll: true,
|
|
11339
|
-
findPrevious,
|
|
11340
|
-
matchDiacritics: true
|
|
11341
|
-
});
|
|
11342
|
-
};
|
|
11343
|
-
return /* @__PURE__ */ jsxs("div", { className: "flex min-h-full flex-col", children: [
|
|
11344
|
-
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 flex-wrap items-center gap-1.5 border-b border-line px-2 py-1.5", children: [
|
|
11345
|
-
/* @__PURE__ */ jsx(
|
|
11346
|
-
Button,
|
|
11347
|
-
{
|
|
11348
|
-
type: "button",
|
|
11349
|
-
variant: "ghost",
|
|
11350
|
-
size: "icon-xs",
|
|
11351
|
-
onClick: () => setViewerPage(pageNumber - 1),
|
|
11352
|
-
disabled: pageNumber <= 1,
|
|
11353
|
-
"aria-label": "Previous PDF page",
|
|
11354
|
-
title: "Previous page",
|
|
11355
|
-
children: /* @__PURE__ */ jsx(ChevronLeftIcon, { className: "size-3.5" })
|
|
11556
|
+
if (artifact.kind === "artifact") {
|
|
11557
|
+
const resolved = sessionId ? resolveArtifactRef(artifact.ref, messages) : null;
|
|
11558
|
+
if (resolved) {
|
|
11559
|
+
return /* @__PURE__ */ jsx(
|
|
11560
|
+
ArtifactRefCard,
|
|
11561
|
+
{
|
|
11562
|
+
artifactId: resolved.artifactId,
|
|
11563
|
+
name: resolved.name || artifact.label,
|
|
11564
|
+
kind: resolved.kind
|
|
11565
|
+
},
|
|
11566
|
+
key
|
|
11567
|
+
);
|
|
11356
11568
|
}
|
|
11357
|
-
|
|
11358
|
-
/* @__PURE__ */
|
|
11359
|
-
|
|
11569
|
+
}
|
|
11570
|
+
return /* @__PURE__ */ jsxs(
|
|
11571
|
+
"div",
|
|
11360
11572
|
{
|
|
11361
|
-
|
|
11362
|
-
min: 1,
|
|
11363
|
-
max: pagesCount || 1,
|
|
11364
|
-
value: pageNumber,
|
|
11365
|
-
onChange: (event) => setViewerPage(Number(event.target.value)),
|
|
11366
|
-
"aria-label": "PDF page number",
|
|
11367
|
-
className: "h-7 w-12 border-input px-1 text-center text-xs"
|
|
11368
|
-
}
|
|
11369
|
-
),
|
|
11370
|
-
/* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
|
|
11371
|
-
"/ ",
|
|
11372
|
-
pagesCount || "-"
|
|
11373
|
-
] }),
|
|
11374
|
-
/* @__PURE__ */ jsx(
|
|
11375
|
-
Button,
|
|
11376
|
-
{
|
|
11377
|
-
type: "button",
|
|
11378
|
-
variant: "ghost",
|
|
11379
|
-
size: "icon-xs",
|
|
11380
|
-
onClick: () => setViewerPage(pageNumber + 1),
|
|
11381
|
-
disabled: pagesCount > 0 && pageNumber >= pagesCount,
|
|
11382
|
-
"aria-label": "Next PDF page",
|
|
11383
|
-
title: "Next page",
|
|
11384
|
-
children: /* @__PURE__ */ jsx(ChevronRightIcon, { className: "size-3.5" })
|
|
11385
|
-
}
|
|
11386
|
-
),
|
|
11387
|
-
/* @__PURE__ */ jsx("div", { className: "mx-1 h-5 w-px bg-line" }),
|
|
11388
|
-
/* @__PURE__ */ jsx(
|
|
11389
|
-
Button,
|
|
11390
|
-
{
|
|
11391
|
-
type: "button",
|
|
11392
|
-
variant: "ghost",
|
|
11393
|
-
size: "icon-xs",
|
|
11394
|
-
onClick: () => setViewerScale(scale - 0.1),
|
|
11395
|
-
disabled: scale <= 0.5,
|
|
11396
|
-
"aria-label": "Zoom out PDF",
|
|
11397
|
-
title: "Zoom out",
|
|
11398
|
-
children: /* @__PURE__ */ jsx(MinusIcon, { className: "size-3.5" })
|
|
11399
|
-
}
|
|
11400
|
-
),
|
|
11401
|
-
/* @__PURE__ */ jsxs(
|
|
11402
|
-
"button",
|
|
11403
|
-
{
|
|
11404
|
-
type: "button",
|
|
11405
|
-
className: "h-7 min-w-12 px-1 text-xs text-muted-foreground hover:text-foreground",
|
|
11406
|
-
onClick: () => {
|
|
11407
|
-
const viewer = pdfViewerRef.current;
|
|
11408
|
-
if (!viewer) return;
|
|
11409
|
-
viewer.currentScaleValue = "page-width";
|
|
11410
|
-
setScale(viewer.currentScale || 1);
|
|
11411
|
-
},
|
|
11412
|
-
"aria-label": "Fit PDF to width",
|
|
11413
|
-
title: "Fit width",
|
|
11573
|
+
className: "flex items-baseline gap-2 text-sm",
|
|
11414
11574
|
children: [
|
|
11415
|
-
|
|
11416
|
-
|
|
11575
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", "aria-hidden": true, children: "\u2022" }),
|
|
11576
|
+
/* @__PURE__ */ jsx(
|
|
11577
|
+
ArtifactRow,
|
|
11578
|
+
{
|
|
11579
|
+
artifact,
|
|
11580
|
+
onFileSelect,
|
|
11581
|
+
onCommandSelect
|
|
11582
|
+
}
|
|
11583
|
+
)
|
|
11417
11584
|
]
|
|
11418
|
-
}
|
|
11419
|
-
|
|
11420
|
-
|
|
11421
|
-
|
|
11422
|
-
{
|
|
11423
|
-
type: "button",
|
|
11424
|
-
variant: "ghost",
|
|
11425
|
-
size: "icon-xs",
|
|
11426
|
-
onClick: () => setViewerScale(scale + 0.1),
|
|
11427
|
-
disabled: scale >= 3,
|
|
11428
|
-
"aria-label": "Zoom in PDF",
|
|
11429
|
-
title: "Zoom in",
|
|
11430
|
-
children: /* @__PURE__ */ jsx(PlusIcon, { className: "size-3.5" })
|
|
11431
|
-
}
|
|
11432
|
-
),
|
|
11433
|
-
/* @__PURE__ */ jsx("div", { className: "mx-1 h-5 w-px bg-line" }),
|
|
11434
|
-
/* @__PURE__ */ jsx(SearchIcon, { className: "size-3.5 text-muted-foreground" }),
|
|
11435
|
-
/* @__PURE__ */ jsx(
|
|
11436
|
-
Input,
|
|
11437
|
-
{
|
|
11438
|
-
type: "search",
|
|
11439
|
-
value: query,
|
|
11440
|
-
onChange: (event) => setQuery(event.target.value),
|
|
11441
|
-
onKeyDown: (event) => {
|
|
11442
|
-
if (event.key === "Enter") runFind(event.shiftKey);
|
|
11443
|
-
},
|
|
11444
|
-
placeholder: "Find",
|
|
11445
|
-
"aria-label": "Find in PDF",
|
|
11446
|
-
className: "h-7 w-28 border-input px-1 text-xs"
|
|
11447
|
-
}
|
|
11448
|
-
),
|
|
11449
|
-
/* @__PURE__ */ jsx(
|
|
11450
|
-
Button,
|
|
11451
|
-
{
|
|
11452
|
-
type: "button",
|
|
11453
|
-
variant: "ghost",
|
|
11454
|
-
size: "xs",
|
|
11455
|
-
onClick: () => runFind(false),
|
|
11456
|
-
disabled: !query.trim(),
|
|
11457
|
-
children: "Find"
|
|
11458
|
-
}
|
|
11459
|
-
)
|
|
11460
|
-
] }),
|
|
11461
|
-
error && /* @__PURE__ */ jsxs("div", { className: "flex w-full items-start gap-2 p-3 text-xs text-destructive", children: [
|
|
11462
|
-
/* @__PURE__ */ jsx(AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0" }),
|
|
11463
|
-
/* @__PURE__ */ jsx("span", { children: error })
|
|
11464
|
-
] }),
|
|
11465
|
-
!error && /* @__PURE__ */ jsx("div", { className: "relative min-h-105 flex-1 bg-muted/20", children: /* @__PURE__ */ jsx(
|
|
11466
|
-
"div",
|
|
11467
|
-
{
|
|
11468
|
-
ref: containerRef,
|
|
11469
|
-
"aria-label": `PDF viewer for ${fileName2}`,
|
|
11470
|
-
className: "absolute inset-0 overflow-auto",
|
|
11471
|
-
children: /* @__PURE__ */ jsx("div", { ref: viewerRef, className: "pdfViewer" })
|
|
11472
|
-
}
|
|
11473
|
-
) })
|
|
11585
|
+
},
|
|
11586
|
+
key
|
|
11587
|
+
);
|
|
11588
|
+
}) })
|
|
11474
11589
|
] });
|
|
11475
11590
|
}
|
|
11476
|
-
function
|
|
11477
|
-
|
|
11478
|
-
|
|
11479
|
-
|
|
11480
|
-
|
|
11591
|
+
function ArtifactRow({
|
|
11592
|
+
artifact,
|
|
11593
|
+
onFileSelect,
|
|
11594
|
+
onCommandSelect
|
|
11595
|
+
}) {
|
|
11596
|
+
if (artifact.kind === "file") {
|
|
11597
|
+
if (!onFileSelect) {
|
|
11598
|
+
return /* @__PURE__ */ jsx("span", { className: "truncate text-muted-foreground", children: artifact.label });
|
|
11599
|
+
}
|
|
11600
|
+
return /* @__PURE__ */ jsx(
|
|
11601
|
+
"button",
|
|
11602
|
+
{
|
|
11603
|
+
type: "button",
|
|
11604
|
+
onClick: () => onFileSelect(artifact.ref),
|
|
11605
|
+
className: "truncate cursor-pointer text-primary hover:underline",
|
|
11606
|
+
children: artifact.label
|
|
11607
|
+
}
|
|
11608
|
+
);
|
|
11481
11609
|
}
|
|
11482
|
-
|
|
11483
|
-
|
|
11484
|
-
|
|
11485
|
-
|
|
11486
|
-
|
|
11610
|
+
if (artifact.kind === "url") {
|
|
11611
|
+
return /* @__PURE__ */ jsx(
|
|
11612
|
+
"a",
|
|
11613
|
+
{
|
|
11614
|
+
href: artifact.ref,
|
|
11615
|
+
target: "_blank",
|
|
11616
|
+
rel: "noopener noreferrer",
|
|
11617
|
+
className: "truncate text-primary hover:underline",
|
|
11618
|
+
children: artifact.label
|
|
11619
|
+
}
|
|
11620
|
+
);
|
|
11487
11621
|
}
|
|
11488
|
-
|
|
11489
|
-
|
|
11490
|
-
|
|
11491
|
-
|
|
11492
|
-
|
|
11493
|
-
|
|
11494
|
-
|
|
11495
|
-
|
|
11496
|
-
|
|
11497
|
-
|
|
11498
|
-
|
|
11499
|
-
|
|
11500
|
-
|
|
11501
|
-
|
|
11502
|
-
|
|
11503
|
-
csv: { icon: FileSpreadsheetIcon, typeLabel: "CSV" },
|
|
11504
|
-
tsv: { icon: FileSpreadsheetIcon, typeLabel: "TSV" },
|
|
11505
|
-
// Presentations / charts
|
|
11506
|
-
ppt: { icon: FileChartLineIcon, typeLabel: "Presentation" },
|
|
11507
|
-
pptx: { icon: FileChartLineIcon, typeLabel: "Presentation" },
|
|
11508
|
-
// Images
|
|
11509
|
-
png: { icon: FileImageIcon, typeLabel: "Image" },
|
|
11510
|
-
jpg: { icon: FileImageIcon, typeLabel: "Image" },
|
|
11511
|
-
jpeg: { icon: FileImageIcon, typeLabel: "Image" },
|
|
11512
|
-
gif: { icon: FileImageIcon, typeLabel: "Image" },
|
|
11513
|
-
svg: { icon: FileImageIcon, typeLabel: "SVG image" },
|
|
11514
|
-
webp: { icon: FileImageIcon, typeLabel: "Image" },
|
|
11515
|
-
bmp: { icon: FileImageIcon, typeLabel: "Image" },
|
|
11516
|
-
// Audio / video
|
|
11517
|
-
mp3: { icon: FileAudioIcon, typeLabel: "Audio" },
|
|
11518
|
-
wav: { icon: FileAudioIcon, typeLabel: "Audio" },
|
|
11519
|
-
ogg: { icon: FileAudioIcon, typeLabel: "Audio" },
|
|
11520
|
-
mp4: { icon: FileVideoIcon, typeLabel: "Video" },
|
|
11521
|
-
mov: { icon: FileVideoIcon, typeLabel: "Video" },
|
|
11522
|
-
webm: { icon: FileVideoIcon, typeLabel: "Video" },
|
|
11523
|
-
// Archives
|
|
11524
|
-
zip: { icon: FileArchiveIcon, typeLabel: "Archive" },
|
|
11525
|
-
tar: { icon: FileArchiveIcon, typeLabel: "Archive" },
|
|
11526
|
-
gz: { icon: FileArchiveIcon, typeLabel: "Archive" },
|
|
11527
|
-
bz2: { icon: FileArchiveIcon, typeLabel: "Archive" },
|
|
11528
|
-
"7z": { icon: FileArchiveIcon, typeLabel: "Archive" },
|
|
11529
|
-
// Code
|
|
11530
|
-
py: { icon: FileCodeIcon, typeLabel: "Python" },
|
|
11531
|
-
js: { icon: FileCodeIcon, typeLabel: "JavaScript" },
|
|
11532
|
-
ts: { icon: FileCodeIcon, typeLabel: "TypeScript" },
|
|
11533
|
-
tsx: { icon: FileCodeIcon, typeLabel: "TypeScript" },
|
|
11534
|
-
jsx: { icon: FileCodeIcon, typeLabel: "JavaScript" },
|
|
11535
|
-
json: { icon: FileCodeIcon, typeLabel: "JSON" },
|
|
11536
|
-
yaml: { icon: FileCodeIcon, typeLabel: "YAML" },
|
|
11537
|
-
yml: { icon: FileCodeIcon, typeLabel: "YAML" },
|
|
11538
|
-
toml: { icon: FileCodeIcon, typeLabel: "TOML" },
|
|
11539
|
-
sh: { icon: CodeIcon, typeLabel: "Shell" },
|
|
11540
|
-
rs: { icon: FileCodeIcon, typeLabel: "Rust" },
|
|
11541
|
-
go: { icon: FileCodeIcon, typeLabel: "Go" },
|
|
11542
|
-
rb: { icon: FileCodeIcon, typeLabel: "Ruby" },
|
|
11543
|
-
java: { icon: FileCodeIcon, typeLabel: "Java" },
|
|
11544
|
-
cpp: { icon: FileCodeIcon, typeLabel: "C++" },
|
|
11545
|
-
c: { icon: FileCodeIcon, typeLabel: "C" },
|
|
11546
|
-
html: { icon: FileCodeIcon, typeLabel: "HTML" },
|
|
11547
|
-
css: { icon: FileCodeIcon, typeLabel: "CSS" },
|
|
11548
|
-
scss: { icon: FileCodeIcon, typeLabel: "Sass" },
|
|
11549
|
-
sql: { icon: FileCodeIcon, typeLabel: "SQL" }
|
|
11550
|
-
};
|
|
11551
|
-
function _extension(path) {
|
|
11552
|
-
const idx = path.lastIndexOf(".");
|
|
11553
|
-
if (idx < 0 || idx === path.length - 1) return "";
|
|
11554
|
-
return path.slice(idx + 1).toLowerCase();
|
|
11622
|
+
if (artifact.kind === "command") {
|
|
11623
|
+
if (!onCommandSelect) {
|
|
11624
|
+
return /* @__PURE__ */ jsx("span", { className: "truncate text-muted-foreground", children: artifact.label });
|
|
11625
|
+
}
|
|
11626
|
+
return /* @__PURE__ */ jsx(
|
|
11627
|
+
"button",
|
|
11628
|
+
{
|
|
11629
|
+
type: "button",
|
|
11630
|
+
onClick: () => onCommandSelect(artifact.ref),
|
|
11631
|
+
className: "truncate text-left cursor-pointer text-primary hover:underline",
|
|
11632
|
+
children: artifact.label
|
|
11633
|
+
}
|
|
11634
|
+
);
|
|
11635
|
+
}
|
|
11636
|
+
return /* @__PURE__ */ jsx("span", { className: "truncate text-muted-foreground", children: artifact.label });
|
|
11555
11637
|
}
|
|
11556
|
-
|
|
11557
|
-
|
|
11558
|
-
return
|
|
11638
|
+
var WORKING_ON_IT_DELAY_MS = 250;
|
|
11639
|
+
function hasUserFacingAskContent(msg) {
|
|
11640
|
+
return !!msg.toolCalls?.some((tc) => tc.toolName === "ask_user_question");
|
|
11559
11641
|
}
|
|
11560
|
-
function
|
|
11561
|
-
|
|
11562
|
-
|
|
11563
|
-
|
|
11564
|
-
|
|
11565
|
-
|
|
11566
|
-
|
|
11567
|
-
|
|
11568
|
-
}
|
|
11569
|
-
|
|
11570
|
-
const kind = _fileKindFor(path);
|
|
11571
|
-
const Icon = kind.icon;
|
|
11572
|
-
const downloadUrl = adapter.getWorkspaceDownloadUrl({ sessionId, path });
|
|
11573
|
-
const downloadName = _basename(path) || label;
|
|
11574
|
-
const [previewOpen, setPreviewOpen] = useState(false);
|
|
11575
|
-
const handleCardClick = useCallback(() => {
|
|
11576
|
-
setPreviewOpen(true);
|
|
11577
|
-
}, []);
|
|
11578
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
11579
|
-
/* @__PURE__ */ jsxs(
|
|
11580
|
-
"div",
|
|
11581
|
-
{
|
|
11582
|
-
role: "button",
|
|
11583
|
-
tabIndex: 0,
|
|
11584
|
-
onClick: handleCardClick,
|
|
11585
|
-
onKeyDown: (e) => {
|
|
11586
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
11587
|
-
e.preventDefault();
|
|
11588
|
-
handleCardClick();
|
|
11589
|
-
}
|
|
11590
|
-
},
|
|
11591
|
-
className: cn(
|
|
11592
|
-
"group/file flex items-center gap-3 rounded-lg border border-border",
|
|
11593
|
-
"bg-muted/20 px-3 py-2 text-left transition-colors",
|
|
11594
|
-
"cursor-pointer hover:bg-muted/40"
|
|
11595
|
-
),
|
|
11596
|
-
children: [
|
|
11597
|
-
/* @__PURE__ */ jsx("div", { className: "flex size-10 shrink-0 items-center justify-center rounded-md border border-border bg-background", children: /* @__PURE__ */ jsx(Icon, { className: "size-5 text-muted-foreground", "aria-hidden": true }) }),
|
|
11598
|
-
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
11599
|
-
/* @__PURE__ */ jsx("div", { className: "truncate text-sm font-medium text-foreground", children: label }),
|
|
11600
|
-
/* @__PURE__ */ jsx("div", { className: "truncate text-xs text-muted-foreground", children: kind.typeLabel })
|
|
11601
|
-
] }),
|
|
11602
|
-
/* @__PURE__ */ jsxs(
|
|
11603
|
-
"a",
|
|
11604
|
-
{
|
|
11605
|
-
href: downloadUrl,
|
|
11606
|
-
download: downloadName,
|
|
11607
|
-
onClick: (e) => e.stopPropagation(),
|
|
11608
|
-
className: cn(
|
|
11609
|
-
"shrink-0 inline-flex items-center gap-1.5 rounded-md border border-border",
|
|
11610
|
-
"bg-background px-2.5 py-1.5 text-xs font-medium text-foreground",
|
|
11611
|
-
"transition-colors hover:bg-muted"
|
|
11612
|
-
),
|
|
11613
|
-
"aria-label": `Download ${downloadName}`,
|
|
11614
|
-
children: [
|
|
11615
|
-
/* @__PURE__ */ jsx(DownloadIcon, { className: "size-3.5", "aria-hidden": true }),
|
|
11616
|
-
"Download"
|
|
11617
|
-
]
|
|
11618
|
-
}
|
|
11619
|
-
)
|
|
11620
|
-
]
|
|
11621
|
-
}
|
|
11622
|
-
),
|
|
11623
|
-
/* @__PURE__ */ jsx(
|
|
11624
|
-
WorkspaceFilePreviewDialog,
|
|
11625
|
-
{
|
|
11626
|
-
open: previewOpen,
|
|
11627
|
-
onOpenChange: setPreviewOpen,
|
|
11628
|
-
sessionId,
|
|
11629
|
-
path,
|
|
11630
|
-
downloadUrl
|
|
11631
|
-
}
|
|
11632
|
-
)
|
|
11633
|
-
] });
|
|
11634
|
-
}
|
|
11635
|
-
function WorkspaceFilePreviewDialog({
|
|
11636
|
-
open,
|
|
11637
|
-
onOpenChange,
|
|
11638
|
-
sessionId,
|
|
11639
|
-
path,
|
|
11640
|
-
downloadUrl
|
|
11641
|
-
}) {
|
|
11642
|
-
const { adapter } = useAgentChatAdapterContext();
|
|
11643
|
-
const [file, setFile] = useState(null);
|
|
11644
|
-
const [loading, setLoading] = useState(false);
|
|
11645
|
-
const [error, setError] = useState(null);
|
|
11646
|
-
useEffect(() => {
|
|
11647
|
-
if (!open) return;
|
|
11648
|
-
let cancelled = false;
|
|
11649
|
-
setLoading(true);
|
|
11650
|
-
setError(null);
|
|
11651
|
-
setFile(null);
|
|
11652
|
-
adapter.getWorkspaceFile({ sessionId, path }).then((result) => {
|
|
11653
|
-
if (cancelled) return;
|
|
11654
|
-
setFile(result);
|
|
11655
|
-
setLoading(false);
|
|
11656
|
-
}).catch((err) => {
|
|
11657
|
-
if (cancelled) return;
|
|
11658
|
-
const message = err instanceof Error ? err.message : "Failed to load file";
|
|
11659
|
-
setError(message);
|
|
11660
|
-
setLoading(false);
|
|
11661
|
-
});
|
|
11662
|
-
return () => {
|
|
11663
|
-
cancelled = true;
|
|
11664
|
-
};
|
|
11665
|
-
}, [adapter, sessionId, path, open]);
|
|
11666
|
-
return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(DialogContent, { className: "flex h-[85vh] w-[95vw] max-w-350! flex-col gap-0 overflow-hidden p-0", children: [
|
|
11667
|
-
/* @__PURE__ */ jsxs(DialogHeader, { className: "flex flex-row items-center gap-2 border-b border-line px-4 py-3", children: [
|
|
11668
|
-
/* @__PURE__ */ jsx(DialogTitle, { className: "min-w-0 flex-1 truncate text-sm", children: _basename(path) }),
|
|
11669
|
-
/* @__PURE__ */ jsxs(
|
|
11670
|
-
"a",
|
|
11671
|
-
{
|
|
11672
|
-
href: downloadUrl,
|
|
11673
|
-
download: _basename(path),
|
|
11674
|
-
className: "mr-8 inline-flex shrink-0 items-center gap-1.5 rounded-md border border-border bg-background px-2.5 py-1 text-xs font-medium text-foreground transition-colors hover:bg-muted",
|
|
11675
|
-
"aria-label": `Download ${_basename(path)}`,
|
|
11676
|
-
children: [
|
|
11677
|
-
/* @__PURE__ */ jsx(DownloadIcon, { className: "size-3.5", "aria-hidden": true }),
|
|
11678
|
-
"Download"
|
|
11679
|
-
]
|
|
11680
|
-
}
|
|
11681
|
-
)
|
|
11682
|
-
] }),
|
|
11683
|
-
/* @__PURE__ */ jsx("div", { className: "flex min-h-0 flex-1 flex-col", children: /* @__PURE__ */ jsx(
|
|
11684
|
-
FileViewer,
|
|
11685
|
-
{
|
|
11686
|
-
file,
|
|
11687
|
-
loading,
|
|
11688
|
-
error,
|
|
11689
|
-
downloadUrl,
|
|
11690
|
-
onDelete: null,
|
|
11691
|
-
onClose: () => onOpenChange(false),
|
|
11692
|
-
hideHeader: true
|
|
11693
|
-
}
|
|
11694
|
-
) })
|
|
11695
|
-
] }) });
|
|
11696
|
-
}
|
|
11697
|
-
function scrollToArtifactBlock(artifactId) {
|
|
11698
|
-
document.querySelector(`[data-artifact-anchor="${CSS.escape(artifactId)}"]`)?.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
11699
|
-
}
|
|
11700
|
-
function ArtifactRefCard({
|
|
11701
|
-
artifactId,
|
|
11702
|
-
name,
|
|
11703
|
-
kind
|
|
11704
|
-
}) {
|
|
11705
|
-
return /* @__PURE__ */ jsxs(
|
|
11706
|
-
"button",
|
|
11707
|
-
{
|
|
11708
|
-
type: "button",
|
|
11709
|
-
onClick: () => scrollToArtifactBlock(artifactId),
|
|
11710
|
-
className: "flex w-full min-w-0 cursor-pointer items-center gap-2 rounded border border-border bg-background px-3 py-2 text-left text-sm transition-colors hover:bg-muted/60",
|
|
11711
|
-
children: [
|
|
11712
|
-
/* @__PURE__ */ jsx(FileTextIcon, { className: "size-4 shrink-0 text-muted-foreground", "aria-hidden": true }),
|
|
11713
|
-
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate font-medium text-foreground", children: name }),
|
|
11714
|
-
/* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: KIND_LABEL[kind] })
|
|
11715
|
-
]
|
|
11716
|
-
}
|
|
11717
|
-
);
|
|
11718
|
-
}
|
|
11719
|
-
function resolveArtifactRef(ref, messages) {
|
|
11720
|
-
for (const msg of messages) {
|
|
11721
|
-
if (msg.role !== "system" || msg.systemKind !== "artifact") continue;
|
|
11722
|
-
const meta = msg.systemMeta ?? {};
|
|
11723
|
-
if (meta.artifact_id !== ref) continue;
|
|
11724
|
-
const kind = meta.kind ?? "markdown";
|
|
11725
|
-
const version = typeof meta.version === "number" ? meta.version : 1;
|
|
11726
|
-
const name = typeof meta.name === "string" ? meta.name : "";
|
|
11727
|
-
return { artifactId: ref, name, kind, version };
|
|
11728
|
-
}
|
|
11729
|
-
return null;
|
|
11730
|
-
}
|
|
11731
|
-
function TurnSummaryCard({
|
|
11732
|
-
summary,
|
|
11733
|
-
sessionId,
|
|
11734
|
-
messages,
|
|
11735
|
-
onFileSelect,
|
|
11736
|
-
onCommandSelect
|
|
11737
|
-
}) {
|
|
11738
|
-
const hasRecap = summary.recap.trim().length > 0;
|
|
11739
|
-
const hasArtifacts = summary.artifacts.length > 0;
|
|
11740
|
-
if (!hasArtifacts) return null;
|
|
11741
|
-
return /* @__PURE__ */ jsxs("div", { className: "mt-3 rounded border border-border bg-muted/50 px-3 py-2 text-sm", children: [
|
|
11742
|
-
/* @__PURE__ */ jsx("div", { className: "mb-2 text-xs font-semibold uppercase tracking-wide", children: "Summary" }),
|
|
11743
|
-
hasRecap && /* @__PURE__ */ jsx("p", { className: "mb-2 whitespace-pre-wrap text-foreground", children: summary.recap }),
|
|
11744
|
-
hasArtifacts && /* @__PURE__ */ jsx("div", { className: "space-y-2", children: summary.artifacts.map((artifact, i) => {
|
|
11745
|
-
const key = `${artifact.kind}:${artifact.ref}:${i}`;
|
|
11746
|
-
if (artifact.kind === "file" && sessionId) {
|
|
11747
|
-
return /* @__PURE__ */ jsx(
|
|
11748
|
-
WorkspaceFileCard,
|
|
11749
|
-
{
|
|
11750
|
-
sessionId,
|
|
11751
|
-
path: artifact.ref,
|
|
11752
|
-
label: artifact.label
|
|
11753
|
-
},
|
|
11754
|
-
key
|
|
11755
|
-
);
|
|
11756
|
-
}
|
|
11757
|
-
if (artifact.kind === "artifact") {
|
|
11758
|
-
const resolved = sessionId ? resolveArtifactRef(artifact.ref, messages) : null;
|
|
11759
|
-
if (resolved) {
|
|
11760
|
-
return /* @__PURE__ */ jsx(
|
|
11761
|
-
ArtifactRefCard,
|
|
11762
|
-
{
|
|
11763
|
-
artifactId: resolved.artifactId,
|
|
11764
|
-
name: resolved.name || artifact.label,
|
|
11765
|
-
kind: resolved.kind
|
|
11766
|
-
},
|
|
11767
|
-
key
|
|
11768
|
-
);
|
|
11769
|
-
}
|
|
11770
|
-
}
|
|
11771
|
-
return /* @__PURE__ */ jsxs(
|
|
11772
|
-
"div",
|
|
11773
|
-
{
|
|
11774
|
-
className: "flex items-baseline gap-2 text-sm",
|
|
11775
|
-
children: [
|
|
11776
|
-
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", "aria-hidden": true, children: "\u2022" }),
|
|
11777
|
-
/* @__PURE__ */ jsx(
|
|
11778
|
-
ArtifactRow,
|
|
11779
|
-
{
|
|
11780
|
-
artifact,
|
|
11781
|
-
onFileSelect,
|
|
11782
|
-
onCommandSelect
|
|
11783
|
-
}
|
|
11784
|
-
)
|
|
11785
|
-
]
|
|
11786
|
-
},
|
|
11787
|
-
key
|
|
11788
|
-
);
|
|
11789
|
-
}) })
|
|
11790
|
-
] });
|
|
11791
|
-
}
|
|
11792
|
-
function ArtifactRow({
|
|
11793
|
-
artifact,
|
|
11794
|
-
onFileSelect,
|
|
11795
|
-
onCommandSelect
|
|
11796
|
-
}) {
|
|
11797
|
-
if (artifact.kind === "file") {
|
|
11798
|
-
if (!onFileSelect) {
|
|
11799
|
-
return /* @__PURE__ */ jsx("span", { className: "truncate text-muted-foreground", children: artifact.label });
|
|
11800
|
-
}
|
|
11801
|
-
return /* @__PURE__ */ jsx(
|
|
11802
|
-
"button",
|
|
11803
|
-
{
|
|
11804
|
-
type: "button",
|
|
11805
|
-
onClick: () => onFileSelect(artifact.ref),
|
|
11806
|
-
className: "truncate cursor-pointer text-primary hover:underline",
|
|
11807
|
-
children: artifact.label
|
|
11808
|
-
}
|
|
11809
|
-
);
|
|
11810
|
-
}
|
|
11811
|
-
if (artifact.kind === "url") {
|
|
11812
|
-
return /* @__PURE__ */ jsx(
|
|
11813
|
-
"a",
|
|
11814
|
-
{
|
|
11815
|
-
href: artifact.ref,
|
|
11816
|
-
target: "_blank",
|
|
11817
|
-
rel: "noopener noreferrer",
|
|
11818
|
-
className: "truncate text-primary hover:underline",
|
|
11819
|
-
children: artifact.label
|
|
11820
|
-
}
|
|
11821
|
-
);
|
|
11822
|
-
}
|
|
11823
|
-
if (artifact.kind === "command") {
|
|
11824
|
-
if (!onCommandSelect) {
|
|
11825
|
-
return /* @__PURE__ */ jsx("span", { className: "truncate text-muted-foreground", children: artifact.label });
|
|
11826
|
-
}
|
|
11827
|
-
return /* @__PURE__ */ jsx(
|
|
11828
|
-
"button",
|
|
11829
|
-
{
|
|
11830
|
-
type: "button",
|
|
11831
|
-
onClick: () => onCommandSelect(artifact.ref),
|
|
11832
|
-
className: "truncate text-left cursor-pointer text-primary hover:underline",
|
|
11833
|
-
children: artifact.label
|
|
11834
|
-
}
|
|
11835
|
-
);
|
|
11836
|
-
}
|
|
11837
|
-
return /* @__PURE__ */ jsx("span", { className: "truncate text-muted-foreground", children: artifact.label });
|
|
11838
|
-
}
|
|
11839
|
-
var WORKING_ON_IT_DELAY_MS = 250;
|
|
11840
|
-
function hasUserFacingAskContent(msg) {
|
|
11841
|
-
return !!msg.toolCalls?.some((tc) => tc.toolName === "ask_user_question");
|
|
11842
|
-
}
|
|
11843
|
-
function pendingAskCall(messages) {
|
|
11844
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
11845
|
-
const m = messages[i];
|
|
11846
|
-
if (m.role === "user") return null;
|
|
11847
|
-
if (m.role !== "assistant") continue;
|
|
11848
|
-
return m.toolCalls?.find(
|
|
11849
|
-
(tc) => tc.toolName === "ask_user_question" && tc.status === "running"
|
|
11850
|
-
) ?? null;
|
|
11851
|
-
}
|
|
11852
|
-
return null;
|
|
11642
|
+
function pendingAskCall(messages) {
|
|
11643
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
11644
|
+
const m = messages[i];
|
|
11645
|
+
if (m.role === "user") return null;
|
|
11646
|
+
if (m.role !== "assistant") continue;
|
|
11647
|
+
return m.toolCalls?.find(
|
|
11648
|
+
(tc) => tc.toolName === "ask_user_question" && tc.status === "running"
|
|
11649
|
+
) ?? null;
|
|
11650
|
+
}
|
|
11651
|
+
return null;
|
|
11853
11652
|
}
|
|
11854
11653
|
function askComposerPolicy(pending) {
|
|
11855
11654
|
if (!pending) return null;
|
|
@@ -12771,7 +12570,7 @@ function RetryBanner({ indicator }) {
|
|
|
12771
12570
|
"attempt ",
|
|
12772
12571
|
indicator.attempt
|
|
12773
12572
|
] }),
|
|
12774
|
-
indicator.detail && (open ? /* @__PURE__ */ jsx(ChevronDown, { className: "size-3" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "size-3" }))
|
|
12573
|
+
indicator.detail && (open ? /* @__PURE__ */ jsx(ChevronDown, { className: "size-3" }) : /* @__PURE__ */ jsx(ChevronRight$1, { className: "size-3" }))
|
|
12775
12574
|
]
|
|
12776
12575
|
}
|
|
12777
12576
|
),
|
|
@@ -12814,6 +12613,7 @@ function ChatThread({
|
|
|
12814
12613
|
retryIndicator,
|
|
12815
12614
|
onRetry,
|
|
12816
12615
|
onComposerError,
|
|
12616
|
+
paneCards,
|
|
12817
12617
|
showBrowser = false,
|
|
12818
12618
|
onToggleBrowser,
|
|
12819
12619
|
showWorkspace = false,
|
|
@@ -12893,6 +12693,54 @@ function ChatThread({
|
|
|
12893
12693
|
const composerArea = /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-4xl px-3 sm:px-6 pb-3 sm:pb-5 pt-3", children: [
|
|
12894
12694
|
retryIndicator && /* @__PURE__ */ jsx("div", { className: "mb-2", children: /* @__PURE__ */ jsx(RetryBanner, { indicator: retryIndicator }) }),
|
|
12895
12695
|
researchSources.length > 0 && /* @__PURE__ */ jsx("div", { className: "mb-2", children: /* @__PURE__ */ jsx(ResearchSourcesPanel, { sources: researchSources }) }),
|
|
12696
|
+
(paneCards?.browser || paneCards?.files) && // Layers sliding out from behind the prompt box: each card is
|
|
12697
|
+
// narrower than the one below it and rounded only on top, with a
|
|
12698
|
+
// negative bottom margin so the next layer covers its flat edge. The
|
|
12699
|
+
// composer is the widest and last, so the stack reads as coming out
|
|
12700
|
+
// of it rather than sitting on top of it.
|
|
12701
|
+
/* @__PURE__ */ jsxs(
|
|
12702
|
+
"div",
|
|
12703
|
+
{
|
|
12704
|
+
"data-testid": "session-pane-cards",
|
|
12705
|
+
className: "flex flex-col",
|
|
12706
|
+
children: [
|
|
12707
|
+
paneCards.browser && /* @__PURE__ */ jsx(
|
|
12708
|
+
SessionPaneCard,
|
|
12709
|
+
{
|
|
12710
|
+
testId: "session-pane-card-browser",
|
|
12711
|
+
title: "Browser",
|
|
12712
|
+
icon: GlobeIcon,
|
|
12713
|
+
subtitle: paneCards.browser.subtitle,
|
|
12714
|
+
thumbnail: paneCards.browser.thumbnail,
|
|
12715
|
+
onOpen: paneCards.browser.onOpen,
|
|
12716
|
+
className: "w-auto rounded-t-xl border-input border-[2px] border-b-0 -mb-[10px]"
|
|
12717
|
+
}
|
|
12718
|
+
),
|
|
12719
|
+
paneCards.files && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
12720
|
+
/* @__PURE__ */ jsx(
|
|
12721
|
+
SessionPaneCard,
|
|
12722
|
+
{
|
|
12723
|
+
testId: "session-pane-card-files",
|
|
12724
|
+
title: "Files",
|
|
12725
|
+
icon: FolderIcon,
|
|
12726
|
+
count: paneCards.files.count,
|
|
12727
|
+
expanded: paneCards.files.open,
|
|
12728
|
+
onOpen: paneCards.files.onToggle,
|
|
12729
|
+
className: "w-auto rounded-t-xl border-input border-[2px] border-b-0 -mb-[10px]"
|
|
12730
|
+
}
|
|
12731
|
+
),
|
|
12732
|
+
paneCards.files.open && /* @__PURE__ */ jsx(
|
|
12733
|
+
"div",
|
|
12734
|
+
{
|
|
12735
|
+
"data-testid": "files-accordion",
|
|
12736
|
+
className: "-mb-[10px] flex h-72 min-h-0 flex-col overflow-hidden border border-b-0 border-line",
|
|
12737
|
+
children: paneCards.files.panel
|
|
12738
|
+
}
|
|
12739
|
+
)
|
|
12740
|
+
] })
|
|
12741
|
+
]
|
|
12742
|
+
}
|
|
12743
|
+
),
|
|
12896
12744
|
/* @__PURE__ */ jsx(
|
|
12897
12745
|
ChatComposer,
|
|
12898
12746
|
{
|
|
@@ -14518,17 +14366,25 @@ function useAgentChatRuntime({
|
|
|
14518
14366
|
}));
|
|
14519
14367
|
}
|
|
14520
14368
|
}).catch(() => void 0);
|
|
14521
|
-
|
|
14522
|
-
|
|
14523
|
-
|
|
14524
|
-
|
|
14525
|
-
|
|
14526
|
-
|
|
14527
|
-
|
|
14528
|
-
|
|
14529
|
-
|
|
14530
|
-
|
|
14369
|
+
const hydrateBrowserState = () => {
|
|
14370
|
+
void adapter.getBrowserState(sessionId).then((browserState) => {
|
|
14371
|
+
if (sessionIdRef.current !== sessionId) return;
|
|
14372
|
+
setState((prev) => ({
|
|
14373
|
+
...prev,
|
|
14374
|
+
browser: browserState ? {
|
|
14375
|
+
status: browserState.status,
|
|
14376
|
+
controlOwner: browserState.controlOwner ?? null
|
|
14377
|
+
} : null
|
|
14378
|
+
}));
|
|
14379
|
+
}).catch(() => void 0);
|
|
14380
|
+
};
|
|
14381
|
+
hydrateBrowserState();
|
|
14382
|
+
const browserSettleTimer = window.setTimeout(
|
|
14383
|
+
hydrateBrowserState,
|
|
14384
|
+
RECONCILE_FIRST_DELAY
|
|
14385
|
+
);
|
|
14531
14386
|
return () => {
|
|
14387
|
+
window.clearTimeout(browserSettleTimer);
|
|
14532
14388
|
clearReconnectTimer();
|
|
14533
14389
|
closeStream();
|
|
14534
14390
|
};
|
|
@@ -16481,10 +16337,10 @@ async function latexToSvg(latex, display = true) {
|
|
|
16481
16337
|
const start = outer.indexOf("<svg");
|
|
16482
16338
|
const end = outer.lastIndexOf("</svg>");
|
|
16483
16339
|
if (start === -1 || end === -1) return EMPTY;
|
|
16484
|
-
const
|
|
16485
|
-
const dims =
|
|
16340
|
+
const svg2 = outer.slice(start, end + "</svg>".length);
|
|
16341
|
+
const dims = svg2.match(/width="([^"]+)"\s+height="([^"]+)"/);
|
|
16486
16342
|
return {
|
|
16487
|
-
svg,
|
|
16343
|
+
svg: svg2,
|
|
16488
16344
|
widthEm: exToEm(dims?.[1]),
|
|
16489
16345
|
heightEm: exToEm(dims?.[2])
|
|
16490
16346
|
};
|
|
@@ -16512,13 +16368,13 @@ var FormulaCache = class {
|
|
|
16512
16368
|
}
|
|
16513
16369
|
async load(latex) {
|
|
16514
16370
|
const key = latex;
|
|
16515
|
-
const { svg, widthEm, heightEm } = await latexToSvg(latex);
|
|
16516
|
-
if (!
|
|
16371
|
+
const { svg: svg2, widthEm, heightEm } = await latexToSvg(latex);
|
|
16372
|
+
if (!svg2) {
|
|
16517
16373
|
this.pending.delete(key);
|
|
16518
16374
|
return;
|
|
16519
16375
|
}
|
|
16520
16376
|
const image = new Image();
|
|
16521
|
-
image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(
|
|
16377
|
+
image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg2)}`;
|
|
16522
16378
|
const settle = (ok) => {
|
|
16523
16379
|
this.pending.delete(key);
|
|
16524
16380
|
if (!ok) return;
|
|
@@ -16552,8 +16408,8 @@ var FormulaCache = class {
|
|
|
16552
16408
|
};
|
|
16553
16409
|
|
|
16554
16410
|
// src/components/whiteboard/input.ts
|
|
16555
|
-
var
|
|
16556
|
-
var
|
|
16411
|
+
var MIN_ZOOM2 = 0.02;
|
|
16412
|
+
var MAX_ZOOM2 = 256;
|
|
16557
16413
|
var ZOOM_DIVISOR = 300;
|
|
16558
16414
|
function screenToLogical(pt, view) {
|
|
16559
16415
|
return { x: view.x + pt.x / view.zoom, y: view.y + pt.y / view.zoom };
|
|
@@ -16563,7 +16419,7 @@ function logicalToScreen(pt, view) {
|
|
|
16563
16419
|
}
|
|
16564
16420
|
var clamp2 = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
|
|
16565
16421
|
function zoomAt(view, screenPt, factor) {
|
|
16566
|
-
const zoom = clamp2(view.zoom * factor,
|
|
16422
|
+
const zoom = clamp2(view.zoom * factor, MIN_ZOOM2, MAX_ZOOM2);
|
|
16567
16423
|
const anchor = screenToLogical(screenPt, view);
|
|
16568
16424
|
return {
|
|
16569
16425
|
zoom,
|
|
@@ -16587,8 +16443,8 @@ function zoomToFit(bounds, size2, padding = 80) {
|
|
|
16587
16443
|
}
|
|
16588
16444
|
const zoom = clamp2(
|
|
16589
16445
|
Math.min(size2.w / (bounds.w + padding * 2), size2.h / (bounds.h + padding * 2)),
|
|
16590
|
-
|
|
16591
|
-
|
|
16446
|
+
MIN_ZOOM2,
|
|
16447
|
+
MAX_ZOOM2
|
|
16592
16448
|
);
|
|
16593
16449
|
return {
|
|
16594
16450
|
zoom,
|
|
@@ -17907,206 +17763,632 @@ function WhiteboardSurface({
|
|
|
17907
17763
|
/* @__PURE__ */ jsx("div", { className: "absolute left-2 top-2 z-10", children: /* @__PURE__ */ jsx(
|
|
17908
17764
|
ToolRail,
|
|
17909
17765
|
{
|
|
17910
|
-
tool,
|
|
17911
|
-
onToolChange: setTool,
|
|
17912
|
-
color,
|
|
17913
|
-
onColorChange: setColor,
|
|
17914
|
-
width,
|
|
17915
|
-
onWidthChange: setWidth,
|
|
17916
|
-
canUndo: undoStack.current.length > 0,
|
|
17917
|
-
canRedo: redoStack.current.length > 0,
|
|
17918
|
-
onUndo: undo,
|
|
17919
|
-
onRedo: redo,
|
|
17920
|
-
onFit: fitToContent,
|
|
17921
|
-
disabled
|
|
17922
|
-
},
|
|
17923
|
-
historyTick
|
|
17924
|
-
) }),
|
|
17925
|
-
/* @__PURE__ */ jsxs("div", { ref: wrapRef, className: "relative min-h-0 flex-1 overflow-hidden", children: [
|
|
17926
|
-
/* @__PURE__ */ jsx(
|
|
17927
|
-
"canvas",
|
|
17928
|
-
{
|
|
17929
|
-
ref: canvasRef,
|
|
17930
|
-
"aria-label": "Whiteboard canvas",
|
|
17931
|
-
className: cn(
|
|
17932
|
-
"h-full w-full touch-none bg-white",
|
|
17933
|
-
tool === "pan" ? "cursor-grab" : "cursor-crosshair",
|
|
17934
|
-
// Held from the first frame, not from when the veil
|
|
17935
|
-
// appears: the load resolves by replacing the document
|
|
17936
|
-
// wholesale, so anything drawn before it lands is thrown
|
|
17937
|
-
// away. Better to decline the stroke than to eat it.
|
|
17938
|
-
//
|
|
17939
|
-
// Held again while the agent works. The atlas and the
|
|
17940
|
-
// occupied cells it is answering were captured when Ask
|
|
17941
|
-
// was pressed, so ink added now is invisible to it — it
|
|
17942
|
-
// places its answer against a board that no longer
|
|
17943
|
-
// matches, and can land on top of what was just drawn.
|
|
17944
|
-
(!canvasReady || runtime.isRunning) && "pointer-events-none"
|
|
17945
|
-
),
|
|
17946
|
-
style: { width: size2.w, height: size2.h },
|
|
17947
|
-
onPointerDown,
|
|
17948
|
-
onPointerMove,
|
|
17949
|
-
onPointerUp,
|
|
17950
|
-
onPointerCancel: onPointerUp,
|
|
17951
|
-
onContextMenu: (e) => e.preventDefault(),
|
|
17952
|
-
onPointerLeave: () => {
|
|
17953
|
-
if (cursorRef.current) {
|
|
17954
|
-
cursorRef.current = null;
|
|
17955
|
-
paint();
|
|
17956
|
-
}
|
|
17957
|
-
},
|
|
17958
|
-
onWheel
|
|
17959
|
-
}
|
|
17960
|
-
),
|
|
17961
|
-
runtime.isRunning ? /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-0 bg-black/10", children: /* @__PURE__ */ jsx("div", { className: "absolute inset-x-0 bottom-2 flex justify-center px-12", children: /* @__PURE__ */ jsxs("span", { className: "flex max-w-full items-center gap-2 rounded-full border bg-background px-4 py-1.5 text-sm text-muted-foreground shadow-sm", children: [
|
|
17962
|
-
/* @__PURE__ */ jsx(Spinner, { className: "size-4 shrink-0" }),
|
|
17963
|
-
/* @__PURE__ */ jsx("span", { className: "truncate", children: progress ?? (askMode === "deep" ? "Working it out\u2026" : "Thinking\u2026") })
|
|
17964
|
-
] }) }) }) : null,
|
|
17965
|
-
restoringVisible ? /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-0 flex items-center justify-center bg-black/10", children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-3 rounded-full border bg-background px-6 py-3 text-sm font-medium text-muted-foreground shadow-md", children: [
|
|
17966
|
-
/* @__PURE__ */ jsx(Spinner, { className: "size-5" }),
|
|
17967
|
-
"Restoring board\u2026"
|
|
17968
|
-
] }) }) : null,
|
|
17969
|
-
artifacts.map((a) => {
|
|
17970
|
-
const tl = logicalToScreen({ x: a.x, y: a.y }, view);
|
|
17971
|
-
const meta = artifactMeta.get(a.artifactId);
|
|
17972
|
-
return /* @__PURE__ */ jsx(
|
|
17973
|
-
"div",
|
|
17974
|
-
{
|
|
17975
|
-
"data-artifact-id": a.artifactId,
|
|
17976
|
-
className: "pointer-events-auto absolute overflow-auto rounded border bg-background",
|
|
17977
|
-
style: {
|
|
17978
|
-
left: tl.x,
|
|
17979
|
-
top: tl.y,
|
|
17980
|
-
width: a.w * view.zoom,
|
|
17981
|
-
height: a.h * view.zoom
|
|
17982
|
-
},
|
|
17983
|
-
children: meta && sessionId ? /* @__PURE__ */ jsx(
|
|
17984
|
-
ArtifactBlock,
|
|
17985
|
-
{
|
|
17986
|
-
sessionId,
|
|
17987
|
-
artifactId: a.artifactId,
|
|
17988
|
-
name: meta.name,
|
|
17989
|
-
kind: meta.kind,
|
|
17990
|
-
version: a.version ?? meta.version
|
|
17991
|
-
}
|
|
17992
|
-
) : null
|
|
17993
|
-
},
|
|
17994
|
-
a.id
|
|
17995
|
-
);
|
|
17996
|
-
}),
|
|
17997
|
-
editor ? /* @__PURE__ */ jsx(
|
|
17998
|
-
"textarea",
|
|
17999
|
-
{
|
|
18000
|
-
autoFocus: true,
|
|
18001
|
-
"aria-label": "Text",
|
|
18002
|
-
className: "absolute rounded border bg-background p-1 text-sm shadow",
|
|
18003
|
-
style: {
|
|
18004
|
-
left: logicalToScreen(editor, view).x,
|
|
18005
|
-
top: logicalToScreen(editor, view).y,
|
|
18006
|
-
width: TEXT_MAX_WIDTH * view.zoom
|
|
18007
|
-
},
|
|
18008
|
-
value: editorText,
|
|
18009
|
-
onChange: (e) => setEditorText(e.target.value),
|
|
18010
|
-
onBlur: commitEditor,
|
|
18011
|
-
onKeyDown: (e) => {
|
|
18012
|
-
if (e.key === "Escape") {
|
|
18013
|
-
e.preventDefault();
|
|
18014
|
-
setEditor(null);
|
|
18015
|
-
setEditorText("");
|
|
18016
|
-
} else if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
|
18017
|
-
e.preventDefault();
|
|
18018
|
-
commitEditor();
|
|
18019
|
-
}
|
|
18020
|
-
}
|
|
18021
|
-
}
|
|
18022
|
-
) : null
|
|
18023
|
-
] })
|
|
18024
|
-
] }),
|
|
18025
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-t p-2", children: [
|
|
17766
|
+
tool,
|
|
17767
|
+
onToolChange: setTool,
|
|
17768
|
+
color,
|
|
17769
|
+
onColorChange: setColor,
|
|
17770
|
+
width,
|
|
17771
|
+
onWidthChange: setWidth,
|
|
17772
|
+
canUndo: undoStack.current.length > 0,
|
|
17773
|
+
canRedo: redoStack.current.length > 0,
|
|
17774
|
+
onUndo: undo,
|
|
17775
|
+
onRedo: redo,
|
|
17776
|
+
onFit: fitToContent,
|
|
17777
|
+
disabled
|
|
17778
|
+
},
|
|
17779
|
+
historyTick
|
|
17780
|
+
) }),
|
|
17781
|
+
/* @__PURE__ */ jsxs("div", { ref: wrapRef, className: "relative min-h-0 flex-1 overflow-hidden", children: [
|
|
17782
|
+
/* @__PURE__ */ jsx(
|
|
17783
|
+
"canvas",
|
|
17784
|
+
{
|
|
17785
|
+
ref: canvasRef,
|
|
17786
|
+
"aria-label": "Whiteboard canvas",
|
|
17787
|
+
className: cn(
|
|
17788
|
+
"h-full w-full touch-none bg-white",
|
|
17789
|
+
tool === "pan" ? "cursor-grab" : "cursor-crosshair",
|
|
17790
|
+
// Held from the first frame, not from when the veil
|
|
17791
|
+
// appears: the load resolves by replacing the document
|
|
17792
|
+
// wholesale, so anything drawn before it lands is thrown
|
|
17793
|
+
// away. Better to decline the stroke than to eat it.
|
|
17794
|
+
//
|
|
17795
|
+
// Held again while the agent works. The atlas and the
|
|
17796
|
+
// occupied cells it is answering were captured when Ask
|
|
17797
|
+
// was pressed, so ink added now is invisible to it — it
|
|
17798
|
+
// places its answer against a board that no longer
|
|
17799
|
+
// matches, and can land on top of what was just drawn.
|
|
17800
|
+
(!canvasReady || runtime.isRunning) && "pointer-events-none"
|
|
17801
|
+
),
|
|
17802
|
+
style: { width: size2.w, height: size2.h },
|
|
17803
|
+
onPointerDown,
|
|
17804
|
+
onPointerMove,
|
|
17805
|
+
onPointerUp,
|
|
17806
|
+
onPointerCancel: onPointerUp,
|
|
17807
|
+
onContextMenu: (e) => e.preventDefault(),
|
|
17808
|
+
onPointerLeave: () => {
|
|
17809
|
+
if (cursorRef.current) {
|
|
17810
|
+
cursorRef.current = null;
|
|
17811
|
+
paint();
|
|
17812
|
+
}
|
|
17813
|
+
},
|
|
17814
|
+
onWheel
|
|
17815
|
+
}
|
|
17816
|
+
),
|
|
17817
|
+
runtime.isRunning ? /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-0 bg-black/10", children: /* @__PURE__ */ jsx("div", { className: "absolute inset-x-0 bottom-2 flex justify-center px-12", children: /* @__PURE__ */ jsxs("span", { className: "flex max-w-full items-center gap-2 rounded-full border bg-background px-4 py-1.5 text-sm text-muted-foreground shadow-sm", children: [
|
|
17818
|
+
/* @__PURE__ */ jsx(Spinner, { className: "size-4 shrink-0" }),
|
|
17819
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: progress ?? (askMode === "deep" ? "Working it out\u2026" : "Thinking\u2026") })
|
|
17820
|
+
] }) }) }) : null,
|
|
17821
|
+
restoringVisible ? /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-0 flex items-center justify-center bg-black/10", children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-3 rounded-full border bg-background px-6 py-3 text-sm font-medium text-muted-foreground shadow-md", children: [
|
|
17822
|
+
/* @__PURE__ */ jsx(Spinner, { className: "size-5" }),
|
|
17823
|
+
"Restoring board\u2026"
|
|
17824
|
+
] }) }) : null,
|
|
17825
|
+
artifacts.map((a) => {
|
|
17826
|
+
const tl = logicalToScreen({ x: a.x, y: a.y }, view);
|
|
17827
|
+
const meta = artifactMeta.get(a.artifactId);
|
|
17828
|
+
return /* @__PURE__ */ jsx(
|
|
17829
|
+
"div",
|
|
17830
|
+
{
|
|
17831
|
+
"data-artifact-id": a.artifactId,
|
|
17832
|
+
className: "pointer-events-auto absolute overflow-auto rounded border bg-background",
|
|
17833
|
+
style: {
|
|
17834
|
+
left: tl.x,
|
|
17835
|
+
top: tl.y,
|
|
17836
|
+
width: a.w * view.zoom,
|
|
17837
|
+
height: a.h * view.zoom
|
|
17838
|
+
},
|
|
17839
|
+
children: meta && sessionId ? /* @__PURE__ */ jsx(
|
|
17840
|
+
ArtifactBlock,
|
|
17841
|
+
{
|
|
17842
|
+
sessionId,
|
|
17843
|
+
artifactId: a.artifactId,
|
|
17844
|
+
name: meta.name,
|
|
17845
|
+
kind: meta.kind,
|
|
17846
|
+
version: a.version ?? meta.version
|
|
17847
|
+
}
|
|
17848
|
+
) : null
|
|
17849
|
+
},
|
|
17850
|
+
a.id
|
|
17851
|
+
);
|
|
17852
|
+
}),
|
|
17853
|
+
editor ? /* @__PURE__ */ jsx(
|
|
17854
|
+
"textarea",
|
|
17855
|
+
{
|
|
17856
|
+
autoFocus: true,
|
|
17857
|
+
"aria-label": "Text",
|
|
17858
|
+
className: "absolute rounded border bg-background p-1 text-sm shadow",
|
|
17859
|
+
style: {
|
|
17860
|
+
left: logicalToScreen(editor, view).x,
|
|
17861
|
+
top: logicalToScreen(editor, view).y,
|
|
17862
|
+
width: TEXT_MAX_WIDTH * view.zoom
|
|
17863
|
+
},
|
|
17864
|
+
value: editorText,
|
|
17865
|
+
onChange: (e) => setEditorText(e.target.value),
|
|
17866
|
+
onBlur: commitEditor,
|
|
17867
|
+
onKeyDown: (e) => {
|
|
17868
|
+
if (e.key === "Escape") {
|
|
17869
|
+
e.preventDefault();
|
|
17870
|
+
setEditor(null);
|
|
17871
|
+
setEditorText("");
|
|
17872
|
+
} else if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
|
17873
|
+
e.preventDefault();
|
|
17874
|
+
commitEditor();
|
|
17875
|
+
}
|
|
17876
|
+
}
|
|
17877
|
+
}
|
|
17878
|
+
) : null
|
|
17879
|
+
] })
|
|
17880
|
+
] }),
|
|
17881
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-t p-2", children: [
|
|
17882
|
+
/* @__PURE__ */ jsx(
|
|
17883
|
+
Input,
|
|
17884
|
+
{
|
|
17885
|
+
className: "w-64 min-w-0 shrink",
|
|
17886
|
+
placeholder: "Ask about the board (optional)",
|
|
17887
|
+
value: question,
|
|
17888
|
+
disabled: busy,
|
|
17889
|
+
onChange: (e) => setQuestion(e.target.value),
|
|
17890
|
+
onKeyDown: (e) => {
|
|
17891
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
17892
|
+
e.preventDefault();
|
|
17893
|
+
void ask("answer");
|
|
17894
|
+
}
|
|
17895
|
+
}
|
|
17896
|
+
}
|
|
17897
|
+
),
|
|
17898
|
+
/* @__PURE__ */ jsxs(TooltipProvider, { children: [
|
|
17899
|
+
/* @__PURE__ */ jsx(ButtonGroup, { "aria-label": "Actions", children: USER_ACTIONS.map((a) => /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
17900
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
17901
|
+
Button,
|
|
17902
|
+
{
|
|
17903
|
+
type: "button",
|
|
17904
|
+
variant: a.id === "answer" ? "default" : "outline",
|
|
17905
|
+
disabled: busy,
|
|
17906
|
+
"aria-label": a.label,
|
|
17907
|
+
onClick: () => void ask(a.id),
|
|
17908
|
+
children: a.label
|
|
17909
|
+
}
|
|
17910
|
+
) }),
|
|
17911
|
+
/* @__PURE__ */ jsx(TooltipContent, { side: "top", children: a.hint })
|
|
17912
|
+
] }, a.id)) }),
|
|
17913
|
+
/* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
17914
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2 px-1", children: [
|
|
17915
|
+
/* @__PURE__ */ jsx(
|
|
17916
|
+
Checkbox,
|
|
17917
|
+
{
|
|
17918
|
+
id: "wb-work-it-out",
|
|
17919
|
+
"aria-label": "Work it out",
|
|
17920
|
+
checked: deep,
|
|
17921
|
+
disabled: busy,
|
|
17922
|
+
onCheckedChange: (v) => setDeep(v === true)
|
|
17923
|
+
}
|
|
17924
|
+
),
|
|
17925
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "wb-work-it-out", children: "Work it out" })
|
|
17926
|
+
] }) }),
|
|
17927
|
+
/* @__PURE__ */ jsx(TooltipContent, { side: "top", children: "Let the agent search, compute and build before it draws, over several passes. Slower; for what the board alone cannot settle. Off, it answers your ink in one pass." })
|
|
17928
|
+
] })
|
|
17929
|
+
] }),
|
|
17930
|
+
onNewBoard ? /* @__PURE__ */ jsx(
|
|
17931
|
+
Button,
|
|
17932
|
+
{
|
|
17933
|
+
type: "button",
|
|
17934
|
+
variant: "ghost",
|
|
17935
|
+
"aria-label": "New board",
|
|
17936
|
+
disabled: busy,
|
|
17937
|
+
onClick: () => {
|
|
17938
|
+
setWantFresh(true);
|
|
17939
|
+
onNewBoard();
|
|
17940
|
+
},
|
|
17941
|
+
children: "New board"
|
|
17942
|
+
}
|
|
17943
|
+
) : null,
|
|
17944
|
+
viewMode && onViewModeChange && hasAnswer ? /* @__PURE__ */ jsx("div", { className: "flex items-center rounded-full border p-0.5", children: ["simple", "expert", "whiteboard"].map((mode) => /* @__PURE__ */ jsx(
|
|
17945
|
+
"button",
|
|
17946
|
+
{
|
|
17947
|
+
type: "button",
|
|
17948
|
+
"aria-label": `${VIEW_MODE_LABELS[mode]} view`,
|
|
17949
|
+
"aria-pressed": viewMode === mode,
|
|
17950
|
+
className: cn(
|
|
17951
|
+
"rounded-full px-3 py-1 text-xs",
|
|
17952
|
+
viewMode === mode ? "bg-background font-medium shadow-sm" : "text-muted-foreground"
|
|
17953
|
+
),
|
|
17954
|
+
onClick: () => onViewModeChange(mode),
|
|
17955
|
+
children: VIEW_MODE_LABELS[mode]
|
|
17956
|
+
},
|
|
17957
|
+
mode
|
|
17958
|
+
)) }) : null
|
|
17959
|
+
] })
|
|
17960
|
+
] });
|
|
17961
|
+
}
|
|
17962
|
+
function chunkPairs(flat) {
|
|
17963
|
+
const pairs = [];
|
|
17964
|
+
for (let i = 0; i + 1 < flat.length; i += 2) pairs.push([flat[i], flat[i + 1]]);
|
|
17965
|
+
return pairs;
|
|
17966
|
+
}
|
|
17967
|
+
var SKELETON_WIDTHS = [70, 85, 55, 90, 60, 78, 45, 82, 65, 72];
|
|
17968
|
+
var PDF_WORKER_SRC = new URL(
|
|
17969
|
+
"pdfjs-dist/legacy/build/pdf.worker.mjs",
|
|
17970
|
+
import.meta.url
|
|
17971
|
+
).toString();
|
|
17972
|
+
function FileViewer({
|
|
17973
|
+
file,
|
|
17974
|
+
loading,
|
|
17975
|
+
error,
|
|
17976
|
+
downloadUrl,
|
|
17977
|
+
onDelete,
|
|
17978
|
+
onClose,
|
|
17979
|
+
hideHeader = false,
|
|
17980
|
+
className
|
|
17981
|
+
}) {
|
|
17982
|
+
const visible = loading || file !== null || error !== null;
|
|
17983
|
+
if (!visible) return null;
|
|
17984
|
+
const fileName2 = file?.path.split("/").pop() ?? "File";
|
|
17985
|
+
const lang = file ? getLanguageHint(file.path) : "";
|
|
17986
|
+
const isPdf = file?.mime_type === "application/pdf" || file?.path.toLowerCase().endsWith(".pdf") || false;
|
|
17987
|
+
const isImage = file?.encoding === "base64" && !isPdf;
|
|
17988
|
+
const HeaderIcon = isImage ? ImageIcon : FileTextIcon;
|
|
17989
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex min-h-0 flex-1 flex-col border-t border-line", className), children: [
|
|
17990
|
+
!hideHeader && /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-2 border-b border-line px-3 py-2", children: [
|
|
17991
|
+
/* @__PURE__ */ jsx(HeaderIcon, { className: "size-4 shrink-0 text-muted-foreground" }),
|
|
17992
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
17993
|
+
/* @__PURE__ */ jsx("div", { className: "truncate font-medium", children: fileName2 }),
|
|
17994
|
+
/* @__PURE__ */ jsxs("div", { className: "truncate text-sm text-muted-foreground", children: [
|
|
17995
|
+
file?.path ?? "Loading...",
|
|
17996
|
+
file && /* @__PURE__ */ jsxs("span", { className: "ml-1.5", children: [
|
|
17997
|
+
formatFileSize(file.size),
|
|
17998
|
+
file.truncated && " (truncated)"
|
|
17999
|
+
] })
|
|
18000
|
+
] })
|
|
18001
|
+
] }),
|
|
18002
|
+
downloadUrl && /* @__PURE__ */ jsx(
|
|
18003
|
+
"a",
|
|
18004
|
+
{
|
|
18005
|
+
href: downloadUrl,
|
|
18006
|
+
download: fileName2,
|
|
18007
|
+
className: "shrink-0 rounded p-0.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
|
18008
|
+
"aria-label": `Download ${fileName2}`,
|
|
18009
|
+
title: "Download",
|
|
18010
|
+
children: /* @__PURE__ */ jsx(DownloadIcon, { className: "size-4" })
|
|
18011
|
+
}
|
|
18012
|
+
),
|
|
18013
|
+
onDelete && /* @__PURE__ */ jsx(
|
|
18014
|
+
"button",
|
|
18015
|
+
{
|
|
18016
|
+
type: "button",
|
|
18017
|
+
onClick: onDelete,
|
|
18018
|
+
className: "shrink-0 rounded p-0.5 text-muted-foreground transition-colors hover:bg-destructive/10 hover:text-destructive",
|
|
18019
|
+
"aria-label": `Delete ${fileName2}`,
|
|
18020
|
+
title: "Delete",
|
|
18021
|
+
children: /* @__PURE__ */ jsx(TrashIcon, { className: "size-4" })
|
|
18022
|
+
}
|
|
18023
|
+
),
|
|
18024
|
+
/* @__PURE__ */ jsx(
|
|
18025
|
+
"button",
|
|
18026
|
+
{
|
|
18027
|
+
type: "button",
|
|
18028
|
+
onClick: onClose,
|
|
18029
|
+
className: "shrink-0 rounded p-0.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
|
18030
|
+
"aria-label": "Close file",
|
|
18031
|
+
children: /* @__PURE__ */ jsx(XIcon, { className: "size-4" })
|
|
18032
|
+
}
|
|
18033
|
+
)
|
|
18034
|
+
] }),
|
|
18035
|
+
file && isPdf && file.encoding === "base64" ? (
|
|
18036
|
+
// PDF has its own scrollable container — bypass ScrollArea so
|
|
18037
|
+
// it can grow with the parent's flex height (Radix Viewport's
|
|
18038
|
+
// internal table wrapper sizes to content and breaks h-full).
|
|
18039
|
+
/* @__PURE__ */ jsx(PdfPreview, { file, fileName: fileName2 })
|
|
18040
|
+
) : /* @__PURE__ */ jsxs(ScrollArea, { className: "min-h-0 flex-1", children: [
|
|
18041
|
+
loading && /* @__PURE__ */ jsx("div", { className: "space-y-1.5 p-3", children: Array.from({ length: 10 }).map((_, index) => /* @__PURE__ */ jsx(
|
|
18042
|
+
Skeleton,
|
|
18043
|
+
{
|
|
18044
|
+
className: "h-3.5 rounded",
|
|
18045
|
+
style: {
|
|
18046
|
+
width: `${SKELETON_WIDTHS[index % SKELETON_WIDTHS.length]}%`
|
|
18047
|
+
}
|
|
18048
|
+
},
|
|
18049
|
+
index
|
|
18050
|
+
)) }),
|
|
18051
|
+
error && /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 p-3 text-xs text-destructive", children: [
|
|
18052
|
+
/* @__PURE__ */ jsx(AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0" }),
|
|
18053
|
+
/* @__PURE__ */ jsx("span", { children: error })
|
|
18054
|
+
] }),
|
|
18055
|
+
file && isPdf && file.encoding !== "base64" && /* @__PURE__ */ jsx(FileViewerError, { message: "PDF preview requires base64 file content." }),
|
|
18056
|
+
file && isImage && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center p-4", children: /* @__PURE__ */ jsx(
|
|
18057
|
+
"img",
|
|
18058
|
+
{
|
|
18059
|
+
src: `data:${file.mime_type ?? "image/png"};base64,${file.content}`,
|
|
18060
|
+
alt: fileName2,
|
|
18061
|
+
className: "max-h-[60vh] max-w-full rounded object-contain"
|
|
18062
|
+
}
|
|
18063
|
+
) }),
|
|
18064
|
+
file && !isImage && !isPdf && /* @__PURE__ */ jsx("pre", { className: "wrap-break-word whitespace-pre-wrap p-3 text-base leading-relaxed text-foreground", children: /* @__PURE__ */ jsx("code", { "data-language": lang, children: file.content }) })
|
|
18065
|
+
] })
|
|
18066
|
+
] });
|
|
18067
|
+
}
|
|
18068
|
+
function FileViewerError({ message }) {
|
|
18069
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 p-3 text-xs text-destructive", children: [
|
|
18070
|
+
/* @__PURE__ */ jsx(AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0" }),
|
|
18071
|
+
/* @__PURE__ */ jsx("span", { children: message })
|
|
18072
|
+
] });
|
|
18073
|
+
}
|
|
18074
|
+
function PdfPreview({
|
|
18075
|
+
file,
|
|
18076
|
+
fileName: fileName2
|
|
18077
|
+
}) {
|
|
18078
|
+
const containerRef = useRef(null);
|
|
18079
|
+
const viewerRef = useRef(null);
|
|
18080
|
+
const pdfViewerRef = useRef(null);
|
|
18081
|
+
const eventBusRef = useRef(null);
|
|
18082
|
+
const findControllerRef = useRef(null);
|
|
18083
|
+
const [error, setError] = useState(null);
|
|
18084
|
+
const [pageNumber, setPageNumber] = useState(1);
|
|
18085
|
+
const [pagesCount, setPagesCount] = useState(0);
|
|
18086
|
+
const [scale, setScale] = useState(1);
|
|
18087
|
+
const [query, setQuery] = useState("");
|
|
18088
|
+
useEffect(() => {
|
|
18089
|
+
let cancelled = false;
|
|
18090
|
+
let loadingTask = null;
|
|
18091
|
+
let pdfDocument = null;
|
|
18092
|
+
const renderPdf = async () => {
|
|
18093
|
+
setError(null);
|
|
18094
|
+
const container = containerRef.current;
|
|
18095
|
+
const viewerElement = viewerRef.current;
|
|
18096
|
+
if (!container || !viewerElement) return;
|
|
18097
|
+
const pdfBytes = decodeBase64(file.content);
|
|
18098
|
+
const pdfjs = await import('pdfjs-dist/legacy/build/pdf.mjs');
|
|
18099
|
+
pdfjs.GlobalWorkerOptions.workerSrc = PDF_WORKER_SRC;
|
|
18100
|
+
globalThis.pdfjsLib = pdfjs;
|
|
18101
|
+
const pdfViewerModule = await import('pdfjs-dist/web/pdf_viewer.mjs');
|
|
18102
|
+
loadingTask = pdfjs.getDocument({ data: pdfBytes });
|
|
18103
|
+
const pdf = await loadingTask.promise;
|
|
18104
|
+
pdfDocument = pdf;
|
|
18105
|
+
if (cancelled) return;
|
|
18106
|
+
const eventBus = new pdfViewerModule.EventBus();
|
|
18107
|
+
const linkService = new pdfViewerModule.PDFLinkService({ eventBus });
|
|
18108
|
+
const findController = new pdfViewerModule.PDFFindController({
|
|
18109
|
+
eventBus,
|
|
18110
|
+
linkService
|
|
18111
|
+
});
|
|
18112
|
+
const pdfViewer = new pdfViewerModule.PDFViewer({
|
|
18113
|
+
container,
|
|
18114
|
+
viewer: viewerElement,
|
|
18115
|
+
eventBus,
|
|
18116
|
+
linkService,
|
|
18117
|
+
findController,
|
|
18118
|
+
removePageBorders: true
|
|
18119
|
+
});
|
|
18120
|
+
linkService.setViewer(pdfViewer);
|
|
18121
|
+
linkService.setDocument(pdf);
|
|
18122
|
+
findController.setDocument(pdf);
|
|
18123
|
+
const onPagesInit = () => {
|
|
18124
|
+
pdfViewer.currentScaleValue = "page-width";
|
|
18125
|
+
setScale(pdfViewer.currentScale || 1);
|
|
18126
|
+
setPagesCount(pdfViewer.pagesCount);
|
|
18127
|
+
setPageNumber(pdfViewer.currentPageNumber);
|
|
18128
|
+
};
|
|
18129
|
+
const onPageChanging = (event) => {
|
|
18130
|
+
if (typeof event.pageNumber === "number") {
|
|
18131
|
+
setPageNumber(event.pageNumber);
|
|
18132
|
+
}
|
|
18133
|
+
};
|
|
18134
|
+
const onScaleChanging = (event) => {
|
|
18135
|
+
if (typeof event.scale === "number") {
|
|
18136
|
+
setScale(event.scale);
|
|
18137
|
+
}
|
|
18138
|
+
};
|
|
18139
|
+
eventBus.on("pagesinit", onPagesInit);
|
|
18140
|
+
eventBus.on("pagechanging", onPageChanging);
|
|
18141
|
+
eventBus.on("scalechanging", onScaleChanging);
|
|
18142
|
+
pdfViewerRef.current = pdfViewer;
|
|
18143
|
+
eventBusRef.current = eventBus;
|
|
18144
|
+
findControllerRef.current = findController;
|
|
18145
|
+
setPagesCount(pdf.numPages);
|
|
18146
|
+
pdfViewer.setDocument(pdf);
|
|
18147
|
+
};
|
|
18148
|
+
void renderPdf().catch((nextError) => {
|
|
18149
|
+
if (cancelled) return;
|
|
18150
|
+
if (nextError instanceof Error && nextError.name === "RenderingCancelledException") {
|
|
18151
|
+
return;
|
|
18152
|
+
}
|
|
18153
|
+
setError(formatPdfPreviewError(nextError));
|
|
18154
|
+
});
|
|
18155
|
+
return () => {
|
|
18156
|
+
cancelled = true;
|
|
18157
|
+
loadingTask?.destroy();
|
|
18158
|
+
pdfDocument?.destroy();
|
|
18159
|
+
pdfViewerRef.current?.cleanup();
|
|
18160
|
+
pdfViewerRef.current = null;
|
|
18161
|
+
eventBusRef.current = null;
|
|
18162
|
+
findControllerRef.current = null;
|
|
18163
|
+
};
|
|
18164
|
+
}, [file.content]);
|
|
18165
|
+
const setViewerPage = (nextPage) => {
|
|
18166
|
+
const viewer = pdfViewerRef.current;
|
|
18167
|
+
if (!viewer) return;
|
|
18168
|
+
const clampedPage = Math.min(
|
|
18169
|
+
Math.max(1, nextPage),
|
|
18170
|
+
Math.max(1, viewer.pagesCount)
|
|
18171
|
+
);
|
|
18172
|
+
viewer.currentPageNumber = clampedPage;
|
|
18173
|
+
setPageNumber(clampedPage);
|
|
18174
|
+
};
|
|
18175
|
+
const setViewerScale = (nextScale) => {
|
|
18176
|
+
const viewer = pdfViewerRef.current;
|
|
18177
|
+
if (!viewer) return;
|
|
18178
|
+
const clampedScale = Math.min(3, Math.max(0.5, nextScale));
|
|
18179
|
+
viewer.currentScale = clampedScale;
|
|
18180
|
+
setScale(clampedScale);
|
|
18181
|
+
};
|
|
18182
|
+
const runFind = (findPrevious = false) => {
|
|
18183
|
+
const eventBus = eventBusRef.current;
|
|
18184
|
+
if (!eventBus || !query.trim()) return;
|
|
18185
|
+
eventBus.dispatch("find", {
|
|
18186
|
+
source: eventBus,
|
|
18187
|
+
type: "again",
|
|
18188
|
+
query,
|
|
18189
|
+
phraseSearch: true,
|
|
18190
|
+
caseSensitive: false,
|
|
18191
|
+
entireWord: false,
|
|
18192
|
+
highlightAll: true,
|
|
18193
|
+
findPrevious,
|
|
18194
|
+
matchDiacritics: true
|
|
18195
|
+
});
|
|
18196
|
+
};
|
|
18197
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex min-h-full flex-col", children: [
|
|
18198
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 flex-wrap items-center gap-1.5 border-b border-line px-2 py-1.5", children: [
|
|
18199
|
+
/* @__PURE__ */ jsx(
|
|
18200
|
+
Button,
|
|
18201
|
+
{
|
|
18202
|
+
type: "button",
|
|
18203
|
+
variant: "ghost",
|
|
18204
|
+
size: "icon-xs",
|
|
18205
|
+
onClick: () => setViewerPage(pageNumber - 1),
|
|
18206
|
+
disabled: pageNumber <= 1,
|
|
18207
|
+
"aria-label": "Previous PDF page",
|
|
18208
|
+
title: "Previous page",
|
|
18209
|
+
children: /* @__PURE__ */ jsx(ChevronLeftIcon, { className: "size-3.5" })
|
|
18210
|
+
}
|
|
18211
|
+
),
|
|
18026
18212
|
/* @__PURE__ */ jsx(
|
|
18027
18213
|
Input,
|
|
18028
18214
|
{
|
|
18029
|
-
|
|
18030
|
-
|
|
18031
|
-
|
|
18032
|
-
|
|
18033
|
-
onChange: (
|
|
18034
|
-
|
|
18035
|
-
|
|
18036
|
-
e.preventDefault();
|
|
18037
|
-
void ask("answer");
|
|
18038
|
-
}
|
|
18039
|
-
}
|
|
18215
|
+
type: "number",
|
|
18216
|
+
min: 1,
|
|
18217
|
+
max: pagesCount || 1,
|
|
18218
|
+
value: pageNumber,
|
|
18219
|
+
onChange: (event) => setViewerPage(Number(event.target.value)),
|
|
18220
|
+
"aria-label": "PDF page number",
|
|
18221
|
+
className: "h-7 w-12 border-input px-1 text-center text-xs"
|
|
18040
18222
|
}
|
|
18041
18223
|
),
|
|
18042
|
-
/* @__PURE__ */ jsxs(
|
|
18043
|
-
|
|
18044
|
-
|
|
18045
|
-
Button,
|
|
18046
|
-
{
|
|
18047
|
-
type: "button",
|
|
18048
|
-
variant: a.id === "answer" ? "default" : "outline",
|
|
18049
|
-
disabled: busy,
|
|
18050
|
-
"aria-label": a.label,
|
|
18051
|
-
onClick: () => void ask(a.id),
|
|
18052
|
-
children: a.label
|
|
18053
|
-
}
|
|
18054
|
-
) }),
|
|
18055
|
-
/* @__PURE__ */ jsx(TooltipContent, { side: "top", children: a.hint })
|
|
18056
|
-
] }, a.id)) }),
|
|
18057
|
-
/* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
18058
|
-
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2 px-1", children: [
|
|
18059
|
-
/* @__PURE__ */ jsx(
|
|
18060
|
-
Checkbox,
|
|
18061
|
-
{
|
|
18062
|
-
id: "wb-work-it-out",
|
|
18063
|
-
"aria-label": "Work it out",
|
|
18064
|
-
checked: deep,
|
|
18065
|
-
disabled: busy,
|
|
18066
|
-
onCheckedChange: (v) => setDeep(v === true)
|
|
18067
|
-
}
|
|
18068
|
-
),
|
|
18069
|
-
/* @__PURE__ */ jsx(Label, { htmlFor: "wb-work-it-out", children: "Work it out" })
|
|
18070
|
-
] }) }),
|
|
18071
|
-
/* @__PURE__ */ jsx(TooltipContent, { side: "top", children: "Let the agent search, compute and build before it draws, over several passes. Slower; for what the board alone cannot settle. Off, it answers your ink in one pass." })
|
|
18072
|
-
] })
|
|
18224
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
|
|
18225
|
+
"/ ",
|
|
18226
|
+
pagesCount || "-"
|
|
18073
18227
|
] }),
|
|
18074
|
-
|
|
18228
|
+
/* @__PURE__ */ jsx(
|
|
18229
|
+
Button,
|
|
18230
|
+
{
|
|
18231
|
+
type: "button",
|
|
18232
|
+
variant: "ghost",
|
|
18233
|
+
size: "icon-xs",
|
|
18234
|
+
onClick: () => setViewerPage(pageNumber + 1),
|
|
18235
|
+
disabled: pagesCount > 0 && pageNumber >= pagesCount,
|
|
18236
|
+
"aria-label": "Next PDF page",
|
|
18237
|
+
title: "Next page",
|
|
18238
|
+
children: /* @__PURE__ */ jsx(ChevronRightIcon, { className: "size-3.5" })
|
|
18239
|
+
}
|
|
18240
|
+
),
|
|
18241
|
+
/* @__PURE__ */ jsx("div", { className: "mx-1 h-5 w-px bg-line" }),
|
|
18242
|
+
/* @__PURE__ */ jsx(
|
|
18243
|
+
Button,
|
|
18244
|
+
{
|
|
18245
|
+
type: "button",
|
|
18246
|
+
variant: "ghost",
|
|
18247
|
+
size: "icon-xs",
|
|
18248
|
+
onClick: () => setViewerScale(scale - 0.1),
|
|
18249
|
+
disabled: scale <= 0.5,
|
|
18250
|
+
"aria-label": "Zoom out PDF",
|
|
18251
|
+
title: "Zoom out",
|
|
18252
|
+
children: /* @__PURE__ */ jsx(MinusIcon, { className: "size-3.5" })
|
|
18253
|
+
}
|
|
18254
|
+
),
|
|
18255
|
+
/* @__PURE__ */ jsxs(
|
|
18256
|
+
"button",
|
|
18257
|
+
{
|
|
18258
|
+
type: "button",
|
|
18259
|
+
className: "h-7 min-w-12 px-1 text-xs text-muted-foreground hover:text-foreground",
|
|
18260
|
+
onClick: () => {
|
|
18261
|
+
const viewer = pdfViewerRef.current;
|
|
18262
|
+
if (!viewer) return;
|
|
18263
|
+
viewer.currentScaleValue = "page-width";
|
|
18264
|
+
setScale(viewer.currentScale || 1);
|
|
18265
|
+
},
|
|
18266
|
+
"aria-label": "Fit PDF to width",
|
|
18267
|
+
title: "Fit width",
|
|
18268
|
+
children: [
|
|
18269
|
+
Math.round(scale * 100),
|
|
18270
|
+
"%"
|
|
18271
|
+
]
|
|
18272
|
+
}
|
|
18273
|
+
),
|
|
18274
|
+
/* @__PURE__ */ jsx(
|
|
18075
18275
|
Button,
|
|
18076
18276
|
{
|
|
18077
18277
|
type: "button",
|
|
18078
18278
|
variant: "ghost",
|
|
18079
|
-
|
|
18080
|
-
|
|
18081
|
-
|
|
18082
|
-
|
|
18083
|
-
|
|
18279
|
+
size: "icon-xs",
|
|
18280
|
+
onClick: () => setViewerScale(scale + 0.1),
|
|
18281
|
+
disabled: scale >= 3,
|
|
18282
|
+
"aria-label": "Zoom in PDF",
|
|
18283
|
+
title: "Zoom in",
|
|
18284
|
+
children: /* @__PURE__ */ jsx(PlusIcon, { className: "size-3.5" })
|
|
18285
|
+
}
|
|
18286
|
+
),
|
|
18287
|
+
/* @__PURE__ */ jsx("div", { className: "mx-1 h-5 w-px bg-line" }),
|
|
18288
|
+
/* @__PURE__ */ jsx(SearchIcon, { className: "size-3.5 text-muted-foreground" }),
|
|
18289
|
+
/* @__PURE__ */ jsx(
|
|
18290
|
+
Input,
|
|
18291
|
+
{
|
|
18292
|
+
type: "search",
|
|
18293
|
+
value: query,
|
|
18294
|
+
onChange: (event) => setQuery(event.target.value),
|
|
18295
|
+
onKeyDown: (event) => {
|
|
18296
|
+
if (event.key === "Enter") runFind(event.shiftKey);
|
|
18084
18297
|
},
|
|
18085
|
-
|
|
18298
|
+
placeholder: "Find",
|
|
18299
|
+
"aria-label": "Find in PDF",
|
|
18300
|
+
className: "h-7 w-28 border-input px-1 text-xs"
|
|
18086
18301
|
}
|
|
18087
|
-
)
|
|
18088
|
-
|
|
18089
|
-
|
|
18302
|
+
),
|
|
18303
|
+
/* @__PURE__ */ jsx(
|
|
18304
|
+
Button,
|
|
18090
18305
|
{
|
|
18091
18306
|
type: "button",
|
|
18092
|
-
"
|
|
18093
|
-
"
|
|
18094
|
-
|
|
18095
|
-
|
|
18096
|
-
|
|
18097
|
-
|
|
18098
|
-
|
|
18099
|
-
|
|
18100
|
-
|
|
18101
|
-
|
|
18102
|
-
|
|
18103
|
-
] })
|
|
18307
|
+
variant: "ghost",
|
|
18308
|
+
size: "xs",
|
|
18309
|
+
onClick: () => runFind(false),
|
|
18310
|
+
disabled: !query.trim(),
|
|
18311
|
+
children: "Find"
|
|
18312
|
+
}
|
|
18313
|
+
)
|
|
18314
|
+
] }),
|
|
18315
|
+
error && /* @__PURE__ */ jsxs("div", { className: "flex w-full items-start gap-2 p-3 text-xs text-destructive", children: [
|
|
18316
|
+
/* @__PURE__ */ jsx(AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0" }),
|
|
18317
|
+
/* @__PURE__ */ jsx("span", { children: error })
|
|
18318
|
+
] }),
|
|
18319
|
+
!error && /* @__PURE__ */ jsx("div", { className: "relative min-h-105 flex-1 bg-muted/20", children: /* @__PURE__ */ jsx(
|
|
18320
|
+
"div",
|
|
18321
|
+
{
|
|
18322
|
+
ref: containerRef,
|
|
18323
|
+
"aria-label": `PDF viewer for ${fileName2}`,
|
|
18324
|
+
className: "absolute inset-0 overflow-auto",
|
|
18325
|
+
children: /* @__PURE__ */ jsx("div", { ref: viewerRef, className: "pdfViewer" })
|
|
18326
|
+
}
|
|
18327
|
+
) })
|
|
18104
18328
|
] });
|
|
18105
18329
|
}
|
|
18106
|
-
function
|
|
18107
|
-
const
|
|
18108
|
-
|
|
18109
|
-
|
|
18330
|
+
function decodeBase64(content) {
|
|
18331
|
+
const binary = globalThis.atob(content);
|
|
18332
|
+
const bytes = new Uint8Array(binary.length);
|
|
18333
|
+
for (let index = 0; index < binary.length; index += 1) {
|
|
18334
|
+
bytes[index] = binary.charCodeAt(index);
|
|
18335
|
+
}
|
|
18336
|
+
return bytes;
|
|
18337
|
+
}
|
|
18338
|
+
function formatPdfPreviewError(error) {
|
|
18339
|
+
if (error instanceof Error && (error.name === "InvalidCharacterError" || error.message === "Invalid character")) {
|
|
18340
|
+
return "PDF preview data is not valid base64.";
|
|
18341
|
+
}
|
|
18342
|
+
return error instanceof Error ? error.message : "Failed to render PDF preview.";
|
|
18343
|
+
}
|
|
18344
|
+
function WorkspaceFileDrawer({
|
|
18345
|
+
adapter,
|
|
18346
|
+
sessionId,
|
|
18347
|
+
path,
|
|
18348
|
+
onClose
|
|
18349
|
+
}) {
|
|
18350
|
+
const [file, setFile] = useState(null);
|
|
18351
|
+
const [loading, setLoading] = useState(true);
|
|
18352
|
+
const [error, setError] = useState(null);
|
|
18353
|
+
const requestRef = useRef(0);
|
|
18354
|
+
useEffect(() => {
|
|
18355
|
+
const request = ++requestRef.current;
|
|
18356
|
+
setFile(null);
|
|
18357
|
+
setLoading(true);
|
|
18358
|
+
setError(null);
|
|
18359
|
+
void (async () => {
|
|
18360
|
+
try {
|
|
18361
|
+
const next = await adapter.getWorkspaceFile({ sessionId, path });
|
|
18362
|
+
if (requestRef.current !== request) return;
|
|
18363
|
+
setFile(next);
|
|
18364
|
+
} catch (fetchError) {
|
|
18365
|
+
if (requestRef.current !== request) return;
|
|
18366
|
+
setFile(null);
|
|
18367
|
+
setError(fetchError.message);
|
|
18368
|
+
} finally {
|
|
18369
|
+
if (requestRef.current === request) setLoading(false);
|
|
18370
|
+
}
|
|
18371
|
+
})();
|
|
18372
|
+
}, [adapter, sessionId, path]);
|
|
18373
|
+
return /* @__PURE__ */ jsx(
|
|
18374
|
+
"aside",
|
|
18375
|
+
{
|
|
18376
|
+
"data-testid": "file-preview-panel",
|
|
18377
|
+
className: "flex h-full min-h-0 w-full flex-col overflow-hidden border-l border-line bg-card",
|
|
18378
|
+
children: /* @__PURE__ */ jsx(
|
|
18379
|
+
FileViewer,
|
|
18380
|
+
{
|
|
18381
|
+
className: "border-t-0",
|
|
18382
|
+
file,
|
|
18383
|
+
loading,
|
|
18384
|
+
error,
|
|
18385
|
+
downloadUrl: file ? adapter.getWorkspaceDownloadUrl({ sessionId, path: file.path }) : null,
|
|
18386
|
+
onDelete: null,
|
|
18387
|
+
onClose
|
|
18388
|
+
}
|
|
18389
|
+
)
|
|
18390
|
+
}
|
|
18391
|
+
);
|
|
18110
18392
|
}
|
|
18111
18393
|
var noop = () => {
|
|
18112
18394
|
};
|
|
@@ -18151,7 +18433,7 @@ function FileTree({
|
|
|
18151
18433
|
return /* @__PURE__ */ jsx(FileTreeContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(
|
|
18152
18434
|
"div",
|
|
18153
18435
|
{
|
|
18154
|
-
className: cn("rounded-lg border
|
|
18436
|
+
className: cn("rounded-lg border text-base", className),
|
|
18155
18437
|
role: "tree",
|
|
18156
18438
|
...props,
|
|
18157
18439
|
children: /* @__PURE__ */ jsx("div", { className: "p-2", children })
|
|
@@ -18275,9 +18557,6 @@ function FileTreeFile({
|
|
|
18275
18557
|
);
|
|
18276
18558
|
}
|
|
18277
18559
|
var SKELETON_WIDTHS2 = [75, 60, 90, 65, 80, 70, 85, 55];
|
|
18278
|
-
var DEFAULT_WIDTH2 = 400;
|
|
18279
|
-
var MIN_WIDTH = 300;
|
|
18280
|
-
var MAX_WIDTH = 900;
|
|
18281
18560
|
function collectExpandedPaths(entries, depth = 0) {
|
|
18282
18561
|
const paths = [];
|
|
18283
18562
|
for (const entry of entries) {
|
|
@@ -18360,7 +18639,7 @@ function RenderEntries({
|
|
|
18360
18639
|
onClick: (event) => event.stopPropagation(),
|
|
18361
18640
|
title: "Download",
|
|
18362
18641
|
"aria-label": `Download ${fileName2}`,
|
|
18363
|
-
children: /* @__PURE__ */ jsx(DownloadIcon, { className: "size-
|
|
18642
|
+
children: /* @__PURE__ */ jsx(DownloadIcon, { className: "size-5" })
|
|
18364
18643
|
}
|
|
18365
18644
|
),
|
|
18366
18645
|
/* @__PURE__ */ jsx(
|
|
@@ -18374,7 +18653,7 @@ function RenderEntries({
|
|
|
18374
18653
|
},
|
|
18375
18654
|
title: "Delete",
|
|
18376
18655
|
"aria-label": `Delete ${fileName2}`,
|
|
18377
|
-
children: /* @__PURE__ */ jsx(TrashIcon, { className: "size-
|
|
18656
|
+
children: /* @__PURE__ */ jsx(TrashIcon, { className: "size-5" })
|
|
18378
18657
|
}
|
|
18379
18658
|
)
|
|
18380
18659
|
] })
|
|
@@ -18386,27 +18665,19 @@ function WorkspacePanel({
|
|
|
18386
18665
|
sessionId,
|
|
18387
18666
|
selectedPath,
|
|
18388
18667
|
onSelectedPathChange,
|
|
18668
|
+
onOpenFile,
|
|
18389
18669
|
refreshSignal = 0,
|
|
18390
|
-
disabled = false
|
|
18391
|
-
fillParent = false
|
|
18670
|
+
disabled = false
|
|
18392
18671
|
}) {
|
|
18393
18672
|
const fileInputRef = useRef(null);
|
|
18394
18673
|
const [entries, setEntries] = useState([]);
|
|
18395
18674
|
const [treeLoading, setTreeLoading] = useState(false);
|
|
18396
18675
|
const [treeError, setTreeError] = useState(null);
|
|
18397
|
-
const [file, setFile] = useState(null);
|
|
18398
|
-
const [fileLoading, setFileLoading] = useState(false);
|
|
18399
|
-
const [fileError, setFileError] = useState(null);
|
|
18400
18676
|
const [notice, setNotice] = useState(null);
|
|
18401
18677
|
const [uploading, setUploading] = useState(false);
|
|
18402
18678
|
const [deleteTarget, setDeleteTarget] = useState(null);
|
|
18403
18679
|
const [expandedPaths, setExpandedPaths] = useState(/* @__PURE__ */ new Set());
|
|
18404
|
-
const [width, setWidth] = useState(DEFAULT_WIDTH2);
|
|
18405
|
-
const narrow = useIsMobile();
|
|
18406
18680
|
const sessionIdRef = useRef(sessionId);
|
|
18407
|
-
const isResizing = useRef(false);
|
|
18408
|
-
const startX = useRef(0);
|
|
18409
|
-
const startWidth = useRef(DEFAULT_WIDTH2);
|
|
18410
18681
|
sessionIdRef.current = sessionId;
|
|
18411
18682
|
const fetchTree = useCallback(async () => {
|
|
18412
18683
|
if (!sessionId) {
|
|
@@ -18436,31 +18707,6 @@ function WorkspacePanel({
|
|
|
18436
18707
|
}
|
|
18437
18708
|
}
|
|
18438
18709
|
}, [adapter, sessionId]);
|
|
18439
|
-
const fetchFile = useCallback(
|
|
18440
|
-
async (path) => {
|
|
18441
|
-
if (!sessionId) return;
|
|
18442
|
-
const requestedSessionId = sessionId;
|
|
18443
|
-
setFileLoading(true);
|
|
18444
|
-
setFileError(null);
|
|
18445
|
-
try {
|
|
18446
|
-
const nextFile = await adapter.getWorkspaceFile({
|
|
18447
|
-
sessionId: requestedSessionId,
|
|
18448
|
-
path
|
|
18449
|
-
});
|
|
18450
|
-
if (sessionIdRef.current !== requestedSessionId) return;
|
|
18451
|
-
setFile(nextFile);
|
|
18452
|
-
} catch (error) {
|
|
18453
|
-
if (sessionIdRef.current !== requestedSessionId) return;
|
|
18454
|
-
setFile(null);
|
|
18455
|
-
setFileError(error.message);
|
|
18456
|
-
} finally {
|
|
18457
|
-
if (sessionIdRef.current === requestedSessionId) {
|
|
18458
|
-
setFileLoading(false);
|
|
18459
|
-
}
|
|
18460
|
-
}
|
|
18461
|
-
},
|
|
18462
|
-
[adapter, sessionId]
|
|
18463
|
-
);
|
|
18464
18710
|
useEffect(() => {
|
|
18465
18711
|
void fetchTree();
|
|
18466
18712
|
}, [fetchTree]);
|
|
@@ -18473,8 +18719,6 @@ function WorkspacePanel({
|
|
|
18473
18719
|
}, [refreshSignal, sessionId, fetchTree]);
|
|
18474
18720
|
useEffect(() => {
|
|
18475
18721
|
if (!sessionId) {
|
|
18476
|
-
setFile(null);
|
|
18477
|
-
setFileError(null);
|
|
18478
18722
|
onSelectedPathChange(null);
|
|
18479
18723
|
}
|
|
18480
18724
|
}, [onSelectedPathChange, sessionId]);
|
|
@@ -18492,22 +18736,15 @@ function WorkspacePanel({
|
|
|
18492
18736
|
return next;
|
|
18493
18737
|
});
|
|
18494
18738
|
}, [selectedPath]);
|
|
18495
|
-
useEffect(() => {
|
|
18496
|
-
if (!selectedPath || !sessionId || entries.length === 0) return;
|
|
18497
|
-
const entry = findEntry(entries, selectedPath);
|
|
18498
|
-
if (entry?.kind === "file" && file?.path !== selectedPath) {
|
|
18499
|
-
void fetchFile(selectedPath);
|
|
18500
|
-
}
|
|
18501
|
-
}, [entries, fetchFile, file?.path, selectedPath, sessionId]);
|
|
18502
18739
|
const handleSelect = useCallback(
|
|
18503
18740
|
(path) => {
|
|
18504
18741
|
onSelectedPathChange(path);
|
|
18505
18742
|
const entry = findEntry(entries, path);
|
|
18506
18743
|
if (entry?.kind === "file") {
|
|
18507
|
-
|
|
18744
|
+
onOpenFile?.(path);
|
|
18508
18745
|
}
|
|
18509
18746
|
},
|
|
18510
|
-
[entries,
|
|
18747
|
+
[entries, onOpenFile, onSelectedPathChange]
|
|
18511
18748
|
);
|
|
18512
18749
|
const handleUpload = useCallback(
|
|
18513
18750
|
async (files) => {
|
|
@@ -18541,8 +18778,6 @@ function WorkspacePanel({
|
|
|
18541
18778
|
await adapter.deleteWorkspaceFile({ sessionId, path });
|
|
18542
18779
|
if (selectedPath === path) {
|
|
18543
18780
|
onSelectedPathChange(null);
|
|
18544
|
-
setFile(null);
|
|
18545
|
-
setFileError(null);
|
|
18546
18781
|
}
|
|
18547
18782
|
setNotice(`Deleted ${path.split("/").pop() ?? path}`);
|
|
18548
18783
|
await fetchTree();
|
|
@@ -18561,188 +18796,131 @@ function WorkspacePanel({
|
|
|
18561
18796
|
sessionId
|
|
18562
18797
|
]
|
|
18563
18798
|
);
|
|
18564
|
-
|
|
18565
|
-
|
|
18566
|
-
|
|
18567
|
-
|
|
18568
|
-
|
|
18569
|
-
|
|
18570
|
-
|
|
18571
|
-
|
|
18572
|
-
|
|
18573
|
-
|
|
18574
|
-
|
|
18575
|
-
|
|
18576
|
-
|
|
18577
|
-
|
|
18578
|
-
|
|
18579
|
-
|
|
18580
|
-
|
|
18581
|
-
|
|
18582
|
-
|
|
18583
|
-
|
|
18584
|
-
|
|
18585
|
-
isResizing.current = false;
|
|
18586
|
-
document.body.style.cursor = "";
|
|
18587
|
-
document.body.style.userSelect = "";
|
|
18588
|
-
};
|
|
18589
|
-
window.addEventListener("mousemove", onMouseMove);
|
|
18590
|
-
window.addEventListener("mouseup", onMouseUp);
|
|
18591
|
-
return () => {
|
|
18592
|
-
window.removeEventListener("mousemove", onMouseMove);
|
|
18593
|
-
window.removeEventListener("mouseup", onMouseUp);
|
|
18594
|
-
};
|
|
18595
|
-
}, []);
|
|
18596
|
-
return /* @__PURE__ */ jsxs(
|
|
18597
|
-
"aside",
|
|
18598
|
-
{
|
|
18599
|
-
"data-testid": "workspace-panel",
|
|
18600
|
-
className: cn(
|
|
18601
|
-
"relative z-10 flex h-full min-h-0 flex-col overflow-hidden border-l border-muted-foreground/20 bg-card",
|
|
18602
|
-
(fillParent || narrow) && "w-full"
|
|
18603
|
-
),
|
|
18604
|
-
style: fillParent || narrow ? void 0 : { width, minWidth: MIN_WIDTH, maxWidth: MAX_WIDTH },
|
|
18605
|
-
children: [
|
|
18606
|
-
!(fillParent || narrow) && /* @__PURE__ */ jsx(
|
|
18607
|
-
"div",
|
|
18608
|
-
{
|
|
18609
|
-
className: "absolute inset-y-0 left-0 z-20 w-1.5 cursor-col-resize transition-colors hover:bg-primary/20 active:bg-primary/30",
|
|
18610
|
-
onMouseDown: onResizeStart
|
|
18611
|
-
}
|
|
18612
|
-
),
|
|
18613
|
-
/* @__PURE__ */ jsx(
|
|
18614
|
-
"input",
|
|
18615
|
-
{
|
|
18616
|
-
ref: fileInputRef,
|
|
18617
|
-
type: "file",
|
|
18618
|
-
multiple: true,
|
|
18619
|
-
className: "hidden",
|
|
18620
|
-
disabled,
|
|
18621
|
-
onChange: (event) => {
|
|
18622
|
-
if (event.target.files) void handleUpload(event.target.files);
|
|
18623
|
-
}
|
|
18624
|
-
}
|
|
18625
|
-
),
|
|
18626
|
-
/* @__PURE__ */ jsxs("div", { className: "flex min-h-14 items-center gap-2 border-b border-line px-3 py-3", children: [
|
|
18627
|
-
/* @__PURE__ */ jsx(FolderOpenIcon, { className: "size-4 shrink-0 text-amber-500" }),
|
|
18628
|
-
/* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx("div", { className: "truncate text-sm font-medium text-foreground", children: disabled ? "Workspace - Read-only" : "Workspace" }) }),
|
|
18629
|
-
/* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
18630
|
-
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
18631
|
-
Button,
|
|
18632
|
-
{
|
|
18633
|
-
variant: "ghost",
|
|
18634
|
-
size: "icon-sm",
|
|
18635
|
-
onClick: () => fileInputRef.current?.click(),
|
|
18636
|
-
disabled: disabled || !sessionId || uploading,
|
|
18637
|
-
"aria-label": "Upload files",
|
|
18638
|
-
children: uploading ? /* @__PURE__ */ jsx(Loader2Icon, { className: "size-4 animate-spin" }) : /* @__PURE__ */ jsx(UploadIcon, { className: "size-4" })
|
|
18799
|
+
return (
|
|
18800
|
+
// Always fills its parent: the panel is the body of the Files
|
|
18801
|
+
// accordion, which owns the frame and the height. The side-drawer
|
|
18802
|
+
// it used to be — resize handle, min/max width, its own left
|
|
18803
|
+
// border — went away with the drawer.
|
|
18804
|
+
/* @__PURE__ */ jsxs(
|
|
18805
|
+
"aside",
|
|
18806
|
+
{
|
|
18807
|
+
"data-testid": "workspace-panel",
|
|
18808
|
+
className: "relative z-10 flex h-full border-input border-[2px] pb-[10px] border-b-0 min-h-0 w-full flex-col overflow-hidden",
|
|
18809
|
+
children: [
|
|
18810
|
+
/* @__PURE__ */ jsx(
|
|
18811
|
+
"input",
|
|
18812
|
+
{
|
|
18813
|
+
ref: fileInputRef,
|
|
18814
|
+
type: "file",
|
|
18815
|
+
multiple: true,
|
|
18816
|
+
className: "hidden",
|
|
18817
|
+
disabled,
|
|
18818
|
+
onChange: (event) => {
|
|
18819
|
+
if (event.target.files) void handleUpload(event.target.files);
|
|
18639
18820
|
}
|
|
18640
|
-
|
|
18641
|
-
|
|
18821
|
+
}
|
|
18822
|
+
),
|
|
18823
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2 border-b border-line px-3 py-1.5", children: [
|
|
18824
|
+
/* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
18825
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
18826
|
+
Button,
|
|
18827
|
+
{
|
|
18828
|
+
variant: "ghost",
|
|
18829
|
+
size: "icon-sm",
|
|
18830
|
+
onClick: () => fileInputRef.current?.click(),
|
|
18831
|
+
disabled: disabled || !sessionId || uploading,
|
|
18832
|
+
"aria-label": "Upload files",
|
|
18833
|
+
children: uploading ? /* @__PURE__ */ jsx(Loader2Icon, { className: "size-4 animate-spin" }) : /* @__PURE__ */ jsx(UploadIcon, { className: "size-4" })
|
|
18834
|
+
}
|
|
18835
|
+
) }),
|
|
18836
|
+
/* @__PURE__ */ jsx(TooltipContent, { side: "bottom", children: "Upload files" })
|
|
18837
|
+
] }),
|
|
18838
|
+
/* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
18839
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
18840
|
+
Button,
|
|
18841
|
+
{
|
|
18842
|
+
variant: "ghost",
|
|
18843
|
+
size: "icon-sm",
|
|
18844
|
+
onClick: () => void fetchTree(),
|
|
18845
|
+
disabled: !sessionId || treeLoading,
|
|
18846
|
+
"aria-label": "Refresh workspace",
|
|
18847
|
+
children: /* @__PURE__ */ jsx(
|
|
18848
|
+
RefreshCwIcon,
|
|
18849
|
+
{
|
|
18850
|
+
className: cn("size-4", treeLoading && "animate-spin")
|
|
18851
|
+
}
|
|
18852
|
+
)
|
|
18853
|
+
}
|
|
18854
|
+
) }),
|
|
18855
|
+
/* @__PURE__ */ jsx(TooltipContent, { side: "bottom", children: "Refresh" })
|
|
18856
|
+
] })
|
|
18642
18857
|
] }),
|
|
18643
|
-
/* @__PURE__ */
|
|
18644
|
-
|
|
18645
|
-
|
|
18858
|
+
notice && /* @__PURE__ */ jsx("div", { className: "border-b border-line px-3 py-2 text-xs text-muted-foreground", children: notice }),
|
|
18859
|
+
/* @__PURE__ */ jsx(ScrollArea, { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsxs("div", { className: "px-1 py-1", children: [
|
|
18860
|
+
treeLoading && entries.length === 0 && /* @__PURE__ */ jsx("div", { className: "space-y-1 p-2", children: Array.from({ length: 8 }).map((_, index) => /* @__PURE__ */ jsx(
|
|
18861
|
+
Skeleton,
|
|
18862
|
+
{
|
|
18863
|
+
className: "h-5 rounded",
|
|
18864
|
+
style: {
|
|
18865
|
+
width: `${SKELETON_WIDTHS2[index % SKELETON_WIDTHS2.length]}%`
|
|
18866
|
+
}
|
|
18867
|
+
},
|
|
18868
|
+
index
|
|
18869
|
+
)) }),
|
|
18870
|
+
treeError && /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 p-3 text-sm text-destructive", children: [
|
|
18871
|
+
/* @__PURE__ */ jsx(AlertCircleIcon, { className: "mt-0.5 size-4 shrink-0" }),
|
|
18872
|
+
/* @__PURE__ */ jsx("span", { children: treeError })
|
|
18873
|
+
] }),
|
|
18874
|
+
!treeLoading && !treeError && entries.length === 0 && /* @__PURE__ */ jsxs("div", { className: "px-4 py-8 text-center text-sm text-faint", children: [
|
|
18875
|
+
/* @__PURE__ */ jsx("p", { children: "No workspace files" }),
|
|
18876
|
+
/* @__PURE__ */ jsxs(
|
|
18877
|
+
Button,
|
|
18878
|
+
{
|
|
18879
|
+
variant: "outline",
|
|
18880
|
+
size: "sm",
|
|
18881
|
+
className: "mt-3 gap-1.5",
|
|
18882
|
+
onClick: () => fileInputRef.current?.click(),
|
|
18883
|
+
disabled: disabled || !sessionId,
|
|
18884
|
+
children: [
|
|
18885
|
+
/* @__PURE__ */ jsx(UploadIcon, { className: "size-3.5" }),
|
|
18886
|
+
"Upload files"
|
|
18887
|
+
]
|
|
18888
|
+
}
|
|
18889
|
+
)
|
|
18890
|
+
] }),
|
|
18891
|
+
entries.length > 0 && /* @__PURE__ */ jsx(
|
|
18892
|
+
FileTree,
|
|
18646
18893
|
{
|
|
18647
|
-
|
|
18648
|
-
|
|
18649
|
-
|
|
18650
|
-
|
|
18651
|
-
|
|
18894
|
+
expanded: expandedPaths,
|
|
18895
|
+
onExpandedChange: setExpandedPaths,
|
|
18896
|
+
selectedPath,
|
|
18897
|
+
onSelect: handleSelect,
|
|
18898
|
+
className: "rounded-none border-0",
|
|
18652
18899
|
children: /* @__PURE__ */ jsx(
|
|
18653
|
-
|
|
18900
|
+
RenderEntries,
|
|
18654
18901
|
{
|
|
18655
|
-
|
|
18902
|
+
entries,
|
|
18903
|
+
onDelete: setDeleteTarget,
|
|
18904
|
+
downloadUrlFor: (path) => sessionId ? adapter.getWorkspaceDownloadUrl({ sessionId, path }) : "#"
|
|
18656
18905
|
}
|
|
18657
18906
|
)
|
|
18658
18907
|
}
|
|
18659
|
-
) }),
|
|
18660
|
-
/* @__PURE__ */ jsx(TooltipContent, { side: "bottom", children: "Refresh" })
|
|
18661
|
-
] })
|
|
18662
|
-
] }),
|
|
18663
|
-
notice && /* @__PURE__ */ jsx("div", { className: "border-b border-line px-3 py-2 text-xs text-muted-foreground", children: notice }),
|
|
18664
|
-
/* @__PURE__ */ jsx(ScrollArea, { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsxs("div", { className: "px-1 py-1", children: [
|
|
18665
|
-
treeLoading && entries.length === 0 && /* @__PURE__ */ jsx("div", { className: "space-y-1 p-2", children: Array.from({ length: 8 }).map((_, index) => /* @__PURE__ */ jsx(
|
|
18666
|
-
Skeleton,
|
|
18667
|
-
{
|
|
18668
|
-
className: "h-5 rounded",
|
|
18669
|
-
style: {
|
|
18670
|
-
width: `${SKELETON_WIDTHS2[index % SKELETON_WIDTHS2.length]}%`
|
|
18671
|
-
}
|
|
18672
|
-
},
|
|
18673
|
-
index
|
|
18674
|
-
)) }),
|
|
18675
|
-
treeError && /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 p-3 text-sm text-destructive", children: [
|
|
18676
|
-
/* @__PURE__ */ jsx(AlertCircleIcon, { className: "mt-0.5 size-4 shrink-0" }),
|
|
18677
|
-
/* @__PURE__ */ jsx("span", { children: treeError })
|
|
18678
|
-
] }),
|
|
18679
|
-
!treeLoading && !treeError && entries.length === 0 && /* @__PURE__ */ jsxs("div", { className: "px-4 py-8 text-center text-sm text-faint", children: [
|
|
18680
|
-
/* @__PURE__ */ jsx("p", { children: "No workspace files" }),
|
|
18681
|
-
/* @__PURE__ */ jsxs(
|
|
18682
|
-
Button,
|
|
18683
|
-
{
|
|
18684
|
-
variant: "outline",
|
|
18685
|
-
size: "sm",
|
|
18686
|
-
className: "mt-3 gap-1.5",
|
|
18687
|
-
onClick: () => fileInputRef.current?.click(),
|
|
18688
|
-
disabled: disabled || !sessionId,
|
|
18689
|
-
children: [
|
|
18690
|
-
/* @__PURE__ */ jsx(UploadIcon, { className: "size-3.5" }),
|
|
18691
|
-
"Upload files"
|
|
18692
|
-
]
|
|
18693
|
-
}
|
|
18694
18908
|
)
|
|
18695
|
-
] }),
|
|
18696
|
-
|
|
18697
|
-
|
|
18909
|
+
] }) }),
|
|
18910
|
+
/* @__PURE__ */ jsx(
|
|
18911
|
+
ConfirmDialog2,
|
|
18698
18912
|
{
|
|
18699
|
-
|
|
18700
|
-
|
|
18701
|
-
|
|
18702
|
-
|
|
18703
|
-
|
|
18704
|
-
|
|
18705
|
-
RenderEntries,
|
|
18706
|
-
{
|
|
18707
|
-
entries,
|
|
18708
|
-
onDelete: setDeleteTarget,
|
|
18709
|
-
downloadUrlFor: (path) => sessionId ? adapter.getWorkspaceDownloadUrl({ sessionId, path }) : "#"
|
|
18710
|
-
}
|
|
18711
|
-
)
|
|
18913
|
+
open: deleteTarget !== null,
|
|
18914
|
+
title: "Delete file?",
|
|
18915
|
+
description: `This will permanently delete ${deleteTarget?.split("/").pop() ?? "this file"} from the workspace.`,
|
|
18916
|
+
confirmLabel: "Delete",
|
|
18917
|
+
onConfirm: () => deleteTarget ? handleDelete(deleteTarget) : Promise.resolve(),
|
|
18918
|
+
onCancel: () => setDeleteTarget(null)
|
|
18712
18919
|
}
|
|
18713
18920
|
)
|
|
18714
|
-
]
|
|
18715
|
-
|
|
18716
|
-
|
|
18717
|
-
{
|
|
18718
|
-
file,
|
|
18719
|
-
loading: fileLoading,
|
|
18720
|
-
error: fileError,
|
|
18721
|
-
downloadUrl: file && sessionId ? adapter.getWorkspaceDownloadUrl({
|
|
18722
|
-
sessionId,
|
|
18723
|
-
path: file.path
|
|
18724
|
-
}) : null,
|
|
18725
|
-
onDelete: file && !disabled ? () => setDeleteTarget(file.path) : null,
|
|
18726
|
-
onClose: () => {
|
|
18727
|
-
onSelectedPathChange(null);
|
|
18728
|
-
setFile(null);
|
|
18729
|
-
setFileError(null);
|
|
18730
|
-
}
|
|
18731
|
-
}
|
|
18732
|
-
),
|
|
18733
|
-
/* @__PURE__ */ jsx(
|
|
18734
|
-
ConfirmDialog2,
|
|
18735
|
-
{
|
|
18736
|
-
open: deleteTarget !== null,
|
|
18737
|
-
title: "Delete file?",
|
|
18738
|
-
description: `This will permanently delete ${deleteTarget?.split("/").pop() ?? "this file"} from the workspace.`,
|
|
18739
|
-
confirmLabel: "Delete",
|
|
18740
|
-
onConfirm: () => deleteTarget ? handleDelete(deleteTarget) : Promise.resolve(),
|
|
18741
|
-
onCancel: () => setDeleteTarget(null)
|
|
18742
|
-
}
|
|
18743
|
-
)
|
|
18744
|
-
]
|
|
18745
|
-
}
|
|
18921
|
+
]
|
|
18922
|
+
}
|
|
18923
|
+
)
|
|
18746
18924
|
);
|
|
18747
18925
|
}
|
|
18748
18926
|
function ConfirmDialog2({
|
|
@@ -18824,12 +19002,15 @@ function readOnlyReasonForSession(session) {
|
|
|
18824
19002
|
return { readOnly: false };
|
|
18825
19003
|
}
|
|
18826
19004
|
var RIGHT_STACK_STYLE = {
|
|
18827
|
-
|
|
19005
|
+
// Both the column's width and the chat panel's right offset read this, so
|
|
19006
|
+
// one value keeps them complementary. 50%: the browser shell renders a real
|
|
19007
|
+
// page, and page layouts are unusable in a 440px strip.
|
|
19008
|
+
["--right-stack-w"]: "50%"
|
|
18828
19009
|
};
|
|
18829
19010
|
var MOBILE_PANE_LABELS = {
|
|
18830
19011
|
chat: "Chat",
|
|
18831
19012
|
browser: "Browser",
|
|
18832
|
-
|
|
19013
|
+
file: "Preview"
|
|
18833
19014
|
};
|
|
18834
19015
|
function AgentChat({
|
|
18835
19016
|
adapter,
|
|
@@ -18855,9 +19036,10 @@ function AgentChat({
|
|
|
18855
19036
|
onOpenBilling
|
|
18856
19037
|
}) {
|
|
18857
19038
|
const [workspacePath, setWorkspacePath] = useState(null);
|
|
19039
|
+
const [previewPath, setPreviewPath] = useState(null);
|
|
18858
19040
|
const [mobileView, setMobileView] = useState("chat");
|
|
18859
|
-
const [
|
|
18860
|
-
const [
|
|
19041
|
+
const [openPane, setOpenPane] = useState(null);
|
|
19042
|
+
const [filesOpen, setFilesOpen] = useState(false);
|
|
18861
19043
|
const runtime = useAgentChatRuntime({
|
|
18862
19044
|
adapter,
|
|
18863
19045
|
agentId,
|
|
@@ -18865,8 +19047,15 @@ function AgentChat({
|
|
|
18865
19047
|
onSessionChange
|
|
18866
19048
|
});
|
|
18867
19049
|
useEffect(() => {
|
|
18868
|
-
|
|
19050
|
+
setOpenPane(null);
|
|
19051
|
+
setFilesOpen(false);
|
|
18869
19052
|
}, [runtime.viewMode]);
|
|
19053
|
+
useEffect(() => {
|
|
19054
|
+
setFilesOpen(false);
|
|
19055
|
+
setWorkspacePath(null);
|
|
19056
|
+
setPreviewPath(null);
|
|
19057
|
+
setOpenPane(null);
|
|
19058
|
+
}, [sessionId]);
|
|
18870
19059
|
const readOnly = readOnlyReasonForSession(runtime.session);
|
|
18871
19060
|
const boardAvailable = whiteboardEnabled && isBoardSession(runtime.session);
|
|
18872
19061
|
const viewMode = runtime.viewMode === "whiteboard" && !boardAvailable ? "simple" : runtime.viewMode;
|
|
@@ -18883,35 +19072,54 @@ function AgentChat({
|
|
|
18883
19072
|
const hideTurnSummary = isSubAgentSession(runtime.session) || orchestratesDeepResearch || isBoardSession(runtime.session);
|
|
18884
19073
|
const browserState = runtime.state.browser;
|
|
18885
19074
|
const browserAvailable = browserState !== null && browserState.status !== "closed" && !!sessionId;
|
|
18886
|
-
const browserVisible = browserAvailable &&
|
|
19075
|
+
const browserVisible = browserAvailable && openPane === "browser";
|
|
18887
19076
|
const workspaceAvailable = !!sessionId;
|
|
18888
|
-
const
|
|
18889
|
-
const rightStackVisible = browserVisible ||
|
|
18890
|
-
const
|
|
19077
|
+
const filePreviewVisible = workspaceAvailable && previewPath !== null && openPane === "file";
|
|
19078
|
+
const rightStackVisible = browserVisible || filePreviewVisible;
|
|
19079
|
+
const browserPreview = useBrowserPreview({
|
|
19080
|
+
adapter,
|
|
19081
|
+
sessionId,
|
|
19082
|
+
enabled: browserAvailable
|
|
19083
|
+
});
|
|
19084
|
+
const browserRunning = browserAvailable;
|
|
19085
|
+
useEffect(() => {
|
|
19086
|
+
if (openPane === "browser" && !browserAvailable) setOpenPane(null);
|
|
19087
|
+
if (openPane === "file" && (!workspaceAvailable || previewPath === null))
|
|
19088
|
+
setOpenPane(null);
|
|
19089
|
+
}, [openPane, browserAvailable, workspaceAvailable, previewPath]);
|
|
18891
19090
|
useEffect(() => {
|
|
18892
19091
|
onMessagesChange?.(runtime.messages);
|
|
18893
19092
|
}, [onMessagesChange, runtime.messages]);
|
|
19093
|
+
const handleOpenFilePreview = useCallback((path) => {
|
|
19094
|
+
setWorkspacePath(path);
|
|
19095
|
+
setPreviewPath(path);
|
|
19096
|
+
setOpenPane("file");
|
|
19097
|
+
setMobileView("file");
|
|
19098
|
+
}, []);
|
|
19099
|
+
const handleSelectedPathChange = useCallback((path) => {
|
|
19100
|
+
setWorkspacePath(path);
|
|
19101
|
+
if (path === null) setPreviewPath(null);
|
|
19102
|
+
}, []);
|
|
18894
19103
|
const handleFileSelect = useCallback(
|
|
18895
19104
|
(path) => {
|
|
18896
|
-
|
|
18897
|
-
setMobileView("workspace");
|
|
18898
|
-
setShowWorkspace(true);
|
|
19105
|
+
handleOpenFilePreview(path);
|
|
18899
19106
|
onFileSelect?.(path);
|
|
18900
19107
|
},
|
|
18901
|
-
[onFileSelect]
|
|
19108
|
+
[handleOpenFilePreview, onFileSelect]
|
|
18902
19109
|
);
|
|
18903
19110
|
const handleToggleBrowser = useCallback(() => {
|
|
18904
|
-
|
|
19111
|
+
setOpenPane((prev) => prev === "browser" ? null : "browser");
|
|
19112
|
+
setMobileView("browser");
|
|
18905
19113
|
}, []);
|
|
18906
|
-
const
|
|
18907
|
-
|
|
19114
|
+
const handleToggleFiles = useCallback(() => {
|
|
19115
|
+
setFilesOpen((open) => !open);
|
|
18908
19116
|
}, []);
|
|
18909
19117
|
const mobilePanes = useMemo(() => {
|
|
18910
19118
|
const panes = ["chat"];
|
|
18911
19119
|
if (browserVisible) panes.push("browser");
|
|
18912
|
-
if (
|
|
19120
|
+
if (filePreviewVisible) panes.push("file");
|
|
18913
19121
|
return panes;
|
|
18914
|
-
}, [browserVisible,
|
|
19122
|
+
}, [browserVisible, filePreviewVisible]);
|
|
18915
19123
|
const activeMobilePane = mobilePanes.includes(mobileView) ? mobileView : "chat";
|
|
18916
19124
|
const showMobileToggle = mobilePanes.length > 1;
|
|
18917
19125
|
return /* @__PURE__ */ jsx(
|
|
@@ -18936,7 +19144,7 @@ function AgentChat({
|
|
|
18936
19144
|
// is visible. With both panes, absolute positioning lets the
|
|
18937
19145
|
// browser/workspace split occupy a fixed width. Without the
|
|
18938
19146
|
// right stack, the chat takes the full width.
|
|
18939
|
-
|
|
19147
|
+
rightStackVisible ? "md:relative md:flex-row" : "md:flex-row"
|
|
18940
19148
|
),
|
|
18941
19149
|
style: { direction: "ltr", ...RIGHT_STACK_STYLE },
|
|
18942
19150
|
children: [
|
|
@@ -18969,14 +19177,12 @@ function AgentChat({
|
|
|
18969
19177
|
className: cn(
|
|
18970
19178
|
// Phone: full width column, hidden while another pane is picked.
|
|
18971
19179
|
"flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden",
|
|
18972
|
-
showMobileToggle && "data-[mobile-view=browser]:hidden data-[mobile-view=
|
|
18973
|
-
// md+:
|
|
18974
|
-
//
|
|
18975
|
-
//
|
|
18976
|
-
//
|
|
18977
|
-
|
|
18978
|
-
// space when nothing else is visible).
|
|
18979
|
-
browserVisible ? "md:absolute md:inset-y-0 md:left-0 md:right-(--right-stack-w,440px) md:flex" : "md:relative md:flex-1"
|
|
19180
|
+
showMobileToggle && "data-[mobile-view=browser]:hidden data-[mobile-view=file]:hidden md:flex!",
|
|
19181
|
+
// md+: when the right column is laid out (browser pane or file
|
|
19182
|
+
// preview), pin it at the configured width and absolutely
|
|
19183
|
+
// position the chat panel beside it. Otherwise the chat panel
|
|
19184
|
+
// fills the space.
|
|
19185
|
+
rightStackVisible ? "md:absolute md:inset-y-0 md:left-0 md:right-(--right-stack-w,440px) md:flex" : "md:relative md:flex-1"
|
|
18980
19186
|
),
|
|
18981
19187
|
children: viewMode === "whiteboard" ? (
|
|
18982
19188
|
// The board replaces the transcript, on the same runtime
|
|
@@ -19016,12 +19222,37 @@ function AgentChat({
|
|
|
19016
19222
|
onSelectBrowserProfile,
|
|
19017
19223
|
browserProfilesEnabled,
|
|
19018
19224
|
browserProfileLocked: !!sessionId,
|
|
19019
|
-
showBrowser,
|
|
19225
|
+
showBrowser: openPane === "browser",
|
|
19020
19226
|
onToggleBrowser: handleToggleBrowser,
|
|
19021
|
-
showWorkspace,
|
|
19022
|
-
onToggleWorkspace:
|
|
19227
|
+
showWorkspace: filesOpen,
|
|
19228
|
+
onToggleWorkspace: handleToggleFiles,
|
|
19023
19229
|
canShowBrowser: browserAvailable,
|
|
19024
19230
|
canShowWorkspace: workspaceAvailable,
|
|
19231
|
+
paneCards: {
|
|
19232
|
+
browser: browserRunning ? {
|
|
19233
|
+
subtitle: browserState?.controlOwner ? `${browserState.controlOwner} has control` : void 0,
|
|
19234
|
+
thumbnail: browserPreview,
|
|
19235
|
+
// Show and hide on the same card, like the Files
|
|
19236
|
+
// accordion header and the composer's Tools item.
|
|
19237
|
+
onOpen: handleToggleBrowser
|
|
19238
|
+
} : null,
|
|
19239
|
+
files: workspaceAvailable ? {
|
|
19240
|
+
open: filesOpen,
|
|
19241
|
+
onToggle: handleToggleFiles,
|
|
19242
|
+
panel: /* @__PURE__ */ jsx(
|
|
19243
|
+
WorkspacePanel,
|
|
19244
|
+
{
|
|
19245
|
+
adapter,
|
|
19246
|
+
sessionId,
|
|
19247
|
+
selectedPath: workspacePath,
|
|
19248
|
+
onSelectedPathChange: handleSelectedPathChange,
|
|
19249
|
+
onOpenFile: handleOpenFilePreview,
|
|
19250
|
+
refreshSignal: runtime.workspaceRefreshKey,
|
|
19251
|
+
disabled: effectiveDisabled
|
|
19252
|
+
}
|
|
19253
|
+
)
|
|
19254
|
+
} : null
|
|
19255
|
+
},
|
|
19025
19256
|
viewMode,
|
|
19026
19257
|
onViewModeChange: runtime.setViewMode,
|
|
19027
19258
|
deepResearchEnabled,
|
|
@@ -19049,13 +19280,10 @@ function AgentChat({
|
|
|
19049
19280
|
// Phone: full width column, hidden when chat tab active.
|
|
19050
19281
|
"flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden",
|
|
19051
19282
|
showMobileToggle && "data-[mobile-view=chat]:hidden md:flex!",
|
|
19052
|
-
// md+:
|
|
19053
|
-
//
|
|
19054
|
-
//
|
|
19055
|
-
|
|
19056
|
-
// WorkspacePanel manage its own width via the resize
|
|
19057
|
-
// handle.
|
|
19058
|
-
browserVisible ? "md:absolute md:inset-y-0 md:right-0 md:w-(--right-stack-w,440px) md:flex-none" : "md:relative md:shrink-0 md:flex-none md:w-auto"
|
|
19283
|
+
// md+: absolute right column at the configured width. The
|
|
19284
|
+
// browser is the only pane that lives here — files expand
|
|
19285
|
+
// as an accordion inside the chat column.
|
|
19286
|
+
"md:absolute md:inset-y-0 md:right-0 md:w-(--right-stack-w,440px) md:flex-none"
|
|
19059
19287
|
),
|
|
19060
19288
|
children: [
|
|
19061
19289
|
browserVisible && /* @__PURE__ */ jsx(
|
|
@@ -19063,42 +19291,25 @@ function AgentChat({
|
|
|
19063
19291
|
{
|
|
19064
19292
|
"data-testid": "browser-panel",
|
|
19065
19293
|
"data-mobile-view": activeMobilePane,
|
|
19066
|
-
className:
|
|
19067
|
-
"min-h-0 w-full overflow-hidden",
|
|
19068
|
-
// With both panes the desktop splits the column in half.
|
|
19069
|
-
// A phone gives whichever pane is selected the whole
|
|
19070
|
-
// height instead — see the mobile toggle above.
|
|
19071
|
-
bothPanesVisible ? "h-full data-[mobile-view=workspace]:hidden md:block! md:h-1/2 md:border-b md:border-line" : "h-full"
|
|
19072
|
-
),
|
|
19294
|
+
className: "min-h-0 h-full w-full overflow-hidden",
|
|
19073
19295
|
children: /* @__PURE__ */ jsx(
|
|
19074
19296
|
BrowserPane,
|
|
19075
19297
|
{
|
|
19076
19298
|
sessionId,
|
|
19077
19299
|
state: browserState,
|
|
19078
19300
|
adapter,
|
|
19079
|
-
onClose: () =>
|
|
19301
|
+
onClose: () => setOpenPane(null)
|
|
19080
19302
|
}
|
|
19081
19303
|
)
|
|
19082
19304
|
}
|
|
19083
19305
|
),
|
|
19084
|
-
|
|
19085
|
-
|
|
19306
|
+
filePreviewVisible && sessionId && previewPath && /* @__PURE__ */ jsx(
|
|
19307
|
+
WorkspaceFileDrawer,
|
|
19086
19308
|
{
|
|
19087
|
-
|
|
19088
|
-
|
|
19089
|
-
|
|
19090
|
-
|
|
19091
|
-
WorkspacePanel,
|
|
19092
|
-
{
|
|
19093
|
-
adapter,
|
|
19094
|
-
sessionId,
|
|
19095
|
-
selectedPath: workspacePath,
|
|
19096
|
-
onSelectedPathChange: setWorkspacePath,
|
|
19097
|
-
refreshSignal: runtime.workspaceRefreshKey,
|
|
19098
|
-
disabled: effectiveDisabled,
|
|
19099
|
-
fillParent: bothPanesVisible
|
|
19100
|
-
}
|
|
19101
|
-
)
|
|
19309
|
+
adapter,
|
|
19310
|
+
sessionId,
|
|
19311
|
+
path: previewPath,
|
|
19312
|
+
onClose: () => setOpenPane(null)
|
|
19102
19313
|
}
|
|
19103
19314
|
)
|
|
19104
19315
|
]
|
|
@@ -23111,6 +23322,6 @@ function SessionTreePanel({
|
|
|
23111
23322
|
] });
|
|
23112
23323
|
}
|
|
23113
23324
|
|
|
23114
|
-
export { ACTIVE_MISSION_STATUSES, AgentChat, AgentChatAdapterProvider, AgentWhiteboard, BrandBeam,
|
|
23325
|
+
export { ACTIVE_MISSION_STATUSES, AgentChat, AgentChatAdapterProvider, AgentWhiteboard, BrandBeam, BrowserShell, CitationText, CodingAgentsPanel, ComposioConnectCard, FetchSseEventStream, InboxPanel, IntegrationsBand, IntegrationsPage, MISSION_EVENT_CATEGORIES, MessageResponse, MissionDashboard, MissionsPanel, ORB_STATE_LABELS, OrbShimmerLabel, ResearchSourcesPanel, SIMPLE_MODE_HIDDEN_TOOLS, SIMPLE_MODE_ORB_OPTIONS, ScheduledWorkPanel, SessionTreePanel, WhiteboardSurface, defaultSelectedMissionTaskId, deriveMissionWorkerActivityLabel, deriveOrbActivity, formatMissionTimestamp, groupMissionTasksByBucket, isHiddenSimpleTool, isTerminalMissionStatus, mergeMissionEvents, messageOrbState, missionEventActorLabel, missionEventCategory, missionEventSummary, missionEventTaskId, missionTaskBlocks, missionTaskBucket, missionTaskRailGroups, missionTaskStatusDotClass, missionTaskTitle, missionWorkerTaskCounts, persistChatViewMode, splitCitations, stripMissionControlMarkup, toolOrbState, useAgentChatAdapterContext, useAgentChatRuntime, useBrowserControl, useChatViewMode, useInboxUnreadCount, useMissionEvents };
|
|
23115
23326
|
//# sourceMappingURL=index.js.map
|
|
23116
23327
|
//# sourceMappingURL=index.js.map
|