@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.cjs
CHANGED
|
@@ -215,6 +215,481 @@ function useAgentChatAdapterContext() {
|
|
|
215
215
|
}
|
|
216
216
|
return value;
|
|
217
217
|
}
|
|
218
|
+
var NAMED_KEYS = /* @__PURE__ */ new Set([
|
|
219
|
+
"Enter",
|
|
220
|
+
"Tab",
|
|
221
|
+
"Escape",
|
|
222
|
+
"Backspace",
|
|
223
|
+
"Delete",
|
|
224
|
+
"ArrowUp",
|
|
225
|
+
"ArrowDown",
|
|
226
|
+
"ArrowLeft",
|
|
227
|
+
"ArrowRight",
|
|
228
|
+
"Home",
|
|
229
|
+
"End",
|
|
230
|
+
"PageUp",
|
|
231
|
+
"PageDown"
|
|
232
|
+
]);
|
|
233
|
+
function toWsUrl(src) {
|
|
234
|
+
const url = new URL(src, window.location.href);
|
|
235
|
+
if (url.protocol === "http:") url.protocol = "ws:";
|
|
236
|
+
else if (url.protocol === "https:") url.protocol = "wss:";
|
|
237
|
+
return url.toString();
|
|
238
|
+
}
|
|
239
|
+
function normalizePoint(clientX, clientY, rect, naturalWidth, naturalHeight) {
|
|
240
|
+
let { width, height } = rect;
|
|
241
|
+
let offsetX = 0;
|
|
242
|
+
let offsetY = 0;
|
|
243
|
+
if (naturalWidth > 0 && naturalHeight > 0) {
|
|
244
|
+
const scale = Math.min(rect.width / naturalWidth, rect.height / naturalHeight);
|
|
245
|
+
width = naturalWidth * scale;
|
|
246
|
+
height = naturalHeight * scale;
|
|
247
|
+
offsetX = (rect.width - width) / 2;
|
|
248
|
+
offsetY = (rect.height - height) / 2;
|
|
249
|
+
}
|
|
250
|
+
if (width <= 0 || height <= 0) return null;
|
|
251
|
+
const x = (clientX - rect.left - offsetX) / width;
|
|
252
|
+
const y = (clientY - rect.top - offsetY) / height;
|
|
253
|
+
if (x < 0 || x > 1 || y < 0 || y > 1) return null;
|
|
254
|
+
return { x, y };
|
|
255
|
+
}
|
|
256
|
+
function hostOf(url) {
|
|
257
|
+
try {
|
|
258
|
+
const parsed = new URL(url);
|
|
259
|
+
return { origin: parsed.host, rest: `${parsed.pathname}${parsed.search}` };
|
|
260
|
+
} catch {
|
|
261
|
+
return { origin: url, rest: "" };
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
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";
|
|
265
|
+
function BrowserShell({
|
|
266
|
+
src,
|
|
267
|
+
hasControl,
|
|
268
|
+
onToggleControl,
|
|
269
|
+
onClose,
|
|
270
|
+
onMaximize,
|
|
271
|
+
onDisconnect,
|
|
272
|
+
testId = "browser-shell"
|
|
273
|
+
}) {
|
|
274
|
+
const socketRef = react.useRef(null);
|
|
275
|
+
const frameRef = react.useRef(null);
|
|
276
|
+
const onDisconnectRef = react.useRef(onDisconnect);
|
|
277
|
+
onDisconnectRef.current = onDisconnect;
|
|
278
|
+
const objectUrlRef = react.useRef(null);
|
|
279
|
+
const [state, setState] = react.useState("connecting");
|
|
280
|
+
const [frame2, setFrame] = react.useState(null);
|
|
281
|
+
const [tabs, setTabs] = react.useState([]);
|
|
282
|
+
const [nav, setNav] = react.useState({ url: "", title: "" });
|
|
283
|
+
const [menuOpen, setMenuOpen] = react.useState(false);
|
|
284
|
+
react.useEffect(() => {
|
|
285
|
+
setState("connecting");
|
|
286
|
+
let disposed = false;
|
|
287
|
+
const socket = new WebSocket(toWsUrl(src));
|
|
288
|
+
socketRef.current = socket;
|
|
289
|
+
socket.onopen = () => {
|
|
290
|
+
if (!disposed) setState("connected");
|
|
291
|
+
};
|
|
292
|
+
socket.onerror = () => {
|
|
293
|
+
if (!disposed) setState("disconnected");
|
|
294
|
+
};
|
|
295
|
+
socket.onclose = (event) => {
|
|
296
|
+
if (disposed) return;
|
|
297
|
+
setState("disconnected");
|
|
298
|
+
onDisconnectRef.current?.(event?.wasClean ?? false);
|
|
299
|
+
};
|
|
300
|
+
socket.onmessage = (event) => {
|
|
301
|
+
if (disposed) return;
|
|
302
|
+
const data = event.data;
|
|
303
|
+
if (typeof data !== "string") {
|
|
304
|
+
const next = URL.createObjectURL(
|
|
305
|
+
data instanceof Blob ? data : new Blob([data])
|
|
306
|
+
);
|
|
307
|
+
if (objectUrlRef.current) URL.revokeObjectURL(objectUrlRef.current);
|
|
308
|
+
objectUrlRef.current = next;
|
|
309
|
+
setFrame(next);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
let message;
|
|
313
|
+
try {
|
|
314
|
+
message = JSON.parse(data);
|
|
315
|
+
} catch {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
if (message.t === "tabs") {
|
|
319
|
+
const next = message.tabs ?? [];
|
|
320
|
+
setTabs(next);
|
|
321
|
+
const active = next.find((tab) => tab.active);
|
|
322
|
+
if (active) setNav({ url: active.url, title: active.title });
|
|
323
|
+
} else if (message.t === "nav") {
|
|
324
|
+
setNav({
|
|
325
|
+
url: String(message.url ?? ""),
|
|
326
|
+
title: String(message.title ?? "")
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
return () => {
|
|
331
|
+
disposed = true;
|
|
332
|
+
socket.onopen = null;
|
|
333
|
+
socket.onmessage = null;
|
|
334
|
+
socket.onclose = null;
|
|
335
|
+
socket.onerror = null;
|
|
336
|
+
socket.close();
|
|
337
|
+
if (objectUrlRef.current) URL.revokeObjectURL(objectUrlRef.current);
|
|
338
|
+
objectUrlRef.current = null;
|
|
339
|
+
socketRef.current = null;
|
|
340
|
+
};
|
|
341
|
+
}, [src]);
|
|
342
|
+
const send = react.useCallback((message) => {
|
|
343
|
+
const socket = socketRef.current;
|
|
344
|
+
if (!socket || socket.readyState !== WebSocket.OPEN) return;
|
|
345
|
+
socket.send(JSON.stringify(message));
|
|
346
|
+
}, []);
|
|
347
|
+
const command = react.useCallback(
|
|
348
|
+
(message) => {
|
|
349
|
+
if (!hasControl) return;
|
|
350
|
+
send(message);
|
|
351
|
+
},
|
|
352
|
+
[hasControl, send]
|
|
353
|
+
);
|
|
354
|
+
const pointerToPage = (event) => {
|
|
355
|
+
const image = frameRef.current;
|
|
356
|
+
if (!image) return null;
|
|
357
|
+
return normalizePoint(
|
|
358
|
+
event.clientX,
|
|
359
|
+
event.clientY,
|
|
360
|
+
image.getBoundingClientRect(),
|
|
361
|
+
image.naturalWidth,
|
|
362
|
+
image.naturalHeight
|
|
363
|
+
);
|
|
364
|
+
};
|
|
365
|
+
const handleKeyDown = (event) => {
|
|
366
|
+
if (!hasControl) return;
|
|
367
|
+
if (NAMED_KEYS.has(event.key)) {
|
|
368
|
+
event.preventDefault();
|
|
369
|
+
command({
|
|
370
|
+
t: "key",
|
|
371
|
+
key: event.key,
|
|
372
|
+
mods: {
|
|
373
|
+
shift: event.shiftKey,
|
|
374
|
+
ctrl: event.ctrlKey,
|
|
375
|
+
alt: event.altKey,
|
|
376
|
+
meta: event.metaKey
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
if (event.key.length === 1 && !event.ctrlKey && !event.metaKey) {
|
|
382
|
+
event.preventDefault();
|
|
383
|
+
command({ t: "type", text: event.key });
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
const { origin, rest } = hostOf(nav.url);
|
|
387
|
+
const showTabs = tabs.length > 1;
|
|
388
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
389
|
+
"div",
|
|
390
|
+
{
|
|
391
|
+
"data-testid": testId,
|
|
392
|
+
className: "flex h-full min-h-0 flex-col bg-background",
|
|
393
|
+
children: [
|
|
394
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-11 items-center gap-1.5 border-b border-line bg-card px-2.5", children: [
|
|
395
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
396
|
+
"button",
|
|
397
|
+
{
|
|
398
|
+
type: "button",
|
|
399
|
+
"aria-label": "Back",
|
|
400
|
+
className: ICON,
|
|
401
|
+
disabled: !hasControl,
|
|
402
|
+
onClick: () => command({ t: "back" }),
|
|
403
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ChevronLeft, {})
|
|
404
|
+
}
|
|
405
|
+
),
|
|
406
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
407
|
+
"button",
|
|
408
|
+
{
|
|
409
|
+
type: "button",
|
|
410
|
+
"aria-label": "Forward",
|
|
411
|
+
className: ICON,
|
|
412
|
+
disabled: !hasControl,
|
|
413
|
+
onClick: () => command({ t: "forward" }),
|
|
414
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ChevronRight, {})
|
|
415
|
+
}
|
|
416
|
+
),
|
|
417
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
418
|
+
"button",
|
|
419
|
+
{
|
|
420
|
+
type: "button",
|
|
421
|
+
"aria-label": "Reload",
|
|
422
|
+
className: ICON,
|
|
423
|
+
disabled: !hasControl,
|
|
424
|
+
onClick: () => command({ t: "reload" }),
|
|
425
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(Reload, {})
|
|
426
|
+
}
|
|
427
|
+
),
|
|
428
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
429
|
+
"button",
|
|
430
|
+
{
|
|
431
|
+
type: "button",
|
|
432
|
+
"data-testid": "browser-shell-control",
|
|
433
|
+
"aria-label": hasControl ? "Return control" : "Take control",
|
|
434
|
+
"aria-pressed": hasControl,
|
|
435
|
+
onClick: onToggleControl,
|
|
436
|
+
className: hasControl ? "flex size-[26px] shrink-0 items-center justify-center rounded-[5px] bg-[#F5A524] text-black" : ICON,
|
|
437
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(Pointer, {})
|
|
438
|
+
}
|
|
439
|
+
),
|
|
440
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
441
|
+
"div",
|
|
442
|
+
{
|
|
443
|
+
"data-testid": "browser-shell-url",
|
|
444
|
+
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",
|
|
445
|
+
children: [
|
|
446
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
447
|
+
"span",
|
|
448
|
+
{
|
|
449
|
+
"aria-hidden": "true",
|
|
450
|
+
className: `size-1.5 shrink-0 rounded-full ${state === "connected" ? "bg-emerald-500" : "bg-muted-foreground"}`
|
|
451
|
+
}
|
|
452
|
+
),
|
|
453
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "truncate", children: [
|
|
454
|
+
origin,
|
|
455
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: rest })
|
|
456
|
+
] })
|
|
457
|
+
]
|
|
458
|
+
}
|
|
459
|
+
),
|
|
460
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
|
|
461
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
462
|
+
"button",
|
|
463
|
+
{
|
|
464
|
+
type: "button",
|
|
465
|
+
"aria-label": "More",
|
|
466
|
+
className: ICON,
|
|
467
|
+
onClick: () => setMenuOpen((open) => !open),
|
|
468
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(Ellipsis, {})
|
|
469
|
+
}
|
|
470
|
+
),
|
|
471
|
+
menuOpen && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
472
|
+
"div",
|
|
473
|
+
{
|
|
474
|
+
"data-testid": "browser-shell-menu",
|
|
475
|
+
className: "absolute right-0 top-8 z-10 min-w-36 rounded-md border border-line bg-card py-1 text-xs shadow-md",
|
|
476
|
+
children: [
|
|
477
|
+
onMaximize && /* @__PURE__ */ jsxRuntime.jsx(
|
|
478
|
+
"button",
|
|
479
|
+
{
|
|
480
|
+
type: "button",
|
|
481
|
+
className: "block w-full px-3 py-1.5 text-left hover:bg-secondary",
|
|
482
|
+
onClick: () => {
|
|
483
|
+
setMenuOpen(false);
|
|
484
|
+
onMaximize();
|
|
485
|
+
},
|
|
486
|
+
children: "Maximize"
|
|
487
|
+
}
|
|
488
|
+
),
|
|
489
|
+
onClose && /* @__PURE__ */ jsxRuntime.jsx(
|
|
490
|
+
"button",
|
|
491
|
+
{
|
|
492
|
+
type: "button",
|
|
493
|
+
className: "block w-full px-3 py-1.5 text-left hover:bg-secondary",
|
|
494
|
+
onClick: () => {
|
|
495
|
+
setMenuOpen(false);
|
|
496
|
+
onClose();
|
|
497
|
+
},
|
|
498
|
+
children: "Close browser"
|
|
499
|
+
}
|
|
500
|
+
)
|
|
501
|
+
]
|
|
502
|
+
}
|
|
503
|
+
)
|
|
504
|
+
] })
|
|
505
|
+
] }),
|
|
506
|
+
showTabs && /* @__PURE__ */ jsxRuntime.jsx(
|
|
507
|
+
"div",
|
|
508
|
+
{
|
|
509
|
+
"data-testid": "browser-shell-tabs",
|
|
510
|
+
className: "flex min-h-[34px] items-center gap-0.5 border-b border-line bg-card px-1.5",
|
|
511
|
+
children: tabs.map((tab) => (
|
|
512
|
+
// A div, not a button: the close control nests inside, and a
|
|
513
|
+
// button inside a button is invalid markup.
|
|
514
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
515
|
+
"div",
|
|
516
|
+
{
|
|
517
|
+
role: "tab",
|
|
518
|
+
tabIndex: 0,
|
|
519
|
+
"aria-selected": tab.active,
|
|
520
|
+
"data-testid": "browser-shell-tab",
|
|
521
|
+
onClick: () => send({ t: "switch_tab", id: tab.id }),
|
|
522
|
+
onKeyDown: (event) => {
|
|
523
|
+
if (event.key === "Enter") send({ t: "switch_tab", id: tab.id });
|
|
524
|
+
},
|
|
525
|
+
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"}`,
|
|
526
|
+
children: [
|
|
527
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: tab.title || tab.url }),
|
|
528
|
+
hasControl && /* @__PURE__ */ jsxRuntime.jsx(
|
|
529
|
+
"button",
|
|
530
|
+
{
|
|
531
|
+
type: "button",
|
|
532
|
+
"data-testid": "browser-shell-tab-close",
|
|
533
|
+
"aria-label": `Close ${tab.title || tab.url}`,
|
|
534
|
+
onClick: (event) => {
|
|
535
|
+
event.stopPropagation();
|
|
536
|
+
command({ t: "close_tab", id: tab.id });
|
|
537
|
+
},
|
|
538
|
+
className: "flex size-3.5 shrink-0 items-center justify-center rounded-sm text-muted-foreground hover:bg-secondary hover:text-foreground",
|
|
539
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(XSmall, {})
|
|
540
|
+
}
|
|
541
|
+
)
|
|
542
|
+
]
|
|
543
|
+
},
|
|
544
|
+
tab.id
|
|
545
|
+
)
|
|
546
|
+
))
|
|
547
|
+
}
|
|
548
|
+
),
|
|
549
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
550
|
+
"div",
|
|
551
|
+
{
|
|
552
|
+
className: `relative min-h-0 flex-1 bg-black ${hasControl ? "ring-2 ring-inset ring-[#F5A524]" : ""}`,
|
|
553
|
+
tabIndex: hasControl ? 0 : -1,
|
|
554
|
+
onKeyDown: handleKeyDown,
|
|
555
|
+
children: [
|
|
556
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
557
|
+
"img",
|
|
558
|
+
{
|
|
559
|
+
ref: frameRef,
|
|
560
|
+
"data-testid": "browser-shell-frame",
|
|
561
|
+
alt: "",
|
|
562
|
+
src: frame2 ?? void 0,
|
|
563
|
+
draggable: false,
|
|
564
|
+
className: "h-full w-full object-contain",
|
|
565
|
+
onMouseDown: (event) => {
|
|
566
|
+
const point = pointerToPage(event);
|
|
567
|
+
if (point) command({ t: "click", ...point, count: event.detail || 1 });
|
|
568
|
+
},
|
|
569
|
+
onWheel: (event) => {
|
|
570
|
+
const point = pointerToPage(event);
|
|
571
|
+
if (point) {
|
|
572
|
+
command({
|
|
573
|
+
t: "scroll",
|
|
574
|
+
...point,
|
|
575
|
+
dx: event.deltaX,
|
|
576
|
+
dy: event.deltaY
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
),
|
|
582
|
+
state !== "connected" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
583
|
+
"div",
|
|
584
|
+
{
|
|
585
|
+
"data-testid": "browser-shell-overlay",
|
|
586
|
+
className: "pointer-events-none absolute inset-0 flex items-center justify-center bg-black/70 text-sm text-muted-foreground",
|
|
587
|
+
children: state === "connecting" ? "Connecting to browser\u2026" : "Browser disconnected."
|
|
588
|
+
}
|
|
589
|
+
),
|
|
590
|
+
hasControl && /* @__PURE__ */ jsxRuntime.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: [
|
|
591
|
+
/* @__PURE__ */ jsxRuntime.jsx(Pointer, {}),
|
|
592
|
+
"You have control \xB7 click the pointer to return"
|
|
593
|
+
] })
|
|
594
|
+
]
|
|
595
|
+
}
|
|
596
|
+
)
|
|
597
|
+
]
|
|
598
|
+
}
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
var svg = {
|
|
602
|
+
width: 14,
|
|
603
|
+
height: 14,
|
|
604
|
+
viewBox: "0 0 24 24",
|
|
605
|
+
fill: "none",
|
|
606
|
+
stroke: "currentColor",
|
|
607
|
+
strokeWidth: 2,
|
|
608
|
+
strokeLinecap: "round",
|
|
609
|
+
strokeLinejoin: "round",
|
|
610
|
+
"aria-hidden": true
|
|
611
|
+
};
|
|
612
|
+
var ChevronLeft = () => /* @__PURE__ */ jsxRuntime.jsx("svg", { ...svg, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m15 18-6-6 6-6" }) });
|
|
613
|
+
var ChevronRight = () => /* @__PURE__ */ jsxRuntime.jsx("svg", { ...svg, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m9 18 6-6-6-6" }) });
|
|
614
|
+
var Reload = () => /* @__PURE__ */ jsxRuntime.jsxs("svg", { ...svg, children: [
|
|
615
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" }),
|
|
616
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 3v5h5" })
|
|
617
|
+
] });
|
|
618
|
+
var Pointer = () => /* @__PURE__ */ jsxRuntime.jsxs("svg", { ...svg, children: [
|
|
619
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12.586 12.586 19 19" }),
|
|
620
|
+
/* @__PURE__ */ jsxRuntime.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" })
|
|
621
|
+
] });
|
|
622
|
+
var XSmall = () => /* @__PURE__ */ jsxRuntime.jsxs("svg", { ...svg, width: 10, height: 10, children: [
|
|
623
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18 6 6 18" }),
|
|
624
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "m6 6 12 12" })
|
|
625
|
+
] });
|
|
626
|
+
var Ellipsis = () => /* @__PURE__ */ jsxRuntime.jsxs("svg", { ...svg, children: [
|
|
627
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "1" }),
|
|
628
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "19", cy: "12", r: "1" }),
|
|
629
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "5", cy: "12", r: "1" })
|
|
630
|
+
] });
|
|
631
|
+
function useBrowserControl({
|
|
632
|
+
sessionId,
|
|
633
|
+
hasControl,
|
|
634
|
+
adapter,
|
|
635
|
+
onControlAcquired,
|
|
636
|
+
onControlReleased,
|
|
637
|
+
onClose
|
|
638
|
+
}) {
|
|
639
|
+
const [pending, setPending] = react.useState(false);
|
|
640
|
+
const [confirmOpen, setConfirmOpen] = react.useState(false);
|
|
641
|
+
const [error, setError] = react.useState(null);
|
|
642
|
+
async function toggleControl() {
|
|
643
|
+
if (!sessionId || pending) return;
|
|
644
|
+
setPending(true);
|
|
645
|
+
setError(null);
|
|
646
|
+
try {
|
|
647
|
+
if (hasControl) {
|
|
648
|
+
await adapter.releaseBrowserControl(sessionId);
|
|
649
|
+
onControlReleased?.();
|
|
650
|
+
} else {
|
|
651
|
+
await adapter.acquireBrowserControl(sessionId);
|
|
652
|
+
onControlAcquired?.();
|
|
653
|
+
}
|
|
654
|
+
} catch (nextError) {
|
|
655
|
+
setError(nextError.message);
|
|
656
|
+
} finally {
|
|
657
|
+
setPending(false);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
async function handleConfirmClose() {
|
|
661
|
+
setError(null);
|
|
662
|
+
try {
|
|
663
|
+
if (hasControl) {
|
|
664
|
+
try {
|
|
665
|
+
await adapter.releaseBrowserControl(sessionId);
|
|
666
|
+
onControlReleased?.();
|
|
667
|
+
} catch (releaseError) {
|
|
668
|
+
console.error(
|
|
669
|
+
"Failed to release browser control before close",
|
|
670
|
+
releaseError
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
if (adapter.closeBrowserSession) {
|
|
675
|
+
await adapter.closeBrowserSession(sessionId);
|
|
676
|
+
}
|
|
677
|
+
setConfirmOpen(false);
|
|
678
|
+
onClose?.();
|
|
679
|
+
} catch (nextError) {
|
|
680
|
+
setError(nextError.message);
|
|
681
|
+
throw nextError;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
return {
|
|
685
|
+
pending,
|
|
686
|
+
error,
|
|
687
|
+
confirmOpen,
|
|
688
|
+
setConfirmOpen,
|
|
689
|
+
toggleControl,
|
|
690
|
+
handleConfirmClose
|
|
691
|
+
};
|
|
692
|
+
}
|
|
218
693
|
|
|
219
694
|
// src/components/ui/button.tsx
|
|
220
695
|
init_utils();
|
|
@@ -449,375 +924,34 @@ function ConfirmDialog({
|
|
|
449
924
|
}
|
|
450
925
|
);
|
|
451
926
|
}
|
|
452
|
-
function
|
|
927
|
+
function BrowserPane({
|
|
453
928
|
sessionId,
|
|
454
|
-
|
|
455
|
-
adapter,
|
|
456
|
-
onControlAcquired,
|
|
457
|
-
onControlReleased,
|
|
458
|
-
onClose
|
|
459
|
-
}) {
|
|
460
|
-
const [pending, setPending] = react.useState(false);
|
|
461
|
-
const [confirmOpen, setConfirmOpen] = react.useState(false);
|
|
462
|
-
const [error, setError] = react.useState(null);
|
|
463
|
-
async function toggleControl() {
|
|
464
|
-
if (!sessionId || pending) return;
|
|
465
|
-
setPending(true);
|
|
466
|
-
setError(null);
|
|
467
|
-
try {
|
|
468
|
-
if (hasControl) {
|
|
469
|
-
await adapter.releaseBrowserControl(sessionId);
|
|
470
|
-
onControlReleased?.();
|
|
471
|
-
} else {
|
|
472
|
-
await adapter.acquireBrowserControl(sessionId);
|
|
473
|
-
onControlAcquired?.();
|
|
474
|
-
}
|
|
475
|
-
} catch (nextError) {
|
|
476
|
-
setError(nextError.message);
|
|
477
|
-
} finally {
|
|
478
|
-
setPending(false);
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
async function handleConfirmClose() {
|
|
482
|
-
setError(null);
|
|
483
|
-
try {
|
|
484
|
-
if (hasControl) {
|
|
485
|
-
try {
|
|
486
|
-
await adapter.releaseBrowserControl(sessionId);
|
|
487
|
-
onControlReleased?.();
|
|
488
|
-
} catch (releaseError) {
|
|
489
|
-
console.error(
|
|
490
|
-
"Failed to release browser control before close",
|
|
491
|
-
releaseError
|
|
492
|
-
);
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
if (adapter.closeBrowserSession) {
|
|
496
|
-
await adapter.closeBrowserSession(sessionId);
|
|
497
|
-
}
|
|
498
|
-
setConfirmOpen(false);
|
|
499
|
-
onClose?.();
|
|
500
|
-
} catch (nextError) {
|
|
501
|
-
setError(nextError.message);
|
|
502
|
-
throw nextError;
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-11 items-center gap-2 border-t border-line bg-card px-3 py-2", children: [
|
|
506
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
507
|
-
Button,
|
|
508
|
-
{
|
|
509
|
-
type: "button",
|
|
510
|
-
size: "xs",
|
|
511
|
-
variant: hasControl ? "secondary" : "default",
|
|
512
|
-
disabled: !sessionId || pending,
|
|
513
|
-
onClick: () => void toggleControl(),
|
|
514
|
-
children: [
|
|
515
|
-
hasControl ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RotateCcwIcon, { className: "size-3", "aria-hidden": "true" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MousePointer2Icon, { className: "size-3", "aria-hidden": "true" }),
|
|
516
|
-
hasControl ? "Return control" : "Take control"
|
|
517
|
-
]
|
|
518
|
-
}
|
|
519
|
-
),
|
|
520
|
-
onClose && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
521
|
-
Button,
|
|
522
|
-
{
|
|
523
|
-
type: "button",
|
|
524
|
-
size: "xs",
|
|
525
|
-
variant: "secondary",
|
|
526
|
-
disabled: !sessionId,
|
|
527
|
-
onClick: () => {
|
|
528
|
-
setError(null);
|
|
529
|
-
setConfirmOpen(true);
|
|
530
|
-
},
|
|
531
|
-
children: [
|
|
532
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.XIcon, { className: "size-3", "aria-hidden": "true" }),
|
|
533
|
-
"Close"
|
|
534
|
-
]
|
|
535
|
-
}
|
|
536
|
-
),
|
|
537
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ml-auto flex items-center gap-1.5 text-[11px] text-muted-foreground", children: [
|
|
538
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "size-1.5 rounded-full bg-red-500", "aria-hidden": "true" }),
|
|
539
|
-
"Live"
|
|
540
|
-
] }),
|
|
541
|
-
error && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "max-w-40 truncate text-[11px] text-destructive", children: error }),
|
|
542
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
543
|
-
ConfirmDialog,
|
|
544
|
-
{
|
|
545
|
-
open: confirmOpen,
|
|
546
|
-
title: "Close browser session?",
|
|
547
|
-
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.",
|
|
548
|
-
confirmLabel: "Close browser",
|
|
549
|
-
variant: "destructive",
|
|
550
|
-
confirmIcon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.XIcon, { className: "size-3.5", "aria-hidden": "true" }),
|
|
551
|
-
onConfirm: handleConfirmClose,
|
|
552
|
-
onCancel: () => setConfirmOpen(false)
|
|
553
|
-
}
|
|
554
|
-
)
|
|
555
|
-
] });
|
|
556
|
-
}
|
|
557
|
-
var MIN_ZOOM = 1;
|
|
558
|
-
var MAX_ZOOM = 3;
|
|
559
|
-
var ZOOM_STEP = 0.5;
|
|
560
|
-
function clampZoom(zoom) {
|
|
561
|
-
const bounded = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, zoom));
|
|
562
|
-
return Math.round(bounded * 100) / 100;
|
|
563
|
-
}
|
|
564
|
-
function toWsUrl(src) {
|
|
565
|
-
const url = new URL(src, window.location.href);
|
|
566
|
-
if (url.protocol === "http:") {
|
|
567
|
-
url.protocol = "ws:";
|
|
568
|
-
} else if (url.protocol === "https:") {
|
|
569
|
-
url.protocol = "wss:";
|
|
570
|
-
}
|
|
571
|
-
return url.toString();
|
|
572
|
-
}
|
|
573
|
-
function BrowserLiveView({
|
|
574
|
-
src,
|
|
575
|
-
testId = "browser-rfb",
|
|
576
|
-
onDisconnect
|
|
577
|
-
}) {
|
|
578
|
-
const containerRef = react.useRef(null);
|
|
579
|
-
const onDisconnectRef = react.useRef(onDisconnect);
|
|
580
|
-
onDisconnectRef.current = onDisconnect;
|
|
581
|
-
const [state, setState] = react.useState("connecting");
|
|
582
|
-
const [zoom, setZoom] = react.useState(MIN_ZOOM);
|
|
583
|
-
react.useEffect(() => {
|
|
584
|
-
const container = containerRef.current;
|
|
585
|
-
if (!container) return;
|
|
586
|
-
setState("connecting");
|
|
587
|
-
setZoom(MIN_ZOOM);
|
|
588
|
-
let rfb;
|
|
589
|
-
let disposed = false;
|
|
590
|
-
const handleConnect = () => setState("connected");
|
|
591
|
-
const handleDisconnect = (event) => {
|
|
592
|
-
const detail = event.detail;
|
|
593
|
-
setState("disconnected");
|
|
594
|
-
onDisconnectRef.current?.(detail?.clean ?? false);
|
|
595
|
-
};
|
|
596
|
-
void import('@novnc/novnc').then((mod) => {
|
|
597
|
-
if (disposed || !containerRef.current) return;
|
|
598
|
-
const instance = new mod.default(containerRef.current, toWsUrl(src), {
|
|
599
|
-
wsProtocols: ["binary"]
|
|
600
|
-
});
|
|
601
|
-
instance.viewOnly = false;
|
|
602
|
-
instance.scaleViewport = true;
|
|
603
|
-
instance.addEventListener("connect", handleConnect);
|
|
604
|
-
instance.addEventListener("disconnect", handleDisconnect);
|
|
605
|
-
rfb = instance;
|
|
606
|
-
}).catch(() => {
|
|
607
|
-
if (!disposed) setState("disconnected");
|
|
608
|
-
});
|
|
609
|
-
return () => {
|
|
610
|
-
disposed = true;
|
|
611
|
-
if (rfb) {
|
|
612
|
-
rfb.removeEventListener("connect", handleConnect);
|
|
613
|
-
rfb.removeEventListener("disconnect", handleDisconnect);
|
|
614
|
-
rfb.disconnect();
|
|
615
|
-
}
|
|
616
|
-
};
|
|
617
|
-
}, [src]);
|
|
618
|
-
const keepCanvasFocus = (event) => event.preventDefault();
|
|
619
|
-
const zoomTo = (next) => setZoom((current) => clampZoom(next || current));
|
|
620
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative h-full w-full bg-black", children: [
|
|
621
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 overflow-auto", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
622
|
-
"div",
|
|
623
|
-
{
|
|
624
|
-
ref: containerRef,
|
|
625
|
-
"data-testid": testId,
|
|
626
|
-
style: { width: `${zoom * 100}%`, height: `${zoom * 100}%` }
|
|
627
|
-
}
|
|
628
|
-
) }),
|
|
629
|
-
state === "connected" && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
630
|
-
"div",
|
|
631
|
-
{
|
|
632
|
-
"data-testid": "browser-rfb-zoom",
|
|
633
|
-
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",
|
|
634
|
-
children: [
|
|
635
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
636
|
-
"button",
|
|
637
|
-
{
|
|
638
|
-
type: "button",
|
|
639
|
-
tabIndex: -1,
|
|
640
|
-
"aria-label": "Zoom out",
|
|
641
|
-
disabled: zoom <= MIN_ZOOM,
|
|
642
|
-
onMouseDown: keepCanvasFocus,
|
|
643
|
-
onClick: () => zoomTo(zoom - ZOOM_STEP),
|
|
644
|
-
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",
|
|
645
|
-
children: "\u2212"
|
|
646
|
-
}
|
|
647
|
-
),
|
|
648
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
649
|
-
"button",
|
|
650
|
-
{
|
|
651
|
-
type: "button",
|
|
652
|
-
tabIndex: -1,
|
|
653
|
-
"aria-label": "Reset zoom",
|
|
654
|
-
disabled: zoom === MIN_ZOOM,
|
|
655
|
-
onMouseDown: keepCanvasFocus,
|
|
656
|
-
onClick: () => setZoom(MIN_ZOOM),
|
|
657
|
-
className: "min-w-10 rounded px-1 text-center text-xs tabular-nums hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none",
|
|
658
|
-
children: [
|
|
659
|
-
Math.round(zoom * 100),
|
|
660
|
-
"%"
|
|
661
|
-
]
|
|
662
|
-
}
|
|
663
|
-
),
|
|
664
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
665
|
-
"button",
|
|
666
|
-
{
|
|
667
|
-
type: "button",
|
|
668
|
-
tabIndex: -1,
|
|
669
|
-
"aria-label": "Zoom in",
|
|
670
|
-
disabled: zoom >= MAX_ZOOM,
|
|
671
|
-
onMouseDown: keepCanvasFocus,
|
|
672
|
-
onClick: () => zoomTo(zoom + ZOOM_STEP),
|
|
673
|
-
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",
|
|
674
|
-
children: "+"
|
|
675
|
-
}
|
|
676
|
-
)
|
|
677
|
-
]
|
|
678
|
-
}
|
|
679
|
-
),
|
|
680
|
-
state !== "connected" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
681
|
-
"div",
|
|
682
|
-
{
|
|
683
|
-
"data-testid": "browser-rfb-overlay",
|
|
684
|
-
className: "pointer-events-none absolute inset-0 flex items-center justify-center bg-black/70 text-sm text-muted-foreground",
|
|
685
|
-
children: state === "connecting" ? "Connecting to browser\u2026" : "Live view disconnected \u2014 take control again to reconnect."
|
|
686
|
-
}
|
|
687
|
-
)
|
|
688
|
-
] });
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
// src/components/browser/browser-status-dot.tsx
|
|
692
|
-
init_utils();
|
|
693
|
-
function BrowserStatusDot({
|
|
694
|
-
status
|
|
695
|
-
}) {
|
|
696
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
697
|
-
"span",
|
|
698
|
-
{
|
|
699
|
-
"aria-hidden": "true",
|
|
700
|
-
className: cn(
|
|
701
|
-
"inline-block size-2 shrink-0 rounded-full",
|
|
702
|
-
status === "live" && "bg-emerald-500",
|
|
703
|
-
status === "user-control" && "bg-amber-500",
|
|
704
|
-
status === "provisioning" && "animate-pulse bg-sky-500",
|
|
705
|
-
status === "closed" && "bg-muted-foreground"
|
|
706
|
-
)
|
|
707
|
-
}
|
|
708
|
-
);
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
// src/components/ui/tooltip.tsx
|
|
712
|
-
init_utils();
|
|
713
|
-
function TooltipProvider({
|
|
714
|
-
delayDuration = 0,
|
|
715
|
-
...props
|
|
716
|
-
}) {
|
|
717
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
718
|
-
radixUi.Tooltip.Provider,
|
|
719
|
-
{
|
|
720
|
-
"data-slot": "tooltip-provider",
|
|
721
|
-
delayDuration,
|
|
722
|
-
...props
|
|
723
|
-
}
|
|
724
|
-
);
|
|
725
|
-
}
|
|
726
|
-
function Tooltip({
|
|
727
|
-
...props
|
|
728
|
-
}) {
|
|
729
|
-
return /* @__PURE__ */ jsxRuntime.jsx(radixUi.Tooltip.Root, { "data-slot": "tooltip", ...props });
|
|
730
|
-
}
|
|
731
|
-
function TooltipTrigger({
|
|
732
|
-
...props
|
|
733
|
-
}) {
|
|
734
|
-
return /* @__PURE__ */ jsxRuntime.jsx(radixUi.Tooltip.Trigger, { "data-slot": "tooltip-trigger", ...props });
|
|
735
|
-
}
|
|
736
|
-
function TooltipContent({
|
|
737
|
-
className,
|
|
738
|
-
sideOffset = 0,
|
|
739
|
-
children,
|
|
740
|
-
...props
|
|
741
|
-
}) {
|
|
742
|
-
return /* @__PURE__ */ jsxRuntime.jsx(radixUi.Tooltip.Portal, { children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
743
|
-
radixUi.Tooltip.Content,
|
|
744
|
-
{
|
|
745
|
-
"data-slot": "tooltip-content",
|
|
746
|
-
sideOffset,
|
|
747
|
-
className: cn(
|
|
748
|
-
"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",
|
|
749
|
-
className
|
|
750
|
-
),
|
|
751
|
-
...props,
|
|
752
|
-
children: [
|
|
753
|
-
children,
|
|
754
|
-
/* @__PURE__ */ jsxRuntime.jsx(radixUi.Tooltip.Arrow, { className: "z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-none bg-foreground fill-foreground" })
|
|
755
|
-
]
|
|
756
|
-
}
|
|
757
|
-
) });
|
|
758
|
-
}
|
|
759
|
-
function BrowserPane({
|
|
760
|
-
sessionId,
|
|
761
|
-
state,
|
|
929
|
+
state,
|
|
762
930
|
adapter,
|
|
763
931
|
onClose
|
|
764
932
|
}) {
|
|
765
933
|
const [fullscreenOpen, setFullscreenOpen] = react.useState(false);
|
|
766
934
|
const [openFullscreenOnControl, setOpenFullscreenOnControl] = react.useState(false);
|
|
767
935
|
const [localControlActive, setLocalControlActive] = react.useState(false);
|
|
768
|
-
const
|
|
769
|
-
const [previewLoading, setPreviewLoading] = react.useState(false);
|
|
770
|
-
const [previewError, setPreviewError] = react.useState(null);
|
|
771
|
-
const hasLiveViewAdapter = typeof adapter.browserLiveViewUrl === "function";
|
|
772
|
-
const hasPreviewAdapter = typeof adapter.getBrowserPreviewSnapshot === "function";
|
|
936
|
+
const hasShellAdapter = typeof adapter.browserShellUrl === "function";
|
|
773
937
|
const hasControlAdapter = typeof adapter.acquireBrowserControl === "function" && typeof adapter.releaseBrowserControl === "function";
|
|
774
|
-
const
|
|
775
|
-
if (!
|
|
776
|
-
return adapter.
|
|
777
|
-
}, [adapter,
|
|
938
|
+
const shellUrl = react.useMemo(() => {
|
|
939
|
+
if (!hasShellAdapter) return "";
|
|
940
|
+
return adapter.browserShellUrl(sessionId);
|
|
941
|
+
}, [adapter, hasShellAdapter, sessionId]);
|
|
778
942
|
const hasLiveView = state.status !== "provisioning" && state.status !== "closed";
|
|
779
|
-
const
|
|
780
|
-
const canUseLiveView = hasLiveView && hasUserControl && Boolean(liveViewUrl);
|
|
781
|
-
const canOpenPreview = hasLiveView && (hasPreviewAdapter || canUseLiveView);
|
|
782
|
-
const canOpenFullscreen = canOpenPreview;
|
|
783
|
-
const refreshPreview = react.useCallback(
|
|
784
|
-
async (signal) => {
|
|
785
|
-
if (!hasPreviewAdapter || canUseLiveView) return;
|
|
786
|
-
setPreviewLoading(true);
|
|
787
|
-
setPreviewError(null);
|
|
788
|
-
try {
|
|
789
|
-
const snapshot = await adapter.getBrowserPreviewSnapshot?.(sessionId);
|
|
790
|
-
if (signal?.aborted) return;
|
|
791
|
-
setPreviewSnapshot(snapshot ?? null);
|
|
792
|
-
if (!snapshot) {
|
|
793
|
-
setPreviewError("Browser preview is unavailable.");
|
|
794
|
-
}
|
|
795
|
-
} catch (error) {
|
|
796
|
-
if (signal?.aborted) return;
|
|
797
|
-
setPreviewSnapshot(null);
|
|
798
|
-
setPreviewError(
|
|
799
|
-
error instanceof Error ? error.message : "Browser preview failed."
|
|
800
|
-
);
|
|
801
|
-
} finally {
|
|
802
|
-
if (!signal?.aborted) setPreviewLoading(false);
|
|
803
|
-
}
|
|
804
|
-
},
|
|
805
|
-
[adapter, canUseLiveView, hasPreviewAdapter, sessionId]
|
|
806
|
-
);
|
|
943
|
+
const canUseShell = hasLiveView && Boolean(shellUrl);
|
|
807
944
|
react.useEffect(() => {
|
|
808
945
|
setFullscreenOpen(false);
|
|
809
946
|
setOpenFullscreenOnControl(false);
|
|
810
947
|
setLocalControlActive(false);
|
|
811
|
-
setPreviewSnapshot(null);
|
|
812
|
-
setPreviewLoading(false);
|
|
813
|
-
setPreviewError(null);
|
|
814
948
|
}, [sessionId]);
|
|
815
949
|
react.useEffect(() => {
|
|
816
950
|
if (!openFullscreenOnControl) return;
|
|
817
|
-
if (!
|
|
951
|
+
if (!canUseShell) return;
|
|
818
952
|
setFullscreenOpen(true);
|
|
819
953
|
setOpenFullscreenOnControl(false);
|
|
820
|
-
}, [
|
|
954
|
+
}, [canUseShell, openFullscreenOnControl]);
|
|
821
955
|
const handleFullscreenOpenChange = react.useCallback(
|
|
822
956
|
(open) => {
|
|
823
957
|
setFullscreenOpen(open);
|
|
@@ -825,7 +959,7 @@ function BrowserPane({
|
|
|
825
959
|
[]
|
|
826
960
|
);
|
|
827
961
|
react.useEffect(() => {
|
|
828
|
-
if (!
|
|
962
|
+
if (!localControlActive || !hasControlAdapter) return;
|
|
829
963
|
const handle = window.setInterval(() => {
|
|
830
964
|
void adapter.acquireBrowserControl(sessionId).catch((error) => {
|
|
831
965
|
console.error("Failed to refresh browser control", error);
|
|
@@ -833,30 +967,31 @@ function BrowserPane({
|
|
|
833
967
|
});
|
|
834
968
|
}, 25e3);
|
|
835
969
|
return () => window.clearInterval(handle);
|
|
836
|
-
}, [adapter,
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
970
|
+
}, [adapter, localControlActive, hasControlAdapter, sessionId]);
|
|
971
|
+
const control = useBrowserControl({
|
|
972
|
+
sessionId,
|
|
973
|
+
hasControl: localControlActive,
|
|
974
|
+
adapter,
|
|
975
|
+
onControlAcquired: () => {
|
|
976
|
+
setLocalControlActive(true);
|
|
977
|
+
setOpenFullscreenOnControl(true);
|
|
978
|
+
},
|
|
979
|
+
onControlReleased: () => setLocalControlActive(false),
|
|
980
|
+
onClose
|
|
981
|
+
});
|
|
982
|
+
const shell = (testId) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
983
|
+
BrowserShell,
|
|
984
|
+
{
|
|
985
|
+
src: shellUrl,
|
|
986
|
+
testId,
|
|
987
|
+
hasControl: localControlActive,
|
|
988
|
+
onToggleControl: hasControlAdapter ? () => void control.toggleControl() : void 0,
|
|
989
|
+
onClose: onClose ? () => control.setConfirmOpen(true) : void 0,
|
|
990
|
+
onMaximize: fullscreenOpen ? void 0 : () => setFullscreenOpen(true),
|
|
991
|
+
onDisconnect: () => setLocalControlActive(false)
|
|
842
992
|
}
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
void refreshPreview(abort.signal);
|
|
846
|
-
const timer = window.setInterval(() => {
|
|
847
|
-
void refreshPreview(abort.signal);
|
|
848
|
-
}, 5e3);
|
|
849
|
-
return () => {
|
|
850
|
-
abort.abort();
|
|
851
|
-
window.clearInterval(timer);
|
|
852
|
-
};
|
|
853
|
-
}, [
|
|
854
|
-
canUseLiveView,
|
|
855
|
-
fullscreenOpen,
|
|
856
|
-
hasLiveView,
|
|
857
|
-
hasPreviewAdapter,
|
|
858
|
-
refreshPreview
|
|
859
|
-
]);
|
|
993
|
+
);
|
|
994
|
+
const placeholder = (message) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: message });
|
|
860
995
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
861
996
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
862
997
|
"div",
|
|
@@ -864,56 +999,8 @@ function BrowserPane({
|
|
|
864
999
|
"data-testid": "browser-pane",
|
|
865
1000
|
className: "flex h-full min-h-0 flex-col bg-background",
|
|
866
1001
|
children: [
|
|
867
|
-
/* @__PURE__ */ jsxRuntime.
|
|
868
|
-
|
|
869
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: "Browser" }),
|
|
870
|
-
/* @__PURE__ */ jsxRuntime.jsx(BrowserStatusDot, { status: state.status }),
|
|
871
|
-
state.controlOwner && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0 truncate text-amber-500", children: [
|
|
872
|
-
state.controlOwner,
|
|
873
|
-
" has control"
|
|
874
|
-
] }),
|
|
875
|
-
canOpenFullscreen && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ml-auto flex items-center", children: /* @__PURE__ */ jsxRuntime.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
|
|
876
|
-
/* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
877
|
-
"button",
|
|
878
|
-
{
|
|
879
|
-
type: "button",
|
|
880
|
-
"aria-label": "Maximize browser",
|
|
881
|
-
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",
|
|
882
|
-
onClick: () => setFullscreenOpen(true),
|
|
883
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Maximize2Icon, { className: "size-3.5", "aria-hidden": "true" })
|
|
884
|
-
}
|
|
885
|
-
) }),
|
|
886
|
-
/* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side: "bottom", children: "Full screen" })
|
|
887
|
-
] }) }) })
|
|
888
|
-
] }),
|
|
889
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-0 flex-1 bg-black", children: state.status === "provisioning" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: "Starting browser..." }) : state.status === "closed" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: "Browser closed." }) : canUseLiveView ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
890
|
-
BrowserLiveView,
|
|
891
|
-
{
|
|
892
|
-
src: liveViewUrl,
|
|
893
|
-
onDisconnect: () => setLocalControlActive(false)
|
|
894
|
-
}
|
|
895
|
-
) : previewSnapshot ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
896
|
-
BrowserPreviewImage,
|
|
897
|
-
{
|
|
898
|
-
src: previewSnapshot.src,
|
|
899
|
-
fit: "contain",
|
|
900
|
-
testId: "browser-preview-image"
|
|
901
|
-
}
|
|
902
|
-
) : previewLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: "Loading browser preview..." }) : previewError ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: previewError }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: "Browser preview is unavailable." }) }),
|
|
903
|
-
hasLiveView && hasControlAdapter && /* @__PURE__ */ jsxRuntime.jsx(
|
|
904
|
-
BrowserControlBar,
|
|
905
|
-
{
|
|
906
|
-
sessionId,
|
|
907
|
-
hasControl: localControlActive,
|
|
908
|
-
adapter,
|
|
909
|
-
onControlAcquired: () => {
|
|
910
|
-
setLocalControlActive(true);
|
|
911
|
-
setOpenFullscreenOnControl(true);
|
|
912
|
-
},
|
|
913
|
-
onControlReleased: () => setLocalControlActive(false),
|
|
914
|
-
onClose
|
|
915
|
-
}
|
|
916
|
-
)
|
|
1002
|
+
/* @__PURE__ */ jsxRuntime.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.") }),
|
|
1003
|
+
control.error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-line bg-card px-3 py-1.5 text-[11px] text-destructive", children: control.error })
|
|
917
1004
|
]
|
|
918
1005
|
}
|
|
919
1006
|
),
|
|
@@ -923,50 +1010,72 @@ function BrowserPane({
|
|
|
923
1010
|
"aria-describedby": void 0,
|
|
924
1011
|
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",
|
|
925
1012
|
children: [
|
|
926
|
-
/* @__PURE__ */ jsxRuntime.
|
|
927
|
-
|
|
928
|
-
/* @__PURE__ */ jsxRuntime.jsx(DialogTitle, { className: "text-xs normal-case tracking-normal", children: "Browser" }),
|
|
929
|
-
/* @__PURE__ */ jsxRuntime.jsx(BrowserStatusDot, { status: state.status }),
|
|
930
|
-
state.controlOwner && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0 truncate text-xs text-amber-500", children: [
|
|
931
|
-
state.controlOwner,
|
|
932
|
-
" has control"
|
|
933
|
-
] })
|
|
934
|
-
] }),
|
|
935
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-0 flex-1 bg-black", children: canUseLiveView ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
936
|
-
BrowserLiveView,
|
|
937
|
-
{
|
|
938
|
-
src: liveViewUrl,
|
|
939
|
-
testId: "browser-fullscreen-rfb",
|
|
940
|
-
onDisconnect: () => setLocalControlActive(false)
|
|
941
|
-
}
|
|
942
|
-
) : previewSnapshot ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
943
|
-
BrowserPreviewImage,
|
|
944
|
-
{
|
|
945
|
-
src: previewSnapshot.src,
|
|
946
|
-
fit: "contain",
|
|
947
|
-
testId: "browser-fullscreen-preview-image"
|
|
948
|
-
}
|
|
949
|
-
) : previewLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: "Loading browser preview..." }) : previewError ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: previewError }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: "Browser preview is unavailable." }) })
|
|
1013
|
+
/* @__PURE__ */ jsxRuntime.jsx(DialogHeader, { className: "sr-only", children: /* @__PURE__ */ jsxRuntime.jsx(DialogTitle, { children: "Browser" }) }),
|
|
1014
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-0 flex-1", children: canUseShell ? shell("browser-fullscreen-shell") : placeholder("Browser view is unavailable.") })
|
|
950
1015
|
]
|
|
951
1016
|
}
|
|
952
|
-
) })
|
|
1017
|
+
) }),
|
|
1018
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1019
|
+
ConfirmDialog,
|
|
1020
|
+
{
|
|
1021
|
+
open: control.confirmOpen,
|
|
1022
|
+
title: "Close browser session?",
|
|
1023
|
+
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.",
|
|
1024
|
+
confirmLabel: "Close browser",
|
|
1025
|
+
variant: "destructive",
|
|
1026
|
+
onConfirm: control.handleConfirmClose,
|
|
1027
|
+
onCancel: () => control.setConfirmOpen(false)
|
|
1028
|
+
}
|
|
1029
|
+
)
|
|
953
1030
|
] });
|
|
954
1031
|
}
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
1032
|
+
var POLL_MS = 15e3;
|
|
1033
|
+
var MAX_FAILURES = 3;
|
|
1034
|
+
function useBrowserPreview({
|
|
1035
|
+
adapter,
|
|
1036
|
+
sessionId,
|
|
1037
|
+
enabled
|
|
959
1038
|
}) {
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
draggable: false
|
|
1039
|
+
const [src, setSrc] = react.useState(null);
|
|
1040
|
+
const adapterRef = react.useRef(adapter);
|
|
1041
|
+
adapterRef.current = adapter;
|
|
1042
|
+
react.useEffect(() => {
|
|
1043
|
+
if (!enabled || !sessionId) {
|
|
1044
|
+
setSrc(null);
|
|
1045
|
+
return;
|
|
968
1046
|
}
|
|
969
|
-
|
|
1047
|
+
let cancelled = false;
|
|
1048
|
+
let failures = 0;
|
|
1049
|
+
let timer = 0;
|
|
1050
|
+
const refresh = async () => {
|
|
1051
|
+
if (document.hidden) return;
|
|
1052
|
+
const fetchSnapshot = adapterRef.current.getBrowserPreviewSnapshot;
|
|
1053
|
+
if (typeof fetchSnapshot !== "function") return;
|
|
1054
|
+
try {
|
|
1055
|
+
const snapshot = await fetchSnapshot(sessionId);
|
|
1056
|
+
if (cancelled) return;
|
|
1057
|
+
if (!snapshot) {
|
|
1058
|
+
window.clearInterval(timer);
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1061
|
+
failures = 0;
|
|
1062
|
+
if (snapshot.src) setSrc(snapshot.src);
|
|
1063
|
+
} catch {
|
|
1064
|
+
if (cancelled) return;
|
|
1065
|
+
failures += 1;
|
|
1066
|
+
if (failures >= MAX_FAILURES) {
|
|
1067
|
+
window.clearInterval(timer);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
};
|
|
1071
|
+
void refresh();
|
|
1072
|
+
timer = window.setInterval(() => void refresh(), POLL_MS);
|
|
1073
|
+
return () => {
|
|
1074
|
+
cancelled = true;
|
|
1075
|
+
window.clearInterval(timer);
|
|
1076
|
+
};
|
|
1077
|
+
}, [enabled, sessionId]);
|
|
1078
|
+
return src;
|
|
970
1079
|
}
|
|
971
1080
|
|
|
972
1081
|
// src/components/ai-elements/conversation.tsx
|
|
@@ -1073,14 +1182,63 @@ function ButtonGroup({
|
|
|
1073
1182
|
);
|
|
1074
1183
|
}
|
|
1075
1184
|
|
|
1076
|
-
// src/components/
|
|
1185
|
+
// src/components/ui/tooltip.tsx
|
|
1077
1186
|
init_utils();
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1187
|
+
function TooltipProvider({
|
|
1188
|
+
delayDuration = 0,
|
|
1189
|
+
...props
|
|
1190
|
+
}) {
|
|
1191
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1192
|
+
radixUi.Tooltip.Provider,
|
|
1193
|
+
{
|
|
1194
|
+
"data-slot": "tooltip-provider",
|
|
1195
|
+
delayDuration,
|
|
1196
|
+
...props
|
|
1197
|
+
}
|
|
1198
|
+
);
|
|
1199
|
+
}
|
|
1200
|
+
function Tooltip({
|
|
1201
|
+
...props
|
|
1202
|
+
}) {
|
|
1203
|
+
return /* @__PURE__ */ jsxRuntime.jsx(radixUi.Tooltip.Root, { "data-slot": "tooltip", ...props });
|
|
1204
|
+
}
|
|
1205
|
+
function TooltipTrigger({
|
|
1206
|
+
...props
|
|
1207
|
+
}) {
|
|
1208
|
+
return /* @__PURE__ */ jsxRuntime.jsx(radixUi.Tooltip.Trigger, { "data-slot": "tooltip-trigger", ...props });
|
|
1209
|
+
}
|
|
1210
|
+
function TooltipContent({
|
|
1211
|
+
className,
|
|
1212
|
+
sideOffset = 0,
|
|
1213
|
+
children,
|
|
1214
|
+
...props
|
|
1215
|
+
}) {
|
|
1216
|
+
return /* @__PURE__ */ jsxRuntime.jsx(radixUi.Tooltip.Portal, { children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1217
|
+
radixUi.Tooltip.Content,
|
|
1218
|
+
{
|
|
1219
|
+
"data-slot": "tooltip-content",
|
|
1220
|
+
sideOffset,
|
|
1221
|
+
className: cn(
|
|
1222
|
+
"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",
|
|
1223
|
+
className
|
|
1224
|
+
),
|
|
1225
|
+
...props,
|
|
1226
|
+
children: [
|
|
1227
|
+
children,
|
|
1228
|
+
/* @__PURE__ */ jsxRuntime.jsx(radixUi.Tooltip.Arrow, { className: "z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-none bg-foreground fill-foreground" })
|
|
1229
|
+
]
|
|
1230
|
+
}
|
|
1231
|
+
) });
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
// src/components/ai-elements/message.tsx
|
|
1235
|
+
init_utils();
|
|
1236
|
+
|
|
1237
|
+
// src/lib/oauth-popup.ts
|
|
1238
|
+
function openOAuthPopup(url) {
|
|
1239
|
+
const w = 600;
|
|
1240
|
+
const h = 720;
|
|
1241
|
+
const dualLeft = window.screenLeft ?? window.screenX ?? 0;
|
|
1084
1242
|
const dualTop = window.screenTop ?? window.screenY ?? 0;
|
|
1085
1243
|
const width = window.innerWidth || document.documentElement.clientWidth || w;
|
|
1086
1244
|
const height = window.innerHeight || document.documentElement.clientHeight || h;
|
|
@@ -2883,6 +3041,25 @@ function hostname(url) {
|
|
|
2883
3041
|
function truncate2(s, n) {
|
|
2884
3042
|
return s.length > n ? `${s.slice(0, n)}\u2026` : s;
|
|
2885
3043
|
}
|
|
3044
|
+
function KnowledgeBaseToolBlock({ tc }) {
|
|
3045
|
+
const args = parseArgs(tc.args) ?? {};
|
|
3046
|
+
const label = {
|
|
3047
|
+
kb_search_pages: "KB Search",
|
|
3048
|
+
kb_list_pages: "KB Browse",
|
|
3049
|
+
kb_read_page: "KB Read"
|
|
3050
|
+
}[tc.toolName] ?? tc.toolName;
|
|
3051
|
+
let detail = "";
|
|
3052
|
+
if (tc.toolName === "kb_search_pages" && args.query) {
|
|
3053
|
+
detail = `\u201C${truncate2(args.query, 60)}\u201D`;
|
|
3054
|
+
} else if (tc.toolName === "kb_read_page" && args.path) {
|
|
3055
|
+
detail = args.pages ? `${args.path} \xB7 pages ${args.pages}` : args.path;
|
|
3056
|
+
}
|
|
3057
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-sm py-0.5", children: [
|
|
3058
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.LibraryIcon, { className: "size-3.5 text-muted-foreground/60 shrink-0" }),
|
|
3059
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-foreground", children: label }),
|
|
3060
|
+
detail && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground/70 truncate text-xs", children: detail })
|
|
3061
|
+
] });
|
|
3062
|
+
}
|
|
2886
3063
|
function VisionAnalyzeBlock({ tc }) {
|
|
2887
3064
|
let image = "";
|
|
2888
3065
|
try {
|
|
@@ -5197,6 +5374,10 @@ function ToolCallBlock({
|
|
|
5197
5374
|
case "web_search":
|
|
5198
5375
|
case "web_crawl":
|
|
5199
5376
|
return /* @__PURE__ */ jsxRuntime.jsx(WebToolBlock, { tc });
|
|
5377
|
+
case "kb_search_pages":
|
|
5378
|
+
case "kb_list_pages":
|
|
5379
|
+
case "kb_read_page":
|
|
5380
|
+
return /* @__PURE__ */ jsxRuntime.jsx(KnowledgeBaseToolBlock, { tc });
|
|
5200
5381
|
case "vision_analyze":
|
|
5201
5382
|
return /* @__PURE__ */ jsxRuntime.jsx(VisionAnalyzeBlock, { tc });
|
|
5202
5383
|
case "read_file":
|
|
@@ -10250,6 +10431,75 @@ function ChatComposerInner({
|
|
|
10250
10431
|
}
|
|
10251
10432
|
);
|
|
10252
10433
|
}
|
|
10434
|
+
|
|
10435
|
+
// src/components/chat/session-pane-card.tsx
|
|
10436
|
+
init_utils();
|
|
10437
|
+
function SessionPaneCard({
|
|
10438
|
+
title,
|
|
10439
|
+
subtitle,
|
|
10440
|
+
thumbnail,
|
|
10441
|
+
count,
|
|
10442
|
+
icon: Icon = lucideReact.GlobeIcon,
|
|
10443
|
+
onOpen,
|
|
10444
|
+
testId = "session-pane-card",
|
|
10445
|
+
className,
|
|
10446
|
+
expanded
|
|
10447
|
+
}) {
|
|
10448
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
10449
|
+
"button",
|
|
10450
|
+
{
|
|
10451
|
+
type: "button",
|
|
10452
|
+
"data-testid": testId,
|
|
10453
|
+
"aria-label": `Open ${title}`,
|
|
10454
|
+
"aria-expanded": expanded === void 0 ? void 0 : expanded,
|
|
10455
|
+
onClick: onOpen,
|
|
10456
|
+
className: cn(
|
|
10457
|
+
// No radius here: the caller sets it. Declaring rounded-xl in the base
|
|
10458
|
+
// and cancelling the bottom from a className leaves both rules live,
|
|
10459
|
+
// and which corner wins comes down to stylesheet order rather than
|
|
10460
|
+
// intent -- which is how the stacked cards kept round bottoms.
|
|
10461
|
+
"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",
|
|
10462
|
+
className
|
|
10463
|
+
),
|
|
10464
|
+
children: [
|
|
10465
|
+
thumbnail ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
10466
|
+
"img",
|
|
10467
|
+
{
|
|
10468
|
+
"data-testid": "session-pane-card-thumb",
|
|
10469
|
+
src: thumbnail,
|
|
10470
|
+
alt: "",
|
|
10471
|
+
className: "h-11 w-16 shrink-0 rounded-md border border-line object-cover object-top"
|
|
10472
|
+
}
|
|
10473
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
10474
|
+
Icon,
|
|
10475
|
+
{
|
|
10476
|
+
"data-testid": "session-pane-card-icon",
|
|
10477
|
+
className: "size-5 shrink-0 text-muted-foreground",
|
|
10478
|
+
"aria-hidden": "true"
|
|
10479
|
+
}
|
|
10480
|
+
),
|
|
10481
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 flex-col", children: [
|
|
10482
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-1.5 text-sm font-medium text-foreground", children: [
|
|
10483
|
+
title,
|
|
10484
|
+
count !== void 0 && count > 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rounded-full bg-secondary px-1.5 text-[11px] font-normal text-muted-foreground", children: count })
|
|
10485
|
+
] }),
|
|
10486
|
+
subtitle && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-xs text-muted-foreground", children: subtitle })
|
|
10487
|
+
] }),
|
|
10488
|
+
expanded !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
10489
|
+
lucideReact.ChevronDownIcon,
|
|
10490
|
+
{
|
|
10491
|
+
"data-testid": "session-pane-card-chevron",
|
|
10492
|
+
className: cn(
|
|
10493
|
+
"ml-auto size-4 shrink-0 text-muted-foreground transition-transform",
|
|
10494
|
+
expanded && "rotate-180"
|
|
10495
|
+
),
|
|
10496
|
+
"aria-hidden": "true"
|
|
10497
|
+
}
|
|
10498
|
+
)
|
|
10499
|
+
]
|
|
10500
|
+
}
|
|
10501
|
+
);
|
|
10502
|
+
}
|
|
10253
10503
|
var MAX_LOGOS = 10;
|
|
10254
10504
|
function IntegrationsBand({
|
|
10255
10505
|
agentId,
|
|
@@ -10836,9 +11086,9 @@ function ArtifactTable({ spec }) {
|
|
|
10836
11086
|
init_utils();
|
|
10837
11087
|
var DEFAULT_HEIGHT_PX = 480;
|
|
10838
11088
|
var EXPANDED_HEIGHT_PX = 800;
|
|
10839
|
-
var
|
|
10840
|
-
var
|
|
10841
|
-
var
|
|
11089
|
+
var MIN_ZOOM = 0.5;
|
|
11090
|
+
var MAX_ZOOM = 2;
|
|
11091
|
+
var ZOOM_STEP = 0.1;
|
|
10842
11092
|
var SAFETY_STYLE_PREAMBLE = "<style>html,body{max-height:100vh}canvas{max-height:80vh!important}</style>";
|
|
10843
11093
|
function injectSafetyStyle(html) {
|
|
10844
11094
|
const headCloseIdx = html.search(/<\/head\s*>/i);
|
|
@@ -10855,7 +11105,7 @@ function ArtifactHtml({
|
|
|
10855
11105
|
const [zoom, setZoom] = react.useState(1);
|
|
10856
11106
|
const safeSrcdoc = react.useMemo(() => injectSafetyStyle(spec.html), [spec.html]);
|
|
10857
11107
|
const viewportHeight = expanded ? EXPANDED_HEIGHT_PX : DEFAULT_HEIGHT_PX;
|
|
10858
|
-
const clamp4 = (v) => Math.round(Math.min(
|
|
11108
|
+
const clamp4 = (v) => Math.round(Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, v)) * 100) / 100;
|
|
10859
11109
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
10860
11110
|
"div",
|
|
10861
11111
|
{
|
|
@@ -10901,8 +11151,8 @@ function ArtifactHtml({
|
|
|
10901
11151
|
"button",
|
|
10902
11152
|
{
|
|
10903
11153
|
type: "button",
|
|
10904
|
-
onClick: () => setZoom((z) => clamp4(z -
|
|
10905
|
-
disabled: zoom <=
|
|
11154
|
+
onClick: () => setZoom((z) => clamp4(z - ZOOM_STEP)),
|
|
11155
|
+
disabled: zoom <= MIN_ZOOM,
|
|
10906
11156
|
className: "hover:text-foreground disabled:opacity-40 cursor-pointer",
|
|
10907
11157
|
"aria-label": "zoom out",
|
|
10908
11158
|
children: "\u2212"
|
|
@@ -10925,8 +11175,8 @@ function ArtifactHtml({
|
|
|
10925
11175
|
"button",
|
|
10926
11176
|
{
|
|
10927
11177
|
type: "button",
|
|
10928
|
-
onClick: () => setZoom((z) => clamp4(z +
|
|
10929
|
-
disabled: zoom >=
|
|
11178
|
+
onClick: () => setZoom((z) => clamp4(z + ZOOM_STEP)),
|
|
11179
|
+
disabled: zoom >= MAX_ZOOM,
|
|
10930
11180
|
className: "hover:text-foreground disabled:opacity-40",
|
|
10931
11181
|
"aria-label": "zoom in",
|
|
10932
11182
|
children: "+"
|
|
@@ -11427,748 +11677,297 @@ var InsufficientCreditsCard = react.memo(function InsufficientCreditsCard2({
|
|
|
11427
11677
|
);
|
|
11428
11678
|
});
|
|
11429
11679
|
init_utils();
|
|
11430
|
-
var
|
|
11431
|
-
|
|
11432
|
-
|
|
11433
|
-
|
|
11434
|
-
|
|
11435
|
-
|
|
11436
|
-
|
|
11437
|
-
|
|
11438
|
-
|
|
11439
|
-
|
|
11440
|
-
|
|
11441
|
-
|
|
11442
|
-
|
|
11680
|
+
var _EXTENSION_KIND = {
|
|
11681
|
+
// Documents
|
|
11682
|
+
pdf: { icon: lucideReact.FileTextIcon, typeLabel: "PDF" },
|
|
11683
|
+
doc: { icon: lucideReact.FileTextIcon, typeLabel: "Word document" },
|
|
11684
|
+
docx: { icon: lucideReact.FileTextIcon, typeLabel: "Word document" },
|
|
11685
|
+
odt: { icon: lucideReact.FileTextIcon, typeLabel: "OpenDocument text" },
|
|
11686
|
+
rtf: { icon: lucideReact.FileTextIcon, typeLabel: "Rich text" },
|
|
11687
|
+
txt: { icon: lucideReact.FileTextIcon, typeLabel: "Text" },
|
|
11688
|
+
md: { icon: lucideReact.FileTextIcon, typeLabel: "Markdown" },
|
|
11689
|
+
// Spreadsheets
|
|
11690
|
+
xls: { icon: lucideReact.FileSpreadsheetIcon, typeLabel: "Spreadsheet" },
|
|
11691
|
+
xlsx: { icon: lucideReact.FileSpreadsheetIcon, typeLabel: "Spreadsheet" },
|
|
11692
|
+
ods: { icon: lucideReact.FileSpreadsheetIcon, typeLabel: "OpenDocument spreadsheet" },
|
|
11693
|
+
csv: { icon: lucideReact.FileSpreadsheetIcon, typeLabel: "CSV" },
|
|
11694
|
+
tsv: { icon: lucideReact.FileSpreadsheetIcon, typeLabel: "TSV" },
|
|
11695
|
+
// Presentations / charts
|
|
11696
|
+
ppt: { icon: lucideReact.FileChartLineIcon, typeLabel: "Presentation" },
|
|
11697
|
+
pptx: { icon: lucideReact.FileChartLineIcon, typeLabel: "Presentation" },
|
|
11698
|
+
// Images
|
|
11699
|
+
png: { icon: lucideReact.FileImageIcon, typeLabel: "Image" },
|
|
11700
|
+
jpg: { icon: lucideReact.FileImageIcon, typeLabel: "Image" },
|
|
11701
|
+
jpeg: { icon: lucideReact.FileImageIcon, typeLabel: "Image" },
|
|
11702
|
+
gif: { icon: lucideReact.FileImageIcon, typeLabel: "Image" },
|
|
11703
|
+
svg: { icon: lucideReact.FileImageIcon, typeLabel: "SVG image" },
|
|
11704
|
+
webp: { icon: lucideReact.FileImageIcon, typeLabel: "Image" },
|
|
11705
|
+
bmp: { icon: lucideReact.FileImageIcon, typeLabel: "Image" },
|
|
11706
|
+
// Audio / video
|
|
11707
|
+
mp3: { icon: lucideReact.FileAudioIcon, typeLabel: "Audio" },
|
|
11708
|
+
wav: { icon: lucideReact.FileAudioIcon, typeLabel: "Audio" },
|
|
11709
|
+
ogg: { icon: lucideReact.FileAudioIcon, typeLabel: "Audio" },
|
|
11710
|
+
mp4: { icon: lucideReact.FileVideoIcon, typeLabel: "Video" },
|
|
11711
|
+
mov: { icon: lucideReact.FileVideoIcon, typeLabel: "Video" },
|
|
11712
|
+
webm: { icon: lucideReact.FileVideoIcon, typeLabel: "Video" },
|
|
11713
|
+
// Archives
|
|
11714
|
+
zip: { icon: lucideReact.FileArchiveIcon, typeLabel: "Archive" },
|
|
11715
|
+
tar: { icon: lucideReact.FileArchiveIcon, typeLabel: "Archive" },
|
|
11716
|
+
gz: { icon: lucideReact.FileArchiveIcon, typeLabel: "Archive" },
|
|
11717
|
+
bz2: { icon: lucideReact.FileArchiveIcon, typeLabel: "Archive" },
|
|
11718
|
+
"7z": { icon: lucideReact.FileArchiveIcon, typeLabel: "Archive" },
|
|
11719
|
+
// Code
|
|
11720
|
+
py: { icon: lucideReact.FileCodeIcon, typeLabel: "Python" },
|
|
11721
|
+
js: { icon: lucideReact.FileCodeIcon, typeLabel: "JavaScript" },
|
|
11722
|
+
ts: { icon: lucideReact.FileCodeIcon, typeLabel: "TypeScript" },
|
|
11723
|
+
tsx: { icon: lucideReact.FileCodeIcon, typeLabel: "TypeScript" },
|
|
11724
|
+
jsx: { icon: lucideReact.FileCodeIcon, typeLabel: "JavaScript" },
|
|
11725
|
+
json: { icon: lucideReact.FileCodeIcon, typeLabel: "JSON" },
|
|
11726
|
+
yaml: { icon: lucideReact.FileCodeIcon, typeLabel: "YAML" },
|
|
11727
|
+
yml: { icon: lucideReact.FileCodeIcon, typeLabel: "YAML" },
|
|
11728
|
+
toml: { icon: lucideReact.FileCodeIcon, typeLabel: "TOML" },
|
|
11729
|
+
sh: { icon: lucideReact.CodeIcon, typeLabel: "Shell" },
|
|
11730
|
+
rs: { icon: lucideReact.FileCodeIcon, typeLabel: "Rust" },
|
|
11731
|
+
go: { icon: lucideReact.FileCodeIcon, typeLabel: "Go" },
|
|
11732
|
+
rb: { icon: lucideReact.FileCodeIcon, typeLabel: "Ruby" },
|
|
11733
|
+
java: { icon: lucideReact.FileCodeIcon, typeLabel: "Java" },
|
|
11734
|
+
cpp: { icon: lucideReact.FileCodeIcon, typeLabel: "C++" },
|
|
11735
|
+
c: { icon: lucideReact.FileCodeIcon, typeLabel: "C" },
|
|
11736
|
+
html: { icon: lucideReact.FileCodeIcon, typeLabel: "HTML" },
|
|
11737
|
+
css: { icon: lucideReact.FileCodeIcon, typeLabel: "CSS" },
|
|
11738
|
+
scss: { icon: lucideReact.FileCodeIcon, typeLabel: "Sass" },
|
|
11739
|
+
sql: { icon: lucideReact.FileCodeIcon, typeLabel: "SQL" }
|
|
11740
|
+
};
|
|
11741
|
+
function _extension(path) {
|
|
11742
|
+
const idx = path.lastIndexOf(".");
|
|
11743
|
+
if (idx < 0 || idx === path.length - 1) return "";
|
|
11744
|
+
return path.slice(idx + 1).toLowerCase();
|
|
11745
|
+
}
|
|
11746
|
+
function _fileKindFor(path) {
|
|
11747
|
+
const ext = _extension(path);
|
|
11748
|
+
return _EXTENSION_KIND[ext] ?? { icon: lucideReact.FileIcon, typeLabel: "File" };
|
|
11749
|
+
}
|
|
11750
|
+
function _basename(path) {
|
|
11751
|
+
const idx = path.lastIndexOf("/");
|
|
11752
|
+
return idx >= 0 ? path.slice(idx + 1) : path;
|
|
11753
|
+
}
|
|
11754
|
+
function WorkspaceFileCard({
|
|
11755
|
+
sessionId,
|
|
11756
|
+
path,
|
|
11757
|
+
label
|
|
11443
11758
|
}) {
|
|
11444
|
-
const
|
|
11445
|
-
|
|
11446
|
-
const
|
|
11447
|
-
const
|
|
11448
|
-
const
|
|
11449
|
-
const
|
|
11450
|
-
|
|
11451
|
-
|
|
11452
|
-
|
|
11453
|
-
|
|
11454
|
-
|
|
11455
|
-
|
|
11456
|
-
|
|
11457
|
-
|
|
11458
|
-
|
|
11459
|
-
|
|
11460
|
-
|
|
11461
|
-
|
|
11462
|
-
] })
|
|
11463
|
-
] }),
|
|
11464
|
-
downloadUrl && /* @__PURE__ */ jsxRuntime.jsx(
|
|
11465
|
-
"a",
|
|
11466
|
-
{
|
|
11467
|
-
href: downloadUrl,
|
|
11468
|
-
download: fileName2,
|
|
11469
|
-
className: "shrink-0 rounded p-0.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
|
11470
|
-
"aria-label": `Download ${fileName2}`,
|
|
11471
|
-
title: "Download",
|
|
11472
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.DownloadIcon, { className: "size-3.5" })
|
|
11473
|
-
}
|
|
11474
|
-
),
|
|
11475
|
-
onDelete && /* @__PURE__ */ jsxRuntime.jsx(
|
|
11476
|
-
"button",
|
|
11477
|
-
{
|
|
11478
|
-
type: "button",
|
|
11479
|
-
onClick: onDelete,
|
|
11480
|
-
className: "shrink-0 rounded p-0.5 text-muted-foreground transition-colors hover:bg-destructive/10 hover:text-destructive",
|
|
11481
|
-
"aria-label": `Delete ${fileName2}`,
|
|
11482
|
-
title: "Delete",
|
|
11483
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.TrashIcon, { className: "size-3.5" })
|
|
11759
|
+
const { adapter, onFileSelect } = useAgentChatAdapterContext();
|
|
11760
|
+
const kind = _fileKindFor(path);
|
|
11761
|
+
const Icon = kind.icon;
|
|
11762
|
+
const downloadUrl = adapter.getWorkspaceDownloadUrl({ sessionId, path });
|
|
11763
|
+
const downloadName = _basename(path) || label;
|
|
11764
|
+
const handleCardClick = react.useCallback(() => {
|
|
11765
|
+
onFileSelect?.(path);
|
|
11766
|
+
}, [onFileSelect, path]);
|
|
11767
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
11768
|
+
"div",
|
|
11769
|
+
{
|
|
11770
|
+
role: "button",
|
|
11771
|
+
tabIndex: 0,
|
|
11772
|
+
onClick: handleCardClick,
|
|
11773
|
+
onKeyDown: (e) => {
|
|
11774
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
11775
|
+
e.preventDefault();
|
|
11776
|
+
handleCardClick();
|
|
11484
11777
|
}
|
|
11778
|
+
},
|
|
11779
|
+
className: cn(
|
|
11780
|
+
"group/file flex items-center gap-3 rounded-lg border border-border",
|
|
11781
|
+
"bg-muted/20 px-3 py-2 text-left transition-colors",
|
|
11782
|
+
"cursor-pointer hover:bg-muted/40"
|
|
11485
11783
|
),
|
|
11486
|
-
|
|
11487
|
-
"
|
|
11488
|
-
{
|
|
11489
|
-
|
|
11490
|
-
|
|
11491
|
-
|
|
11492
|
-
|
|
11493
|
-
|
|
11494
|
-
|
|
11495
|
-
|
|
11496
|
-
|
|
11497
|
-
|
|
11498
|
-
|
|
11499
|
-
|
|
11500
|
-
|
|
11501
|
-
|
|
11502
|
-
|
|
11503
|
-
|
|
11504
|
-
|
|
11505
|
-
|
|
11506
|
-
|
|
11507
|
-
|
|
11508
|
-
|
|
11509
|
-
|
|
11510
|
-
|
|
11511
|
-
|
|
11512
|
-
|
|
11513
|
-
error && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-2 p-3 text-xs text-destructive", children: [
|
|
11514
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0" }),
|
|
11515
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children: error })
|
|
11516
|
-
] }),
|
|
11517
|
-
file && isPdf && file.encoding !== "base64" && /* @__PURE__ */ jsxRuntime.jsx(FileViewerError, { message: "PDF preview requires base64 file content." }),
|
|
11518
|
-
file && isImage && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center p-4", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
11519
|
-
"img",
|
|
11520
|
-
{
|
|
11521
|
-
src: `data:${file.mime_type ?? "image/png"};base64,${file.content}`,
|
|
11522
|
-
alt: fileName2,
|
|
11523
|
-
className: "max-h-[60vh] max-w-full rounded object-contain"
|
|
11524
|
-
}
|
|
11525
|
-
) }),
|
|
11526
|
-
file && !isImage && !isPdf && /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "wrap-break-word whitespace-pre-wrap p-3 text-[11px] leading-relaxed text-foreground", children: /* @__PURE__ */ jsxRuntime.jsx("code", { "data-language": lang, children: file.content }) })
|
|
11527
|
-
] })
|
|
11528
|
-
] });
|
|
11784
|
+
children: [
|
|
11785
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex size-10 shrink-0 items-center justify-center rounded-md border border-border bg-background", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "size-5 text-muted-foreground", "aria-hidden": true }) }),
|
|
11786
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
11787
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate text-sm font-medium text-foreground", children: label }),
|
|
11788
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate text-xs text-muted-foreground", children: kind.typeLabel })
|
|
11789
|
+
] }),
|
|
11790
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
11791
|
+
"a",
|
|
11792
|
+
{
|
|
11793
|
+
href: downloadUrl,
|
|
11794
|
+
download: downloadName,
|
|
11795
|
+
onClick: (e) => e.stopPropagation(),
|
|
11796
|
+
className: cn(
|
|
11797
|
+
"shrink-0 inline-flex items-center gap-1.5 rounded-md border border-border",
|
|
11798
|
+
"bg-background px-2.5 py-1.5 text-xs font-medium text-foreground",
|
|
11799
|
+
"transition-colors hover:bg-muted"
|
|
11800
|
+
),
|
|
11801
|
+
"aria-label": `Download ${downloadName}`,
|
|
11802
|
+
children: [
|
|
11803
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.DownloadIcon, { className: "size-4", "aria-hidden": true }),
|
|
11804
|
+
"Download"
|
|
11805
|
+
]
|
|
11806
|
+
}
|
|
11807
|
+
)
|
|
11808
|
+
]
|
|
11809
|
+
}
|
|
11810
|
+
);
|
|
11529
11811
|
}
|
|
11530
|
-
function
|
|
11531
|
-
|
|
11532
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0" }),
|
|
11533
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children: message })
|
|
11534
|
-
] });
|
|
11812
|
+
function scrollToArtifactBlock(artifactId) {
|
|
11813
|
+
document.querySelector(`[data-artifact-anchor="${CSS.escape(artifactId)}"]`)?.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
11535
11814
|
}
|
|
11536
|
-
function
|
|
11537
|
-
|
|
11538
|
-
|
|
11815
|
+
function ArtifactRefCard({
|
|
11816
|
+
artifactId,
|
|
11817
|
+
name,
|
|
11818
|
+
kind
|
|
11539
11819
|
}) {
|
|
11540
|
-
|
|
11541
|
-
|
|
11542
|
-
|
|
11543
|
-
|
|
11544
|
-
|
|
11545
|
-
|
|
11546
|
-
|
|
11547
|
-
|
|
11548
|
-
|
|
11549
|
-
|
|
11550
|
-
|
|
11551
|
-
|
|
11552
|
-
|
|
11553
|
-
|
|
11554
|
-
|
|
11555
|
-
|
|
11556
|
-
|
|
11557
|
-
|
|
11558
|
-
|
|
11559
|
-
|
|
11560
|
-
|
|
11561
|
-
|
|
11562
|
-
|
|
11563
|
-
|
|
11564
|
-
|
|
11565
|
-
|
|
11566
|
-
|
|
11567
|
-
|
|
11568
|
-
|
|
11569
|
-
|
|
11570
|
-
|
|
11571
|
-
|
|
11572
|
-
|
|
11573
|
-
|
|
11574
|
-
|
|
11575
|
-
|
|
11576
|
-
|
|
11577
|
-
|
|
11578
|
-
|
|
11579
|
-
|
|
11580
|
-
|
|
11581
|
-
|
|
11582
|
-
|
|
11583
|
-
|
|
11584
|
-
|
|
11585
|
-
|
|
11586
|
-
|
|
11587
|
-
|
|
11588
|
-
|
|
11589
|
-
|
|
11590
|
-
|
|
11591
|
-
const onPageChanging = (event) => {
|
|
11592
|
-
if (typeof event.pageNumber === "number") {
|
|
11593
|
-
setPageNumber(event.pageNumber);
|
|
11594
|
-
}
|
|
11595
|
-
};
|
|
11596
|
-
const onScaleChanging = (event) => {
|
|
11597
|
-
if (typeof event.scale === "number") {
|
|
11598
|
-
setScale(event.scale);
|
|
11599
|
-
}
|
|
11600
|
-
};
|
|
11601
|
-
eventBus.on("pagesinit", onPagesInit);
|
|
11602
|
-
eventBus.on("pagechanging", onPageChanging);
|
|
11603
|
-
eventBus.on("scalechanging", onScaleChanging);
|
|
11604
|
-
pdfViewerRef.current = pdfViewer;
|
|
11605
|
-
eventBusRef.current = eventBus;
|
|
11606
|
-
findControllerRef.current = findController;
|
|
11607
|
-
setPagesCount(pdf.numPages);
|
|
11608
|
-
pdfViewer.setDocument(pdf);
|
|
11609
|
-
};
|
|
11610
|
-
void renderPdf().catch((nextError) => {
|
|
11611
|
-
if (cancelled) return;
|
|
11612
|
-
if (nextError instanceof Error && nextError.name === "RenderingCancelledException") {
|
|
11613
|
-
return;
|
|
11820
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
11821
|
+
"button",
|
|
11822
|
+
{
|
|
11823
|
+
type: "button",
|
|
11824
|
+
onClick: () => scrollToArtifactBlock(artifactId),
|
|
11825
|
+
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",
|
|
11826
|
+
children: [
|
|
11827
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileTextIcon, { className: "size-4 shrink-0 text-muted-foreground", "aria-hidden": true }),
|
|
11828
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate font-medium text-foreground", children: name }),
|
|
11829
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: KIND_LABEL[kind] })
|
|
11830
|
+
]
|
|
11831
|
+
}
|
|
11832
|
+
);
|
|
11833
|
+
}
|
|
11834
|
+
function resolveArtifactRef(ref, messages) {
|
|
11835
|
+
for (const msg of messages) {
|
|
11836
|
+
if (msg.role !== "system" || msg.systemKind !== "artifact") continue;
|
|
11837
|
+
const meta = msg.systemMeta ?? {};
|
|
11838
|
+
if (meta.artifact_id !== ref) continue;
|
|
11839
|
+
const kind = meta.kind ?? "markdown";
|
|
11840
|
+
const version = typeof meta.version === "number" ? meta.version : 1;
|
|
11841
|
+
const name = typeof meta.name === "string" ? meta.name : "";
|
|
11842
|
+
return { artifactId: ref, name, kind, version };
|
|
11843
|
+
}
|
|
11844
|
+
return null;
|
|
11845
|
+
}
|
|
11846
|
+
function TurnSummaryCard({
|
|
11847
|
+
summary,
|
|
11848
|
+
sessionId,
|
|
11849
|
+
messages,
|
|
11850
|
+
onFileSelect,
|
|
11851
|
+
onCommandSelect
|
|
11852
|
+
}) {
|
|
11853
|
+
const hasRecap = summary.recap.trim().length > 0;
|
|
11854
|
+
const hasArtifacts = summary.artifacts.length > 0;
|
|
11855
|
+
if (!hasArtifacts) return null;
|
|
11856
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 rounded border border-border bg-muted/50 px-3 py-2 text-sm", children: [
|
|
11857
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-2 text-xs font-semibold uppercase tracking-wide", children: "Summary" }),
|
|
11858
|
+
hasRecap && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 whitespace-pre-wrap text-foreground", children: summary.recap }),
|
|
11859
|
+
hasArtifacts && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2", children: summary.artifacts.map((artifact, i) => {
|
|
11860
|
+
const key = `${artifact.kind}:${artifact.ref}:${i}`;
|
|
11861
|
+
if (artifact.kind === "file" && sessionId) {
|
|
11862
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
11863
|
+
WorkspaceFileCard,
|
|
11864
|
+
{
|
|
11865
|
+
sessionId,
|
|
11866
|
+
path: artifact.ref,
|
|
11867
|
+
label: artifact.label
|
|
11868
|
+
},
|
|
11869
|
+
key
|
|
11870
|
+
);
|
|
11614
11871
|
}
|
|
11615
|
-
|
|
11616
|
-
|
|
11617
|
-
|
|
11618
|
-
|
|
11619
|
-
|
|
11620
|
-
|
|
11621
|
-
|
|
11622
|
-
|
|
11623
|
-
|
|
11624
|
-
|
|
11625
|
-
|
|
11626
|
-
|
|
11627
|
-
const setViewerPage = (nextPage) => {
|
|
11628
|
-
const viewer = pdfViewerRef.current;
|
|
11629
|
-
if (!viewer) return;
|
|
11630
|
-
const clampedPage = Math.min(
|
|
11631
|
-
Math.max(1, nextPage),
|
|
11632
|
-
Math.max(1, viewer.pagesCount)
|
|
11633
|
-
);
|
|
11634
|
-
viewer.currentPageNumber = clampedPage;
|
|
11635
|
-
setPageNumber(clampedPage);
|
|
11636
|
-
};
|
|
11637
|
-
const setViewerScale = (nextScale) => {
|
|
11638
|
-
const viewer = pdfViewerRef.current;
|
|
11639
|
-
if (!viewer) return;
|
|
11640
|
-
const clampedScale = Math.min(3, Math.max(0.5, nextScale));
|
|
11641
|
-
viewer.currentScale = clampedScale;
|
|
11642
|
-
setScale(clampedScale);
|
|
11643
|
-
};
|
|
11644
|
-
const runFind = (findPrevious = false) => {
|
|
11645
|
-
const eventBus = eventBusRef.current;
|
|
11646
|
-
if (!eventBus || !query.trim()) return;
|
|
11647
|
-
eventBus.dispatch("find", {
|
|
11648
|
-
source: eventBus,
|
|
11649
|
-
type: "again",
|
|
11650
|
-
query,
|
|
11651
|
-
phraseSearch: true,
|
|
11652
|
-
caseSensitive: false,
|
|
11653
|
-
entireWord: false,
|
|
11654
|
-
highlightAll: true,
|
|
11655
|
-
findPrevious,
|
|
11656
|
-
matchDiacritics: true
|
|
11657
|
-
});
|
|
11658
|
-
};
|
|
11659
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-full flex-col", children: [
|
|
11660
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 flex-wrap items-center gap-1.5 border-b border-line px-2 py-1.5", children: [
|
|
11661
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
11662
|
-
Button,
|
|
11663
|
-
{
|
|
11664
|
-
type: "button",
|
|
11665
|
-
variant: "ghost",
|
|
11666
|
-
size: "icon-xs",
|
|
11667
|
-
onClick: () => setViewerPage(pageNumber - 1),
|
|
11668
|
-
disabled: pageNumber <= 1,
|
|
11669
|
-
"aria-label": "Previous PDF page",
|
|
11670
|
-
title: "Previous page",
|
|
11671
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeftIcon, { className: "size-3.5" })
|
|
11872
|
+
if (artifact.kind === "artifact") {
|
|
11873
|
+
const resolved = sessionId ? resolveArtifactRef(artifact.ref, messages) : null;
|
|
11874
|
+
if (resolved) {
|
|
11875
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
11876
|
+
ArtifactRefCard,
|
|
11877
|
+
{
|
|
11878
|
+
artifactId: resolved.artifactId,
|
|
11879
|
+
name: resolved.name || artifact.label,
|
|
11880
|
+
kind: resolved.kind
|
|
11881
|
+
},
|
|
11882
|
+
key
|
|
11883
|
+
);
|
|
11672
11884
|
}
|
|
11673
|
-
|
|
11674
|
-
/* @__PURE__ */ jsxRuntime.
|
|
11675
|
-
|
|
11885
|
+
}
|
|
11886
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
11887
|
+
"div",
|
|
11676
11888
|
{
|
|
11677
|
-
|
|
11678
|
-
min: 1,
|
|
11679
|
-
max: pagesCount || 1,
|
|
11680
|
-
value: pageNumber,
|
|
11681
|
-
onChange: (event) => setViewerPage(Number(event.target.value)),
|
|
11682
|
-
"aria-label": "PDF page number",
|
|
11683
|
-
className: "h-7 w-12 border-input px-1 text-center text-xs"
|
|
11684
|
-
}
|
|
11685
|
-
),
|
|
11686
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground", children: [
|
|
11687
|
-
"/ ",
|
|
11688
|
-
pagesCount || "-"
|
|
11689
|
-
] }),
|
|
11690
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
11691
|
-
Button,
|
|
11692
|
-
{
|
|
11693
|
-
type: "button",
|
|
11694
|
-
variant: "ghost",
|
|
11695
|
-
size: "icon-xs",
|
|
11696
|
-
onClick: () => setViewerPage(pageNumber + 1),
|
|
11697
|
-
disabled: pagesCount > 0 && pageNumber >= pagesCount,
|
|
11698
|
-
"aria-label": "Next PDF page",
|
|
11699
|
-
title: "Next page",
|
|
11700
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRightIcon, { className: "size-3.5" })
|
|
11701
|
-
}
|
|
11702
|
-
),
|
|
11703
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-1 h-5 w-px bg-line" }),
|
|
11704
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
11705
|
-
Button,
|
|
11706
|
-
{
|
|
11707
|
-
type: "button",
|
|
11708
|
-
variant: "ghost",
|
|
11709
|
-
size: "icon-xs",
|
|
11710
|
-
onClick: () => setViewerScale(scale - 0.1),
|
|
11711
|
-
disabled: scale <= 0.5,
|
|
11712
|
-
"aria-label": "Zoom out PDF",
|
|
11713
|
-
title: "Zoom out",
|
|
11714
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MinusIcon, { className: "size-3.5" })
|
|
11715
|
-
}
|
|
11716
|
-
),
|
|
11717
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
11718
|
-
"button",
|
|
11719
|
-
{
|
|
11720
|
-
type: "button",
|
|
11721
|
-
className: "h-7 min-w-12 px-1 text-xs text-muted-foreground hover:text-foreground",
|
|
11722
|
-
onClick: () => {
|
|
11723
|
-
const viewer = pdfViewerRef.current;
|
|
11724
|
-
if (!viewer) return;
|
|
11725
|
-
viewer.currentScaleValue = "page-width";
|
|
11726
|
-
setScale(viewer.currentScale || 1);
|
|
11727
|
-
},
|
|
11728
|
-
"aria-label": "Fit PDF to width",
|
|
11729
|
-
title: "Fit width",
|
|
11889
|
+
className: "flex items-baseline gap-2 text-sm",
|
|
11730
11890
|
children: [
|
|
11731
|
-
|
|
11732
|
-
|
|
11891
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", "aria-hidden": true, children: "\u2022" }),
|
|
11892
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
11893
|
+
ArtifactRow,
|
|
11894
|
+
{
|
|
11895
|
+
artifact,
|
|
11896
|
+
onFileSelect,
|
|
11897
|
+
onCommandSelect
|
|
11898
|
+
}
|
|
11899
|
+
)
|
|
11733
11900
|
]
|
|
11734
|
-
}
|
|
11735
|
-
|
|
11736
|
-
|
|
11737
|
-
|
|
11738
|
-
{
|
|
11739
|
-
type: "button",
|
|
11740
|
-
variant: "ghost",
|
|
11741
|
-
size: "icon-xs",
|
|
11742
|
-
onClick: () => setViewerScale(scale + 0.1),
|
|
11743
|
-
disabled: scale >= 3,
|
|
11744
|
-
"aria-label": "Zoom in PDF",
|
|
11745
|
-
title: "Zoom in",
|
|
11746
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PlusIcon, { className: "size-3.5" })
|
|
11747
|
-
}
|
|
11748
|
-
),
|
|
11749
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-1 h-5 w-px bg-line" }),
|
|
11750
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.SearchIcon, { className: "size-3.5 text-muted-foreground" }),
|
|
11751
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
11752
|
-
Input,
|
|
11753
|
-
{
|
|
11754
|
-
type: "search",
|
|
11755
|
-
value: query,
|
|
11756
|
-
onChange: (event) => setQuery(event.target.value),
|
|
11757
|
-
onKeyDown: (event) => {
|
|
11758
|
-
if (event.key === "Enter") runFind(event.shiftKey);
|
|
11759
|
-
},
|
|
11760
|
-
placeholder: "Find",
|
|
11761
|
-
"aria-label": "Find in PDF",
|
|
11762
|
-
className: "h-7 w-28 border-input px-1 text-xs"
|
|
11763
|
-
}
|
|
11764
|
-
),
|
|
11765
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
11766
|
-
Button,
|
|
11767
|
-
{
|
|
11768
|
-
type: "button",
|
|
11769
|
-
variant: "ghost",
|
|
11770
|
-
size: "xs",
|
|
11771
|
-
onClick: () => runFind(false),
|
|
11772
|
-
disabled: !query.trim(),
|
|
11773
|
-
children: "Find"
|
|
11774
|
-
}
|
|
11775
|
-
)
|
|
11776
|
-
] }),
|
|
11777
|
-
error && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full items-start gap-2 p-3 text-xs text-destructive", children: [
|
|
11778
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0" }),
|
|
11779
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children: error })
|
|
11780
|
-
] }),
|
|
11781
|
-
!error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative min-h-105 flex-1 bg-muted/20", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
11782
|
-
"div",
|
|
11783
|
-
{
|
|
11784
|
-
ref: containerRef,
|
|
11785
|
-
"aria-label": `PDF viewer for ${fileName2}`,
|
|
11786
|
-
className: "absolute inset-0 overflow-auto",
|
|
11787
|
-
children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: viewerRef, className: "pdfViewer" })
|
|
11788
|
-
}
|
|
11789
|
-
) })
|
|
11901
|
+
},
|
|
11902
|
+
key
|
|
11903
|
+
);
|
|
11904
|
+
}) })
|
|
11790
11905
|
] });
|
|
11791
11906
|
}
|
|
11792
|
-
function
|
|
11793
|
-
|
|
11794
|
-
|
|
11795
|
-
|
|
11796
|
-
|
|
11907
|
+
function ArtifactRow({
|
|
11908
|
+
artifact,
|
|
11909
|
+
onFileSelect,
|
|
11910
|
+
onCommandSelect
|
|
11911
|
+
}) {
|
|
11912
|
+
if (artifact.kind === "file") {
|
|
11913
|
+
if (!onFileSelect) {
|
|
11914
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-muted-foreground", children: artifact.label });
|
|
11915
|
+
}
|
|
11916
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
11917
|
+
"button",
|
|
11918
|
+
{
|
|
11919
|
+
type: "button",
|
|
11920
|
+
onClick: () => onFileSelect(artifact.ref),
|
|
11921
|
+
className: "truncate cursor-pointer text-primary hover:underline",
|
|
11922
|
+
children: artifact.label
|
|
11923
|
+
}
|
|
11924
|
+
);
|
|
11797
11925
|
}
|
|
11798
|
-
|
|
11799
|
-
|
|
11800
|
-
|
|
11801
|
-
|
|
11802
|
-
|
|
11926
|
+
if (artifact.kind === "url") {
|
|
11927
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
11928
|
+
"a",
|
|
11929
|
+
{
|
|
11930
|
+
href: artifact.ref,
|
|
11931
|
+
target: "_blank",
|
|
11932
|
+
rel: "noopener noreferrer",
|
|
11933
|
+
className: "truncate text-primary hover:underline",
|
|
11934
|
+
children: artifact.label
|
|
11935
|
+
}
|
|
11936
|
+
);
|
|
11803
11937
|
}
|
|
11804
|
-
|
|
11938
|
+
if (artifact.kind === "command") {
|
|
11939
|
+
if (!onCommandSelect) {
|
|
11940
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-muted-foreground", children: artifact.label });
|
|
11941
|
+
}
|
|
11942
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
11943
|
+
"button",
|
|
11944
|
+
{
|
|
11945
|
+
type: "button",
|
|
11946
|
+
onClick: () => onCommandSelect(artifact.ref),
|
|
11947
|
+
className: "truncate text-left cursor-pointer text-primary hover:underline",
|
|
11948
|
+
children: artifact.label
|
|
11949
|
+
}
|
|
11950
|
+
);
|
|
11951
|
+
}
|
|
11952
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-muted-foreground", children: artifact.label });
|
|
11805
11953
|
}
|
|
11806
|
-
|
|
11807
|
-
|
|
11808
|
-
|
|
11809
|
-
|
|
11810
|
-
|
|
11811
|
-
|
|
11812
|
-
rtf: { icon: lucideReact.FileTextIcon, typeLabel: "Rich text" },
|
|
11813
|
-
txt: { icon: lucideReact.FileTextIcon, typeLabel: "Text" },
|
|
11814
|
-
md: { icon: lucideReact.FileTextIcon, typeLabel: "Markdown" },
|
|
11815
|
-
// Spreadsheets
|
|
11816
|
-
xls: { icon: lucideReact.FileSpreadsheetIcon, typeLabel: "Spreadsheet" },
|
|
11817
|
-
xlsx: { icon: lucideReact.FileSpreadsheetIcon, typeLabel: "Spreadsheet" },
|
|
11818
|
-
ods: { icon: lucideReact.FileSpreadsheetIcon, typeLabel: "OpenDocument spreadsheet" },
|
|
11819
|
-
csv: { icon: lucideReact.FileSpreadsheetIcon, typeLabel: "CSV" },
|
|
11820
|
-
tsv: { icon: lucideReact.FileSpreadsheetIcon, typeLabel: "TSV" },
|
|
11821
|
-
// Presentations / charts
|
|
11822
|
-
ppt: { icon: lucideReact.FileChartLineIcon, typeLabel: "Presentation" },
|
|
11823
|
-
pptx: { icon: lucideReact.FileChartLineIcon, typeLabel: "Presentation" },
|
|
11824
|
-
// Images
|
|
11825
|
-
png: { icon: lucideReact.FileImageIcon, typeLabel: "Image" },
|
|
11826
|
-
jpg: { icon: lucideReact.FileImageIcon, typeLabel: "Image" },
|
|
11827
|
-
jpeg: { icon: lucideReact.FileImageIcon, typeLabel: "Image" },
|
|
11828
|
-
gif: { icon: lucideReact.FileImageIcon, typeLabel: "Image" },
|
|
11829
|
-
svg: { icon: lucideReact.FileImageIcon, typeLabel: "SVG image" },
|
|
11830
|
-
webp: { icon: lucideReact.FileImageIcon, typeLabel: "Image" },
|
|
11831
|
-
bmp: { icon: lucideReact.FileImageIcon, typeLabel: "Image" },
|
|
11832
|
-
// Audio / video
|
|
11833
|
-
mp3: { icon: lucideReact.FileAudioIcon, typeLabel: "Audio" },
|
|
11834
|
-
wav: { icon: lucideReact.FileAudioIcon, typeLabel: "Audio" },
|
|
11835
|
-
ogg: { icon: lucideReact.FileAudioIcon, typeLabel: "Audio" },
|
|
11836
|
-
mp4: { icon: lucideReact.FileVideoIcon, typeLabel: "Video" },
|
|
11837
|
-
mov: { icon: lucideReact.FileVideoIcon, typeLabel: "Video" },
|
|
11838
|
-
webm: { icon: lucideReact.FileVideoIcon, typeLabel: "Video" },
|
|
11839
|
-
// Archives
|
|
11840
|
-
zip: { icon: lucideReact.FileArchiveIcon, typeLabel: "Archive" },
|
|
11841
|
-
tar: { icon: lucideReact.FileArchiveIcon, typeLabel: "Archive" },
|
|
11842
|
-
gz: { icon: lucideReact.FileArchiveIcon, typeLabel: "Archive" },
|
|
11843
|
-
bz2: { icon: lucideReact.FileArchiveIcon, typeLabel: "Archive" },
|
|
11844
|
-
"7z": { icon: lucideReact.FileArchiveIcon, typeLabel: "Archive" },
|
|
11845
|
-
// Code
|
|
11846
|
-
py: { icon: lucideReact.FileCodeIcon, typeLabel: "Python" },
|
|
11847
|
-
js: { icon: lucideReact.FileCodeIcon, typeLabel: "JavaScript" },
|
|
11848
|
-
ts: { icon: lucideReact.FileCodeIcon, typeLabel: "TypeScript" },
|
|
11849
|
-
tsx: { icon: lucideReact.FileCodeIcon, typeLabel: "TypeScript" },
|
|
11850
|
-
jsx: { icon: lucideReact.FileCodeIcon, typeLabel: "JavaScript" },
|
|
11851
|
-
json: { icon: lucideReact.FileCodeIcon, typeLabel: "JSON" },
|
|
11852
|
-
yaml: { icon: lucideReact.FileCodeIcon, typeLabel: "YAML" },
|
|
11853
|
-
yml: { icon: lucideReact.FileCodeIcon, typeLabel: "YAML" },
|
|
11854
|
-
toml: { icon: lucideReact.FileCodeIcon, typeLabel: "TOML" },
|
|
11855
|
-
sh: { icon: lucideReact.CodeIcon, typeLabel: "Shell" },
|
|
11856
|
-
rs: { icon: lucideReact.FileCodeIcon, typeLabel: "Rust" },
|
|
11857
|
-
go: { icon: lucideReact.FileCodeIcon, typeLabel: "Go" },
|
|
11858
|
-
rb: { icon: lucideReact.FileCodeIcon, typeLabel: "Ruby" },
|
|
11859
|
-
java: { icon: lucideReact.FileCodeIcon, typeLabel: "Java" },
|
|
11860
|
-
cpp: { icon: lucideReact.FileCodeIcon, typeLabel: "C++" },
|
|
11861
|
-
c: { icon: lucideReact.FileCodeIcon, typeLabel: "C" },
|
|
11862
|
-
html: { icon: lucideReact.FileCodeIcon, typeLabel: "HTML" },
|
|
11863
|
-
css: { icon: lucideReact.FileCodeIcon, typeLabel: "CSS" },
|
|
11864
|
-
scss: { icon: lucideReact.FileCodeIcon, typeLabel: "Sass" },
|
|
11865
|
-
sql: { icon: lucideReact.FileCodeIcon, typeLabel: "SQL" }
|
|
11866
|
-
};
|
|
11867
|
-
function _extension(path) {
|
|
11868
|
-
const idx = path.lastIndexOf(".");
|
|
11869
|
-
if (idx < 0 || idx === path.length - 1) return "";
|
|
11870
|
-
return path.slice(idx + 1).toLowerCase();
|
|
11954
|
+
|
|
11955
|
+
// src/components/chat/chat-thread.tsx
|
|
11956
|
+
init_utils();
|
|
11957
|
+
var WORKING_ON_IT_DELAY_MS = 250;
|
|
11958
|
+
function hasUserFacingAskContent(msg) {
|
|
11959
|
+
return !!msg.toolCalls?.some((tc) => tc.toolName === "ask_user_question");
|
|
11871
11960
|
}
|
|
11872
|
-
function
|
|
11873
|
-
|
|
11874
|
-
|
|
11875
|
-
|
|
11876
|
-
|
|
11877
|
-
|
|
11878
|
-
|
|
11879
|
-
|
|
11880
|
-
|
|
11881
|
-
|
|
11882
|
-
path,
|
|
11883
|
-
label
|
|
11884
|
-
}) {
|
|
11885
|
-
const { adapter } = useAgentChatAdapterContext();
|
|
11886
|
-
const kind = _fileKindFor(path);
|
|
11887
|
-
const Icon = kind.icon;
|
|
11888
|
-
const downloadUrl = adapter.getWorkspaceDownloadUrl({ sessionId, path });
|
|
11889
|
-
const downloadName = _basename(path) || label;
|
|
11890
|
-
const [previewOpen, setPreviewOpen] = react.useState(false);
|
|
11891
|
-
const handleCardClick = react.useCallback(() => {
|
|
11892
|
-
setPreviewOpen(true);
|
|
11893
|
-
}, []);
|
|
11894
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
11895
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
11896
|
-
"div",
|
|
11897
|
-
{
|
|
11898
|
-
role: "button",
|
|
11899
|
-
tabIndex: 0,
|
|
11900
|
-
onClick: handleCardClick,
|
|
11901
|
-
onKeyDown: (e) => {
|
|
11902
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
11903
|
-
e.preventDefault();
|
|
11904
|
-
handleCardClick();
|
|
11905
|
-
}
|
|
11906
|
-
},
|
|
11907
|
-
className: cn(
|
|
11908
|
-
"group/file flex items-center gap-3 rounded-lg border border-border",
|
|
11909
|
-
"bg-muted/20 px-3 py-2 text-left transition-colors",
|
|
11910
|
-
"cursor-pointer hover:bg-muted/40"
|
|
11911
|
-
),
|
|
11912
|
-
children: [
|
|
11913
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex size-10 shrink-0 items-center justify-center rounded-md border border-border bg-background", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "size-5 text-muted-foreground", "aria-hidden": true }) }),
|
|
11914
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
11915
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate text-sm font-medium text-foreground", children: label }),
|
|
11916
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate text-xs text-muted-foreground", children: kind.typeLabel })
|
|
11917
|
-
] }),
|
|
11918
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
11919
|
-
"a",
|
|
11920
|
-
{
|
|
11921
|
-
href: downloadUrl,
|
|
11922
|
-
download: downloadName,
|
|
11923
|
-
onClick: (e) => e.stopPropagation(),
|
|
11924
|
-
className: cn(
|
|
11925
|
-
"shrink-0 inline-flex items-center gap-1.5 rounded-md border border-border",
|
|
11926
|
-
"bg-background px-2.5 py-1.5 text-xs font-medium text-foreground",
|
|
11927
|
-
"transition-colors hover:bg-muted"
|
|
11928
|
-
),
|
|
11929
|
-
"aria-label": `Download ${downloadName}`,
|
|
11930
|
-
children: [
|
|
11931
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.DownloadIcon, { className: "size-3.5", "aria-hidden": true }),
|
|
11932
|
-
"Download"
|
|
11933
|
-
]
|
|
11934
|
-
}
|
|
11935
|
-
)
|
|
11936
|
-
]
|
|
11937
|
-
}
|
|
11938
|
-
),
|
|
11939
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
11940
|
-
WorkspaceFilePreviewDialog,
|
|
11941
|
-
{
|
|
11942
|
-
open: previewOpen,
|
|
11943
|
-
onOpenChange: setPreviewOpen,
|
|
11944
|
-
sessionId,
|
|
11945
|
-
path,
|
|
11946
|
-
downloadUrl
|
|
11947
|
-
}
|
|
11948
|
-
)
|
|
11949
|
-
] });
|
|
11950
|
-
}
|
|
11951
|
-
function WorkspaceFilePreviewDialog({
|
|
11952
|
-
open,
|
|
11953
|
-
onOpenChange,
|
|
11954
|
-
sessionId,
|
|
11955
|
-
path,
|
|
11956
|
-
downloadUrl
|
|
11957
|
-
}) {
|
|
11958
|
-
const { adapter } = useAgentChatAdapterContext();
|
|
11959
|
-
const [file, setFile] = react.useState(null);
|
|
11960
|
-
const [loading, setLoading] = react.useState(false);
|
|
11961
|
-
const [error, setError] = react.useState(null);
|
|
11962
|
-
react.useEffect(() => {
|
|
11963
|
-
if (!open) return;
|
|
11964
|
-
let cancelled = false;
|
|
11965
|
-
setLoading(true);
|
|
11966
|
-
setError(null);
|
|
11967
|
-
setFile(null);
|
|
11968
|
-
adapter.getWorkspaceFile({ sessionId, path }).then((result) => {
|
|
11969
|
-
if (cancelled) return;
|
|
11970
|
-
setFile(result);
|
|
11971
|
-
setLoading(false);
|
|
11972
|
-
}).catch((err) => {
|
|
11973
|
-
if (cancelled) return;
|
|
11974
|
-
const message = err instanceof Error ? err.message : "Failed to load file";
|
|
11975
|
-
setError(message);
|
|
11976
|
-
setLoading(false);
|
|
11977
|
-
});
|
|
11978
|
-
return () => {
|
|
11979
|
-
cancelled = true;
|
|
11980
|
-
};
|
|
11981
|
-
}, [adapter, sessionId, path, open]);
|
|
11982
|
-
return /* @__PURE__ */ jsxRuntime.jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(DialogContent, { className: "flex h-[85vh] w-[95vw] max-w-350! flex-col gap-0 overflow-hidden p-0", children: [
|
|
11983
|
-
/* @__PURE__ */ jsxRuntime.jsxs(DialogHeader, { className: "flex flex-row items-center gap-2 border-b border-line px-4 py-3", children: [
|
|
11984
|
-
/* @__PURE__ */ jsxRuntime.jsx(DialogTitle, { className: "min-w-0 flex-1 truncate text-sm", children: _basename(path) }),
|
|
11985
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
11986
|
-
"a",
|
|
11987
|
-
{
|
|
11988
|
-
href: downloadUrl,
|
|
11989
|
-
download: _basename(path),
|
|
11990
|
-
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",
|
|
11991
|
-
"aria-label": `Download ${_basename(path)}`,
|
|
11992
|
-
children: [
|
|
11993
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.DownloadIcon, { className: "size-3.5", "aria-hidden": true }),
|
|
11994
|
-
"Download"
|
|
11995
|
-
]
|
|
11996
|
-
}
|
|
11997
|
-
)
|
|
11998
|
-
] }),
|
|
11999
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-0 flex-1 flex-col", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
12000
|
-
FileViewer,
|
|
12001
|
-
{
|
|
12002
|
-
file,
|
|
12003
|
-
loading,
|
|
12004
|
-
error,
|
|
12005
|
-
downloadUrl,
|
|
12006
|
-
onDelete: null,
|
|
12007
|
-
onClose: () => onOpenChange(false),
|
|
12008
|
-
hideHeader: true
|
|
12009
|
-
}
|
|
12010
|
-
) })
|
|
12011
|
-
] }) });
|
|
12012
|
-
}
|
|
12013
|
-
function scrollToArtifactBlock(artifactId) {
|
|
12014
|
-
document.querySelector(`[data-artifact-anchor="${CSS.escape(artifactId)}"]`)?.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
12015
|
-
}
|
|
12016
|
-
function ArtifactRefCard({
|
|
12017
|
-
artifactId,
|
|
12018
|
-
name,
|
|
12019
|
-
kind
|
|
12020
|
-
}) {
|
|
12021
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
12022
|
-
"button",
|
|
12023
|
-
{
|
|
12024
|
-
type: "button",
|
|
12025
|
-
onClick: () => scrollToArtifactBlock(artifactId),
|
|
12026
|
-
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",
|
|
12027
|
-
children: [
|
|
12028
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileTextIcon, { className: "size-4 shrink-0 text-muted-foreground", "aria-hidden": true }),
|
|
12029
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate font-medium text-foreground", children: name }),
|
|
12030
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: KIND_LABEL[kind] })
|
|
12031
|
-
]
|
|
12032
|
-
}
|
|
12033
|
-
);
|
|
12034
|
-
}
|
|
12035
|
-
function resolveArtifactRef(ref, messages) {
|
|
12036
|
-
for (const msg of messages) {
|
|
12037
|
-
if (msg.role !== "system" || msg.systemKind !== "artifact") continue;
|
|
12038
|
-
const meta = msg.systemMeta ?? {};
|
|
12039
|
-
if (meta.artifact_id !== ref) continue;
|
|
12040
|
-
const kind = meta.kind ?? "markdown";
|
|
12041
|
-
const version = typeof meta.version === "number" ? meta.version : 1;
|
|
12042
|
-
const name = typeof meta.name === "string" ? meta.name : "";
|
|
12043
|
-
return { artifactId: ref, name, kind, version };
|
|
12044
|
-
}
|
|
12045
|
-
return null;
|
|
12046
|
-
}
|
|
12047
|
-
function TurnSummaryCard({
|
|
12048
|
-
summary,
|
|
12049
|
-
sessionId,
|
|
12050
|
-
messages,
|
|
12051
|
-
onFileSelect,
|
|
12052
|
-
onCommandSelect
|
|
12053
|
-
}) {
|
|
12054
|
-
const hasRecap = summary.recap.trim().length > 0;
|
|
12055
|
-
const hasArtifacts = summary.artifacts.length > 0;
|
|
12056
|
-
if (!hasArtifacts) return null;
|
|
12057
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 rounded border border-border bg-muted/50 px-3 py-2 text-sm", children: [
|
|
12058
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-2 text-xs font-semibold uppercase tracking-wide", children: "Summary" }),
|
|
12059
|
-
hasRecap && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 whitespace-pre-wrap text-foreground", children: summary.recap }),
|
|
12060
|
-
hasArtifacts && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2", children: summary.artifacts.map((artifact, i) => {
|
|
12061
|
-
const key = `${artifact.kind}:${artifact.ref}:${i}`;
|
|
12062
|
-
if (artifact.kind === "file" && sessionId) {
|
|
12063
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
12064
|
-
WorkspaceFileCard,
|
|
12065
|
-
{
|
|
12066
|
-
sessionId,
|
|
12067
|
-
path: artifact.ref,
|
|
12068
|
-
label: artifact.label
|
|
12069
|
-
},
|
|
12070
|
-
key
|
|
12071
|
-
);
|
|
12072
|
-
}
|
|
12073
|
-
if (artifact.kind === "artifact") {
|
|
12074
|
-
const resolved = sessionId ? resolveArtifactRef(artifact.ref, messages) : null;
|
|
12075
|
-
if (resolved) {
|
|
12076
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
12077
|
-
ArtifactRefCard,
|
|
12078
|
-
{
|
|
12079
|
-
artifactId: resolved.artifactId,
|
|
12080
|
-
name: resolved.name || artifact.label,
|
|
12081
|
-
kind: resolved.kind
|
|
12082
|
-
},
|
|
12083
|
-
key
|
|
12084
|
-
);
|
|
12085
|
-
}
|
|
12086
|
-
}
|
|
12087
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
12088
|
-
"div",
|
|
12089
|
-
{
|
|
12090
|
-
className: "flex items-baseline gap-2 text-sm",
|
|
12091
|
-
children: [
|
|
12092
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", "aria-hidden": true, children: "\u2022" }),
|
|
12093
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
12094
|
-
ArtifactRow,
|
|
12095
|
-
{
|
|
12096
|
-
artifact,
|
|
12097
|
-
onFileSelect,
|
|
12098
|
-
onCommandSelect
|
|
12099
|
-
}
|
|
12100
|
-
)
|
|
12101
|
-
]
|
|
12102
|
-
},
|
|
12103
|
-
key
|
|
12104
|
-
);
|
|
12105
|
-
}) })
|
|
12106
|
-
] });
|
|
12107
|
-
}
|
|
12108
|
-
function ArtifactRow({
|
|
12109
|
-
artifact,
|
|
12110
|
-
onFileSelect,
|
|
12111
|
-
onCommandSelect
|
|
12112
|
-
}) {
|
|
12113
|
-
if (artifact.kind === "file") {
|
|
12114
|
-
if (!onFileSelect) {
|
|
12115
|
-
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-muted-foreground", children: artifact.label });
|
|
12116
|
-
}
|
|
12117
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
12118
|
-
"button",
|
|
12119
|
-
{
|
|
12120
|
-
type: "button",
|
|
12121
|
-
onClick: () => onFileSelect(artifact.ref),
|
|
12122
|
-
className: "truncate cursor-pointer text-primary hover:underline",
|
|
12123
|
-
children: artifact.label
|
|
12124
|
-
}
|
|
12125
|
-
);
|
|
12126
|
-
}
|
|
12127
|
-
if (artifact.kind === "url") {
|
|
12128
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
12129
|
-
"a",
|
|
12130
|
-
{
|
|
12131
|
-
href: artifact.ref,
|
|
12132
|
-
target: "_blank",
|
|
12133
|
-
rel: "noopener noreferrer",
|
|
12134
|
-
className: "truncate text-primary hover:underline",
|
|
12135
|
-
children: artifact.label
|
|
12136
|
-
}
|
|
12137
|
-
);
|
|
12138
|
-
}
|
|
12139
|
-
if (artifact.kind === "command") {
|
|
12140
|
-
if (!onCommandSelect) {
|
|
12141
|
-
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-muted-foreground", children: artifact.label });
|
|
12142
|
-
}
|
|
12143
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
12144
|
-
"button",
|
|
12145
|
-
{
|
|
12146
|
-
type: "button",
|
|
12147
|
-
onClick: () => onCommandSelect(artifact.ref),
|
|
12148
|
-
className: "truncate text-left cursor-pointer text-primary hover:underline",
|
|
12149
|
-
children: artifact.label
|
|
12150
|
-
}
|
|
12151
|
-
);
|
|
12152
|
-
}
|
|
12153
|
-
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-muted-foreground", children: artifact.label });
|
|
12154
|
-
}
|
|
12155
|
-
|
|
12156
|
-
// src/components/chat/chat-thread.tsx
|
|
12157
|
-
init_utils();
|
|
12158
|
-
var WORKING_ON_IT_DELAY_MS = 250;
|
|
12159
|
-
function hasUserFacingAskContent(msg) {
|
|
12160
|
-
return !!msg.toolCalls?.some((tc) => tc.toolName === "ask_user_question");
|
|
12161
|
-
}
|
|
12162
|
-
function pendingAskCall(messages) {
|
|
12163
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
12164
|
-
const m = messages[i];
|
|
12165
|
-
if (m.role === "user") return null;
|
|
12166
|
-
if (m.role !== "assistant") continue;
|
|
12167
|
-
return m.toolCalls?.find(
|
|
12168
|
-
(tc) => tc.toolName === "ask_user_question" && tc.status === "running"
|
|
12169
|
-
) ?? null;
|
|
12170
|
-
}
|
|
12171
|
-
return null;
|
|
11961
|
+
function pendingAskCall(messages) {
|
|
11962
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
11963
|
+
const m = messages[i];
|
|
11964
|
+
if (m.role === "user") return null;
|
|
11965
|
+
if (m.role !== "assistant") continue;
|
|
11966
|
+
return m.toolCalls?.find(
|
|
11967
|
+
(tc) => tc.toolName === "ask_user_question" && tc.status === "running"
|
|
11968
|
+
) ?? null;
|
|
11969
|
+
}
|
|
11970
|
+
return null;
|
|
12172
11971
|
}
|
|
12173
11972
|
function askComposerPolicy(pending) {
|
|
12174
11973
|
if (!pending) return null;
|
|
@@ -13133,6 +12932,7 @@ function ChatThread({
|
|
|
13133
12932
|
retryIndicator,
|
|
13134
12933
|
onRetry,
|
|
13135
12934
|
onComposerError,
|
|
12935
|
+
paneCards,
|
|
13136
12936
|
showBrowser = false,
|
|
13137
12937
|
onToggleBrowser,
|
|
13138
12938
|
showWorkspace = false,
|
|
@@ -13212,18 +13012,66 @@ function ChatThread({
|
|
|
13212
13012
|
const composerArea = /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto w-full max-w-4xl px-3 sm:px-6 pb-3 sm:pb-5 pt-3", children: [
|
|
13213
13013
|
retryIndicator && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-2", children: /* @__PURE__ */ jsxRuntime.jsx(RetryBanner, { indicator: retryIndicator }) }),
|
|
13214
13014
|
researchSources.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-2", children: /* @__PURE__ */ jsxRuntime.jsx(ResearchSourcesPanel, { sources: researchSources }) }),
|
|
13215
|
-
|
|
13216
|
-
|
|
13015
|
+
(paneCards?.browser || paneCards?.files) && // Layers sliding out from behind the prompt box: each card is
|
|
13016
|
+
// narrower than the one below it and rounded only on top, with a
|
|
13017
|
+
// negative bottom margin so the next layer covers its flat edge. The
|
|
13018
|
+
// composer is the widest and last, so the stack reads as coming out
|
|
13019
|
+
// of it rather than sitting on top of it.
|
|
13020
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
13021
|
+
"div",
|
|
13217
13022
|
{
|
|
13218
|
-
|
|
13219
|
-
|
|
13220
|
-
|
|
13221
|
-
|
|
13222
|
-
|
|
13223
|
-
|
|
13224
|
-
|
|
13225
|
-
|
|
13226
|
-
|
|
13023
|
+
"data-testid": "session-pane-cards",
|
|
13024
|
+
className: "flex flex-col",
|
|
13025
|
+
children: [
|
|
13026
|
+
paneCards.browser && /* @__PURE__ */ jsxRuntime.jsx(
|
|
13027
|
+
SessionPaneCard,
|
|
13028
|
+
{
|
|
13029
|
+
testId: "session-pane-card-browser",
|
|
13030
|
+
title: "Browser",
|
|
13031
|
+
icon: lucideReact.GlobeIcon,
|
|
13032
|
+
subtitle: paneCards.browser.subtitle,
|
|
13033
|
+
thumbnail: paneCards.browser.thumbnail,
|
|
13034
|
+
onOpen: paneCards.browser.onOpen,
|
|
13035
|
+
className: "w-auto rounded-t-xl border-input border-[2px] border-b-0 -mb-[10px]"
|
|
13036
|
+
}
|
|
13037
|
+
),
|
|
13038
|
+
paneCards.files && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
13039
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
13040
|
+
SessionPaneCard,
|
|
13041
|
+
{
|
|
13042
|
+
testId: "session-pane-card-files",
|
|
13043
|
+
title: "Files",
|
|
13044
|
+
icon: lucideReact.FolderIcon,
|
|
13045
|
+
count: paneCards.files.count,
|
|
13046
|
+
expanded: paneCards.files.open,
|
|
13047
|
+
onOpen: paneCards.files.onToggle,
|
|
13048
|
+
className: "w-auto rounded-t-xl border-input border-[2px] border-b-0 -mb-[10px]"
|
|
13049
|
+
}
|
|
13050
|
+
),
|
|
13051
|
+
paneCards.files.open && /* @__PURE__ */ jsxRuntime.jsx(
|
|
13052
|
+
"div",
|
|
13053
|
+
{
|
|
13054
|
+
"data-testid": "files-accordion",
|
|
13055
|
+
className: "-mb-[10px] flex h-72 min-h-0 flex-col overflow-hidden border border-b-0 border-line",
|
|
13056
|
+
children: paneCards.files.panel
|
|
13057
|
+
}
|
|
13058
|
+
)
|
|
13059
|
+
] })
|
|
13060
|
+
]
|
|
13061
|
+
}
|
|
13062
|
+
),
|
|
13063
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
13064
|
+
ChatComposer,
|
|
13065
|
+
{
|
|
13066
|
+
onSend,
|
|
13067
|
+
onStop,
|
|
13068
|
+
isRunning,
|
|
13069
|
+
disabled: composerDisabled,
|
|
13070
|
+
disabledReason: composerDisabledReason,
|
|
13071
|
+
awaitingAnswer: askPolicy !== null && !askPolicy.locked,
|
|
13072
|
+
placeholder: askPolicy?.placeholder,
|
|
13073
|
+
tokenUsage,
|
|
13074
|
+
onComposerError,
|
|
13227
13075
|
showBrowser,
|
|
13228
13076
|
onToggleBrowser,
|
|
13229
13077
|
showWorkspace,
|
|
@@ -14840,17 +14688,25 @@ function useAgentChatRuntime({
|
|
|
14840
14688
|
}));
|
|
14841
14689
|
}
|
|
14842
14690
|
}).catch(() => void 0);
|
|
14843
|
-
|
|
14844
|
-
|
|
14845
|
-
|
|
14846
|
-
|
|
14847
|
-
|
|
14848
|
-
|
|
14849
|
-
|
|
14850
|
-
|
|
14851
|
-
|
|
14852
|
-
|
|
14691
|
+
const hydrateBrowserState = () => {
|
|
14692
|
+
void adapter.getBrowserState(sessionId).then((browserState) => {
|
|
14693
|
+
if (sessionIdRef.current !== sessionId) return;
|
|
14694
|
+
setState((prev) => ({
|
|
14695
|
+
...prev,
|
|
14696
|
+
browser: browserState ? {
|
|
14697
|
+
status: browserState.status,
|
|
14698
|
+
controlOwner: browserState.controlOwner ?? null
|
|
14699
|
+
} : null
|
|
14700
|
+
}));
|
|
14701
|
+
}).catch(() => void 0);
|
|
14702
|
+
};
|
|
14703
|
+
hydrateBrowserState();
|
|
14704
|
+
const browserSettleTimer = window.setTimeout(
|
|
14705
|
+
hydrateBrowserState,
|
|
14706
|
+
RECONCILE_FIRST_DELAY
|
|
14707
|
+
);
|
|
14853
14708
|
return () => {
|
|
14709
|
+
window.clearTimeout(browserSettleTimer);
|
|
14854
14710
|
clearReconnectTimer();
|
|
14855
14711
|
closeStream();
|
|
14856
14712
|
};
|
|
@@ -16809,10 +16665,10 @@ async function latexToSvg(latex, display = true) {
|
|
|
16809
16665
|
const start = outer.indexOf("<svg");
|
|
16810
16666
|
const end = outer.lastIndexOf("</svg>");
|
|
16811
16667
|
if (start === -1 || end === -1) return EMPTY;
|
|
16812
|
-
const
|
|
16813
|
-
const dims =
|
|
16668
|
+
const svg2 = outer.slice(start, end + "</svg>".length);
|
|
16669
|
+
const dims = svg2.match(/width="([^"]+)"\s+height="([^"]+)"/);
|
|
16814
16670
|
return {
|
|
16815
|
-
svg,
|
|
16671
|
+
svg: svg2,
|
|
16816
16672
|
widthEm: exToEm(dims?.[1]),
|
|
16817
16673
|
heightEm: exToEm(dims?.[2])
|
|
16818
16674
|
};
|
|
@@ -16840,13 +16696,13 @@ var FormulaCache = class {
|
|
|
16840
16696
|
}
|
|
16841
16697
|
async load(latex) {
|
|
16842
16698
|
const key = latex;
|
|
16843
|
-
const { svg, widthEm, heightEm } = await latexToSvg(latex);
|
|
16844
|
-
if (!
|
|
16699
|
+
const { svg: svg2, widthEm, heightEm } = await latexToSvg(latex);
|
|
16700
|
+
if (!svg2) {
|
|
16845
16701
|
this.pending.delete(key);
|
|
16846
16702
|
return;
|
|
16847
16703
|
}
|
|
16848
16704
|
const image = new Image();
|
|
16849
|
-
image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(
|
|
16705
|
+
image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg2)}`;
|
|
16850
16706
|
const settle = (ok) => {
|
|
16851
16707
|
this.pending.delete(key);
|
|
16852
16708
|
if (!ok) return;
|
|
@@ -16880,8 +16736,8 @@ var FormulaCache = class {
|
|
|
16880
16736
|
};
|
|
16881
16737
|
|
|
16882
16738
|
// src/components/whiteboard/input.ts
|
|
16883
|
-
var
|
|
16884
|
-
var
|
|
16739
|
+
var MIN_ZOOM2 = 0.02;
|
|
16740
|
+
var MAX_ZOOM2 = 256;
|
|
16885
16741
|
var ZOOM_DIVISOR = 300;
|
|
16886
16742
|
function screenToLogical(pt, view) {
|
|
16887
16743
|
return { x: view.x + pt.x / view.zoom, y: view.y + pt.y / view.zoom };
|
|
@@ -16891,7 +16747,7 @@ function logicalToScreen(pt, view) {
|
|
|
16891
16747
|
}
|
|
16892
16748
|
var clamp2 = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
|
|
16893
16749
|
function zoomAt(view, screenPt, factor) {
|
|
16894
|
-
const zoom = clamp2(view.zoom * factor,
|
|
16750
|
+
const zoom = clamp2(view.zoom * factor, MIN_ZOOM2, MAX_ZOOM2);
|
|
16895
16751
|
const anchor = screenToLogical(screenPt, view);
|
|
16896
16752
|
return {
|
|
16897
16753
|
zoom,
|
|
@@ -16915,8 +16771,8 @@ function zoomToFit(bounds, size2, padding = 80) {
|
|
|
16915
16771
|
}
|
|
16916
16772
|
const zoom = clamp2(
|
|
16917
16773
|
Math.min(size2.w / (bounds.w + padding * 2), size2.h / (bounds.h + padding * 2)),
|
|
16918
|
-
|
|
16919
|
-
|
|
16774
|
+
MIN_ZOOM2,
|
|
16775
|
+
MAX_ZOOM2
|
|
16920
16776
|
);
|
|
16921
16777
|
return {
|
|
16922
16778
|
zoom,
|
|
@@ -18238,206 +18094,633 @@ function WhiteboardSurface({
|
|
|
18238
18094
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-2 top-2 z-10", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
18239
18095
|
ToolRail,
|
|
18240
18096
|
{
|
|
18241
|
-
tool,
|
|
18242
|
-
onToolChange: setTool,
|
|
18243
|
-
color,
|
|
18244
|
-
onColorChange: setColor,
|
|
18245
|
-
width,
|
|
18246
|
-
onWidthChange: setWidth,
|
|
18247
|
-
canUndo: undoStack.current.length > 0,
|
|
18248
|
-
canRedo: redoStack.current.length > 0,
|
|
18249
|
-
onUndo: undo,
|
|
18250
|
-
onRedo: redo,
|
|
18251
|
-
onFit: fitToContent,
|
|
18252
|
-
disabled
|
|
18253
|
-
},
|
|
18254
|
-
historyTick
|
|
18255
|
-
) }),
|
|
18256
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { ref: wrapRef, className: "relative min-h-0 flex-1 overflow-hidden", children: [
|
|
18257
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
18258
|
-
"canvas",
|
|
18259
|
-
{
|
|
18260
|
-
ref: canvasRef,
|
|
18261
|
-
"aria-label": "Whiteboard canvas",
|
|
18262
|
-
className: cn(
|
|
18263
|
-
"h-full w-full touch-none bg-white",
|
|
18264
|
-
tool === "pan" ? "cursor-grab" : "cursor-crosshair",
|
|
18265
|
-
// Held from the first frame, not from when the veil
|
|
18266
|
-
// appears: the load resolves by replacing the document
|
|
18267
|
-
// wholesale, so anything drawn before it lands is thrown
|
|
18268
|
-
// away. Better to decline the stroke than to eat it.
|
|
18269
|
-
//
|
|
18270
|
-
// Held again while the agent works. The atlas and the
|
|
18271
|
-
// occupied cells it is answering were captured when Ask
|
|
18272
|
-
// was pressed, so ink added now is invisible to it — it
|
|
18273
|
-
// places its answer against a board that no longer
|
|
18274
|
-
// matches, and can land on top of what was just drawn.
|
|
18275
|
-
(!canvasReady || runtime.isRunning) && "pointer-events-none"
|
|
18276
|
-
),
|
|
18277
|
-
style: { width: size2.w, height: size2.h },
|
|
18278
|
-
onPointerDown,
|
|
18279
|
-
onPointerMove,
|
|
18280
|
-
onPointerUp,
|
|
18281
|
-
onPointerCancel: onPointerUp,
|
|
18282
|
-
onContextMenu: (e) => e.preventDefault(),
|
|
18283
|
-
onPointerLeave: () => {
|
|
18284
|
-
if (cursorRef.current) {
|
|
18285
|
-
cursorRef.current = null;
|
|
18286
|
-
paint();
|
|
18287
|
-
}
|
|
18288
|
-
},
|
|
18289
|
-
onWheel
|
|
18290
|
-
}
|
|
18291
|
-
),
|
|
18292
|
-
runtime.isRunning ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pointer-events-none absolute inset-0 bg-black/10", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-x-0 bottom-2 flex justify-center px-12", children: /* @__PURE__ */ jsxRuntime.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: [
|
|
18293
|
-
/* @__PURE__ */ jsxRuntime.jsx(Spinner, { className: "size-4 shrink-0" }),
|
|
18294
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: progress ?? (askMode === "deep" ? "Working it out\u2026" : "Thinking\u2026") })
|
|
18295
|
-
] }) }) }) : null,
|
|
18296
|
-
restoringVisible ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pointer-events-none absolute inset-0 flex items-center justify-center bg-black/10", children: /* @__PURE__ */ jsxRuntime.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: [
|
|
18297
|
-
/* @__PURE__ */ jsxRuntime.jsx(Spinner, { className: "size-5" }),
|
|
18298
|
-
"Restoring board\u2026"
|
|
18299
|
-
] }) }) : null,
|
|
18300
|
-
artifacts.map((a) => {
|
|
18301
|
-
const tl = logicalToScreen({ x: a.x, y: a.y }, view);
|
|
18302
|
-
const meta = artifactMeta.get(a.artifactId);
|
|
18303
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
18304
|
-
"div",
|
|
18305
|
-
{
|
|
18306
|
-
"data-artifact-id": a.artifactId,
|
|
18307
|
-
className: "pointer-events-auto absolute overflow-auto rounded border bg-background",
|
|
18308
|
-
style: {
|
|
18309
|
-
left: tl.x,
|
|
18310
|
-
top: tl.y,
|
|
18311
|
-
width: a.w * view.zoom,
|
|
18312
|
-
height: a.h * view.zoom
|
|
18313
|
-
},
|
|
18314
|
-
children: meta && sessionId ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
18315
|
-
ArtifactBlock,
|
|
18316
|
-
{
|
|
18317
|
-
sessionId,
|
|
18318
|
-
artifactId: a.artifactId,
|
|
18319
|
-
name: meta.name,
|
|
18320
|
-
kind: meta.kind,
|
|
18321
|
-
version: a.version ?? meta.version
|
|
18322
|
-
}
|
|
18323
|
-
) : null
|
|
18324
|
-
},
|
|
18325
|
-
a.id
|
|
18326
|
-
);
|
|
18327
|
-
}),
|
|
18328
|
-
editor ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
18329
|
-
"textarea",
|
|
18330
|
-
{
|
|
18331
|
-
autoFocus: true,
|
|
18332
|
-
"aria-label": "Text",
|
|
18333
|
-
className: "absolute rounded border bg-background p-1 text-sm shadow",
|
|
18334
|
-
style: {
|
|
18335
|
-
left: logicalToScreen(editor, view).x,
|
|
18336
|
-
top: logicalToScreen(editor, view).y,
|
|
18337
|
-
width: TEXT_MAX_WIDTH * view.zoom
|
|
18338
|
-
},
|
|
18339
|
-
value: editorText,
|
|
18340
|
-
onChange: (e) => setEditorText(e.target.value),
|
|
18341
|
-
onBlur: commitEditor,
|
|
18342
|
-
onKeyDown: (e) => {
|
|
18343
|
-
if (e.key === "Escape") {
|
|
18344
|
-
e.preventDefault();
|
|
18345
|
-
setEditor(null);
|
|
18346
|
-
setEditorText("");
|
|
18347
|
-
} else if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
|
18348
|
-
e.preventDefault();
|
|
18349
|
-
commitEditor();
|
|
18350
|
-
}
|
|
18351
|
-
}
|
|
18352
|
-
}
|
|
18353
|
-
) : null
|
|
18354
|
-
] })
|
|
18355
|
-
] }),
|
|
18356
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 border-t p-2", children: [
|
|
18097
|
+
tool,
|
|
18098
|
+
onToolChange: setTool,
|
|
18099
|
+
color,
|
|
18100
|
+
onColorChange: setColor,
|
|
18101
|
+
width,
|
|
18102
|
+
onWidthChange: setWidth,
|
|
18103
|
+
canUndo: undoStack.current.length > 0,
|
|
18104
|
+
canRedo: redoStack.current.length > 0,
|
|
18105
|
+
onUndo: undo,
|
|
18106
|
+
onRedo: redo,
|
|
18107
|
+
onFit: fitToContent,
|
|
18108
|
+
disabled
|
|
18109
|
+
},
|
|
18110
|
+
historyTick
|
|
18111
|
+
) }),
|
|
18112
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { ref: wrapRef, className: "relative min-h-0 flex-1 overflow-hidden", children: [
|
|
18113
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
18114
|
+
"canvas",
|
|
18115
|
+
{
|
|
18116
|
+
ref: canvasRef,
|
|
18117
|
+
"aria-label": "Whiteboard canvas",
|
|
18118
|
+
className: cn(
|
|
18119
|
+
"h-full w-full touch-none bg-white",
|
|
18120
|
+
tool === "pan" ? "cursor-grab" : "cursor-crosshair",
|
|
18121
|
+
// Held from the first frame, not from when the veil
|
|
18122
|
+
// appears: the load resolves by replacing the document
|
|
18123
|
+
// wholesale, so anything drawn before it lands is thrown
|
|
18124
|
+
// away. Better to decline the stroke than to eat it.
|
|
18125
|
+
//
|
|
18126
|
+
// Held again while the agent works. The atlas and the
|
|
18127
|
+
// occupied cells it is answering were captured when Ask
|
|
18128
|
+
// was pressed, so ink added now is invisible to it — it
|
|
18129
|
+
// places its answer against a board that no longer
|
|
18130
|
+
// matches, and can land on top of what was just drawn.
|
|
18131
|
+
(!canvasReady || runtime.isRunning) && "pointer-events-none"
|
|
18132
|
+
),
|
|
18133
|
+
style: { width: size2.w, height: size2.h },
|
|
18134
|
+
onPointerDown,
|
|
18135
|
+
onPointerMove,
|
|
18136
|
+
onPointerUp,
|
|
18137
|
+
onPointerCancel: onPointerUp,
|
|
18138
|
+
onContextMenu: (e) => e.preventDefault(),
|
|
18139
|
+
onPointerLeave: () => {
|
|
18140
|
+
if (cursorRef.current) {
|
|
18141
|
+
cursorRef.current = null;
|
|
18142
|
+
paint();
|
|
18143
|
+
}
|
|
18144
|
+
},
|
|
18145
|
+
onWheel
|
|
18146
|
+
}
|
|
18147
|
+
),
|
|
18148
|
+
runtime.isRunning ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pointer-events-none absolute inset-0 bg-black/10", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-x-0 bottom-2 flex justify-center px-12", children: /* @__PURE__ */ jsxRuntime.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: [
|
|
18149
|
+
/* @__PURE__ */ jsxRuntime.jsx(Spinner, { className: "size-4 shrink-0" }),
|
|
18150
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: progress ?? (askMode === "deep" ? "Working it out\u2026" : "Thinking\u2026") })
|
|
18151
|
+
] }) }) }) : null,
|
|
18152
|
+
restoringVisible ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pointer-events-none absolute inset-0 flex items-center justify-center bg-black/10", children: /* @__PURE__ */ jsxRuntime.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: [
|
|
18153
|
+
/* @__PURE__ */ jsxRuntime.jsx(Spinner, { className: "size-5" }),
|
|
18154
|
+
"Restoring board\u2026"
|
|
18155
|
+
] }) }) : null,
|
|
18156
|
+
artifacts.map((a) => {
|
|
18157
|
+
const tl = logicalToScreen({ x: a.x, y: a.y }, view);
|
|
18158
|
+
const meta = artifactMeta.get(a.artifactId);
|
|
18159
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
18160
|
+
"div",
|
|
18161
|
+
{
|
|
18162
|
+
"data-artifact-id": a.artifactId,
|
|
18163
|
+
className: "pointer-events-auto absolute overflow-auto rounded border bg-background",
|
|
18164
|
+
style: {
|
|
18165
|
+
left: tl.x,
|
|
18166
|
+
top: tl.y,
|
|
18167
|
+
width: a.w * view.zoom,
|
|
18168
|
+
height: a.h * view.zoom
|
|
18169
|
+
},
|
|
18170
|
+
children: meta && sessionId ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
18171
|
+
ArtifactBlock,
|
|
18172
|
+
{
|
|
18173
|
+
sessionId,
|
|
18174
|
+
artifactId: a.artifactId,
|
|
18175
|
+
name: meta.name,
|
|
18176
|
+
kind: meta.kind,
|
|
18177
|
+
version: a.version ?? meta.version
|
|
18178
|
+
}
|
|
18179
|
+
) : null
|
|
18180
|
+
},
|
|
18181
|
+
a.id
|
|
18182
|
+
);
|
|
18183
|
+
}),
|
|
18184
|
+
editor ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
18185
|
+
"textarea",
|
|
18186
|
+
{
|
|
18187
|
+
autoFocus: true,
|
|
18188
|
+
"aria-label": "Text",
|
|
18189
|
+
className: "absolute rounded border bg-background p-1 text-sm shadow",
|
|
18190
|
+
style: {
|
|
18191
|
+
left: logicalToScreen(editor, view).x,
|
|
18192
|
+
top: logicalToScreen(editor, view).y,
|
|
18193
|
+
width: TEXT_MAX_WIDTH * view.zoom
|
|
18194
|
+
},
|
|
18195
|
+
value: editorText,
|
|
18196
|
+
onChange: (e) => setEditorText(e.target.value),
|
|
18197
|
+
onBlur: commitEditor,
|
|
18198
|
+
onKeyDown: (e) => {
|
|
18199
|
+
if (e.key === "Escape") {
|
|
18200
|
+
e.preventDefault();
|
|
18201
|
+
setEditor(null);
|
|
18202
|
+
setEditorText("");
|
|
18203
|
+
} else if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
|
18204
|
+
e.preventDefault();
|
|
18205
|
+
commitEditor();
|
|
18206
|
+
}
|
|
18207
|
+
}
|
|
18208
|
+
}
|
|
18209
|
+
) : null
|
|
18210
|
+
] })
|
|
18211
|
+
] }),
|
|
18212
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 border-t p-2", children: [
|
|
18213
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
18214
|
+
Input,
|
|
18215
|
+
{
|
|
18216
|
+
className: "w-64 min-w-0 shrink",
|
|
18217
|
+
placeholder: "Ask about the board (optional)",
|
|
18218
|
+
value: question,
|
|
18219
|
+
disabled: busy,
|
|
18220
|
+
onChange: (e) => setQuestion(e.target.value),
|
|
18221
|
+
onKeyDown: (e) => {
|
|
18222
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
18223
|
+
e.preventDefault();
|
|
18224
|
+
void ask("answer");
|
|
18225
|
+
}
|
|
18226
|
+
}
|
|
18227
|
+
}
|
|
18228
|
+
),
|
|
18229
|
+
/* @__PURE__ */ jsxRuntime.jsxs(TooltipProvider, { children: [
|
|
18230
|
+
/* @__PURE__ */ jsxRuntime.jsx(ButtonGroup, { "aria-label": "Actions", children: USER_ACTIONS.map((a) => /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
|
|
18231
|
+
/* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
18232
|
+
Button,
|
|
18233
|
+
{
|
|
18234
|
+
type: "button",
|
|
18235
|
+
variant: a.id === "answer" ? "default" : "outline",
|
|
18236
|
+
disabled: busy,
|
|
18237
|
+
"aria-label": a.label,
|
|
18238
|
+
onClick: () => void ask(a.id),
|
|
18239
|
+
children: a.label
|
|
18240
|
+
}
|
|
18241
|
+
) }),
|
|
18242
|
+
/* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side: "top", children: a.hint })
|
|
18243
|
+
] }, a.id)) }),
|
|
18244
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
|
|
18245
|
+
/* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-2 px-1", children: [
|
|
18246
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
18247
|
+
Checkbox,
|
|
18248
|
+
{
|
|
18249
|
+
id: "wb-work-it-out",
|
|
18250
|
+
"aria-label": "Work it out",
|
|
18251
|
+
checked: deep,
|
|
18252
|
+
disabled: busy,
|
|
18253
|
+
onCheckedChange: (v) => setDeep(v === true)
|
|
18254
|
+
}
|
|
18255
|
+
),
|
|
18256
|
+
/* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "wb-work-it-out", children: "Work it out" })
|
|
18257
|
+
] }) }),
|
|
18258
|
+
/* @__PURE__ */ jsxRuntime.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." })
|
|
18259
|
+
] })
|
|
18260
|
+
] }),
|
|
18261
|
+
onNewBoard ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
18262
|
+
Button,
|
|
18263
|
+
{
|
|
18264
|
+
type: "button",
|
|
18265
|
+
variant: "ghost",
|
|
18266
|
+
"aria-label": "New board",
|
|
18267
|
+
disabled: busy,
|
|
18268
|
+
onClick: () => {
|
|
18269
|
+
setWantFresh(true);
|
|
18270
|
+
onNewBoard();
|
|
18271
|
+
},
|
|
18272
|
+
children: "New board"
|
|
18273
|
+
}
|
|
18274
|
+
) : null,
|
|
18275
|
+
viewMode && onViewModeChange && hasAnswer ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center rounded-full border p-0.5", children: ["simple", "expert", "whiteboard"].map((mode) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
18276
|
+
"button",
|
|
18277
|
+
{
|
|
18278
|
+
type: "button",
|
|
18279
|
+
"aria-label": `${VIEW_MODE_LABELS[mode]} view`,
|
|
18280
|
+
"aria-pressed": viewMode === mode,
|
|
18281
|
+
className: cn(
|
|
18282
|
+
"rounded-full px-3 py-1 text-xs",
|
|
18283
|
+
viewMode === mode ? "bg-background font-medium shadow-sm" : "text-muted-foreground"
|
|
18284
|
+
),
|
|
18285
|
+
onClick: () => onViewModeChange(mode),
|
|
18286
|
+
children: VIEW_MODE_LABELS[mode]
|
|
18287
|
+
},
|
|
18288
|
+
mode
|
|
18289
|
+
)) }) : null
|
|
18290
|
+
] })
|
|
18291
|
+
] });
|
|
18292
|
+
}
|
|
18293
|
+
function chunkPairs(flat) {
|
|
18294
|
+
const pairs = [];
|
|
18295
|
+
for (let i = 0; i + 1 < flat.length; i += 2) pairs.push([flat[i], flat[i + 1]]);
|
|
18296
|
+
return pairs;
|
|
18297
|
+
}
|
|
18298
|
+
init_utils();
|
|
18299
|
+
var SKELETON_WIDTHS = [70, 85, 55, 90, 60, 78, 45, 82, 65, 72];
|
|
18300
|
+
var PDF_WORKER_SRC = new URL(
|
|
18301
|
+
"pdfjs-dist/legacy/build/pdf.worker.mjs",
|
|
18302
|
+
(typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))
|
|
18303
|
+
).toString();
|
|
18304
|
+
function FileViewer({
|
|
18305
|
+
file,
|
|
18306
|
+
loading,
|
|
18307
|
+
error,
|
|
18308
|
+
downloadUrl,
|
|
18309
|
+
onDelete,
|
|
18310
|
+
onClose,
|
|
18311
|
+
hideHeader = false,
|
|
18312
|
+
className
|
|
18313
|
+
}) {
|
|
18314
|
+
const visible = loading || file !== null || error !== null;
|
|
18315
|
+
if (!visible) return null;
|
|
18316
|
+
const fileName2 = file?.path.split("/").pop() ?? "File";
|
|
18317
|
+
const lang = file ? getLanguageHint(file.path) : "";
|
|
18318
|
+
const isPdf = file?.mime_type === "application/pdf" || file?.path.toLowerCase().endsWith(".pdf") || false;
|
|
18319
|
+
const isImage = file?.encoding === "base64" && !isPdf;
|
|
18320
|
+
const HeaderIcon = isImage ? lucideReact.ImageIcon : lucideReact.FileTextIcon;
|
|
18321
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex min-h-0 flex-1 flex-col border-t border-line", className), children: [
|
|
18322
|
+
!hideHeader && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-2 border-b border-line px-3 py-2", children: [
|
|
18323
|
+
/* @__PURE__ */ jsxRuntime.jsx(HeaderIcon, { className: "size-4 shrink-0 text-muted-foreground" }),
|
|
18324
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
18325
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate font-medium", children: fileName2 }),
|
|
18326
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "truncate text-sm text-muted-foreground", children: [
|
|
18327
|
+
file?.path ?? "Loading...",
|
|
18328
|
+
file && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ml-1.5", children: [
|
|
18329
|
+
formatFileSize(file.size),
|
|
18330
|
+
file.truncated && " (truncated)"
|
|
18331
|
+
] })
|
|
18332
|
+
] })
|
|
18333
|
+
] }),
|
|
18334
|
+
downloadUrl && /* @__PURE__ */ jsxRuntime.jsx(
|
|
18335
|
+
"a",
|
|
18336
|
+
{
|
|
18337
|
+
href: downloadUrl,
|
|
18338
|
+
download: fileName2,
|
|
18339
|
+
className: "shrink-0 rounded p-0.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
|
18340
|
+
"aria-label": `Download ${fileName2}`,
|
|
18341
|
+
title: "Download",
|
|
18342
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.DownloadIcon, { className: "size-4" })
|
|
18343
|
+
}
|
|
18344
|
+
),
|
|
18345
|
+
onDelete && /* @__PURE__ */ jsxRuntime.jsx(
|
|
18346
|
+
"button",
|
|
18347
|
+
{
|
|
18348
|
+
type: "button",
|
|
18349
|
+
onClick: onDelete,
|
|
18350
|
+
className: "shrink-0 rounded p-0.5 text-muted-foreground transition-colors hover:bg-destructive/10 hover:text-destructive",
|
|
18351
|
+
"aria-label": `Delete ${fileName2}`,
|
|
18352
|
+
title: "Delete",
|
|
18353
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.TrashIcon, { className: "size-4" })
|
|
18354
|
+
}
|
|
18355
|
+
),
|
|
18356
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
18357
|
+
"button",
|
|
18358
|
+
{
|
|
18359
|
+
type: "button",
|
|
18360
|
+
onClick: onClose,
|
|
18361
|
+
className: "shrink-0 rounded p-0.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
|
18362
|
+
"aria-label": "Close file",
|
|
18363
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.XIcon, { className: "size-4" })
|
|
18364
|
+
}
|
|
18365
|
+
)
|
|
18366
|
+
] }),
|
|
18367
|
+
file && isPdf && file.encoding === "base64" ? (
|
|
18368
|
+
// PDF has its own scrollable container — bypass ScrollArea so
|
|
18369
|
+
// it can grow with the parent's flex height (Radix Viewport's
|
|
18370
|
+
// internal table wrapper sizes to content and breaks h-full).
|
|
18371
|
+
/* @__PURE__ */ jsxRuntime.jsx(PdfPreview, { file, fileName: fileName2 })
|
|
18372
|
+
) : /* @__PURE__ */ jsxRuntime.jsxs(ScrollArea, { className: "min-h-0 flex-1", children: [
|
|
18373
|
+
loading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1.5 p-3", children: Array.from({ length: 10 }).map((_, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
18374
|
+
Skeleton,
|
|
18375
|
+
{
|
|
18376
|
+
className: "h-3.5 rounded",
|
|
18377
|
+
style: {
|
|
18378
|
+
width: `${SKELETON_WIDTHS[index % SKELETON_WIDTHS.length]}%`
|
|
18379
|
+
}
|
|
18380
|
+
},
|
|
18381
|
+
index
|
|
18382
|
+
)) }),
|
|
18383
|
+
error && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-2 p-3 text-xs text-destructive", children: [
|
|
18384
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0" }),
|
|
18385
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: error })
|
|
18386
|
+
] }),
|
|
18387
|
+
file && isPdf && file.encoding !== "base64" && /* @__PURE__ */ jsxRuntime.jsx(FileViewerError, { message: "PDF preview requires base64 file content." }),
|
|
18388
|
+
file && isImage && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center p-4", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
18389
|
+
"img",
|
|
18390
|
+
{
|
|
18391
|
+
src: `data:${file.mime_type ?? "image/png"};base64,${file.content}`,
|
|
18392
|
+
alt: fileName2,
|
|
18393
|
+
className: "max-h-[60vh] max-w-full rounded object-contain"
|
|
18394
|
+
}
|
|
18395
|
+
) }),
|
|
18396
|
+
file && !isImage && !isPdf && /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "wrap-break-word whitespace-pre-wrap p-3 text-base leading-relaxed text-foreground", children: /* @__PURE__ */ jsxRuntime.jsx("code", { "data-language": lang, children: file.content }) })
|
|
18397
|
+
] })
|
|
18398
|
+
] });
|
|
18399
|
+
}
|
|
18400
|
+
function FileViewerError({ message }) {
|
|
18401
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-2 p-3 text-xs text-destructive", children: [
|
|
18402
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0" }),
|
|
18403
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: message })
|
|
18404
|
+
] });
|
|
18405
|
+
}
|
|
18406
|
+
function PdfPreview({
|
|
18407
|
+
file,
|
|
18408
|
+
fileName: fileName2
|
|
18409
|
+
}) {
|
|
18410
|
+
const containerRef = react.useRef(null);
|
|
18411
|
+
const viewerRef = react.useRef(null);
|
|
18412
|
+
const pdfViewerRef = react.useRef(null);
|
|
18413
|
+
const eventBusRef = react.useRef(null);
|
|
18414
|
+
const findControllerRef = react.useRef(null);
|
|
18415
|
+
const [error, setError] = react.useState(null);
|
|
18416
|
+
const [pageNumber, setPageNumber] = react.useState(1);
|
|
18417
|
+
const [pagesCount, setPagesCount] = react.useState(0);
|
|
18418
|
+
const [scale, setScale] = react.useState(1);
|
|
18419
|
+
const [query, setQuery] = react.useState("");
|
|
18420
|
+
react.useEffect(() => {
|
|
18421
|
+
let cancelled = false;
|
|
18422
|
+
let loadingTask = null;
|
|
18423
|
+
let pdfDocument = null;
|
|
18424
|
+
const renderPdf = async () => {
|
|
18425
|
+
setError(null);
|
|
18426
|
+
const container = containerRef.current;
|
|
18427
|
+
const viewerElement = viewerRef.current;
|
|
18428
|
+
if (!container || !viewerElement) return;
|
|
18429
|
+
const pdfBytes = decodeBase64(file.content);
|
|
18430
|
+
const pdfjs = await import('pdfjs-dist/legacy/build/pdf.mjs');
|
|
18431
|
+
pdfjs.GlobalWorkerOptions.workerSrc = PDF_WORKER_SRC;
|
|
18432
|
+
globalThis.pdfjsLib = pdfjs;
|
|
18433
|
+
const pdfViewerModule = await import('pdfjs-dist/web/pdf_viewer.mjs');
|
|
18434
|
+
loadingTask = pdfjs.getDocument({ data: pdfBytes });
|
|
18435
|
+
const pdf = await loadingTask.promise;
|
|
18436
|
+
pdfDocument = pdf;
|
|
18437
|
+
if (cancelled) return;
|
|
18438
|
+
const eventBus = new pdfViewerModule.EventBus();
|
|
18439
|
+
const linkService = new pdfViewerModule.PDFLinkService({ eventBus });
|
|
18440
|
+
const findController = new pdfViewerModule.PDFFindController({
|
|
18441
|
+
eventBus,
|
|
18442
|
+
linkService
|
|
18443
|
+
});
|
|
18444
|
+
const pdfViewer = new pdfViewerModule.PDFViewer({
|
|
18445
|
+
container,
|
|
18446
|
+
viewer: viewerElement,
|
|
18447
|
+
eventBus,
|
|
18448
|
+
linkService,
|
|
18449
|
+
findController,
|
|
18450
|
+
removePageBorders: true
|
|
18451
|
+
});
|
|
18452
|
+
linkService.setViewer(pdfViewer);
|
|
18453
|
+
linkService.setDocument(pdf);
|
|
18454
|
+
findController.setDocument(pdf);
|
|
18455
|
+
const onPagesInit = () => {
|
|
18456
|
+
pdfViewer.currentScaleValue = "page-width";
|
|
18457
|
+
setScale(pdfViewer.currentScale || 1);
|
|
18458
|
+
setPagesCount(pdfViewer.pagesCount);
|
|
18459
|
+
setPageNumber(pdfViewer.currentPageNumber);
|
|
18460
|
+
};
|
|
18461
|
+
const onPageChanging = (event) => {
|
|
18462
|
+
if (typeof event.pageNumber === "number") {
|
|
18463
|
+
setPageNumber(event.pageNumber);
|
|
18464
|
+
}
|
|
18465
|
+
};
|
|
18466
|
+
const onScaleChanging = (event) => {
|
|
18467
|
+
if (typeof event.scale === "number") {
|
|
18468
|
+
setScale(event.scale);
|
|
18469
|
+
}
|
|
18470
|
+
};
|
|
18471
|
+
eventBus.on("pagesinit", onPagesInit);
|
|
18472
|
+
eventBus.on("pagechanging", onPageChanging);
|
|
18473
|
+
eventBus.on("scalechanging", onScaleChanging);
|
|
18474
|
+
pdfViewerRef.current = pdfViewer;
|
|
18475
|
+
eventBusRef.current = eventBus;
|
|
18476
|
+
findControllerRef.current = findController;
|
|
18477
|
+
setPagesCount(pdf.numPages);
|
|
18478
|
+
pdfViewer.setDocument(pdf);
|
|
18479
|
+
};
|
|
18480
|
+
void renderPdf().catch((nextError) => {
|
|
18481
|
+
if (cancelled) return;
|
|
18482
|
+
if (nextError instanceof Error && nextError.name === "RenderingCancelledException") {
|
|
18483
|
+
return;
|
|
18484
|
+
}
|
|
18485
|
+
setError(formatPdfPreviewError(nextError));
|
|
18486
|
+
});
|
|
18487
|
+
return () => {
|
|
18488
|
+
cancelled = true;
|
|
18489
|
+
loadingTask?.destroy();
|
|
18490
|
+
pdfDocument?.destroy();
|
|
18491
|
+
pdfViewerRef.current?.cleanup();
|
|
18492
|
+
pdfViewerRef.current = null;
|
|
18493
|
+
eventBusRef.current = null;
|
|
18494
|
+
findControllerRef.current = null;
|
|
18495
|
+
};
|
|
18496
|
+
}, [file.content]);
|
|
18497
|
+
const setViewerPage = (nextPage) => {
|
|
18498
|
+
const viewer = pdfViewerRef.current;
|
|
18499
|
+
if (!viewer) return;
|
|
18500
|
+
const clampedPage = Math.min(
|
|
18501
|
+
Math.max(1, nextPage),
|
|
18502
|
+
Math.max(1, viewer.pagesCount)
|
|
18503
|
+
);
|
|
18504
|
+
viewer.currentPageNumber = clampedPage;
|
|
18505
|
+
setPageNumber(clampedPage);
|
|
18506
|
+
};
|
|
18507
|
+
const setViewerScale = (nextScale) => {
|
|
18508
|
+
const viewer = pdfViewerRef.current;
|
|
18509
|
+
if (!viewer) return;
|
|
18510
|
+
const clampedScale = Math.min(3, Math.max(0.5, nextScale));
|
|
18511
|
+
viewer.currentScale = clampedScale;
|
|
18512
|
+
setScale(clampedScale);
|
|
18513
|
+
};
|
|
18514
|
+
const runFind = (findPrevious = false) => {
|
|
18515
|
+
const eventBus = eventBusRef.current;
|
|
18516
|
+
if (!eventBus || !query.trim()) return;
|
|
18517
|
+
eventBus.dispatch("find", {
|
|
18518
|
+
source: eventBus,
|
|
18519
|
+
type: "again",
|
|
18520
|
+
query,
|
|
18521
|
+
phraseSearch: true,
|
|
18522
|
+
caseSensitive: false,
|
|
18523
|
+
entireWord: false,
|
|
18524
|
+
highlightAll: true,
|
|
18525
|
+
findPrevious,
|
|
18526
|
+
matchDiacritics: true
|
|
18527
|
+
});
|
|
18528
|
+
};
|
|
18529
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-full flex-col", children: [
|
|
18530
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 flex-wrap items-center gap-1.5 border-b border-line px-2 py-1.5", children: [
|
|
18531
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
18532
|
+
Button,
|
|
18533
|
+
{
|
|
18534
|
+
type: "button",
|
|
18535
|
+
variant: "ghost",
|
|
18536
|
+
size: "icon-xs",
|
|
18537
|
+
onClick: () => setViewerPage(pageNumber - 1),
|
|
18538
|
+
disabled: pageNumber <= 1,
|
|
18539
|
+
"aria-label": "Previous PDF page",
|
|
18540
|
+
title: "Previous page",
|
|
18541
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeftIcon, { className: "size-3.5" })
|
|
18542
|
+
}
|
|
18543
|
+
),
|
|
18357
18544
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
18358
18545
|
Input,
|
|
18359
18546
|
{
|
|
18360
|
-
|
|
18361
|
-
|
|
18362
|
-
|
|
18363
|
-
|
|
18364
|
-
onChange: (
|
|
18365
|
-
|
|
18366
|
-
|
|
18367
|
-
e.preventDefault();
|
|
18368
|
-
void ask("answer");
|
|
18369
|
-
}
|
|
18370
|
-
}
|
|
18547
|
+
type: "number",
|
|
18548
|
+
min: 1,
|
|
18549
|
+
max: pagesCount || 1,
|
|
18550
|
+
value: pageNumber,
|
|
18551
|
+
onChange: (event) => setViewerPage(Number(event.target.value)),
|
|
18552
|
+
"aria-label": "PDF page number",
|
|
18553
|
+
className: "h-7 w-12 border-input px-1 text-center text-xs"
|
|
18371
18554
|
}
|
|
18372
18555
|
),
|
|
18373
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
18374
|
-
|
|
18375
|
-
|
|
18376
|
-
Button,
|
|
18377
|
-
{
|
|
18378
|
-
type: "button",
|
|
18379
|
-
variant: a.id === "answer" ? "default" : "outline",
|
|
18380
|
-
disabled: busy,
|
|
18381
|
-
"aria-label": a.label,
|
|
18382
|
-
onClick: () => void ask(a.id),
|
|
18383
|
-
children: a.label
|
|
18384
|
-
}
|
|
18385
|
-
) }),
|
|
18386
|
-
/* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side: "top", children: a.hint })
|
|
18387
|
-
] }, a.id)) }),
|
|
18388
|
-
/* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
|
|
18389
|
-
/* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-2 px-1", children: [
|
|
18390
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
18391
|
-
Checkbox,
|
|
18392
|
-
{
|
|
18393
|
-
id: "wb-work-it-out",
|
|
18394
|
-
"aria-label": "Work it out",
|
|
18395
|
-
checked: deep,
|
|
18396
|
-
disabled: busy,
|
|
18397
|
-
onCheckedChange: (v) => setDeep(v === true)
|
|
18398
|
-
}
|
|
18399
|
-
),
|
|
18400
|
-
/* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "wb-work-it-out", children: "Work it out" })
|
|
18401
|
-
] }) }),
|
|
18402
|
-
/* @__PURE__ */ jsxRuntime.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." })
|
|
18403
|
-
] })
|
|
18556
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground", children: [
|
|
18557
|
+
"/ ",
|
|
18558
|
+
pagesCount || "-"
|
|
18404
18559
|
] }),
|
|
18405
|
-
|
|
18560
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
18561
|
+
Button,
|
|
18562
|
+
{
|
|
18563
|
+
type: "button",
|
|
18564
|
+
variant: "ghost",
|
|
18565
|
+
size: "icon-xs",
|
|
18566
|
+
onClick: () => setViewerPage(pageNumber + 1),
|
|
18567
|
+
disabled: pagesCount > 0 && pageNumber >= pagesCount,
|
|
18568
|
+
"aria-label": "Next PDF page",
|
|
18569
|
+
title: "Next page",
|
|
18570
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRightIcon, { className: "size-3.5" })
|
|
18571
|
+
}
|
|
18572
|
+
),
|
|
18573
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-1 h-5 w-px bg-line" }),
|
|
18574
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
18575
|
+
Button,
|
|
18576
|
+
{
|
|
18577
|
+
type: "button",
|
|
18578
|
+
variant: "ghost",
|
|
18579
|
+
size: "icon-xs",
|
|
18580
|
+
onClick: () => setViewerScale(scale - 0.1),
|
|
18581
|
+
disabled: scale <= 0.5,
|
|
18582
|
+
"aria-label": "Zoom out PDF",
|
|
18583
|
+
title: "Zoom out",
|
|
18584
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MinusIcon, { className: "size-3.5" })
|
|
18585
|
+
}
|
|
18586
|
+
),
|
|
18587
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
18588
|
+
"button",
|
|
18589
|
+
{
|
|
18590
|
+
type: "button",
|
|
18591
|
+
className: "h-7 min-w-12 px-1 text-xs text-muted-foreground hover:text-foreground",
|
|
18592
|
+
onClick: () => {
|
|
18593
|
+
const viewer = pdfViewerRef.current;
|
|
18594
|
+
if (!viewer) return;
|
|
18595
|
+
viewer.currentScaleValue = "page-width";
|
|
18596
|
+
setScale(viewer.currentScale || 1);
|
|
18597
|
+
},
|
|
18598
|
+
"aria-label": "Fit PDF to width",
|
|
18599
|
+
title: "Fit width",
|
|
18600
|
+
children: [
|
|
18601
|
+
Math.round(scale * 100),
|
|
18602
|
+
"%"
|
|
18603
|
+
]
|
|
18604
|
+
}
|
|
18605
|
+
),
|
|
18606
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
18406
18607
|
Button,
|
|
18407
18608
|
{
|
|
18408
18609
|
type: "button",
|
|
18409
18610
|
variant: "ghost",
|
|
18410
|
-
|
|
18411
|
-
|
|
18412
|
-
|
|
18413
|
-
|
|
18414
|
-
|
|
18611
|
+
size: "icon-xs",
|
|
18612
|
+
onClick: () => setViewerScale(scale + 0.1),
|
|
18613
|
+
disabled: scale >= 3,
|
|
18614
|
+
"aria-label": "Zoom in PDF",
|
|
18615
|
+
title: "Zoom in",
|
|
18616
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PlusIcon, { className: "size-3.5" })
|
|
18617
|
+
}
|
|
18618
|
+
),
|
|
18619
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-1 h-5 w-px bg-line" }),
|
|
18620
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.SearchIcon, { className: "size-3.5 text-muted-foreground" }),
|
|
18621
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
18622
|
+
Input,
|
|
18623
|
+
{
|
|
18624
|
+
type: "search",
|
|
18625
|
+
value: query,
|
|
18626
|
+
onChange: (event) => setQuery(event.target.value),
|
|
18627
|
+
onKeyDown: (event) => {
|
|
18628
|
+
if (event.key === "Enter") runFind(event.shiftKey);
|
|
18415
18629
|
},
|
|
18416
|
-
|
|
18630
|
+
placeholder: "Find",
|
|
18631
|
+
"aria-label": "Find in PDF",
|
|
18632
|
+
className: "h-7 w-28 border-input px-1 text-xs"
|
|
18417
18633
|
}
|
|
18418
|
-
)
|
|
18419
|
-
|
|
18420
|
-
|
|
18634
|
+
),
|
|
18635
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
18636
|
+
Button,
|
|
18421
18637
|
{
|
|
18422
18638
|
type: "button",
|
|
18423
|
-
"
|
|
18424
|
-
"
|
|
18425
|
-
|
|
18426
|
-
|
|
18427
|
-
|
|
18428
|
-
|
|
18429
|
-
|
|
18430
|
-
|
|
18431
|
-
|
|
18432
|
-
|
|
18433
|
-
|
|
18434
|
-
] })
|
|
18639
|
+
variant: "ghost",
|
|
18640
|
+
size: "xs",
|
|
18641
|
+
onClick: () => runFind(false),
|
|
18642
|
+
disabled: !query.trim(),
|
|
18643
|
+
children: "Find"
|
|
18644
|
+
}
|
|
18645
|
+
)
|
|
18646
|
+
] }),
|
|
18647
|
+
error && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full items-start gap-2 p-3 text-xs text-destructive", children: [
|
|
18648
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0" }),
|
|
18649
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: error })
|
|
18650
|
+
] }),
|
|
18651
|
+
!error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative min-h-105 flex-1 bg-muted/20", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
18652
|
+
"div",
|
|
18653
|
+
{
|
|
18654
|
+
ref: containerRef,
|
|
18655
|
+
"aria-label": `PDF viewer for ${fileName2}`,
|
|
18656
|
+
className: "absolute inset-0 overflow-auto",
|
|
18657
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: viewerRef, className: "pdfViewer" })
|
|
18658
|
+
}
|
|
18659
|
+
) })
|
|
18435
18660
|
] });
|
|
18436
18661
|
}
|
|
18437
|
-
function
|
|
18438
|
-
const
|
|
18439
|
-
|
|
18440
|
-
|
|
18662
|
+
function decodeBase64(content) {
|
|
18663
|
+
const binary = globalThis.atob(content);
|
|
18664
|
+
const bytes = new Uint8Array(binary.length);
|
|
18665
|
+
for (let index = 0; index < binary.length; index += 1) {
|
|
18666
|
+
bytes[index] = binary.charCodeAt(index);
|
|
18667
|
+
}
|
|
18668
|
+
return bytes;
|
|
18669
|
+
}
|
|
18670
|
+
function formatPdfPreviewError(error) {
|
|
18671
|
+
if (error instanceof Error && (error.name === "InvalidCharacterError" || error.message === "Invalid character")) {
|
|
18672
|
+
return "PDF preview data is not valid base64.";
|
|
18673
|
+
}
|
|
18674
|
+
return error instanceof Error ? error.message : "Failed to render PDF preview.";
|
|
18675
|
+
}
|
|
18676
|
+
function WorkspaceFileDrawer({
|
|
18677
|
+
adapter,
|
|
18678
|
+
sessionId,
|
|
18679
|
+
path,
|
|
18680
|
+
onClose
|
|
18681
|
+
}) {
|
|
18682
|
+
const [file, setFile] = react.useState(null);
|
|
18683
|
+
const [loading, setLoading] = react.useState(true);
|
|
18684
|
+
const [error, setError] = react.useState(null);
|
|
18685
|
+
const requestRef = react.useRef(0);
|
|
18686
|
+
react.useEffect(() => {
|
|
18687
|
+
const request = ++requestRef.current;
|
|
18688
|
+
setFile(null);
|
|
18689
|
+
setLoading(true);
|
|
18690
|
+
setError(null);
|
|
18691
|
+
void (async () => {
|
|
18692
|
+
try {
|
|
18693
|
+
const next = await adapter.getWorkspaceFile({ sessionId, path });
|
|
18694
|
+
if (requestRef.current !== request) return;
|
|
18695
|
+
setFile(next);
|
|
18696
|
+
} catch (fetchError) {
|
|
18697
|
+
if (requestRef.current !== request) return;
|
|
18698
|
+
setFile(null);
|
|
18699
|
+
setError(fetchError.message);
|
|
18700
|
+
} finally {
|
|
18701
|
+
if (requestRef.current === request) setLoading(false);
|
|
18702
|
+
}
|
|
18703
|
+
})();
|
|
18704
|
+
}, [adapter, sessionId, path]);
|
|
18705
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
18706
|
+
"aside",
|
|
18707
|
+
{
|
|
18708
|
+
"data-testid": "file-preview-panel",
|
|
18709
|
+
className: "flex h-full min-h-0 w-full flex-col overflow-hidden border-l border-line bg-card",
|
|
18710
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
18711
|
+
FileViewer,
|
|
18712
|
+
{
|
|
18713
|
+
className: "border-t-0",
|
|
18714
|
+
file,
|
|
18715
|
+
loading,
|
|
18716
|
+
error,
|
|
18717
|
+
downloadUrl: file ? adapter.getWorkspaceDownloadUrl({ sessionId, path: file.path }) : null,
|
|
18718
|
+
onDelete: null,
|
|
18719
|
+
onClose
|
|
18720
|
+
}
|
|
18721
|
+
)
|
|
18722
|
+
}
|
|
18723
|
+
);
|
|
18441
18724
|
}
|
|
18442
18725
|
init_utils();
|
|
18443
18726
|
|
|
@@ -18486,7 +18769,7 @@ function FileTree({
|
|
|
18486
18769
|
return /* @__PURE__ */ jsxRuntime.jsx(FileTreeContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
18487
18770
|
"div",
|
|
18488
18771
|
{
|
|
18489
|
-
className: cn("rounded-lg border
|
|
18772
|
+
className: cn("rounded-lg border text-base", className),
|
|
18490
18773
|
role: "tree",
|
|
18491
18774
|
...props,
|
|
18492
18775
|
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-2", children })
|
|
@@ -18610,9 +18893,6 @@ function FileTreeFile({
|
|
|
18610
18893
|
);
|
|
18611
18894
|
}
|
|
18612
18895
|
var SKELETON_WIDTHS2 = [75, 60, 90, 65, 80, 70, 85, 55];
|
|
18613
|
-
var DEFAULT_WIDTH2 = 400;
|
|
18614
|
-
var MIN_WIDTH = 300;
|
|
18615
|
-
var MAX_WIDTH = 900;
|
|
18616
18896
|
function collectExpandedPaths(entries, depth = 0) {
|
|
18617
18897
|
const paths = [];
|
|
18618
18898
|
for (const entry of entries) {
|
|
@@ -18695,7 +18975,7 @@ function RenderEntries({
|
|
|
18695
18975
|
onClick: (event) => event.stopPropagation(),
|
|
18696
18976
|
title: "Download",
|
|
18697
18977
|
"aria-label": `Download ${fileName2}`,
|
|
18698
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.DownloadIcon, { className: "size-
|
|
18978
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.DownloadIcon, { className: "size-5" })
|
|
18699
18979
|
}
|
|
18700
18980
|
),
|
|
18701
18981
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -18709,7 +18989,7 @@ function RenderEntries({
|
|
|
18709
18989
|
},
|
|
18710
18990
|
title: "Delete",
|
|
18711
18991
|
"aria-label": `Delete ${fileName2}`,
|
|
18712
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.TrashIcon, { className: "size-
|
|
18992
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.TrashIcon, { className: "size-5" })
|
|
18713
18993
|
}
|
|
18714
18994
|
)
|
|
18715
18995
|
] })
|
|
@@ -18721,27 +19001,19 @@ function WorkspacePanel({
|
|
|
18721
19001
|
sessionId,
|
|
18722
19002
|
selectedPath,
|
|
18723
19003
|
onSelectedPathChange,
|
|
19004
|
+
onOpenFile,
|
|
18724
19005
|
refreshSignal = 0,
|
|
18725
|
-
disabled = false
|
|
18726
|
-
fillParent = false
|
|
19006
|
+
disabled = false
|
|
18727
19007
|
}) {
|
|
18728
19008
|
const fileInputRef = react.useRef(null);
|
|
18729
19009
|
const [entries, setEntries] = react.useState([]);
|
|
18730
19010
|
const [treeLoading, setTreeLoading] = react.useState(false);
|
|
18731
19011
|
const [treeError, setTreeError] = react.useState(null);
|
|
18732
|
-
const [file, setFile] = react.useState(null);
|
|
18733
|
-
const [fileLoading, setFileLoading] = react.useState(false);
|
|
18734
|
-
const [fileError, setFileError] = react.useState(null);
|
|
18735
19012
|
const [notice, setNotice] = react.useState(null);
|
|
18736
19013
|
const [uploading, setUploading] = react.useState(false);
|
|
18737
19014
|
const [deleteTarget, setDeleteTarget] = react.useState(null);
|
|
18738
19015
|
const [expandedPaths, setExpandedPaths] = react.useState(/* @__PURE__ */ new Set());
|
|
18739
|
-
const [width, setWidth] = react.useState(DEFAULT_WIDTH2);
|
|
18740
|
-
const narrow = useIsMobile();
|
|
18741
19016
|
const sessionIdRef = react.useRef(sessionId);
|
|
18742
|
-
const isResizing = react.useRef(false);
|
|
18743
|
-
const startX = react.useRef(0);
|
|
18744
|
-
const startWidth = react.useRef(DEFAULT_WIDTH2);
|
|
18745
19017
|
sessionIdRef.current = sessionId;
|
|
18746
19018
|
const fetchTree = react.useCallback(async () => {
|
|
18747
19019
|
if (!sessionId) {
|
|
@@ -18771,31 +19043,6 @@ function WorkspacePanel({
|
|
|
18771
19043
|
}
|
|
18772
19044
|
}
|
|
18773
19045
|
}, [adapter, sessionId]);
|
|
18774
|
-
const fetchFile = react.useCallback(
|
|
18775
|
-
async (path) => {
|
|
18776
|
-
if (!sessionId) return;
|
|
18777
|
-
const requestedSessionId = sessionId;
|
|
18778
|
-
setFileLoading(true);
|
|
18779
|
-
setFileError(null);
|
|
18780
|
-
try {
|
|
18781
|
-
const nextFile = await adapter.getWorkspaceFile({
|
|
18782
|
-
sessionId: requestedSessionId,
|
|
18783
|
-
path
|
|
18784
|
-
});
|
|
18785
|
-
if (sessionIdRef.current !== requestedSessionId) return;
|
|
18786
|
-
setFile(nextFile);
|
|
18787
|
-
} catch (error) {
|
|
18788
|
-
if (sessionIdRef.current !== requestedSessionId) return;
|
|
18789
|
-
setFile(null);
|
|
18790
|
-
setFileError(error.message);
|
|
18791
|
-
} finally {
|
|
18792
|
-
if (sessionIdRef.current === requestedSessionId) {
|
|
18793
|
-
setFileLoading(false);
|
|
18794
|
-
}
|
|
18795
|
-
}
|
|
18796
|
-
},
|
|
18797
|
-
[adapter, sessionId]
|
|
18798
|
-
);
|
|
18799
19046
|
react.useEffect(() => {
|
|
18800
19047
|
void fetchTree();
|
|
18801
19048
|
}, [fetchTree]);
|
|
@@ -18808,8 +19055,6 @@ function WorkspacePanel({
|
|
|
18808
19055
|
}, [refreshSignal, sessionId, fetchTree]);
|
|
18809
19056
|
react.useEffect(() => {
|
|
18810
19057
|
if (!sessionId) {
|
|
18811
|
-
setFile(null);
|
|
18812
|
-
setFileError(null);
|
|
18813
19058
|
onSelectedPathChange(null);
|
|
18814
19059
|
}
|
|
18815
19060
|
}, [onSelectedPathChange, sessionId]);
|
|
@@ -18827,22 +19072,15 @@ function WorkspacePanel({
|
|
|
18827
19072
|
return next;
|
|
18828
19073
|
});
|
|
18829
19074
|
}, [selectedPath]);
|
|
18830
|
-
react.useEffect(() => {
|
|
18831
|
-
if (!selectedPath || !sessionId || entries.length === 0) return;
|
|
18832
|
-
const entry = findEntry(entries, selectedPath);
|
|
18833
|
-
if (entry?.kind === "file" && file?.path !== selectedPath) {
|
|
18834
|
-
void fetchFile(selectedPath);
|
|
18835
|
-
}
|
|
18836
|
-
}, [entries, fetchFile, file?.path, selectedPath, sessionId]);
|
|
18837
19075
|
const handleSelect = react.useCallback(
|
|
18838
19076
|
(path) => {
|
|
18839
19077
|
onSelectedPathChange(path);
|
|
18840
19078
|
const entry = findEntry(entries, path);
|
|
18841
19079
|
if (entry?.kind === "file") {
|
|
18842
|
-
|
|
19080
|
+
onOpenFile?.(path);
|
|
18843
19081
|
}
|
|
18844
19082
|
},
|
|
18845
|
-
[entries,
|
|
19083
|
+
[entries, onOpenFile, onSelectedPathChange]
|
|
18846
19084
|
);
|
|
18847
19085
|
const handleUpload = react.useCallback(
|
|
18848
19086
|
async (files) => {
|
|
@@ -18876,8 +19114,6 @@ function WorkspacePanel({
|
|
|
18876
19114
|
await adapter.deleteWorkspaceFile({ sessionId, path });
|
|
18877
19115
|
if (selectedPath === path) {
|
|
18878
19116
|
onSelectedPathChange(null);
|
|
18879
|
-
setFile(null);
|
|
18880
|
-
setFileError(null);
|
|
18881
19117
|
}
|
|
18882
19118
|
setNotice(`Deleted ${path.split("/").pop() ?? path}`);
|
|
18883
19119
|
await fetchTree();
|
|
@@ -18896,188 +19132,131 @@ function WorkspacePanel({
|
|
|
18896
19132
|
sessionId
|
|
18897
19133
|
]
|
|
18898
19134
|
);
|
|
18899
|
-
|
|
18900
|
-
|
|
18901
|
-
|
|
18902
|
-
|
|
18903
|
-
|
|
18904
|
-
|
|
18905
|
-
|
|
18906
|
-
|
|
18907
|
-
|
|
18908
|
-
|
|
18909
|
-
|
|
18910
|
-
|
|
18911
|
-
|
|
18912
|
-
|
|
18913
|
-
|
|
18914
|
-
|
|
18915
|
-
|
|
18916
|
-
|
|
18917
|
-
|
|
18918
|
-
|
|
18919
|
-
|
|
18920
|
-
isResizing.current = false;
|
|
18921
|
-
document.body.style.cursor = "";
|
|
18922
|
-
document.body.style.userSelect = "";
|
|
18923
|
-
};
|
|
18924
|
-
window.addEventListener("mousemove", onMouseMove);
|
|
18925
|
-
window.addEventListener("mouseup", onMouseUp);
|
|
18926
|
-
return () => {
|
|
18927
|
-
window.removeEventListener("mousemove", onMouseMove);
|
|
18928
|
-
window.removeEventListener("mouseup", onMouseUp);
|
|
18929
|
-
};
|
|
18930
|
-
}, []);
|
|
18931
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
18932
|
-
"aside",
|
|
18933
|
-
{
|
|
18934
|
-
"data-testid": "workspace-panel",
|
|
18935
|
-
className: cn(
|
|
18936
|
-
"relative z-10 flex h-full min-h-0 flex-col overflow-hidden border-l border-muted-foreground/20 bg-card",
|
|
18937
|
-
(fillParent || narrow) && "w-full"
|
|
18938
|
-
),
|
|
18939
|
-
style: fillParent || narrow ? void 0 : { width, minWidth: MIN_WIDTH, maxWidth: MAX_WIDTH },
|
|
18940
|
-
children: [
|
|
18941
|
-
!(fillParent || narrow) && /* @__PURE__ */ jsxRuntime.jsx(
|
|
18942
|
-
"div",
|
|
18943
|
-
{
|
|
18944
|
-
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",
|
|
18945
|
-
onMouseDown: onResizeStart
|
|
18946
|
-
}
|
|
18947
|
-
),
|
|
18948
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
18949
|
-
"input",
|
|
18950
|
-
{
|
|
18951
|
-
ref: fileInputRef,
|
|
18952
|
-
type: "file",
|
|
18953
|
-
multiple: true,
|
|
18954
|
-
className: "hidden",
|
|
18955
|
-
disabled,
|
|
18956
|
-
onChange: (event) => {
|
|
18957
|
-
if (event.target.files) void handleUpload(event.target.files);
|
|
18958
|
-
}
|
|
18959
|
-
}
|
|
18960
|
-
),
|
|
18961
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-14 items-center gap-2 border-b border-line px-3 py-3", children: [
|
|
18962
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.FolderOpenIcon, { className: "size-4 shrink-0 text-amber-500" }),
|
|
18963
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate text-sm font-medium text-foreground", children: disabled ? "Workspace - Read-only" : "Workspace" }) }),
|
|
18964
|
-
/* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
|
|
18965
|
-
/* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
18966
|
-
Button,
|
|
18967
|
-
{
|
|
18968
|
-
variant: "ghost",
|
|
18969
|
-
size: "icon-sm",
|
|
18970
|
-
onClick: () => fileInputRef.current?.click(),
|
|
18971
|
-
disabled: disabled || !sessionId || uploading,
|
|
18972
|
-
"aria-label": "Upload files",
|
|
18973
|
-
children: uploading ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2Icon, { className: "size-4 animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.UploadIcon, { className: "size-4" })
|
|
19135
|
+
return (
|
|
19136
|
+
// Always fills its parent: the panel is the body of the Files
|
|
19137
|
+
// accordion, which owns the frame and the height. The side-drawer
|
|
19138
|
+
// it used to be — resize handle, min/max width, its own left
|
|
19139
|
+
// border — went away with the drawer.
|
|
19140
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
19141
|
+
"aside",
|
|
19142
|
+
{
|
|
19143
|
+
"data-testid": "workspace-panel",
|
|
19144
|
+
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",
|
|
19145
|
+
children: [
|
|
19146
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
19147
|
+
"input",
|
|
19148
|
+
{
|
|
19149
|
+
ref: fileInputRef,
|
|
19150
|
+
type: "file",
|
|
19151
|
+
multiple: true,
|
|
19152
|
+
className: "hidden",
|
|
19153
|
+
disabled,
|
|
19154
|
+
onChange: (event) => {
|
|
19155
|
+
if (event.target.files) void handleUpload(event.target.files);
|
|
18974
19156
|
}
|
|
18975
|
-
|
|
18976
|
-
|
|
19157
|
+
}
|
|
19158
|
+
),
|
|
19159
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-end gap-2 border-b border-line px-3 py-1.5", children: [
|
|
19160
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
|
|
19161
|
+
/* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
19162
|
+
Button,
|
|
19163
|
+
{
|
|
19164
|
+
variant: "ghost",
|
|
19165
|
+
size: "icon-sm",
|
|
19166
|
+
onClick: () => fileInputRef.current?.click(),
|
|
19167
|
+
disabled: disabled || !sessionId || uploading,
|
|
19168
|
+
"aria-label": "Upload files",
|
|
19169
|
+
children: uploading ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2Icon, { className: "size-4 animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.UploadIcon, { className: "size-4" })
|
|
19170
|
+
}
|
|
19171
|
+
) }),
|
|
19172
|
+
/* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side: "bottom", children: "Upload files" })
|
|
19173
|
+
] }),
|
|
19174
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
|
|
19175
|
+
/* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
19176
|
+
Button,
|
|
19177
|
+
{
|
|
19178
|
+
variant: "ghost",
|
|
19179
|
+
size: "icon-sm",
|
|
19180
|
+
onClick: () => void fetchTree(),
|
|
19181
|
+
disabled: !sessionId || treeLoading,
|
|
19182
|
+
"aria-label": "Refresh workspace",
|
|
19183
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
19184
|
+
lucideReact.RefreshCwIcon,
|
|
19185
|
+
{
|
|
19186
|
+
className: cn("size-4", treeLoading && "animate-spin")
|
|
19187
|
+
}
|
|
19188
|
+
)
|
|
19189
|
+
}
|
|
19190
|
+
) }),
|
|
19191
|
+
/* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side: "bottom", children: "Refresh" })
|
|
19192
|
+
] })
|
|
18977
19193
|
] }),
|
|
18978
|
-
/* @__PURE__ */ jsxRuntime.
|
|
18979
|
-
|
|
18980
|
-
|
|
19194
|
+
notice && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-b border-line px-3 py-2 text-xs text-muted-foreground", children: notice }),
|
|
19195
|
+
/* @__PURE__ */ jsxRuntime.jsx(ScrollArea, { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-1 py-1", children: [
|
|
19196
|
+
treeLoading && entries.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1 p-2", children: Array.from({ length: 8 }).map((_, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
19197
|
+
Skeleton,
|
|
19198
|
+
{
|
|
19199
|
+
className: "h-5 rounded",
|
|
19200
|
+
style: {
|
|
19201
|
+
width: `${SKELETON_WIDTHS2[index % SKELETON_WIDTHS2.length]}%`
|
|
19202
|
+
}
|
|
19203
|
+
},
|
|
19204
|
+
index
|
|
19205
|
+
)) }),
|
|
19206
|
+
treeError && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-2 p-3 text-sm text-destructive", children: [
|
|
19207
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircleIcon, { className: "mt-0.5 size-4 shrink-0" }),
|
|
19208
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: treeError })
|
|
19209
|
+
] }),
|
|
19210
|
+
!treeLoading && !treeError && entries.length === 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-4 py-8 text-center text-sm text-faint", children: [
|
|
19211
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { children: "No workspace files" }),
|
|
19212
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
19213
|
+
Button,
|
|
19214
|
+
{
|
|
19215
|
+
variant: "outline",
|
|
19216
|
+
size: "sm",
|
|
19217
|
+
className: "mt-3 gap-1.5",
|
|
19218
|
+
onClick: () => fileInputRef.current?.click(),
|
|
19219
|
+
disabled: disabled || !sessionId,
|
|
19220
|
+
children: [
|
|
19221
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.UploadIcon, { className: "size-3.5" }),
|
|
19222
|
+
"Upload files"
|
|
19223
|
+
]
|
|
19224
|
+
}
|
|
19225
|
+
)
|
|
19226
|
+
] }),
|
|
19227
|
+
entries.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
19228
|
+
FileTree,
|
|
18981
19229
|
{
|
|
18982
|
-
|
|
18983
|
-
|
|
18984
|
-
|
|
18985
|
-
|
|
18986
|
-
|
|
19230
|
+
expanded: expandedPaths,
|
|
19231
|
+
onExpandedChange: setExpandedPaths,
|
|
19232
|
+
selectedPath,
|
|
19233
|
+
onSelect: handleSelect,
|
|
19234
|
+
className: "rounded-none border-0",
|
|
18987
19235
|
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
18988
|
-
|
|
19236
|
+
RenderEntries,
|
|
18989
19237
|
{
|
|
18990
|
-
|
|
19238
|
+
entries,
|
|
19239
|
+
onDelete: setDeleteTarget,
|
|
19240
|
+
downloadUrlFor: (path) => sessionId ? adapter.getWorkspaceDownloadUrl({ sessionId, path }) : "#"
|
|
18991
19241
|
}
|
|
18992
19242
|
)
|
|
18993
19243
|
}
|
|
18994
|
-
) }),
|
|
18995
|
-
/* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side: "bottom", children: "Refresh" })
|
|
18996
|
-
] })
|
|
18997
|
-
] }),
|
|
18998
|
-
notice && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-b border-line px-3 py-2 text-xs text-muted-foreground", children: notice }),
|
|
18999
|
-
/* @__PURE__ */ jsxRuntime.jsx(ScrollArea, { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-1 py-1", children: [
|
|
19000
|
-
treeLoading && entries.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1 p-2", children: Array.from({ length: 8 }).map((_, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
19001
|
-
Skeleton,
|
|
19002
|
-
{
|
|
19003
|
-
className: "h-5 rounded",
|
|
19004
|
-
style: {
|
|
19005
|
-
width: `${SKELETON_WIDTHS2[index % SKELETON_WIDTHS2.length]}%`
|
|
19006
|
-
}
|
|
19007
|
-
},
|
|
19008
|
-
index
|
|
19009
|
-
)) }),
|
|
19010
|
-
treeError && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-2 p-3 text-sm text-destructive", children: [
|
|
19011
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircleIcon, { className: "mt-0.5 size-4 shrink-0" }),
|
|
19012
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children: treeError })
|
|
19013
|
-
] }),
|
|
19014
|
-
!treeLoading && !treeError && entries.length === 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-4 py-8 text-center text-sm text-faint", children: [
|
|
19015
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { children: "No workspace files" }),
|
|
19016
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
19017
|
-
Button,
|
|
19018
|
-
{
|
|
19019
|
-
variant: "outline",
|
|
19020
|
-
size: "sm",
|
|
19021
|
-
className: "mt-3 gap-1.5",
|
|
19022
|
-
onClick: () => fileInputRef.current?.click(),
|
|
19023
|
-
disabled: disabled || !sessionId,
|
|
19024
|
-
children: [
|
|
19025
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.UploadIcon, { className: "size-3.5" }),
|
|
19026
|
-
"Upload files"
|
|
19027
|
-
]
|
|
19028
|
-
}
|
|
19029
19244
|
)
|
|
19030
|
-
] }),
|
|
19031
|
-
|
|
19032
|
-
|
|
19245
|
+
] }) }),
|
|
19246
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
19247
|
+
ConfirmDialog2,
|
|
19033
19248
|
{
|
|
19034
|
-
|
|
19035
|
-
|
|
19036
|
-
|
|
19037
|
-
|
|
19038
|
-
|
|
19039
|
-
|
|
19040
|
-
RenderEntries,
|
|
19041
|
-
{
|
|
19042
|
-
entries,
|
|
19043
|
-
onDelete: setDeleteTarget,
|
|
19044
|
-
downloadUrlFor: (path) => sessionId ? adapter.getWorkspaceDownloadUrl({ sessionId, path }) : "#"
|
|
19045
|
-
}
|
|
19046
|
-
)
|
|
19249
|
+
open: deleteTarget !== null,
|
|
19250
|
+
title: "Delete file?",
|
|
19251
|
+
description: `This will permanently delete ${deleteTarget?.split("/").pop() ?? "this file"} from the workspace.`,
|
|
19252
|
+
confirmLabel: "Delete",
|
|
19253
|
+
onConfirm: () => deleteTarget ? handleDelete(deleteTarget) : Promise.resolve(),
|
|
19254
|
+
onCancel: () => setDeleteTarget(null)
|
|
19047
19255
|
}
|
|
19048
19256
|
)
|
|
19049
|
-
]
|
|
19050
|
-
|
|
19051
|
-
|
|
19052
|
-
{
|
|
19053
|
-
file,
|
|
19054
|
-
loading: fileLoading,
|
|
19055
|
-
error: fileError,
|
|
19056
|
-
downloadUrl: file && sessionId ? adapter.getWorkspaceDownloadUrl({
|
|
19057
|
-
sessionId,
|
|
19058
|
-
path: file.path
|
|
19059
|
-
}) : null,
|
|
19060
|
-
onDelete: file && !disabled ? () => setDeleteTarget(file.path) : null,
|
|
19061
|
-
onClose: () => {
|
|
19062
|
-
onSelectedPathChange(null);
|
|
19063
|
-
setFile(null);
|
|
19064
|
-
setFileError(null);
|
|
19065
|
-
}
|
|
19066
|
-
}
|
|
19067
|
-
),
|
|
19068
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
19069
|
-
ConfirmDialog2,
|
|
19070
|
-
{
|
|
19071
|
-
open: deleteTarget !== null,
|
|
19072
|
-
title: "Delete file?",
|
|
19073
|
-
description: `This will permanently delete ${deleteTarget?.split("/").pop() ?? "this file"} from the workspace.`,
|
|
19074
|
-
confirmLabel: "Delete",
|
|
19075
|
-
onConfirm: () => deleteTarget ? handleDelete(deleteTarget) : Promise.resolve(),
|
|
19076
|
-
onCancel: () => setDeleteTarget(null)
|
|
19077
|
-
}
|
|
19078
|
-
)
|
|
19079
|
-
]
|
|
19080
|
-
}
|
|
19257
|
+
]
|
|
19258
|
+
}
|
|
19259
|
+
)
|
|
19081
19260
|
);
|
|
19082
19261
|
}
|
|
19083
19262
|
function ConfirmDialog2({
|
|
@@ -19162,12 +19341,15 @@ function readOnlyReasonForSession(session) {
|
|
|
19162
19341
|
return { readOnly: false };
|
|
19163
19342
|
}
|
|
19164
19343
|
var RIGHT_STACK_STYLE = {
|
|
19165
|
-
|
|
19344
|
+
// Both the column's width and the chat panel's right offset read this, so
|
|
19345
|
+
// one value keeps them complementary. 50%: the browser shell renders a real
|
|
19346
|
+
// page, and page layouts are unusable in a 440px strip.
|
|
19347
|
+
["--right-stack-w"]: "50%"
|
|
19166
19348
|
};
|
|
19167
19349
|
var MOBILE_PANE_LABELS = {
|
|
19168
19350
|
chat: "Chat",
|
|
19169
19351
|
browser: "Browser",
|
|
19170
|
-
|
|
19352
|
+
file: "Preview"
|
|
19171
19353
|
};
|
|
19172
19354
|
function AgentChat({
|
|
19173
19355
|
adapter,
|
|
@@ -19193,9 +19375,10 @@ function AgentChat({
|
|
|
19193
19375
|
onOpenBilling
|
|
19194
19376
|
}) {
|
|
19195
19377
|
const [workspacePath, setWorkspacePath] = react.useState(null);
|
|
19378
|
+
const [previewPath, setPreviewPath] = react.useState(null);
|
|
19196
19379
|
const [mobileView, setMobileView] = react.useState("chat");
|
|
19197
|
-
const [
|
|
19198
|
-
const [
|
|
19380
|
+
const [openPane, setOpenPane] = react.useState(null);
|
|
19381
|
+
const [filesOpen, setFilesOpen] = react.useState(false);
|
|
19199
19382
|
const runtime = useAgentChatRuntime({
|
|
19200
19383
|
adapter,
|
|
19201
19384
|
agentId,
|
|
@@ -19203,8 +19386,15 @@ function AgentChat({
|
|
|
19203
19386
|
onSessionChange
|
|
19204
19387
|
});
|
|
19205
19388
|
react.useEffect(() => {
|
|
19206
|
-
|
|
19389
|
+
setOpenPane(null);
|
|
19390
|
+
setFilesOpen(false);
|
|
19207
19391
|
}, [runtime.viewMode]);
|
|
19392
|
+
react.useEffect(() => {
|
|
19393
|
+
setFilesOpen(false);
|
|
19394
|
+
setWorkspacePath(null);
|
|
19395
|
+
setPreviewPath(null);
|
|
19396
|
+
setOpenPane(null);
|
|
19397
|
+
}, [sessionId]);
|
|
19208
19398
|
const readOnly = readOnlyReasonForSession(runtime.session);
|
|
19209
19399
|
const boardAvailable = whiteboardEnabled && isBoardSession(runtime.session);
|
|
19210
19400
|
const viewMode = runtime.viewMode === "whiteboard" && !boardAvailable ? "simple" : runtime.viewMode;
|
|
@@ -19221,35 +19411,54 @@ function AgentChat({
|
|
|
19221
19411
|
const hideTurnSummary = isSubAgentSession(runtime.session) || orchestratesDeepResearch || isBoardSession(runtime.session);
|
|
19222
19412
|
const browserState = runtime.state.browser;
|
|
19223
19413
|
const browserAvailable = browserState !== null && browserState.status !== "closed" && !!sessionId;
|
|
19224
|
-
const browserVisible = browserAvailable &&
|
|
19414
|
+
const browserVisible = browserAvailable && openPane === "browser";
|
|
19225
19415
|
const workspaceAvailable = !!sessionId;
|
|
19226
|
-
const
|
|
19227
|
-
const rightStackVisible = browserVisible ||
|
|
19228
|
-
const
|
|
19416
|
+
const filePreviewVisible = workspaceAvailable && previewPath !== null && openPane === "file";
|
|
19417
|
+
const rightStackVisible = browserVisible || filePreviewVisible;
|
|
19418
|
+
const browserPreview = useBrowserPreview({
|
|
19419
|
+
adapter,
|
|
19420
|
+
sessionId,
|
|
19421
|
+
enabled: browserAvailable
|
|
19422
|
+
});
|
|
19423
|
+
const browserRunning = browserAvailable;
|
|
19424
|
+
react.useEffect(() => {
|
|
19425
|
+
if (openPane === "browser" && !browserAvailable) setOpenPane(null);
|
|
19426
|
+
if (openPane === "file" && (!workspaceAvailable || previewPath === null))
|
|
19427
|
+
setOpenPane(null);
|
|
19428
|
+
}, [openPane, browserAvailable, workspaceAvailable, previewPath]);
|
|
19229
19429
|
react.useEffect(() => {
|
|
19230
19430
|
onMessagesChange?.(runtime.messages);
|
|
19231
19431
|
}, [onMessagesChange, runtime.messages]);
|
|
19432
|
+
const handleOpenFilePreview = react.useCallback((path) => {
|
|
19433
|
+
setWorkspacePath(path);
|
|
19434
|
+
setPreviewPath(path);
|
|
19435
|
+
setOpenPane("file");
|
|
19436
|
+
setMobileView("file");
|
|
19437
|
+
}, []);
|
|
19438
|
+
const handleSelectedPathChange = react.useCallback((path) => {
|
|
19439
|
+
setWorkspacePath(path);
|
|
19440
|
+
if (path === null) setPreviewPath(null);
|
|
19441
|
+
}, []);
|
|
19232
19442
|
const handleFileSelect = react.useCallback(
|
|
19233
19443
|
(path) => {
|
|
19234
|
-
|
|
19235
|
-
setMobileView("workspace");
|
|
19236
|
-
setShowWorkspace(true);
|
|
19444
|
+
handleOpenFilePreview(path);
|
|
19237
19445
|
onFileSelect?.(path);
|
|
19238
19446
|
},
|
|
19239
|
-
[onFileSelect]
|
|
19447
|
+
[handleOpenFilePreview, onFileSelect]
|
|
19240
19448
|
);
|
|
19241
19449
|
const handleToggleBrowser = react.useCallback(() => {
|
|
19242
|
-
|
|
19450
|
+
setOpenPane((prev) => prev === "browser" ? null : "browser");
|
|
19451
|
+
setMobileView("browser");
|
|
19243
19452
|
}, []);
|
|
19244
|
-
const
|
|
19245
|
-
|
|
19453
|
+
const handleToggleFiles = react.useCallback(() => {
|
|
19454
|
+
setFilesOpen((open) => !open);
|
|
19246
19455
|
}, []);
|
|
19247
19456
|
const mobilePanes = react.useMemo(() => {
|
|
19248
19457
|
const panes = ["chat"];
|
|
19249
19458
|
if (browserVisible) panes.push("browser");
|
|
19250
|
-
if (
|
|
19459
|
+
if (filePreviewVisible) panes.push("file");
|
|
19251
19460
|
return panes;
|
|
19252
|
-
}, [browserVisible,
|
|
19461
|
+
}, [browserVisible, filePreviewVisible]);
|
|
19253
19462
|
const activeMobilePane = mobilePanes.includes(mobileView) ? mobileView : "chat";
|
|
19254
19463
|
const showMobileToggle = mobilePanes.length > 1;
|
|
19255
19464
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -19274,7 +19483,7 @@ function AgentChat({
|
|
|
19274
19483
|
// is visible. With both panes, absolute positioning lets the
|
|
19275
19484
|
// browser/workspace split occupy a fixed width. Without the
|
|
19276
19485
|
// right stack, the chat takes the full width.
|
|
19277
|
-
|
|
19486
|
+
rightStackVisible ? "md:relative md:flex-row" : "md:flex-row"
|
|
19278
19487
|
),
|
|
19279
19488
|
style: { direction: "ltr", ...RIGHT_STACK_STYLE },
|
|
19280
19489
|
children: [
|
|
@@ -19307,14 +19516,12 @@ function AgentChat({
|
|
|
19307
19516
|
className: cn(
|
|
19308
19517
|
// Phone: full width column, hidden while another pane is picked.
|
|
19309
19518
|
"flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden",
|
|
19310
|
-
showMobileToggle && "data-[mobile-view=browser]:hidden data-[mobile-view=
|
|
19311
|
-
// md+:
|
|
19312
|
-
//
|
|
19313
|
-
//
|
|
19314
|
-
//
|
|
19315
|
-
|
|
19316
|
-
// space when nothing else is visible).
|
|
19317
|
-
browserVisible ? "md:absolute md:inset-y-0 md:left-0 md:right-(--right-stack-w,440px) md:flex" : "md:relative md:flex-1"
|
|
19519
|
+
showMobileToggle && "data-[mobile-view=browser]:hidden data-[mobile-view=file]:hidden md:flex!",
|
|
19520
|
+
// md+: when the right column is laid out (browser pane or file
|
|
19521
|
+
// preview), pin it at the configured width and absolutely
|
|
19522
|
+
// position the chat panel beside it. Otherwise the chat panel
|
|
19523
|
+
// fills the space.
|
|
19524
|
+
rightStackVisible ? "md:absolute md:inset-y-0 md:left-0 md:right-(--right-stack-w,440px) md:flex" : "md:relative md:flex-1"
|
|
19318
19525
|
),
|
|
19319
19526
|
children: viewMode === "whiteboard" ? (
|
|
19320
19527
|
// The board replaces the transcript, on the same runtime
|
|
@@ -19354,12 +19561,37 @@ function AgentChat({
|
|
|
19354
19561
|
onSelectBrowserProfile,
|
|
19355
19562
|
browserProfilesEnabled,
|
|
19356
19563
|
browserProfileLocked: !!sessionId,
|
|
19357
|
-
showBrowser,
|
|
19564
|
+
showBrowser: openPane === "browser",
|
|
19358
19565
|
onToggleBrowser: handleToggleBrowser,
|
|
19359
|
-
showWorkspace,
|
|
19360
|
-
onToggleWorkspace:
|
|
19566
|
+
showWorkspace: filesOpen,
|
|
19567
|
+
onToggleWorkspace: handleToggleFiles,
|
|
19361
19568
|
canShowBrowser: browserAvailable,
|
|
19362
19569
|
canShowWorkspace: workspaceAvailable,
|
|
19570
|
+
paneCards: {
|
|
19571
|
+
browser: browserRunning ? {
|
|
19572
|
+
subtitle: browserState?.controlOwner ? `${browserState.controlOwner} has control` : void 0,
|
|
19573
|
+
thumbnail: browserPreview,
|
|
19574
|
+
// Show and hide on the same card, like the Files
|
|
19575
|
+
// accordion header and the composer's Tools item.
|
|
19576
|
+
onOpen: handleToggleBrowser
|
|
19577
|
+
} : null,
|
|
19578
|
+
files: workspaceAvailable ? {
|
|
19579
|
+
open: filesOpen,
|
|
19580
|
+
onToggle: handleToggleFiles,
|
|
19581
|
+
panel: /* @__PURE__ */ jsxRuntime.jsx(
|
|
19582
|
+
WorkspacePanel,
|
|
19583
|
+
{
|
|
19584
|
+
adapter,
|
|
19585
|
+
sessionId,
|
|
19586
|
+
selectedPath: workspacePath,
|
|
19587
|
+
onSelectedPathChange: handleSelectedPathChange,
|
|
19588
|
+
onOpenFile: handleOpenFilePreview,
|
|
19589
|
+
refreshSignal: runtime.workspaceRefreshKey,
|
|
19590
|
+
disabled: effectiveDisabled
|
|
19591
|
+
}
|
|
19592
|
+
)
|
|
19593
|
+
} : null
|
|
19594
|
+
},
|
|
19363
19595
|
viewMode,
|
|
19364
19596
|
onViewModeChange: runtime.setViewMode,
|
|
19365
19597
|
deepResearchEnabled,
|
|
@@ -19387,13 +19619,10 @@ function AgentChat({
|
|
|
19387
19619
|
// Phone: full width column, hidden when chat tab active.
|
|
19388
19620
|
"flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden",
|
|
19389
19621
|
showMobileToggle && "data-[mobile-view=chat]:hidden md:flex!",
|
|
19390
|
-
// md+:
|
|
19391
|
-
//
|
|
19392
|
-
//
|
|
19393
|
-
|
|
19394
|
-
// WorkspacePanel manage its own width via the resize
|
|
19395
|
-
// handle.
|
|
19396
|
-
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"
|
|
19622
|
+
// md+: absolute right column at the configured width. The
|
|
19623
|
+
// browser is the only pane that lives here — files expand
|
|
19624
|
+
// as an accordion inside the chat column.
|
|
19625
|
+
"md:absolute md:inset-y-0 md:right-0 md:w-(--right-stack-w,440px) md:flex-none"
|
|
19397
19626
|
),
|
|
19398
19627
|
children: [
|
|
19399
19628
|
browserVisible && /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -19401,42 +19630,25 @@ function AgentChat({
|
|
|
19401
19630
|
{
|
|
19402
19631
|
"data-testid": "browser-panel",
|
|
19403
19632
|
"data-mobile-view": activeMobilePane,
|
|
19404
|
-
className:
|
|
19405
|
-
"min-h-0 w-full overflow-hidden",
|
|
19406
|
-
// With both panes the desktop splits the column in half.
|
|
19407
|
-
// A phone gives whichever pane is selected the whole
|
|
19408
|
-
// height instead — see the mobile toggle above.
|
|
19409
|
-
bothPanesVisible ? "h-full data-[mobile-view=workspace]:hidden md:block! md:h-1/2 md:border-b md:border-line" : "h-full"
|
|
19410
|
-
),
|
|
19633
|
+
className: "min-h-0 h-full w-full overflow-hidden",
|
|
19411
19634
|
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
19412
19635
|
BrowserPane,
|
|
19413
19636
|
{
|
|
19414
19637
|
sessionId,
|
|
19415
19638
|
state: browserState,
|
|
19416
19639
|
adapter,
|
|
19417
|
-
onClose: () =>
|
|
19640
|
+
onClose: () => setOpenPane(null)
|
|
19418
19641
|
}
|
|
19419
19642
|
)
|
|
19420
19643
|
}
|
|
19421
19644
|
),
|
|
19422
|
-
|
|
19423
|
-
|
|
19645
|
+
filePreviewVisible && sessionId && previewPath && /* @__PURE__ */ jsxRuntime.jsx(
|
|
19646
|
+
WorkspaceFileDrawer,
|
|
19424
19647
|
{
|
|
19425
|
-
|
|
19426
|
-
|
|
19427
|
-
|
|
19428
|
-
|
|
19429
|
-
WorkspacePanel,
|
|
19430
|
-
{
|
|
19431
|
-
adapter,
|
|
19432
|
-
sessionId,
|
|
19433
|
-
selectedPath: workspacePath,
|
|
19434
|
-
onSelectedPathChange: setWorkspacePath,
|
|
19435
|
-
refreshSignal: runtime.workspaceRefreshKey,
|
|
19436
|
-
disabled: effectiveDisabled,
|
|
19437
|
-
fillParent: bothPanesVisible
|
|
19438
|
-
}
|
|
19439
|
-
)
|
|
19648
|
+
adapter,
|
|
19649
|
+
sessionId,
|
|
19650
|
+
path: previewPath,
|
|
19651
|
+
onClose: () => setOpenPane(null)
|
|
19440
19652
|
}
|
|
19441
19653
|
)
|
|
19442
19654
|
]
|
|
@@ -23463,7 +23675,7 @@ exports.AgentChat = AgentChat;
|
|
|
23463
23675
|
exports.AgentChatAdapterProvider = AgentChatAdapterProvider;
|
|
23464
23676
|
exports.AgentWhiteboard = AgentWhiteboard;
|
|
23465
23677
|
exports.BrandBeam = BrandBeam;
|
|
23466
|
-
exports.
|
|
23678
|
+
exports.BrowserShell = BrowserShell;
|
|
23467
23679
|
exports.CitationText = CitationText;
|
|
23468
23680
|
exports.CodingAgentsPanel = CodingAgentsPanel;
|
|
23469
23681
|
exports.ComposioConnectCard = ComposioConnectCard;
|
|
@@ -23508,6 +23720,7 @@ exports.stripMissionControlMarkup = stripMissionControlMarkup;
|
|
|
23508
23720
|
exports.toolOrbState = toolOrbState;
|
|
23509
23721
|
exports.useAgentChatAdapterContext = useAgentChatAdapterContext;
|
|
23510
23722
|
exports.useAgentChatRuntime = useAgentChatRuntime;
|
|
23723
|
+
exports.useBrowserControl = useBrowserControl;
|
|
23511
23724
|
exports.useChatViewMode = useChatViewMode;
|
|
23512
23725
|
exports.useInboxUnreadCount = useInboxUnreadCount;
|
|
23513
23726
|
exports.useMissionEvents = useMissionEvents;
|