@shapesos/clay 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +46 -1
  2. package/dist/artifacts.cjs +741 -0
  3. package/dist/artifacts.cjs.map +1 -0
  4. package/dist/artifacts.d.cts +56 -0
  5. package/dist/artifacts.d.ts +56 -0
  6. package/dist/artifacts.js +23 -0
  7. package/dist/artifacts.js.map +1 -0
  8. package/dist/blocks.cjs +1282 -0
  9. package/dist/blocks.cjs.map +1 -0
  10. package/dist/blocks.css +2 -0
  11. package/dist/blocks.d.cts +41 -0
  12. package/dist/blocks.d.ts +41 -0
  13. package/dist/blocks.js +31 -0
  14. package/dist/blocks.js.map +1 -0
  15. package/dist/button.d.cts +2 -2
  16. package/dist/button.d.ts +2 -2
  17. package/dist/chat.cjs +755 -281
  18. package/dist/chat.cjs.map +1 -1
  19. package/dist/chat.d.cts +31 -10
  20. package/dist/chat.d.ts +31 -10
  21. package/dist/chat.js +8 -3
  22. package/dist/chunk-BX5TCEPR.js +436 -0
  23. package/dist/chunk-BX5TCEPR.js.map +1 -0
  24. package/dist/chunk-JGMN6W72.js +12 -0
  25. package/dist/chunk-JGMN6W72.js.map +1 -0
  26. package/dist/{chunk-27GJUWVN.js → chunk-JJUIBBBU.js} +14 -8
  27. package/dist/chunk-JJUIBBBU.js.map +1 -0
  28. package/dist/chunk-L35M3OD5.js +99 -0
  29. package/dist/chunk-L35M3OD5.js.map +1 -0
  30. package/dist/chunk-MEJESPTZ.js +1 -0
  31. package/dist/chunk-MEJESPTZ.js.map +1 -0
  32. package/dist/chunk-MXOPG747.js +238 -0
  33. package/dist/chunk-MXOPG747.js.map +1 -0
  34. package/dist/{chunk-MLCRDVQ2.js → chunk-OBOXCBDL.js} +13 -5
  35. package/dist/chunk-OBOXCBDL.js.map +1 -0
  36. package/dist/chunk-OUW6PUEB.js +223 -0
  37. package/dist/chunk-OUW6PUEB.js.map +1 -0
  38. package/dist/{chunk-TDMJUF4A.js → chunk-WPQQVKWY.js} +4 -4
  39. package/dist/icon.cjs +12 -4
  40. package/dist/icon.cjs.map +1 -1
  41. package/dist/icon.d.cts +19 -6
  42. package/dist/icon.d.ts +19 -6
  43. package/dist/icon.js +1 -1
  44. package/dist/index.cjs +789 -273
  45. package/dist/index.cjs.map +1 -1
  46. package/dist/index.d.cts +7 -3
  47. package/dist/index.d.ts +7 -3
  48. package/dist/index.js +61 -21
  49. package/dist/lottie.d.cts +2 -2
  50. package/dist/lottie.d.ts +2 -2
  51. package/dist/table.cjs +142 -0
  52. package/dist/table.cjs.map +1 -0
  53. package/dist/table.d.cts +44 -0
  54. package/dist/table.d.ts +44 -0
  55. package/dist/table.js +24 -0
  56. package/dist/table.js.map +1 -0
  57. package/dist/text-area.d.cts +2 -2
  58. package/dist/text-area.d.ts +2 -2
  59. package/dist/text-area.js +2 -2
  60. package/dist/types-3Gzk7cRt.d.cts +121 -0
  61. package/dist/types-3Gzk7cRt.d.ts +121 -0
  62. package/dist/types-C9XX-Uhk.d.ts +68 -0
  63. package/dist/types-DuuRI4ll.d.cts +68 -0
  64. package/dist/utils.cjs +14 -7
  65. package/dist/utils.cjs.map +1 -1
  66. package/dist/utils.d.cts +18 -1
  67. package/dist/utils.d.ts +18 -1
  68. package/dist/utils.js +6 -4
  69. package/package.json +34 -4
  70. package/dist/chunk-27GJUWVN.js.map +0 -1
  71. package/dist/chunk-A77BGJH4.js +0 -458
  72. package/dist/chunk-A77BGJH4.js.map +0 -1
  73. package/dist/chunk-MLCRDVQ2.js.map +0 -1
  74. /package/dist/{chunk-TDMJUF4A.js.map → chunk-WPQQVKWY.js.map} +0 -0
package/dist/chat.cjs CHANGED
@@ -33,32 +33,173 @@ __export(chat_exports, {
33
33
  ChatContext: () => ChatContext,
34
34
  ChatMessage: () => ChatMessage,
35
35
  MESSAGE_ROLE: () => MESSAGE_ROLE,
36
+ copyMessageText: () => copyMessageText,
36
37
  useChatContext: () => useChatContext,
37
38
  useCopyToClipboard: () => useCopyToClipboard
38
39
  });
39
40
  module.exports = __toCommonJS(chat_exports);
40
41
 
41
- // src/components/chat/chat-message-content/chat-message-content.tsx
42
- var import_react_markdown = __toESM(require("react-markdown"), 1);
43
- var import_remark_breaks = __toESM(require("remark-breaks"), 1);
44
- var import_remark_gfm = __toESM(require("remark-gfm"), 1);
42
+ // src/components/chat/chat-message/chat-message.tsx
43
+ var import_react11 = require("react");
45
44
 
46
- // src/components/chat/scrollable-table/scrollable-table.tsx
47
- var import_react2 = require("react");
45
+ // src/components/blocks/types.ts
46
+ var blockTypes = {
47
+ TEXT: "TEXT",
48
+ ARTIFACT_REF: "ARTIFACT_REF"
49
+ };
48
50
 
49
- // src/components/chat/chat-context/chat-context.ts
51
+ // src/components/artifacts/types.ts
52
+ var artifactTypes = {
53
+ TABLE: "TABLE"
54
+ };
55
+
56
+ // src/components/artifacts/table-artifact/table-artifact.tsx
57
+ var import_react7 = require("react");
58
+
59
+ // src/lib/utils.ts
60
+ var import_clsx = require("clsx");
61
+ var import_tailwind_merge = require("tailwind-merge");
62
+ function cn(...inputs) {
63
+ return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs));
64
+ }
65
+
66
+ // src/components/ai-elements/artifact.tsx
67
+ var import_jsx_runtime = require("react/jsx-runtime");
68
+ var Artifact = ({ className, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
69
+ "div",
70
+ {
71
+ className: cn("flex flex-col overflow-hidden rounded-lg border bg-background shadow-sm", className),
72
+ ...props
73
+ }
74
+ );
75
+ var ArtifactHeader = ({ className, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: cn("flex items-center justify-between border-b bg-muted/50 px-4 py-3", className), ...props });
76
+ var ArtifactTitle = ({ className, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: cn("font-medium text-foreground text-sm", className), ...props });
77
+ var ArtifactActions = ({ className, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: cn("flex items-center gap-1", className), ...props });
78
+ var ArtifactContent = ({ className, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: cn("flex-1 overflow-auto p-4", className), ...props });
79
+
80
+ // node_modules/@tabler/icons-react/dist/esm/createReactComponent.mjs
50
81
  var import_react = require("react");
51
- var ChatContext = (0, import_react.createContext)(null);
52
- function useChatContext() {
53
- const context = (0, import_react.useContext)(ChatContext);
54
- if (!context) {
55
- throw new Error("useChatContext must be used within a Chat.Root component");
82
+
83
+ // node_modules/@tabler/icons-react/dist/esm/defaultAttributes.mjs
84
+ var defaultAttributes = {
85
+ outline: {
86
+ xmlns: "http://www.w3.org/2000/svg",
87
+ width: 24,
88
+ height: 24,
89
+ viewBox: "0 0 24 24",
90
+ fill: "none",
91
+ stroke: "currentColor",
92
+ strokeWidth: 2,
93
+ strokeLinecap: "round",
94
+ strokeLinejoin: "round"
95
+ },
96
+ filled: {
97
+ xmlns: "http://www.w3.org/2000/svg",
98
+ width: 24,
99
+ height: 24,
100
+ viewBox: "0 0 24 24",
101
+ fill: "currentColor",
102
+ stroke: "none"
56
103
  }
57
- return context;
58
- }
104
+ };
59
105
 
60
- // src/components/chat/scrollable-table/scrollable-table-styles.ts
106
+ // node_modules/@tabler/icons-react/dist/esm/createReactComponent.mjs
107
+ var createReactComponent = (type, iconName, iconNamePascal, iconNode) => {
108
+ const Component = (0, import_react.forwardRef)(
109
+ ({ color = "currentColor", size = 24, stroke = 2, title, className, children, ...rest }, ref) => (0, import_react.createElement)(
110
+ "svg",
111
+ {
112
+ ref,
113
+ ...defaultAttributes[type],
114
+ width: size,
115
+ height: size,
116
+ className: [`tabler-icon`, `tabler-icon-${iconName}`, className].join(" "),
117
+ ...type === "filled" ? {
118
+ fill: color
119
+ } : {
120
+ strokeWidth: stroke,
121
+ stroke: color
122
+ },
123
+ ...rest
124
+ },
125
+ [
126
+ title && (0, import_react.createElement)("title", { key: "svg-title" }, title),
127
+ ...iconNode.map(([tag, attrs]) => (0, import_react.createElement)(tag, attrs)),
128
+ ...Array.isArray(children) ? children : [children]
129
+ ]
130
+ )
131
+ );
132
+ Component.displayName = `${iconNamePascal}`;
133
+ return Component;
134
+ };
135
+
136
+ // node_modules/@tabler/icons-react/dist/esm/icons/IconCheck.mjs
137
+ var __iconNode = [["path", { "d": "M5 12l5 5l10 -10", "key": "svg-0" }]];
138
+ var IconCheck = createReactComponent("outline", "check", "Check", __iconNode);
139
+
140
+ // node_modules/@tabler/icons-react/dist/esm/icons/IconCopy.mjs
141
+ var __iconNode2 = [["path", { "d": "M7 9.667a2.667 2.667 0 0 1 2.667 -2.667h8.666a2.667 2.667 0 0 1 2.667 2.667v8.666a2.667 2.667 0 0 1 -2.667 2.667h-8.666a2.667 2.667 0 0 1 -2.667 -2.667l0 -8.666", "key": "svg-0" }], ["path", { "d": "M4.012 16.737a2.005 2.005 0 0 1 -1.012 -1.737v-10c0 -1.1 .9 -2 2 -2h10c.75 0 1.158 .385 1.5 1", "key": "svg-1" }]];
142
+ var IconCopy = createReactComponent("outline", "copy", "Copy", __iconNode2);
143
+
144
+ // node_modules/@tabler/icons-react/dist/esm/icons/IconDownload.mjs
145
+ var __iconNode3 = [["path", { "d": "M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2v-2", "key": "svg-0" }], ["path", { "d": "M7 11l5 5l5 -5", "key": "svg-1" }], ["path", { "d": "M12 4l0 12", "key": "svg-2" }]];
146
+ var IconDownload = createReactComponent("outline", "download", "Download", __iconNode3);
147
+
148
+ // node_modules/@tabler/icons-react/dist/esm/icons/IconThumbDown.mjs
149
+ var __iconNode4 = [["path", { "d": "M7 13v-8a1 1 0 0 0 -1 -1h-2a1 1 0 0 0 -1 1v7a1 1 0 0 0 1 1h3a4 4 0 0 1 4 4v1a2 2 0 0 0 4 0v-5h3a2 2 0 0 0 2 -2l-1 -5a2 3 0 0 0 -2 -2h-7a3 3 0 0 0 -3 3", "key": "svg-0" }]];
150
+ var IconThumbDown = createReactComponent("outline", "thumb-down", "ThumbDown", __iconNode4);
151
+
152
+ // node_modules/@tabler/icons-react/dist/esm/icons/IconThumbUp.mjs
153
+ var __iconNode5 = [["path", { "d": "M7 11v8a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1v-7a1 1 0 0 1 1 -1h3a4 4 0 0 0 4 -4v-1a2 2 0 0 1 4 0v5h3a2 2 0 0 1 2 2l-1 5a2 3 0 0 1 -2 2h-7a3 3 0 0 1 -3 -3", "key": "svg-0" }]];
154
+ var IconThumbUp = createReactComponent("outline", "thumb-up", "ThumbUp", __iconNode5);
155
+
156
+ // src/components/icon-button/icon-button.tsx
157
+ var import_react3 = require("react");
158
+
159
+ // src/components/icon/icon.tsx
160
+ var import_react2 = require("react");
161
+
162
+ // src/components/icon/icon-styles.ts
61
163
  var import_styled_components = __toESM(require("styled-components"), 1);
164
+ var STROKE_WIDTH_BY_SIZE = {
165
+ 12: 1.4,
166
+ 14: 1.4,
167
+ 16: 1.8,
168
+ 18: 1.8,
169
+ 20: 1.8,
170
+ 24: 1.8
171
+ };
172
+ var DEFAULT_STROKE_WIDTH = 1.8;
173
+ function getStrokeWidth(size) {
174
+ return STROKE_WIDTH_BY_SIZE[size] ?? DEFAULT_STROKE_WIDTH;
175
+ }
176
+ var IconWrapper = import_styled_components.default.span`
177
+ display: inline-flex;
178
+ align-items: center;
179
+ justify-content: center;
180
+ color: ${({ $color }) => $color ?? "currentColor"};
181
+ flex-shrink: 0;
182
+ `;
183
+
184
+ // src/components/icon/icon.tsx
185
+ var import_jsx_runtime2 = require("react/jsx-runtime");
186
+ var DEFAULT_SIZE = 16;
187
+ var Icon = (0, import_react2.forwardRef)(function Icon2({ icon: IconComponent, size = DEFAULT_SIZE, color, className, "aria-label": ariaLabel }, ref) {
188
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
189
+ IconWrapper,
190
+ {
191
+ ref,
192
+ className,
193
+ $color: color,
194
+ "aria-label": ariaLabel,
195
+ role: ariaLabel ? "img" : void 0,
196
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(IconComponent, { width: size, height: size, strokeWidth: getStrokeWidth(size) })
197
+ }
198
+ );
199
+ });
200
+
201
+ // src/components/icon-button/icon-button-styles.ts
202
+ var import_styled_components2 = __toESM(require("styled-components"), 1);
62
203
 
63
204
  // src/tokens/colors.ts
64
205
  var colors = {
@@ -148,47 +289,533 @@ var colors = {
148
289
  "red-600": "#DC2626"
149
290
  };
150
291
 
151
- // src/components/chat/scrollable-table/scrollable-table-styles.ts
152
- var TableContainer = import_styled_components.default.div`
292
+ // src/components/icon-button/icon-button-styles.ts
293
+ var PADDING = {
294
+ short: {
295
+ small: "4px",
296
+ medium: "6px"
297
+ },
298
+ long: {
299
+ small: "4px 6px",
300
+ medium: "6px 10px"
301
+ }
302
+ };
303
+ var BORDER_RADIUS = {
304
+ small: 6,
305
+ medium: 10
306
+ };
307
+ var Button = import_styled_components2.default.button`
308
+ display: inline-flex;
309
+ align-items: center;
310
+ justify-content: center;
311
+ padding: ${({ $size, $variant }) => PADDING[$variant][$size]};
312
+ border: none;
313
+ border-radius: ${({ $size }) => BORDER_RADIUS[$size]}px;
314
+ background: ${({ $isSelected }) => $isSelected ? colors["brown-40"] : "transparent"};
315
+ color: ${colors["brown-100"]};
316
+ cursor: pointer;
317
+ transition: background-color 100ms ease;
318
+
319
+ &:hover {
320
+ background: ${({ $isSelected }) => $isSelected ? colors["brown-40"] : colors["brown-20"]};
321
+ }
322
+
323
+ &:active {
324
+ background: ${colors["brown-40"]};
325
+ }
326
+
327
+ &:disabled {
328
+ color: ${colors["brown-50"]};
329
+ background: transparent;
330
+ cursor: not-allowed;
331
+ }
332
+ `;
333
+
334
+ // src/components/icon-button/icon-button.tsx
335
+ var import_jsx_runtime3 = require("react/jsx-runtime");
336
+ var ICON_SIZE_BY_BUTTON_SIZE = {
337
+ small: 16,
338
+ medium: 20
339
+ };
340
+ var IconButton = (0, import_react3.forwardRef)(function IconButton2({
341
+ icon,
342
+ size = "small",
343
+ variant = "short",
344
+ isSelected = false,
345
+ disabled = false,
346
+ onClick,
347
+ className,
348
+ id,
349
+ as,
350
+ "aria-label": ariaLabel,
351
+ ...rest
352
+ }, ref) {
353
+ const isNativeButton = as === void 0 || as === "button";
354
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
355
+ Button,
356
+ {
357
+ ref,
358
+ className,
359
+ id,
360
+ as,
361
+ $size: size,
362
+ $variant: variant,
363
+ $isSelected: isSelected,
364
+ disabled,
365
+ onClick: disabled ? void 0 : onClick,
366
+ "aria-label": ariaLabel,
367
+ ...isNativeButton ? { type: "button" } : {},
368
+ ...rest,
369
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Icon, { icon, size: ICON_SIZE_BY_BUTTON_SIZE[size] })
370
+ }
371
+ );
372
+ });
373
+
374
+ // src/components/ui/tooltip.tsx
375
+ var React = __toESM(require("react"), 1);
376
+ var TooltipPrimitive = __toESM(require("@radix-ui/react-tooltip"), 1);
377
+ var import_jsx_runtime4 = require("react/jsx-runtime");
378
+ var TooltipProvider = TooltipPrimitive.Provider;
379
+ var Tooltip = TooltipPrimitive.Root;
380
+ var TooltipTrigger = TooltipPrimitive.Trigger;
381
+ var TooltipContent = React.forwardRef(({ className, sideOffset = 4, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
382
+ TooltipPrimitive.Content,
383
+ {
384
+ ref,
385
+ sideOffset,
386
+ className: cn(
387
+ "z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 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 origin-[--radix-tooltip-content-transform-origin]",
388
+ className
389
+ ),
390
+ ...props
391
+ }
392
+ ));
393
+ TooltipContent.displayName = TooltipPrimitive.Content.displayName;
394
+
395
+ // src/components/artifacts/artifact-download-button/artifact-download-button.tsx
396
+ var import_jsx_runtime5 = require("react/jsx-runtime");
397
+ function ArtifactDownloadButton({ href }) {
398
+ if (!isSafeDownloadUrl(href)) return null;
399
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(TooltipProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Tooltip, { children: [
400
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
401
+ IconButton,
402
+ {
403
+ as: "a",
404
+ href,
405
+ download: true,
406
+ rel: "noopener noreferrer",
407
+ icon: IconDownload,
408
+ size: "small",
409
+ "aria-label": "Download artifact"
410
+ }
411
+ ) }),
412
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(TooltipContent, { children: "Download" })
413
+ ] }) });
414
+ }
415
+ function isSafeDownloadUrl(url) {
416
+ try {
417
+ const parsed = new URL(url);
418
+ return parsed.protocol === "https:" || parsed.protocol === "http:";
419
+ } catch {
420
+ return false;
421
+ }
422
+ }
423
+
424
+ // src/components/ui/table.tsx
425
+ var React2 = __toESM(require("react"), 1);
426
+ var import_jsx_runtime6 = require("react/jsx-runtime");
427
+ var Table = React2.forwardRef(({ className, containerClassName, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: cn("relative w-full overflow-auto", containerClassName), children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("table", { ref, className: cn("w-full caption-bottom text-sm", className), ...props }) }));
428
+ Table.displayName = "Table";
429
+ var TableHeader = React2.forwardRef(
430
+ ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("thead", { ref, className: cn("[&_tr]:border-b", className), ...props })
431
+ );
432
+ TableHeader.displayName = "TableHeader";
433
+ var TableBody = React2.forwardRef(
434
+ ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("tbody", { ref, className: cn("[&_tr:last-child]:border-0", className), ...props })
435
+ );
436
+ TableBody.displayName = "TableBody";
437
+ var TableFooter = React2.forwardRef(
438
+ ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("tfoot", { ref, className: cn("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", className), ...props })
439
+ );
440
+ TableFooter.displayName = "TableFooter";
441
+ var TableRow = React2.forwardRef(
442
+ ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
443
+ "tr",
444
+ {
445
+ ref,
446
+ className: cn("border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted", className),
447
+ ...props
448
+ }
449
+ )
450
+ );
451
+ TableRow.displayName = "TableRow";
452
+ var TableHead = React2.forwardRef(
453
+ ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
454
+ "th",
455
+ {
456
+ ref,
457
+ className: cn(
458
+ "h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
459
+ className
460
+ ),
461
+ ...props
462
+ }
463
+ )
464
+ );
465
+ TableHead.displayName = "TableHead";
466
+ var TableCell = React2.forwardRef(
467
+ ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("td", { ref, className: cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className), ...props })
468
+ );
469
+ TableCell.displayName = "TableCell";
470
+ var TableCaption = React2.forwardRef(
471
+ ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("caption", { ref, className: cn("mt-4 text-sm text-muted-foreground", className), ...props })
472
+ );
473
+ TableCaption.displayName = "TableCaption";
474
+
475
+ // src/components/table/use-scroll-shadow.ts
476
+ var import_react4 = require("react");
477
+ function useScrollShadow() {
478
+ const targetRef = (0, import_react4.useRef)(null);
479
+ const [isScrolled, setIsScrolled] = (0, import_react4.useState)(false);
480
+ (0, import_react4.useEffect)(() => {
481
+ const target = targetRef.current;
482
+ if (!target) return;
483
+ let scrollContainer = target;
484
+ while (scrollContainer) {
485
+ const overflowY = getComputedStyle(scrollContainer).overflowY;
486
+ if (overflowY === "auto" || overflowY === "scroll") break;
487
+ scrollContainer = scrollContainer.parentElement;
488
+ }
489
+ if (!scrollContainer) return;
490
+ setIsScrolled(scrollContainer.scrollTop > 0);
491
+ const handleScroll = () => {
492
+ if (!scrollContainer) return;
493
+ setIsScrolled(scrollContainer.scrollTop > 0);
494
+ };
495
+ scrollContainer.addEventListener("scroll", handleScroll, { passive: true });
496
+ return () => scrollContainer.removeEventListener("scroll", handleScroll);
497
+ }, []);
498
+ return { targetRef, isScrolled };
499
+ }
500
+
501
+ // src/components/artifacts/table-artifact/table-artifact-styles.ts
502
+ var import_react5 = require("react");
503
+ function StatusMessage({ $tone, children }) {
504
+ return (0, import_react5.createElement)(
505
+ "div",
506
+ {
507
+ className: `py-6 px-2 text-center text-sm font-medium leading-5 ${$tone === "error" ? "text-red-600" : "text-brown-70"}`
508
+ },
509
+ children
510
+ );
511
+ }
512
+
513
+ // src/components/artifacts/table-artifact/table-artifact-content.constants.ts
514
+ var TABLE_STATUS = {
515
+ LOADING: "loading",
516
+ READY: "ready",
517
+ ERROR: "error",
518
+ UNAVAILABLE: "unavailable"
519
+ };
520
+
521
+ // src/components/artifacts/table-artifact/table-artifact-content.utils.ts
522
+ function formatCell(row, column) {
523
+ return row[column.key] ?? "";
524
+ }
525
+ var HEAD_CLASS = "h-auto py-2 px-4 text-xs font-medium text-foreground whitespace-nowrap text-left";
526
+ var CELL_CLASS = "p-4 text-sm font-normal text-foreground align-top min-w-[150px] text-left";
527
+ var ROW_CLASSES = "border-b-brown-30 bg-background/70 hover:bg-secondary";
528
+ var DATA_ROW_CLASSES = `${ROW_CLASSES} animate-in fade-in duration-300`;
529
+ var SCROLL_SHADOW = "0 1px 2px rgb(0 0 0 / 0.01), 0 6px 12px -2px rgb(0 0 0 / 0.03)";
530
+
531
+ // src/components/artifacts/table-artifact/table-artifact-content.tsx
532
+ var import_jsx_runtime7 = require("react/jsx-runtime");
533
+ function TableArtifactContent({
534
+ columns,
535
+ data,
536
+ knownRowCount,
537
+ maxShimmerRows,
538
+ maxBodyHeight,
539
+ unavailableMessage
540
+ }) {
541
+ if (data.status === TABLE_STATUS.ERROR) {
542
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(StatusMessage, { $tone: "error", children: "Couldn't load table data." });
543
+ }
544
+ if (data.status === TABLE_STATUS.UNAVAILABLE) {
545
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(StatusMessage, { $tone: "info", children: unavailableMessage });
546
+ }
547
+ if (data.status === TABLE_STATUS.LOADING) {
548
+ if (typeof knownRowCount === "number" && knownRowCount > 0) {
549
+ const shimmerCount = Math.min(knownRowCount, maxShimmerRows);
550
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ArtifactTable, { columns, maxBodyHeight, children: renderShimmerRows(columns, shimmerCount) });
551
+ }
552
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(StatusMessage, { $tone: "info", children: "Loading table\u2026" });
553
+ }
554
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ArtifactTable, { columns, maxBodyHeight, children: data.rows.length === 0 ? renderEmptyRow(columns.length) : renderDataRows(columns, data.rows) });
555
+ }
556
+ function ArtifactTable({ columns, maxBodyHeight, children }) {
557
+ const { targetRef, isScrolled } = useScrollShadow();
558
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { ref: targetRef, className: "overscroll-none", style: { maxHeight: maxBodyHeight, overflowY: "auto" }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Table, { containerClassName: "overflow-visible", children: [
559
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
560
+ TableHeader,
561
+ {
562
+ "data-scrolled": isScrolled ? "true" : void 0,
563
+ className: "sticky top-0 z-10 bg-background/70 backdrop-blur-lg transition-shadow",
564
+ style: isScrolled ? { boxShadow: SCROLL_SHADOW } : void 0,
565
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TableRow, { className: "hover:bg-transparent", children: columns.map((column) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TableHead, { scope: "col", className: HEAD_CLASS, children: column.label }, column.key)) })
566
+ }
567
+ ),
568
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TableBody, { children })
569
+ ] }) });
570
+ }
571
+ function renderShimmerRows(columns, count) {
572
+ return Array.from({ length: count }).map((_, index) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TableRow, { className: ROW_CLASSES, "aria-hidden": "true", children: columns.map((column) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TableCell, { className: CELL_CLASS, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "block h-3 w-3/4 animate-pulse rounded bg-muted-foreground/20" }) }, column.key)) }, `shimmer-${index}`));
573
+ }
574
+ function renderDataRows(columns, rows) {
575
+ return rows.map((row, index) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TableRow, { className: DATA_ROW_CLASSES, children: columns.map((column) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TableCell, { className: CELL_CLASS, children: formatCell(row, column) }, column.key)) }, index));
576
+ }
577
+ function renderEmptyRow(colSpan) {
578
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TableRow, { className: "hover:bg-transparent", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TableCell, { colSpan, className: "py-6 text-center text-sm text-muted-foreground", children: "No rows to display." }) });
579
+ }
580
+
581
+ // src/components/artifacts/table-artifact/use-table-artifact-data.ts
582
+ var import_react6 = require("react");
583
+
584
+ // src/components/artifacts/table-artifact/parse-csv.ts
585
+ var import_papaparse = __toESM(require("papaparse"), 1);
586
+ function parseCsv(text, columns) {
587
+ const result = import_papaparse.default.parse(text, {
588
+ header: true,
589
+ skipEmptyLines: true,
590
+ dynamicTyping: false
591
+ });
592
+ const csvHeaders = new Set(result.meta?.fields ?? []);
593
+ const presentColumns = columns.filter((col) => csvHeaders.has(col.key));
594
+ return result.data.map((rawRow) => {
595
+ const row = {};
596
+ for (const column of presentColumns) {
597
+ const value = rawRow[column.key];
598
+ row[column.key] = value === void 0 || value === "" ? null : value;
599
+ }
600
+ return row;
601
+ });
602
+ }
603
+
604
+ // src/components/artifacts/table-artifact/use-table-artifact-data.ts
605
+ var LOADING = { status: "loading", rows: null, error: null };
606
+ var UNAVAILABLE = { status: "unavailable", rows: null, error: null };
607
+ var INITIAL_INTERNAL = { cacheKey: null, data: LOADING };
608
+ function columnsFingerprint(columns) {
609
+ return columns.map((c) => `${c.key}:${c.type ?? "string"}`).join("|");
610
+ }
611
+ function useTableArtifactData(presignedUrl, columns) {
612
+ const colsKey = (0, import_react6.useMemo)(() => columnsFingerprint(columns), [columns]);
613
+ const cacheKey = presignedUrl ? `${presignedUrl}::${colsKey}` : null;
614
+ const [state, setState] = (0, import_react6.useState)(INITIAL_INTERNAL);
615
+ (0, import_react6.useEffect)(() => {
616
+ if (!presignedUrl) return;
617
+ const controller = new AbortController();
618
+ fetch(presignedUrl, {
619
+ signal: controller.signal,
620
+ headers: { Accept: "text/csv" },
621
+ // Presigned URLs are cross-origin S3 in production and don't need credentials. Explicit
622
+ // omit so a same-origin URL (compromised payload, local dev) can't accidentally receive
623
+ // session cookies or auth headers.
624
+ credentials: "omit"
625
+ }).then(async (response) => {
626
+ if (!response.ok) throw new Error(`Failed to load table data (${response.status})`);
627
+ return response.text();
628
+ }).then((text) => {
629
+ if (controller.signal.aborted) return;
630
+ try {
631
+ const rows = parseCsv(text, columns);
632
+ setState({ cacheKey: `${presignedUrl}::${colsKey}`, data: { status: "ready", rows, error: null } });
633
+ } catch (parseError) {
634
+ console.error("[TableArtifact] CSV parse failed", parseError);
635
+ const err = parseError instanceof Error ? parseError : new Error(String(parseError));
636
+ setState({ cacheKey: `${presignedUrl}::${colsKey}`, data: { status: "error", rows: null, error: err } });
637
+ }
638
+ }).catch((error) => {
639
+ if (controller.signal.aborted) return;
640
+ console.error("[TableArtifact] Fetch failed", error);
641
+ const err = error instanceof Error ? error : new Error(String(error));
642
+ setState({ cacheKey: `${presignedUrl}::${colsKey}`, data: { status: "error", rows: null, error: err } });
643
+ });
644
+ return () => {
645
+ controller.abort();
646
+ };
647
+ }, [presignedUrl, colsKey]);
648
+ if (!presignedUrl) return UNAVAILABLE;
649
+ if (state.cacheKey !== cacheKey) return LOADING;
650
+ return state.data;
651
+ }
652
+
653
+ // src/components/artifacts/table-artifact/validate.ts
654
+ function isValidTableConfig(config) {
655
+ if (!config || typeof config !== "object") return false;
656
+ const candidate = config;
657
+ if (!Array.isArray(candidate.columns)) return false;
658
+ if (candidate.columns.length === 0) return false;
659
+ return candidate.columns.every(isValidTableColumn);
660
+ }
661
+ function isValidTableColumn(column) {
662
+ if (!column || typeof column !== "object") return false;
663
+ const candidate = column;
664
+ if (typeof candidate.key !== "string" || candidate.key.length === 0) return false;
665
+ if (typeof candidate.label !== "string") return false;
666
+ return true;
667
+ }
668
+
669
+ // src/components/artifacts/table-artifact/table-artifact.tsx
670
+ var import_jsx_runtime8 = require("react/jsx-runtime");
671
+ var TITLE_CLASSES = "text-sm font-semibold text-foreground";
672
+ var TABLE_MAX_BODY_HEIGHT = 480;
673
+ var MAX_SHIMMER_ROWS = 10;
674
+ var MSG_UNAVAILABLE = "Table data is unavailable.";
675
+ function TableArtifact({ artifact }) {
676
+ const validConfig = isValidTableConfig(artifact.config);
677
+ const validColumns = validConfig ? artifact.config.columns : null;
678
+ (0, import_react7.useEffect)(() => {
679
+ if (validConfig) return;
680
+ console.warn("[clay] TableArtifact: malformed `config` \u2014 skipping render.");
681
+ }, [validConfig]);
682
+ const data = useTableArtifactData(
683
+ validConfig ? artifact.protectedAsset?.presignedUrl ?? null : null,
684
+ validColumns ?? []
685
+ );
686
+ const presignedUrl = artifact.protectedAsset?.presignedUrl ?? null;
687
+ if (!validConfig || !validColumns) {
688
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Artifact, { className: "shadow-none w-full rounded-2xl bg-background/70", children: [
689
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(ArtifactHeader, { className: "bg-background/70", children: [
690
+ artifact.title ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ArtifactTitle, { className: TITLE_CLASSES, children: artifact.title }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", {}),
691
+ presignedUrl ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ArtifactActions, { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ArtifactDownloadButton, { href: presignedUrl }) }) : null
692
+ ] }),
693
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ArtifactContent, { className: "p-0 overflow-auto", style: { maxHeight: TABLE_MAX_BODY_HEIGHT }, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(StatusMessage, { $tone: "info", children: MSG_UNAVAILABLE }) })
694
+ ] });
695
+ }
696
+ return (
697
+ // The table body owns its own scroll container (`ArtifactTable` inside `TableArtifactContent`).
698
+ // Disable the outer overflow container so only one scroll context exists — this prevents
699
+ // double-scroll nesting and lets `useScrollShadow` find the correct scroll ancestor.
700
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Artifact, { className: "shadow-none w-full rounded-2xl bg-background/70", children: [
701
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(ArtifactHeader, { className: "bg-background/70", children: [
702
+ artifact.title ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ArtifactTitle, { className: TITLE_CLASSES, children: artifact.title }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", {}),
703
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ArtifactActions, { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ArtifactDownloadButton, { href: presignedUrl ?? "" }) })
704
+ ] }),
705
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ArtifactContent, { className: "p-0 overflow-visible", style: { maxHeight: "none" }, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
706
+ TableArtifactContent,
707
+ {
708
+ columns: validColumns,
709
+ data,
710
+ knownRowCount: artifact.config.stats?.rowCount,
711
+ maxShimmerRows: MAX_SHIMMER_ROWS,
712
+ maxBodyHeight: TABLE_MAX_BODY_HEIGHT,
713
+ unavailableMessage: MSG_UNAVAILABLE
714
+ }
715
+ ) })
716
+ ] })
717
+ );
718
+ }
719
+
720
+ // src/components/artifacts/table-artifact/to-clipboard-text.ts
721
+ function tableArtifactToClipboardText(artifact) {
722
+ const trimmedTitle = artifact.title?.trim();
723
+ return trimmedTitle || "Table";
724
+ }
725
+
726
+ // src/components/artifacts/artifact-services/table-artifact-service.ts
727
+ var TableArtifactService = {
728
+ type: artifactTypes.TABLE,
729
+ Component: TableArtifact,
730
+ toClipboardText: tableArtifactToClipboardText
731
+ };
732
+
733
+ // src/components/artifacts/artifact-services/index.ts
734
+ var ArtifactServices = {
735
+ [artifactTypes.TABLE]: TableArtifactService
736
+ };
737
+ function artifactToClipboardText(artifact) {
738
+ const entry = ArtifactServices[artifact.type];
739
+ if (!entry) {
740
+ return `[Artifact: ${artifact.title ?? artifact.type}]`;
741
+ }
742
+ return entry.toClipboardText(artifact);
743
+ }
744
+
745
+ // src/components/blocks/artifact-ref-block/artifact-ref-block.tsx
746
+ var import_jsx_runtime9 = require("react/jsx-runtime");
747
+ function ArtifactRefBlock({ block }) {
748
+ const { artifact } = block.payload;
749
+ const entry = ArtifactServices[artifact.type];
750
+ if (!entry) return null;
751
+ const { Component } = entry;
752
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Component, { artifact });
753
+ }
754
+
755
+ // src/components/blocks/block-services/artifact-ref-block-service.ts
756
+ var ArtifactRefBlockService = {
757
+ type: blockTypes.ARTIFACT_REF,
758
+ Component: ArtifactRefBlock,
759
+ /**
760
+ * Delegates to the registered artifact component's `toClipboardText`. Falls back to
761
+ * `[Artifact: <title>]` via `artifactToClipboardText`'s own fallback when the artifact type
762
+ * has no registered component, so copy never silently produces an empty string.
763
+ */
764
+ toClipboardText: (block) => artifactToClipboardText(block.payload.artifact)
765
+ };
766
+
767
+ // src/utils/markdown-to-plain-text.ts
768
+ var PLACEHOLDER_PREFIX = "\u200B\u200BIC";
769
+ var PLACEHOLDER_SUFFIX = "\u200B\u200B";
770
+ var PLACEHOLDER_REGEX = new RegExp(`${PLACEHOLDER_PREFIX}(\\d+)${PLACEHOLDER_SUFFIX}`, "g");
771
+ function stripInlineMarks(text) {
772
+ return text.replace(/\*{3}(.+?)\*{3}/g, "$1").replace(/\*{2}(.+?)\*{2}/g, "$1").replace(/_{2}(.+?)_{2}/g, "$1").replace(/\*(.+?)\*/g, "$1").replace(/_(.+?)_/g, "$1").replace(/~~(.+?)~~/g, "$1");
773
+ }
774
+ function markdownToPlainText(markdown) {
775
+ if (!markdown) return "";
776
+ const inlineCodeSnippets = [];
777
+ let result = markdown.replace(/^<!--[\s\S]*?--!?>\n?/gm, "").replace(/```[\s\S]*?```/g, "").replace(/`([^`]+)`/g, (_match, code) => {
778
+ inlineCodeSnippets.push(code);
779
+ return `${PLACEHOLDER_PREFIX}${inlineCodeSnippets.length - 1}${PLACEHOLDER_SUFFIX}`;
780
+ }).replace(/^#{1,6}\s+/gm, "").replace(/^(?:---|\*\*\*|___)\s*$/gm, "");
781
+ result = stripInlineMarks(result).replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/^>\s?/gm, "").replace(/^\|[-:\s|]+\|\s*$/gm, "").replace(/^\|\s?/gm, "").replace(/\s?\|$/gm, "").replace(/\n{3,}/g, "\n\n").trim();
782
+ result = result.replace(PLACEHOLDER_REGEX, (_match, index) => inlineCodeSnippets[Number(index)]);
783
+ return result;
784
+ }
785
+
786
+ // src/components/blocks/text-block/text-block.tsx
787
+ var import_react_markdown = __toESM(require("react-markdown"), 1);
788
+ var import_remark_breaks = __toESM(require("remark-breaks"), 1);
789
+ var import_remark_gfm = __toESM(require("remark-gfm"), 1);
790
+
791
+ // src/components/blocks/scrollable-table/scrollable-table-styles.ts
792
+ var import_styled_components3 = __toESM(require("styled-components"), 1);
793
+ var TableContainer = import_styled_components3.default.div`
153
794
  position: relative;
154
795
  margin-block: 8px 16px;
155
796
  `;
156
- var TableScroll = import_styled_components.default.div`
797
+ var TableScroll = import_styled_components3.default.div`
157
798
  width: 100%;
158
799
  overflow-x: auto;
159
800
  overflow-y: hidden;
160
801
  `;
161
- var TableActionsWrapper = import_styled_components.default.div`
162
- display: flex;
163
- justify-content: flex-start;
164
- margin-block: 8px 0;
165
- `;
166
- var TableActionsDivider = import_styled_components.default.div`
167
- height: 1px;
168
- background: ${colors["brown-40"]};
169
- margin-block: 8px 0;
170
- `;
171
802
 
172
- // src/components/chat/scrollable-table/scrollable-table.tsx
173
- var import_jsx_runtime = require("react/jsx-runtime");
174
- function ScrollableTable({ node: _node, messageId, counter, ...props }) {
175
- const currentIndex = counter ? counter.value : 0;
176
- if (counter) {
177
- counter.value += 1;
178
- }
179
- const context = (0, import_react2.useContext)(ChatContext);
180
- const TableActions = context?.TableActions;
181
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TableContainer, { children: [
182
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableScroll, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("table", { ...props }) }),
183
- TableActions && messageId && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
184
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableActionsWrapper, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableActions, { messageId, tableIndex: currentIndex }) }),
185
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableActionsDivider, {})
186
- ] })
187
- ] });
803
+ // src/components/blocks/scrollable-table/scrollable-table.tsx
804
+ var import_jsx_runtime10 = require("react/jsx-runtime");
805
+ function ScrollableTable({ node: _node, ...props }) {
806
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(TableContainer, { children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(TableScroll, { children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("table", { ...props }) }) });
188
807
  }
189
808
 
190
- // src/components/chat/chat-message-content/chat-message-content-styles.ts
191
- var import_styled_components2 = __toESM(require("styled-components"), 1);
809
+ // src/components/blocks/text-block/sanitize.ts
810
+ function sanitize(text) {
811
+ return text.split("\n").filter((line) => {
812
+ const t = line.trim();
813
+ return !(t.startsWith("<!-- table-title:") && t.endsWith("-->"));
814
+ }).join("\n");
815
+ }
816
+
817
+ // src/components/blocks/text-block/text-block-styles.ts
818
+ var import_styled_components4 = __toESM(require("styled-components"), 1);
192
819
 
193
820
  // src/tokens/typography.ts
194
821
  var fontFamilies = {
@@ -485,8 +1112,8 @@ function typographyMixin(type) {
485
1112
  ].join(";\n ");
486
1113
  }
487
1114
 
488
- // src/components/chat/chat-message-content/chat-message-content-styles.ts
489
- var ContentWrapper = import_styled_components2.default.div`
1115
+ // src/components/blocks/text-block/text-block-styles.ts
1116
+ var TextBlockWrapper = import_styled_components4.default.div`
490
1117
  ${typographyMixin(typographyTypes.GEIST_BODY_S_REGULAR)};
491
1118
  color: ${colors["brown-100"]};
492
1119
  word-break: break-word;
@@ -603,217 +1230,72 @@ var ContentWrapper = import_styled_components2.default.div`
603
1230
  }
604
1231
  `;
605
1232
 
606
- // src/components/chat/chat-message-content/chat-message-content.tsx
607
- var import_jsx_runtime2 = require("react/jsx-runtime");
608
- function ChatMessageContent({ content, messageId }) {
609
- const counter = { value: 0 };
610
- const sanitizedContent = content.split("\n").filter((line) => {
611
- const t = line.trim();
612
- return !(t.startsWith("<!-- table-title:") && t.endsWith("-->"));
613
- }).join("\n");
614
- const markdownComponents = {
615
- table: ((tableProps) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ScrollableTable, { ...tableProps, messageId, counter }))
616
- };
617
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ContentWrapper, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_markdown.default, { remarkPlugins: [import_remark_gfm.default, import_remark_breaks.default], components: markdownComponents, children: sanitizedContent }) });
1233
+ // src/components/blocks/text-block/text-block.tsx
1234
+ var import_jsx_runtime11 = require("react/jsx-runtime");
1235
+ var markdownComponents = {
1236
+ table: ((tableProps) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ScrollableTable, { ...tableProps }))
1237
+ };
1238
+ function TextBlock({ block }) {
1239
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(TextBlockWrapper, { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_markdown.default, { remarkPlugins: [import_remark_gfm.default, import_remark_breaks.default], components: markdownComponents, children: sanitize(block.payload.text) }) });
618
1240
  }
619
1241
 
620
- // src/components/chat/chat-message-actions/chat-message-actions.tsx
621
- var import_react6 = require("react");
622
-
623
- // node_modules/@tabler/icons-react/dist/esm/createReactComponent.mjs
624
- var import_react3 = require("react");
625
-
626
- // node_modules/@tabler/icons-react/dist/esm/defaultAttributes.mjs
627
- var defaultAttributes = {
628
- outline: {
629
- xmlns: "http://www.w3.org/2000/svg",
630
- width: 24,
631
- height: 24,
632
- viewBox: "0 0 24 24",
633
- fill: "none",
634
- stroke: "currentColor",
635
- strokeWidth: 2,
636
- strokeLinecap: "round",
637
- strokeLinejoin: "round"
638
- },
639
- filled: {
640
- xmlns: "http://www.w3.org/2000/svg",
641
- width: 24,
642
- height: 24,
643
- viewBox: "0 0 24 24",
644
- fill: "currentColor",
645
- stroke: "none"
646
- }
1242
+ // src/components/blocks/block-services/text-block-service.ts
1243
+ var TextBlockService = {
1244
+ type: blockTypes.TEXT,
1245
+ Component: TextBlock,
1246
+ /**
1247
+ * Converts the block's markdown source to plain text so the final clipboard value is clean
1248
+ * when pasted into tools that don't render markdown. Each block type owns its own clipboard
1249
+ * conversion so `useCopyToClipboard` can write text verbatim without a second pass.
1250
+ */
1251
+ toClipboardText: (block) => markdownToPlainText(block.payload.text)
647
1252
  };
648
1253
 
649
- // node_modules/@tabler/icons-react/dist/esm/createReactComponent.mjs
650
- var createReactComponent = (type, iconName, iconNamePascal, iconNode) => {
651
- const Component = (0, import_react3.forwardRef)(
652
- ({ color = "currentColor", size = 24, stroke = 2, title, className, children, ...rest }, ref) => (0, import_react3.createElement)(
653
- "svg",
654
- {
655
- ref,
656
- ...defaultAttributes[type],
657
- width: size,
658
- height: size,
659
- className: [`tabler-icon`, `tabler-icon-${iconName}`, className].join(" "),
660
- ...type === "filled" ? {
661
- fill: color
662
- } : {
663
- strokeWidth: stroke,
664
- stroke: color
665
- },
666
- ...rest
667
- },
668
- [
669
- title && (0, import_react3.createElement)("title", { key: "svg-title" }, title),
670
- ...iconNode.map(([tag, attrs]) => (0, import_react3.createElement)(tag, attrs)),
671
- ...Array.isArray(children) ? children : [children]
672
- ]
673
- )
674
- );
675
- Component.displayName = `${iconNamePascal}`;
676
- return Component;
1254
+ // src/components/blocks/block-services/index.ts
1255
+ var BlockServices = {
1256
+ [blockTypes.TEXT]: TextBlockService,
1257
+ [blockTypes.ARTIFACT_REF]: ArtifactRefBlockService
677
1258
  };
678
1259
 
679
- // node_modules/@tabler/icons-react/dist/esm/icons/IconCheck.mjs
680
- var __iconNode = [["path", { "d": "M5 12l5 5l10 -10", "key": "svg-0" }]];
681
- var IconCheck = createReactComponent("outline", "check", "Check", __iconNode);
682
-
683
- // node_modules/@tabler/icons-react/dist/esm/icons/IconCopy.mjs
684
- var __iconNode2 = [["path", { "d": "M7 9.667a2.667 2.667 0 0 1 2.667 -2.667h8.666a2.667 2.667 0 0 1 2.667 2.667v8.666a2.667 2.667 0 0 1 -2.667 2.667h-8.666a2.667 2.667 0 0 1 -2.667 -2.667l0 -8.666", "key": "svg-0" }], ["path", { "d": "M4.012 16.737a2.005 2.005 0 0 1 -1.012 -1.737v-10c0 -1.1 .9 -2 2 -2h10c.75 0 1.158 .385 1.5 1", "key": "svg-1" }]];
685
- var IconCopy = createReactComponent("outline", "copy", "Copy", __iconNode2);
686
-
687
- // node_modules/@tabler/icons-react/dist/esm/icons/IconThumbDown.mjs
688
- var __iconNode3 = [["path", { "d": "M7 13v-8a1 1 0 0 0 -1 -1h-2a1 1 0 0 0 -1 1v7a1 1 0 0 0 1 1h3a4 4 0 0 1 4 4v1a2 2 0 0 0 4 0v-5h3a2 2 0 0 0 2 -2l-1 -5a2 3 0 0 0 -2 -2h-7a3 3 0 0 0 -3 3", "key": "svg-0" }]];
689
- var IconThumbDown = createReactComponent("outline", "thumb-down", "ThumbDown", __iconNode3);
690
-
691
- // node_modules/@tabler/icons-react/dist/esm/icons/IconThumbUp.mjs
692
- var __iconNode4 = [["path", { "d": "M7 11v8a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1v-7a1 1 0 0 1 1 -1h3a4 4 0 0 0 4 -4v-1a2 2 0 0 1 4 0v5h3a2 2 0 0 1 2 2l-1 5a2 3 0 0 1 -2 2h-7a3 3 0 0 1 -3 -3", "key": "svg-0" }]];
693
- var IconThumbUp = createReactComponent("outline", "thumb-up", "ThumbUp", __iconNode4);
694
-
695
- // src/components/icon/icon.tsx
696
- var import_react4 = require("react");
697
-
698
- // src/components/icon/icon-styles.ts
699
- var import_styled_components3 = __toESM(require("styled-components"), 1);
700
- var STROKE_WIDTH_BY_SIZE = {
701
- 12: 1.4,
702
- 14: 1.4,
703
- 16: 1.8,
704
- 18: 1.8,
705
- 20: 1.8,
706
- 24: 1.8
707
- };
708
- var DEFAULT_STROKE_WIDTH = 1.8;
709
- function getStrokeWidth(size) {
710
- return STROKE_WIDTH_BY_SIZE[size] ?? DEFAULT_STROKE_WIDTH;
1260
+ // src/components/blocks/block/block.tsx
1261
+ var import_jsx_runtime12 = require("react/jsx-runtime");
1262
+ function Block({ block }) {
1263
+ const service = BlockServices[block.type];
1264
+ if (!service) return null;
1265
+ const { Component } = service;
1266
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Component, { block });
711
1267
  }
712
- var IconWrapper = import_styled_components3.default.span`
713
- display: inline-flex;
714
- align-items: center;
715
- justify-content: center;
716
- color: ${({ $color }) => $color ?? "currentColor"};
717
- flex-shrink: 0;
718
- `;
719
-
720
- // src/components/icon/icon.tsx
721
- var import_jsx_runtime3 = require("react/jsx-runtime");
722
- var DEFAULT_SIZE = 16;
723
- var Icon = (0, import_react4.forwardRef)(function Icon2({ icon: IconComponent, size = DEFAULT_SIZE, color, className, "aria-label": ariaLabel }, ref) {
724
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
725
- IconWrapper,
726
- {
727
- ref,
728
- className,
729
- $color: color,
730
- "aria-label": ariaLabel,
731
- role: ariaLabel ? "img" : void 0,
732
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(IconComponent, { width: size, height: size, strokeWidth: getStrokeWidth(size) })
733
- }
734
- );
735
- });
736
-
737
- // src/components/icon-button/icon-button-styles.ts
738
- var import_styled_components4 = __toESM(require("styled-components"), 1);
739
- var PADDING = {
740
- short: {
741
- small: "4px",
742
- medium: "6px"
743
- },
744
- long: {
745
- small: "4px 6px",
746
- medium: "6px 10px"
747
- }
748
- };
749
- var BORDER_RADIUS = {
750
- small: 6,
751
- medium: 10
752
- };
753
- var Button = import_styled_components4.default.button`
754
- display: inline-flex;
755
- align-items: center;
756
- justify-content: center;
757
- padding: ${({ $size, $variant }) => PADDING[$variant][$size]};
758
- border: none;
759
- border-radius: ${({ $size }) => BORDER_RADIUS[$size]}px;
760
- background: ${({ $isSelected }) => $isSelected ? colors["brown-40"] : "transparent"};
761
- color: ${colors["brown-100"]};
762
- cursor: pointer;
763
- transition: background-color 100ms ease;
764
1268
 
765
- &:hover {
766
- background: ${({ $isSelected }) => $isSelected ? colors["brown-40"] : colors["brown-20"]};
1269
+ // src/components/chat/hooks/copy-message-text.ts
1270
+ function copyMessageText(message) {
1271
+ const parts = [];
1272
+ for (const block of message.blocks) {
1273
+ const service = BlockServices[block.type];
1274
+ if (!service) continue;
1275
+ if (!service.toClipboardText) continue;
1276
+ const text = service.toClipboardText(block);
1277
+ if (text) parts.push(text);
767
1278
  }
1279
+ if (parts.length === 0) return message.fallbackText;
1280
+ return parts.join("\n\n");
1281
+ }
768
1282
 
769
- &:active {
770
- background: ${colors["brown-40"]};
771
- }
1283
+ // src/components/chat/chat-message-actions/chat-message-actions.tsx
1284
+ var import_react10 = require("react");
772
1285
 
773
- &:disabled {
774
- color: ${colors["brown-50"]};
775
- background: transparent;
776
- cursor: not-allowed;
1286
+ // src/components/chat/chat-context/chat-context.ts
1287
+ var import_react8 = require("react");
1288
+ var ChatContext = (0, import_react8.createContext)(null);
1289
+ function useChatContext() {
1290
+ const context = (0, import_react8.useContext)(ChatContext);
1291
+ if (!context) {
1292
+ throw new Error("useChatContext must be used within a Chat.Root component");
777
1293
  }
778
- `;
779
-
780
- // src/components/icon-button/icon-button.tsx
781
- var import_jsx_runtime4 = require("react/jsx-runtime");
782
- var ICON_SIZE_BY_BUTTON_SIZE = {
783
- small: 16,
784
- medium: 20
785
- };
786
- function IconButton({
787
- icon,
788
- size = "small",
789
- variant = "short",
790
- isSelected = false,
791
- disabled = false,
792
- onClick,
793
- className,
794
- id,
795
- as,
796
- "aria-label": ariaLabel
797
- }) {
798
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
799
- Button,
800
- {
801
- className,
802
- id,
803
- as,
804
- $size: size,
805
- $variant: variant,
806
- $isSelected: isSelected,
807
- disabled,
808
- onClick: disabled ? void 0 : onClick,
809
- "aria-label": ariaLabel,
810
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Icon, { icon, size: ICON_SIZE_BY_BUTTON_SIZE[size] })
811
- }
812
- );
1294
+ return context;
813
1295
  }
814
1296
 
815
1297
  // src/components/chat/hooks/use-copy-to-clipboard.ts
816
- var import_react5 = require("react");
1298
+ var import_react9 = require("react");
817
1299
 
818
1300
  // src/utils/clipboard.ts
819
1301
  function copyToClipboard(text, options) {
@@ -823,37 +1305,18 @@ function copyToClipboard(text, options) {
823
1305
  );
824
1306
  }
825
1307
 
826
- // src/utils/markdown-to-plain-text.ts
827
- var PLACEHOLDER_PREFIX = "\u200B\u200BIC";
828
- var PLACEHOLDER_SUFFIX = "\u200B\u200B";
829
- var PLACEHOLDER_REGEX = new RegExp(`${PLACEHOLDER_PREFIX}(\\d+)${PLACEHOLDER_SUFFIX}`, "g");
830
- function stripInlineMarks(text) {
831
- return text.replace(/\*{3}(.+?)\*{3}/g, "$1").replace(/\*{2}(.+?)\*{2}/g, "$1").replace(/_{2}(.+?)_{2}/g, "$1").replace(/\*(.+?)\*/g, "$1").replace(/_(.+?)_/g, "$1").replace(/~~(.+?)~~/g, "$1");
832
- }
833
- function markdownToPlainText(markdown) {
834
- if (!markdown) return "";
835
- const inlineCodeSnippets = [];
836
- let result = markdown.replace(/^<!--[\s\S]*?--!?>\n?/gm, "").replace(/```[\s\S]*?```/g, "").replace(/`([^`]+)`/g, (_match, code) => {
837
- inlineCodeSnippets.push(code);
838
- return `${PLACEHOLDER_PREFIX}${inlineCodeSnippets.length - 1}${PLACEHOLDER_SUFFIX}`;
839
- }).replace(/^#{1,6}\s+/gm, "").replace(/^(?:---|\*\*\*|___)\s*$/gm, "");
840
- result = stripInlineMarks(result).replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/^>\s?/gm, "").replace(/^\|[-:\s|]+\|\s*$/gm, "").replace(/^\|\s?/gm, "").replace(/\s?\|$/gm, "").replace(/\n{3,}/g, "\n\n").trim();
841
- result = result.replace(PLACEHOLDER_REGEX, (_match, index) => inlineCodeSnippets[Number(index)]);
842
- return result;
843
- }
844
-
845
1308
  // src/components/chat/hooks/use-copy-to-clipboard.ts
846
1309
  var RESET_DELAY_MS = 2e3;
847
1310
  function useCopyToClipboard() {
848
- const [isCopied, setIsCopied] = (0, import_react5.useState)(false);
849
- const timeoutRef = (0, import_react5.useRef)(null);
850
- (0, import_react5.useEffect)(() => {
1311
+ const [isCopied, setIsCopied] = (0, import_react9.useState)(false);
1312
+ const timeoutRef = (0, import_react9.useRef)(null);
1313
+ (0, import_react9.useEffect)(() => {
851
1314
  return () => {
852
1315
  if (timeoutRef.current) clearTimeout(timeoutRef.current);
853
1316
  };
854
1317
  }, []);
855
- const copy = (0, import_react5.useCallback)((text) => {
856
- copyToClipboard(markdownToPlainText(text), {
1318
+ const copy = (0, import_react9.useCallback)((text) => {
1319
+ copyToClipboard(text, {
857
1320
  onSuccess: () => {
858
1321
  setIsCopied(true);
859
1322
  if (timeoutRef.current) clearTimeout(timeoutRef.current);
@@ -892,24 +1355,24 @@ var AnimatedAction = import_styled_components5.default.div`
892
1355
  `;
893
1356
 
894
1357
  // src/components/chat/chat-message-actions/chat-message-actions.tsx
895
- var import_jsx_runtime5 = require("react/jsx-runtime");
1358
+ var import_jsx_runtime13 = require("react/jsx-runtime");
896
1359
  function ChatMessageActions({ className, messageId, content, role, isHelpful }) {
897
1360
  const { onCopyMessage, onThumbUpClick, onThumbDownClick } = useChatContext();
898
1361
  const { isCopied, copy } = useCopyToClipboard();
899
- const handleCopy = (0, import_react6.useCallback)(() => {
1362
+ const handleCopy = (0, import_react10.useCallback)(() => {
900
1363
  copy(content);
901
1364
  onCopyMessage?.(messageId);
902
1365
  }, [content, messageId, copy, onCopyMessage]);
903
- const handleThumbUp = (0, import_react6.useCallback)(() => {
1366
+ const handleThumbUp = (0, import_react10.useCallback)(() => {
904
1367
  onThumbUpClick?.(messageId, isHelpful === true ? null : true);
905
1368
  }, [messageId, isHelpful, onThumbUpClick]);
906
- const handleThumbDown = (0, import_react6.useCallback)(() => {
1369
+ const handleThumbDown = (0, import_react10.useCallback)(() => {
907
1370
  onThumbDownClick?.(messageId, isHelpful === false ? null : false);
908
1371
  }, [messageId, isHelpful, onThumbDownClick]);
909
1372
  const isAssistant = role === MESSAGE_ROLE.ASSISTANT;
910
1373
  const hasFeedback = isAssistant && (onThumbUpClick || onThumbDownClick);
911
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(ActionsContainer, { $role: role, className, children: [
912
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1374
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(ActionsContainer, { $role: role, className, children: [
1375
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
913
1376
  IconButton,
914
1377
  {
915
1378
  icon: isCopied ? IconCheck : IconCopy,
@@ -917,7 +1380,7 @@ function ChatMessageActions({ className, messageId, content, role, isHelpful })
917
1380
  "aria-label": isCopied ? "Copied" : "Copy message"
918
1381
  }
919
1382
  ),
920
- hasFeedback && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(AnimatedAction, { $visible: isHelpful !== false, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1383
+ hasFeedback && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(AnimatedAction, { $visible: isHelpful !== false, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
921
1384
  IconButton,
922
1385
  {
923
1386
  icon: IconThumbUp,
@@ -926,7 +1389,7 @@ function ChatMessageActions({ className, messageId, content, role, isHelpful })
926
1389
  "aria-label": "Good response"
927
1390
  }
928
1391
  ) }),
929
- hasFeedback && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(AnimatedAction, { $visible: isHelpful !== true, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1392
+ hasFeedback && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(AnimatedAction, { $visible: isHelpful !== true, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
930
1393
  IconButton,
931
1394
  {
932
1395
  icon: IconThumbDown,
@@ -964,19 +1427,28 @@ var MessageBubble = import_styled_components6.default.div`
964
1427
  padding: ${({ $role }) => $role === MESSAGE_ROLE.USER ? "12px 16px" : "0"};
965
1428
  border-radius: ${({ $role }) => $role === MESSAGE_ROLE.USER ? "16px" : "0"};
966
1429
  max-width: 100%;
1430
+ width: 100%;
967
1431
  min-width: 0;
1432
+ /* Vertical rhythm between consecutive blocks — renderer-owned, not agent-emitted.
1433
+ * Industry standard (Claude, ChatGPT, Vercel AI Elements): the chat renderer applies
1434
+ * spacing via CSS; agents emit semantic blocks only. Value matches the MessageContainer
1435
+ * gap. TODO: replace 12px with a clay spacing token when a spacing scale is added. */
1436
+ display: flex;
1437
+ flex-direction: column;
1438
+ gap: 12px;
968
1439
  `;
969
1440
 
970
1441
  // src/components/chat/chat-message/chat-message.tsx
971
- var import_jsx_runtime6 = require("react/jsx-runtime");
1442
+ var import_jsx_runtime14 = require("react/jsx-runtime");
972
1443
  function ChatMessage({ message }) {
973
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MessageRow, { $role: message.role, role: "article", "aria-label": `${message.role} message`, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(MessageContainer, { $role: message.role, children: [
974
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MessageBubble, { $role: message.role, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ChatMessageContent, { content: message.content, messageId: message.id }) }),
975
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1444
+ const clipboardText = (0, import_react11.useMemo)(() => copyMessageText(message), [message]);
1445
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(MessageRow, { $role: message.role, role: "article", "aria-label": `${message.role} message`, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(MessageContainer, { $role: message.role, children: [
1446
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(MessageBubble, { $role: message.role, "data-testid": "message-bubble", children: message.blocks.map((block, index) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Block, { block }, index)) }),
1447
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
976
1448
  ChatMessageActions,
977
1449
  {
978
1450
  messageId: message.id,
979
- content: message.content,
1451
+ content: clipboardText,
980
1452
  role: message.role,
981
1453
  isHelpful: message.isHelpful
982
1454
  }
@@ -988,6 +1460,7 @@ function ChatMessage({ message }) {
988
1460
  ChatContext,
989
1461
  ChatMessage,
990
1462
  MESSAGE_ROLE,
1463
+ copyMessageText,
991
1464
  useChatContext,
992
1465
  useCopyToClipboard
993
1466
  });
@@ -997,6 +1470,7 @@ function ChatMessage({ message }) {
997
1470
  @tabler/icons-react/dist/esm/createReactComponent.mjs:
998
1471
  @tabler/icons-react/dist/esm/icons/IconCheck.mjs:
999
1472
  @tabler/icons-react/dist/esm/icons/IconCopy.mjs:
1473
+ @tabler/icons-react/dist/esm/icons/IconDownload.mjs:
1000
1474
  @tabler/icons-react/dist/esm/icons/IconThumbDown.mjs:
1001
1475
  @tabler/icons-react/dist/esm/icons/IconThumbUp.mjs:
1002
1476
  @tabler/icons-react/dist/esm/tabler-icons-react.mjs: