@tutti-os/agent-gui 0.0.40 → 0.0.41

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.
Files changed (40) hide show
  1. package/dist/{AgentMessageMarkdown-GDgLA-im.d.ts → AgentMessageMarkdown-Cts0dAIm.d.ts} +1 -0
  2. package/dist/agent-conversation/index.d.ts +1 -1
  3. package/dist/agent-conversation/index.js +4 -4
  4. package/dist/agent-message-center/index.js +6 -6
  5. package/dist/agent-message-center/index.js.map +1 -1
  6. package/dist/app/renderer/agentactivity.css +298 -21
  7. package/dist/{chunk-UUM2GZXH.js → chunk-3PVSIL3H.js} +4 -4
  8. package/dist/{chunk-RE3AHBX6.js → chunk-3WZWZHAJ.js} +2 -2
  9. package/dist/{chunk-Y2DQJPTL.js → chunk-42WP3P5C.js} +2 -2
  10. package/dist/{chunk-Y5SE2XMU.js → chunk-5LVFYNIP.js} +5 -2
  11. package/dist/{chunk-Y5SE2XMU.js.map → chunk-5LVFYNIP.js.map} +1 -1
  12. package/dist/{chunk-EQ4RSPQE.js → chunk-5U4SXENC.js} +4 -4
  13. package/dist/{chunk-WJPEKD3F.js → chunk-LKOPLDPM.js} +5 -1
  14. package/dist/chunk-LKOPLDPM.js.map +1 -0
  15. package/dist/{chunk-DO7D7OOL.js → chunk-MUJYJDZH.js} +11 -10
  16. package/dist/chunk-MUJYJDZH.js.map +1 -0
  17. package/dist/{chunk-WC34LVH6.js → chunk-OLZP2EFK.js} +230 -45
  18. package/dist/chunk-OLZP2EFK.js.map +1 -0
  19. package/dist/{chunk-R7XH3C4Y.js → chunk-QIPMH6XO.js} +135 -35
  20. package/dist/chunk-QIPMH6XO.js.map +1 -0
  21. package/dist/context-mention-palette/index.js +5 -5
  22. package/dist/i18n/index.d.ts +4 -0
  23. package/dist/i18n/index.js +1 -1
  24. package/dist/index.d.ts +12 -2
  25. package/dist/index.js +136 -102
  26. package/dist/index.js.map +1 -1
  27. package/dist/workbench/contribution.d.ts +4 -0
  28. package/dist/workbench/contribution.js +1 -1
  29. package/dist/workbench/index.d.ts +10 -3
  30. package/dist/workbench/index.js +1 -1
  31. package/dist/workspace-agent-generated-files.js +2 -2
  32. package/package.json +12 -12
  33. package/dist/chunk-DO7D7OOL.js.map +0 -1
  34. package/dist/chunk-R7XH3C4Y.js.map +0 -1
  35. package/dist/chunk-WC34LVH6.js.map +0 -1
  36. package/dist/chunk-WJPEKD3F.js.map +0 -1
  37. /package/dist/{chunk-UUM2GZXH.js.map → chunk-3PVSIL3H.js.map} +0 -0
  38. /package/dist/{chunk-RE3AHBX6.js.map → chunk-3WZWZHAJ.js.map} +0 -0
  39. /package/dist/{chunk-Y2DQJPTL.js.map → chunk-42WP3P5C.js.map} +0 -0
  40. /package/dist/{chunk-EQ4RSPQE.js.map → chunk-5U4SXENC.js.map} +0 -0
@@ -5,7 +5,7 @@ import {
5
5
  import {
6
6
  translate,
7
7
  useTranslation
8
- } from "./chunk-WJPEKD3F.js";
8
+ } from "./chunk-LKOPLDPM.js";
9
9
  import {
10
10
  resolveAgentWorkspaceFileVisualKind
11
11
  } from "./chunk-PJP5BUU6.js";
@@ -192,7 +192,7 @@ import {
192
192
  useEffect as useEffect3,
193
193
  useContext,
194
194
  memo,
195
- useMemo as useMemo2,
195
+ useMemo,
196
196
  useState as useState4
197
197
  } from "react";
198
198
 
@@ -202,9 +202,14 @@ import {
202
202
  isValidElement,
203
203
  useCallback as useCallback2,
204
204
  useEffect,
205
- useMemo,
206
205
  useState as useState2
207
206
  } from "react";
207
+ import {
208
+ ToastProvider,
209
+ ToastRoot,
210
+ ToastTitle,
211
+ ToastViewport
212
+ } from "@tutti-os/ui-system";
208
213
  import { CopyIcon, DownloadIcon } from "lucide-react";
209
214
  import Zoom from "react-medium-image-zoom";
210
215
 
@@ -261,12 +266,19 @@ async function blobToBase64(blob) {
261
266
  reader.readAsDataURL(blob);
262
267
  });
263
268
  }
264
- async function copyImageWithWebClipboard(blob) {
269
+ async function pngBlobFromSrc(src) {
270
+ const blob = await imageSrcToPngBlob(src);
271
+ if (!blob) {
272
+ throw new Error("image conversion failed");
273
+ }
274
+ return blob;
275
+ }
276
+ async function copyImageWithWebClipboard(data) {
265
277
  if (typeof navigator === "undefined" || typeof navigator.clipboard?.write !== "function" || typeof ClipboardItem === "undefined") {
266
278
  return false;
267
279
  }
268
280
  try {
269
- await navigator.clipboard.write([new ClipboardItem({ "image/png": blob })]);
281
+ await navigator.clipboard.write([new ClipboardItem({ "image/png": data })]);
270
282
  return true;
271
283
  } catch {
272
284
  return false;
@@ -274,19 +286,20 @@ async function copyImageWithWebClipboard(blob) {
274
286
  }
275
287
  async function copyImageToClipboard(src, hostClipboard) {
276
288
  try {
289
+ if (!hostClipboard?.writeImage) {
290
+ return await copyImageWithWebClipboard(pngBlobFromSrc(src));
291
+ }
277
292
  const blob = await imageSrcToPngBlob(src);
278
293
  if (!blob) {
279
294
  return false;
280
295
  }
281
- if (hostClipboard?.writeImage) {
282
- try {
283
- await hostClipboard.writeImage({
284
- data: await blobToBase64(blob),
285
- mimeType: "image/png"
286
- });
287
- return true;
288
- } catch {
289
- }
296
+ try {
297
+ await hostClipboard.writeImage({
298
+ data: await blobToBase64(blob),
299
+ mimeType: "image/png"
300
+ });
301
+ return true;
302
+ } catch {
290
303
  }
291
304
  return copyImageWithWebClipboard(blob);
292
305
  } catch {
@@ -338,6 +351,7 @@ function ConversationImageContextMenu({
338
351
  // app/renderer/components/ZoomableImage.tsx
339
352
  import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
340
353
  function ZoomableImage({
354
+ alt,
341
355
  className,
342
356
  downloadName,
343
357
  onContextMenu,
@@ -346,13 +360,11 @@ function ZoomableImage({
346
360
  ...props
347
361
  }) {
348
362
  const { t } = useTranslation();
363
+ const agentHostApi = useOptionalAgentHostApi();
349
364
  const actionSource = typeof src === "string" && src.trim() ? src.trim() : null;
350
365
  const hasImageActions = Boolean(actionSource && downloadName !== void 0);
351
- const resolvedDownloadName = useMemo(
352
- () => resolveImageDownloadName(downloadName, actionSource),
353
- [actionSource, downloadName]
354
- );
355
366
  const [contextMenuPosition, setContextMenuPosition] = useState2(null);
367
+ const [copyStatus, setCopyStatus] = useState2(null);
356
368
  const closeContextMenu = useCallback2(() => {
357
369
  setContextMenuPosition(null);
358
370
  }, []);
@@ -367,6 +379,13 @@ function ZoomableImage({
367
379
  document.removeEventListener("scroll", closeContextMenu, true);
368
380
  };
369
381
  }, [closeContextMenu, contextMenuPosition]);
382
+ useEffect(() => {
383
+ if (!copyStatus || copyStatus.busy) {
384
+ return;
385
+ }
386
+ const timer = setTimeout(() => setCopyStatus(null), 1600);
387
+ return () => clearTimeout(timer);
388
+ }, [copyStatus]);
370
389
  const handleContextMenu = useCallback2(
371
390
  (event) => {
372
391
  onContextMenu?.(event);
@@ -374,7 +393,12 @@ function ZoomableImage({
374
393
  return;
375
394
  }
376
395
  event.preventDefault();
377
- setContextMenuPosition({ x: event.clientX, y: event.clientY });
396
+ event.stopPropagation();
397
+ setContextMenuPosition({
398
+ x: event.clientX,
399
+ y: event.clientY,
400
+ inZoomDialog: Boolean(event.currentTarget.closest(".tsh-zoom-dialog"))
401
+ });
378
402
  },
379
403
  [actionSource, hasImageActions, onContextMenu]
380
404
  );
@@ -382,9 +406,28 @@ function ZoomableImage({
382
406
  if (!actionSource) {
383
407
  return;
384
408
  }
409
+ const copyingMessage = t("common.copying");
410
+ setCopyStatus({
411
+ busy: true,
412
+ message: copyingMessage,
413
+ variant: "success"
414
+ });
385
415
  closeContextMenu();
386
- await copyImageToClipboard(actionSource);
387
- }, [actionSource, closeContextMenu]);
416
+ const copied = await Promise.race([
417
+ copyImageToClipboard(actionSource, agentHostApi?.clipboard),
418
+ new Promise((resolve) => {
419
+ window.setTimeout(() => resolve(false), 5e3);
420
+ })
421
+ ]);
422
+ const message = t(
423
+ copied ? "agentHost.agentGui.messageCopied" : "common.copyFailed"
424
+ );
425
+ setCopyStatus({
426
+ busy: false,
427
+ message,
428
+ variant: copied ? "success" : "destructive"
429
+ });
430
+ }, [actionSource, agentHostApi?.clipboard, closeContextMenu, t]);
388
431
  const handleCopyImageAction = useCallback2(() => {
389
432
  void handleCopyImage().catch(() => void 0);
390
433
  }, [handleCopyImage]);
@@ -393,8 +436,11 @@ function ZoomableImage({
393
436
  return;
394
437
  }
395
438
  closeContextMenu();
396
- downloadImage(actionSource, resolvedDownloadName);
397
- }, [actionSource, closeContextMenu, resolvedDownloadName]);
439
+ downloadImage(
440
+ actionSource,
441
+ resolveImageDownloadName(downloadName, actionSource, alt)
442
+ );
443
+ }, [actionSource, alt, closeContextMenu, downloadName]);
398
444
  const actionButtons = hasImageActions ? /* @__PURE__ */ jsx2(
399
445
  ImageActionButtons,
400
446
  {
@@ -410,7 +456,9 @@ function ZoomableImage({
410
456
  }) => {
411
457
  const zoomSrc = isValidElement(img) && typeof img.props.src === "string" ? img.props.src : null;
412
458
  return /* @__PURE__ */ jsxs2(Fragment, { children: [
413
- !actionButtons && img && zoomSrc ? /* @__PURE__ */ jsx2(
459
+ actionButtons && img && zoomSrc ? cloneElement(img, {
460
+ onContextMenu: handleContextMenu
461
+ }) : !actionButtons && img && zoomSrc ? /* @__PURE__ */ jsx2(
414
462
  ConversationImageContextMenu,
415
463
  {
416
464
  src: zoomSrc,
@@ -419,7 +467,37 @@ function ZoomableImage({
419
467
  children: img
420
468
  }
421
469
  ) : img,
422
- actionButtons ? /* @__PURE__ */ jsx2("div", { className: "tsh-zoom-dialog__image-actions nodrag tsh-desktop-no-drag", children: actionButtons }) : null,
470
+ actionButtons ? /* @__PURE__ */ jsx2("div", { className: "tsh-zoom-dialog__image-actions nodrag tsh-desktop-no-drag", children: /* @__PURE__ */ jsx2(
471
+ ImageActionButtons,
472
+ {
473
+ copyLabel: t("common.copyImage"),
474
+ downloadLabel: t("common.downloadImage"),
475
+ onCopy: handleCopyImageAction,
476
+ onDownload: handleDownloadImage
477
+ }
478
+ ) }) : null,
479
+ contextMenuPosition?.inZoomDialog && actionButtons ? /* @__PURE__ */ jsx2(
480
+ "div",
481
+ {
482
+ className: "tsh-image-context-menu nodrag tsh-desktop-no-drag",
483
+ style: {
484
+ left: contextMenuPosition.x,
485
+ top: contextMenuPosition.y
486
+ },
487
+ role: "menu",
488
+ onClick: (event) => event.stopPropagation(),
489
+ children: /* @__PURE__ */ jsx2(
490
+ ImageActionButtons,
491
+ {
492
+ copyLabel: t("common.copyImage"),
493
+ downloadLabel: t("common.downloadImage"),
494
+ itemRole: "menuitem",
495
+ onCopy: handleCopyImageAction,
496
+ onDownload: handleDownloadImage
497
+ }
498
+ )
499
+ }
500
+ ) : null,
423
501
  cloneElement(buttonUnzoom, {
424
502
  className: cn(
425
503
  buttonUnzoom.props.className,
@@ -442,6 +520,7 @@ function ZoomableImage({
442
520
  "img",
443
521
  {
444
522
  ...props,
523
+ alt,
445
524
  src,
446
525
  onContextMenu: hasImageActions ? handleContextMenu : onContextMenu,
447
526
  className: cn("nodrag tsh-desktop-no-drag cursor-zoom-in", className)
@@ -449,7 +528,7 @@ function ZoomableImage({
449
528
  )
450
529
  }
451
530
  ),
452
- contextMenuPosition && actionButtons ? /* @__PURE__ */ jsx2(
531
+ contextMenuPosition && !contextMenuPosition.inZoomDialog && actionButtons ? /* @__PURE__ */ jsx2(
453
532
  "div",
454
533
  {
455
534
  className: "tsh-image-context-menu nodrag tsh-desktop-no-drag",
@@ -470,9 +549,52 @@ function ZoomableImage({
470
549
  }
471
550
  )
472
551
  }
552
+ ) : null,
553
+ copyStatus ? /* @__PURE__ */ jsx2(
554
+ ImageCopyStatusToast,
555
+ {
556
+ busy: copyStatus.busy,
557
+ message: copyStatus.message,
558
+ variant: copyStatus.variant,
559
+ onOpenChange: (open) => {
560
+ if (!open) {
561
+ setCopyStatus(null);
562
+ }
563
+ }
564
+ }
473
565
  ) : null
474
566
  ] });
475
567
  }
568
+ function ImageCopyStatusToast({
569
+ busy,
570
+ message,
571
+ onOpenChange,
572
+ variant
573
+ }) {
574
+ return /* @__PURE__ */ jsxs2(ToastProvider, { duration: 1600, swipeDirection: "right", children: [
575
+ /* @__PURE__ */ jsx2(
576
+ ToastRoot,
577
+ {
578
+ open: true,
579
+ anchor: "viewport",
580
+ busy,
581
+ variant,
582
+ onOpenChange,
583
+ children: /* @__PURE__ */ jsx2(ToastTitle, { children: message })
584
+ }
585
+ ),
586
+ /* @__PURE__ */ jsx2(
587
+ ToastViewport,
588
+ {
589
+ className: "nodrag tsh-desktop-no-drag",
590
+ style: {
591
+ top: "max(20px, calc(var(--cove-titlebar-reserve, 0px) + 10px))",
592
+ zIndex: 100303
593
+ }
594
+ }
595
+ )
596
+ ] });
597
+ }
476
598
  function ImageActionButtons({
477
599
  copyLabel,
478
600
  downloadLabel,
@@ -481,17 +603,34 @@ function ImageActionButtons({
481
603
  onDownload
482
604
  }) {
483
605
  return /* @__PURE__ */ jsxs2(Fragment, { children: [
484
- /* @__PURE__ */ jsxs2("button", { type: "button", role: itemRole, title: copyLabel, onClick: onCopy, children: [
485
- /* @__PURE__ */ jsx2(CopyIcon, { "aria-hidden": "true", className: "size-4" }),
486
- /* @__PURE__ */ jsx2("span", { children: copyLabel })
487
- ] }),
606
+ /* @__PURE__ */ jsxs2(
607
+ "button",
608
+ {
609
+ type: "button",
610
+ role: itemRole,
611
+ title: copyLabel,
612
+ onClick: (event) => {
613
+ event.preventDefault();
614
+ event.stopPropagation();
615
+ onCopy();
616
+ },
617
+ children: [
618
+ /* @__PURE__ */ jsx2(CopyIcon, { "aria-hidden": "true", className: "size-4" }),
619
+ /* @__PURE__ */ jsx2("span", { children: copyLabel })
620
+ ]
621
+ }
622
+ ),
488
623
  /* @__PURE__ */ jsxs2(
489
624
  "button",
490
625
  {
491
626
  type: "button",
492
627
  role: itemRole,
493
628
  title: downloadLabel,
494
- onClick: onDownload,
629
+ onClick: (event) => {
630
+ event.preventDefault();
631
+ event.stopPropagation();
632
+ onDownload();
633
+ },
495
634
  children: [
496
635
  /* @__PURE__ */ jsx2(DownloadIcon, { "aria-hidden": "true", className: "size-4" }),
497
636
  /* @__PURE__ */ jsx2("span", { children: downloadLabel })
@@ -509,14 +648,61 @@ function downloadImage(src, name) {
509
648
  link.click();
510
649
  link.remove();
511
650
  }
512
- function resolveImageDownloadName(name, src) {
513
- const trimmedName = name?.trim();
514
- if (trimmedName) {
515
- return trimmedName;
651
+ function resolveImageDownloadName(name, src, alt) {
652
+ const semanticName = resolveImageNameBase(name) ?? resolveImageNameBase(alt) ?? resolveImageNameBase(src) ?? "image";
653
+ const extension = resolveImageNameExtension(name) ?? resolveImageNameExtension(src) ?? resolveDataImageExtension(src) ?? "png";
654
+ return `${semanticName}-${formatImageDownloadTimestamp(/* @__PURE__ */ new Date())}-${createDownloadRandomSuffix()}.${extension}`;
655
+ }
656
+ function resolveImageNameBase(value) {
657
+ const segment = imageNameSegment(value);
658
+ if (!segment) {
659
+ return null;
660
+ }
661
+ const base = segment.replace(/\.[A-Za-z0-9]{2,8}$/u, "");
662
+ const sanitized = stripControlCharacters(base).replace(/[\\/:*?"<>|#%&{}$!'@+`=]+/gu, "-").replace(/\s+/gu, "-").replace(/-+/gu, "-").replace(/^-|-$/gu, "").slice(0, 80);
663
+ return sanitized || null;
664
+ }
665
+ function stripControlCharacters(value) {
666
+ return Array.from(value).filter((char) => char.charCodeAt(0) >= 32).join("");
667
+ }
668
+ function resolveImageNameExtension(value) {
669
+ const segment = imageNameSegment(value);
670
+ const match = segment?.match(/\.([A-Za-z0-9]{2,8})$/u);
671
+ if (!match?.[1]) {
672
+ return null;
673
+ }
674
+ return normalizeImageExtension(match[1]);
675
+ }
676
+ function imageNameSegment(value) {
677
+ const trimmed = value?.trim();
678
+ if (!trimmed) {
679
+ return null;
680
+ }
681
+ const withoutQuery = decodeURIComponentSafe(
682
+ trimmed.split(/[?#]/, 1)[0] ?? ""
683
+ );
684
+ return withoutQuery.split(/[\\/]/).pop()?.trim() || null;
685
+ }
686
+ function resolveDataImageExtension(src) {
687
+ const match = src?.match(/^data:image\/([A-Za-z0-9.+-]+)[;,]/u);
688
+ return match?.[1] ? normalizeImageExtension(match[1]) : null;
689
+ }
690
+ function normalizeImageExtension(extension) {
691
+ const normalized = extension.toLowerCase();
692
+ if (normalized === "jpeg") {
693
+ return "jpg";
694
+ }
695
+ if (normalized === "svg+xml") {
696
+ return "svg";
516
697
  }
517
- const srcName = src ? decodeURIComponentSafe(src.split(/[?#]/, 1)[0] ?? "") : "";
518
- const lastSegment = srcName.split(/[\\/]/).pop()?.trim();
519
- return lastSegment || "image.png";
698
+ return normalized.replace(/[^a-z0-9]/gu, "") || "png";
699
+ }
700
+ function formatImageDownloadTimestamp(date) {
701
+ const pad = (value) => String(value).padStart(2, "0");
702
+ return `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}-${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}`;
703
+ }
704
+ function createDownloadRandomSuffix() {
705
+ return Math.random().toString(36).slice(2, 6).padEnd(4, "0");
520
706
  }
521
707
  function decodeURIComponentSafe(value) {
522
708
  try {
@@ -1281,7 +1467,7 @@ function AgentMessageMarkdown({
1281
1467
  maxCharsPerSecond: STREAMING_MARKDOWN_MAX_CHARS_PER_SECOND,
1282
1468
  trailingFlushChars: STREAMING_MARKDOWN_TAIL_FLUSH_CHARS
1283
1469
  });
1284
- const stabilizedContent = useMemo2(
1470
+ const stabilizedContent = useMemo(
1285
1471
  () => stabilizeStreamingMarkdownTail(visibleContent, {
1286
1472
  streaming
1287
1473
  }).content,
@@ -1295,11 +1481,11 @@ function AgentMessageMarkdown({
1295
1481
  const shouldCollapse = collapsible && isLikelyLongerThanLineLimit(stabilizedContent);
1296
1482
  const isCollapsed = shouldCollapse && !isExpanded;
1297
1483
  const ContainerTag = inline ? "span" : "div";
1298
- const contentSignature = useMemo2(
1484
+ const contentSignature = useMemo(
1299
1485
  () => hashMarkdownProfilerContent(stabilizedContent),
1300
1486
  [stabilizedContent]
1301
1487
  );
1302
- const normalizedContent = useMemo2(
1488
+ const normalizedContent = useMemo(
1303
1489
  () => linkBareLocalAbsolutePaths(
1304
1490
  normalizeMentionMarkdownLinks(
1305
1491
  normalizePlainIssueMentionTitle ? normalizePlainIssueMentionTitleContent(
@@ -1345,7 +1531,7 @@ function AgentMessageMarkdown({
1345
1531
  },
1346
1532
  [handleLinkClick]
1347
1533
  );
1348
- const markdownComponents = useMemo2(
1534
+ const markdownComponents = useMemo(
1349
1535
  () => ({
1350
1536
  a: (props) => /* @__PURE__ */ jsx3(
1351
1537
  MarkdownLink,
@@ -1444,7 +1630,7 @@ function StreamingMarkdownBlocks({
1444
1630
  content,
1445
1631
  components
1446
1632
  }) {
1447
- const blocks = useMemo2(
1633
+ const blocks = useMemo(
1448
1634
  () => splitStreamingMarkdownBlocks(content),
1449
1635
  [content]
1450
1636
  );
@@ -3018,11 +3204,10 @@ export {
3018
3204
  resolveWorkspaceFilePathCandidate,
3019
3205
  resolveWorkspaceFileLinkAction,
3020
3206
  resolveWorkspaceLinkAction,
3021
- ConversationImageContextMenu,
3022
3207
  ZoomableImage,
3023
3208
  AgentMessageMarkdown,
3024
3209
  AgentGUIConversation_styles_default,
3025
3210
  CustomScrollArea,
3026
3211
  MessageSquareMoreIcon
3027
3212
  };
3028
- //# sourceMappingURL=chunk-WC34LVH6.js.map
3213
+ //# sourceMappingURL=chunk-OLZP2EFK.js.map