@shapesos/clay 0.10.0 → 0.12.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 (85) hide show
  1. package/README.md +60 -0
  2. package/dist/artifacts.cjs +1467 -0
  3. package/dist/artifacts.cjs.map +1 -0
  4. package/dist/artifacts.d.cts +89 -0
  5. package/dist/artifacts.d.ts +89 -0
  6. package/dist/artifacts.js +33 -0
  7. package/dist/blocks.cjs +1418 -68
  8. package/dist/blocks.cjs.map +1 -1
  9. package/dist/blocks.css +2 -0
  10. package/dist/blocks.d.cts +36 -11
  11. package/dist/blocks.d.ts +36 -11
  12. package/dist/blocks.js +18 -5
  13. package/dist/button.d.cts +2 -2
  14. package/dist/button.d.ts +2 -2
  15. package/dist/chart.cjs +594 -0
  16. package/dist/chart.cjs.map +1 -0
  17. package/dist/chart.d.cts +439 -0
  18. package/dist/chart.d.ts +439 -0
  19. package/dist/chart.js +32 -0
  20. package/dist/chart.js.map +1 -0
  21. package/dist/chat.cjs +1462 -312
  22. package/dist/chat.cjs.map +1 -1
  23. package/dist/chat.d.cts +28 -10
  24. package/dist/chat.d.ts +28 -10
  25. package/dist/chat.js +10 -3
  26. package/dist/{chunk-R3BGPOAM.js → chunk-36CB624W.js} +53 -45
  27. package/dist/chunk-36CB624W.js.map +1 -0
  28. package/dist/chunk-4MZZH3WX.js +93 -0
  29. package/dist/chunk-4MZZH3WX.js.map +1 -0
  30. package/dist/chunk-AQEJRMRN.js +1 -0
  31. package/dist/chunk-AQEJRMRN.js.map +1 -0
  32. package/dist/{chunk-WS4IPADR.js → chunk-FFX3CAOX.js} +41 -109
  33. package/dist/chunk-FFX3CAOX.js.map +1 -0
  34. package/dist/chunk-JGMN6W72.js +12 -0
  35. package/dist/chunk-JGMN6W72.js.map +1 -0
  36. package/dist/{chunk-27GJUWVN.js → chunk-JJUIBBBU.js} +14 -8
  37. package/dist/chunk-JJUIBBBU.js.map +1 -0
  38. package/dist/{chunk-MLCRDVQ2.js → chunk-OBOXCBDL.js} +13 -5
  39. package/dist/chunk-OBOXCBDL.js.map +1 -0
  40. package/dist/chunk-P6GUNIAE.js +11 -0
  41. package/dist/chunk-P6GUNIAE.js.map +1 -0
  42. package/dist/chunk-QXGYMDIA.js +477 -0
  43. package/dist/chunk-QXGYMDIA.js.map +1 -0
  44. package/dist/chunk-Z5JWF24N.js +719 -0
  45. package/dist/chunk-Z5JWF24N.js.map +1 -0
  46. package/dist/icon.cjs +12 -4
  47. package/dist/icon.cjs.map +1 -1
  48. package/dist/icon.d.cts +19 -6
  49. package/dist/icon.d.ts +19 -6
  50. package/dist/icon.js +1 -1
  51. package/dist/index.cjs +1520 -332
  52. package/dist/index.cjs.map +1 -1
  53. package/dist/index.d.cts +7 -4
  54. package/dist/index.d.ts +7 -4
  55. package/dist/index.js +58 -25
  56. package/dist/lottie.d.cts +2 -2
  57. package/dist/lottie.d.ts +2 -2
  58. package/dist/table.cjs +142 -0
  59. package/dist/table.cjs.map +1 -0
  60. package/dist/table.d.cts +44 -0
  61. package/dist/table.d.ts +44 -0
  62. package/dist/table.js +25 -0
  63. package/dist/table.js.map +1 -0
  64. package/dist/text-area.d.cts +2 -2
  65. package/dist/text-area.d.ts +2 -2
  66. package/dist/types-B2aYk82c.d.cts +29 -0
  67. package/dist/types-B2aYk82c.d.ts +29 -0
  68. package/dist/types-C0BvwliI.d.cts +332 -0
  69. package/dist/types-C5bFH4v3.d.ts +332 -0
  70. package/dist/types-DCutaXjZ.d.cts +83 -0
  71. package/dist/types-uPfn67Dc.d.ts +83 -0
  72. package/dist/utils.cjs +14 -7
  73. package/dist/utils.cjs.map +1 -1
  74. package/dist/utils.d.cts +18 -1
  75. package/dist/utils.d.ts +18 -1
  76. package/dist/utils.js +6 -4
  77. package/package.json +41 -4
  78. package/dist/chunk-27GJUWVN.js.map +0 -1
  79. package/dist/chunk-MLCRDVQ2.js.map +0 -1
  80. package/dist/chunk-OKPNST44.js +0 -1
  81. package/dist/chunk-R3BGPOAM.js.map +0 -1
  82. package/dist/chunk-WS4IPADR.js.map +0 -1
  83. package/dist/types-Q9aqd9nq.d.cts +0 -34
  84. package/dist/types-Q9aqd9nq.d.ts +0 -34
  85. /package/dist/{chunk-OKPNST44.js.map → artifacts.js.map} +0 -0
package/dist/chat.cjs CHANGED
@@ -33,28 +33,193 @@ __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
42
  // src/components/chat/chat-message/chat-message.tsx
42
- var import_react7 = require("react");
43
+ var import_react15 = require("react");
43
44
 
44
- // src/components/blocks/text-block/text-block.tsx
45
- var import_react_markdown = __toESM(require("react-markdown"), 1);
46
- var import_remark_breaks = __toESM(require("remark-breaks"), 1);
47
- var import_remark_gfm = __toESM(require("remark-gfm"), 1);
45
+ // src/components/blocks/types.ts
46
+ var blockTypes = {
47
+ TEXT: "TEXT",
48
+ ARTIFACT_REF: "ARTIFACT_REF"
49
+ };
48
50
 
49
- // src/components/blocks/block-context/block-context.tsx
50
- var import_react = require("react");
51
- var BlockContext = (0, import_react.createContext)(null);
52
- function useBlockContext() {
53
- return (0, import_react.useContext)(BlockContext) ?? {};
51
+ // src/components/artifacts/artifact-base.ts
52
+ var artifactTypes = {
53
+ TABLE: "TABLE",
54
+ CHART: "CHART"
55
+ };
56
+
57
+ // src/components/artifacts/chart-artifact/types.ts
58
+ var DEFAULT_CHART_ARTIFACT_LABELS = {
59
+ download: "Download CSV",
60
+ unavailable: "Chart data is unavailable.",
61
+ loadError: "Couldn't load chart data.",
62
+ loading: "Loading chart\u2026",
63
+ empty: "No data to chart.",
64
+ othersCategoryLabels: ["Others", "Other"]
65
+ };
66
+
67
+ // src/components/artifacts/table-artifact/types.ts
68
+ var DEFAULT_TABLE_ARTIFACT_LABELS = {
69
+ download: "Download CSV",
70
+ unavailable: "Table data is unavailable.",
71
+ loadError: "Couldn't load table data.",
72
+ loading: "Loading table\u2026",
73
+ empty: "No rows to display."
74
+ };
75
+
76
+ // src/components/artifacts/chart-artifact/chart-artifact.tsx
77
+ var import_react9 = require("react");
78
+
79
+ // src/lib/utils.ts
80
+ var import_clsx = require("clsx");
81
+ var import_tailwind_merge = require("tailwind-merge");
82
+ function cn(...inputs) {
83
+ return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs));
54
84
  }
55
85
 
56
- // src/components/blocks/scrollable-table/scrollable-table-styles.ts
86
+ // src/components/ai-elements/artifact.tsx
87
+ var import_jsx_runtime = require("react/jsx-runtime");
88
+ var Artifact = ({ className, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
89
+ "div",
90
+ {
91
+ className: cn("flex flex-col overflow-hidden rounded-lg border bg-background shadow-sm", className),
92
+ ...props
93
+ }
94
+ );
95
+ 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 });
96
+ var ArtifactTitle = ({ className, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: cn("font-medium text-foreground text-sm", className), ...props });
97
+ var ArtifactActions = ({ className, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: cn("flex items-center gap-1", className), ...props });
98
+ var ArtifactContent = ({ className, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: cn("flex-1 overflow-auto p-4", className), ...props });
99
+
100
+ // node_modules/@tabler/icons-react/dist/esm/createReactComponent.mjs
101
+ var import_react = require("react");
102
+
103
+ // node_modules/@tabler/icons-react/dist/esm/defaultAttributes.mjs
104
+ var defaultAttributes = {
105
+ outline: {
106
+ xmlns: "http://www.w3.org/2000/svg",
107
+ width: 24,
108
+ height: 24,
109
+ viewBox: "0 0 24 24",
110
+ fill: "none",
111
+ stroke: "currentColor",
112
+ strokeWidth: 2,
113
+ strokeLinecap: "round",
114
+ strokeLinejoin: "round"
115
+ },
116
+ filled: {
117
+ xmlns: "http://www.w3.org/2000/svg",
118
+ width: 24,
119
+ height: 24,
120
+ viewBox: "0 0 24 24",
121
+ fill: "currentColor",
122
+ stroke: "none"
123
+ }
124
+ };
125
+
126
+ // node_modules/@tabler/icons-react/dist/esm/createReactComponent.mjs
127
+ var createReactComponent = (type, iconName, iconNamePascal, iconNode) => {
128
+ const Component = (0, import_react.forwardRef)(
129
+ ({ color = "currentColor", size = 24, stroke = 2, title, className, children, ...rest }, ref) => (0, import_react.createElement)(
130
+ "svg",
131
+ {
132
+ ref,
133
+ ...defaultAttributes[type],
134
+ width: size,
135
+ height: size,
136
+ className: [`tabler-icon`, `tabler-icon-${iconName}`, className].join(" "),
137
+ ...type === "filled" ? {
138
+ fill: color
139
+ } : {
140
+ strokeWidth: stroke,
141
+ stroke: color
142
+ },
143
+ ...rest
144
+ },
145
+ [
146
+ title && (0, import_react.createElement)("title", { key: "svg-title" }, title),
147
+ ...iconNode.map(([tag, attrs]) => (0, import_react.createElement)(tag, attrs)),
148
+ ...Array.isArray(children) ? children : [children]
149
+ ]
150
+ )
151
+ );
152
+ Component.displayName = `${iconNamePascal}`;
153
+ return Component;
154
+ };
155
+
156
+ // node_modules/@tabler/icons-react/dist/esm/icons/IconCheck.mjs
157
+ var __iconNode = [["path", { "d": "M5 12l5 5l10 -10", "key": "svg-0" }]];
158
+ var IconCheck = createReactComponent("outline", "check", "Check", __iconNode);
159
+
160
+ // node_modules/@tabler/icons-react/dist/esm/icons/IconCopy.mjs
161
+ 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" }]];
162
+ var IconCopy = createReactComponent("outline", "copy", "Copy", __iconNode2);
163
+
164
+ // node_modules/@tabler/icons-react/dist/esm/icons/IconDownload.mjs
165
+ 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" }]];
166
+ var IconDownload = createReactComponent("outline", "download", "Download", __iconNode3);
167
+
168
+ // node_modules/@tabler/icons-react/dist/esm/icons/IconThumbDown.mjs
169
+ 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" }]];
170
+ var IconThumbDown = createReactComponent("outline", "thumb-down", "ThumbDown", __iconNode4);
171
+
172
+ // node_modules/@tabler/icons-react/dist/esm/icons/IconThumbUp.mjs
173
+ 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" }]];
174
+ var IconThumbUp = createReactComponent("outline", "thumb-up", "ThumbUp", __iconNode5);
175
+
176
+ // src/components/icon-button/icon-button.tsx
177
+ var import_react3 = require("react");
178
+
179
+ // src/components/icon/icon.tsx
180
+ var import_react2 = require("react");
181
+
182
+ // src/components/icon/icon-styles.ts
57
183
  var import_styled_components = __toESM(require("styled-components"), 1);
184
+ var STROKE_WIDTH_BY_SIZE = {
185
+ 12: 1.4,
186
+ 14: 1.4,
187
+ 16: 1.8,
188
+ 18: 1.8,
189
+ 20: 1.8,
190
+ 24: 1.8
191
+ };
192
+ var DEFAULT_STROKE_WIDTH = 1.8;
193
+ function getStrokeWidth(size) {
194
+ return STROKE_WIDTH_BY_SIZE[size] ?? DEFAULT_STROKE_WIDTH;
195
+ }
196
+ var IconWrapper = import_styled_components.default.span`
197
+ display: inline-flex;
198
+ align-items: center;
199
+ justify-content: center;
200
+ color: ${({ $color }) => $color ?? "currentColor"};
201
+ flex-shrink: 0;
202
+ `;
203
+
204
+ // src/components/icon/icon.tsx
205
+ var import_jsx_runtime2 = require("react/jsx-runtime");
206
+ var DEFAULT_SIZE = 16;
207
+ var Icon = (0, import_react2.forwardRef)(function Icon2({ icon: IconComponent, size = DEFAULT_SIZE, color, className, "aria-label": ariaLabel }, ref) {
208
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
209
+ IconWrapper,
210
+ {
211
+ ref,
212
+ className,
213
+ $color: color,
214
+ "aria-label": ariaLabel,
215
+ role: ariaLabel ? "img" : void 0,
216
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(IconComponent, { width: size, height: size, strokeWidth: getStrokeWidth(size) })
217
+ }
218
+ );
219
+ });
220
+
221
+ // src/components/icon-button/icon-button-styles.ts
222
+ var import_styled_components2 = __toESM(require("styled-components"), 1);
58
223
 
59
224
  // src/tokens/colors.ts
60
225
  var colors = {
@@ -144,54 +309,229 @@ var colors = {
144
309
  "red-600": "#DC2626"
145
310
  };
146
311
 
147
- // src/components/blocks/scrollable-table/scrollable-table-styles.ts
148
- var TableContainer = import_styled_components.default.div`
149
- position: relative;
150
- margin-block: 8px 16px;
151
- `;
152
- var TableScroll = import_styled_components.default.div`
153
- width: 100%;
154
- overflow-x: auto;
155
- overflow-y: hidden;
156
- `;
157
- var TableActionsWrapper = import_styled_components.default.div`
158
- display: flex;
159
- justify-content: flex-start;
160
- margin-block: 8px 0;
161
- `;
162
- var TableActionsDivider = import_styled_components.default.div`
163
- height: 1px;
164
- background: ${colors["brown-40"]};
165
- margin-block: 8px 0;
312
+ // src/components/icon-button/icon-button-styles.ts
313
+ var PADDING = {
314
+ short: {
315
+ small: "4px",
316
+ medium: "6px"
317
+ },
318
+ long: {
319
+ small: "4px 6px",
320
+ medium: "6px 10px"
321
+ }
322
+ };
323
+ var BORDER_RADIUS = {
324
+ small: 6,
325
+ medium: 10
326
+ };
327
+ var Button = import_styled_components2.default.button`
328
+ display: inline-flex;
329
+ align-items: center;
330
+ justify-content: center;
331
+ padding: ${({ $size, $variant }) => PADDING[$variant][$size]};
332
+ border: none;
333
+ border-radius: ${({ $size }) => BORDER_RADIUS[$size]}px;
334
+ background: ${({ $isSelected }) => $isSelected ? colors["brown-40"] : "transparent"};
335
+ color: ${colors["brown-100"]};
336
+ cursor: pointer;
337
+ transition: background-color 100ms ease;
338
+
339
+ &:hover {
340
+ background: ${({ $isSelected }) => $isSelected ? colors["brown-40"] : colors["brown-20"]};
341
+ }
342
+
343
+ &:active {
344
+ background: ${colors["brown-40"]};
345
+ }
346
+
347
+ &:disabled {
348
+ color: ${colors["brown-50"]};
349
+ background: transparent;
350
+ cursor: not-allowed;
351
+ }
166
352
  `;
167
353
 
168
- // src/components/blocks/scrollable-table/scrollable-table.tsx
169
- var import_jsx_runtime = require("react/jsx-runtime");
170
- function ScrollableTable({ node: _node, counter, ...props }) {
171
- const currentIndex = counter ? counter.value : 0;
172
- if (counter) {
173
- counter.value += 1;
354
+ // src/components/icon-button/icon-button.tsx
355
+ var import_jsx_runtime3 = require("react/jsx-runtime");
356
+ var ICON_SIZE_BY_BUTTON_SIZE = {
357
+ small: 16,
358
+ medium: 20
359
+ };
360
+ var IconButton = (0, import_react3.forwardRef)(function IconButton2({
361
+ icon,
362
+ size = "small",
363
+ variant = "short",
364
+ isSelected = false,
365
+ disabled = false,
366
+ onClick,
367
+ className,
368
+ id,
369
+ as,
370
+ "aria-label": ariaLabel,
371
+ ...rest
372
+ }, ref) {
373
+ const isNativeButton = as === void 0 || as === "button";
374
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
375
+ Button,
376
+ {
377
+ ref,
378
+ className,
379
+ id,
380
+ as,
381
+ $size: size,
382
+ $variant: variant,
383
+ $isSelected: isSelected,
384
+ disabled,
385
+ onClick: disabled ? void 0 : onClick,
386
+ "aria-label": ariaLabel,
387
+ ...isNativeButton ? { type: "button" } : {},
388
+ ...rest,
389
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Icon, { icon, size: ICON_SIZE_BY_BUTTON_SIZE[size] })
390
+ }
391
+ );
392
+ });
393
+
394
+ // src/components/ui/tooltip.tsx
395
+ var React = __toESM(require("react"), 1);
396
+ var TooltipPrimitive = __toESM(require("@radix-ui/react-tooltip"), 1);
397
+ var import_jsx_runtime4 = require("react/jsx-runtime");
398
+ var TooltipProvider = TooltipPrimitive.Provider;
399
+ var Tooltip = TooltipPrimitive.Root;
400
+ var TooltipTrigger = TooltipPrimitive.Trigger;
401
+ var TooltipContent = React.forwardRef(({ className, sideOffset = 4, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
402
+ TooltipPrimitive.Content,
403
+ {
404
+ ref,
405
+ sideOffset,
406
+ className: cn(
407
+ "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]",
408
+ className
409
+ ),
410
+ ...props
411
+ }
412
+ ));
413
+ TooltipContent.displayName = TooltipPrimitive.Content.displayName;
414
+
415
+ // src/components/artifacts/artifact-download-button/artifact-download-button.tsx
416
+ var import_jsx_runtime5 = require("react/jsx-runtime");
417
+ var DEFAULT_LABEL = "Download CSV";
418
+ function ArtifactDownloadButton({
419
+ href,
420
+ label = DEFAULT_LABEL,
421
+ onClick
422
+ }) {
423
+ if (!isSafeDownloadUrl(href)) return null;
424
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(TooltipProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Tooltip, { children: [
425
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
426
+ IconButton,
427
+ {
428
+ as: "a",
429
+ href,
430
+ download: true,
431
+ rel: "noopener noreferrer",
432
+ onClick,
433
+ icon: IconDownload,
434
+ size: "small",
435
+ "aria-label": label
436
+ }
437
+ ) }),
438
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(TooltipContent, { children: label })
439
+ ] }) });
440
+ }
441
+ function isSafeDownloadUrl(url) {
442
+ try {
443
+ const parsed = new URL(url);
444
+ return parsed.protocol === "https:" || parsed.protocol === "http:";
445
+ } catch {
446
+ return false;
174
447
  }
175
- const { TableActions } = useBlockContext();
176
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TableContainer, { children: [
177
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableScroll, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("table", { ...props }) }),
178
- TableActions && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
179
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableActionsWrapper, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableActions, { tableIndex: currentIndex }) }),
180
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableActionsDivider, {})
181
- ] })
182
- ] });
183
448
  }
184
449
 
185
- // src/components/blocks/text-block/sanitize.ts
186
- function sanitize(text) {
187
- return text.split("\n").filter((line) => {
188
- const t = line.trim();
189
- return !(t.startsWith("<!-- table-title:") && t.endsWith("-->"));
190
- }).join("\n");
450
+ // src/components/artifacts/status-message.tsx
451
+ var import_react4 = require("react");
452
+ function StatusMessage({ $tone, children }) {
453
+ return (0, import_react4.createElement)(
454
+ "div",
455
+ {
456
+ className: `py-6 px-2 text-center text-sm font-medium leading-5 ${$tone === "error" ? "text-red-600" : "text-brown-70"}`
457
+ },
458
+ children
459
+ );
191
460
  }
192
461
 
193
- // src/components/blocks/text-block/text-block-styles.ts
194
- var import_styled_components2 = __toESM(require("styled-components"), 1);
462
+ // src/components/chart/bar-chart/bar-chart.tsx
463
+ var import_react5 = require("react");
464
+ var import_recharts2 = require("recharts");
465
+
466
+ // src/components/chart/chart-container.tsx
467
+ var import_recharts = require("recharts");
468
+ var import_jsx_runtime6 = require("react/jsx-runtime");
469
+ function ChartContainer({
470
+ children,
471
+ height = 320,
472
+ width = "100%",
473
+ className,
474
+ style
475
+ }) {
476
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: cn("text-xs text-foreground", className), style: { width, height, ...style }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_recharts.ResponsiveContainer, { width: "100%", height: "100%", children }) });
477
+ }
478
+
479
+ // src/components/chart/chart-legend-content.tsx
480
+ var import_jsx_runtime7 = require("react/jsx-runtime");
481
+ function ChartLegendContent({ payload, className, iconType = "square" }) {
482
+ if (!payload || payload.length === 0) return null;
483
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
484
+ "ul",
485
+ {
486
+ className: cn("flex flex-wrap items-center justify-center gap-x-4 gap-y-2 text-xs text-brown-80", className),
487
+ children: payload.map((entry, i) => {
488
+ const swatchColor = typeof entry.color === "string" ? entry.color : "transparent";
489
+ const label = entry.value !== void 0 ? String(entry.value) : "";
490
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("li", { className: "flex items-center gap-1.5", children: [
491
+ iconType === "square" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "inline-block h-2.5 w-2.5 shrink-0 rounded-sm", style: { background: swatchColor } }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "inline-block h-[3px] w-4 shrink-0 rounded-full", style: { background: swatchColor } }),
492
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: label })
493
+ ] }, `${label}-${i}`);
494
+ })
495
+ }
496
+ );
497
+ }
498
+
499
+ // src/components/chart/chart-tooltip-content.tsx
500
+ var import_jsx_runtime8 = require("react/jsx-runtime");
501
+ function ChartTooltipContent({
502
+ active,
503
+ payload,
504
+ label,
505
+ className,
506
+ showLabel = true,
507
+ formatValue
508
+ }) {
509
+ if (!active || !payload || payload.length === 0) return null;
510
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
511
+ "div",
512
+ {
513
+ className: cn(
514
+ "rounded-lg border border-brown-40 bg-brown-10 px-3 py-2 text-xs shadow-sm",
515
+ "min-w-32 text-foreground",
516
+ className
517
+ ),
518
+ children: [
519
+ showLabel && label !== void 0 && label !== null ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "mb-1.5 font-medium text-brown-90", children: String(label) }) : null,
520
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("ul", { className: "space-y-1", children: payload.map((entry, i) => {
521
+ const name = entry.name !== void 0 ? String(entry.name) : "";
522
+ const rawValue = entry.value;
523
+ const display = formatValue ? formatValue(rawValue, name) : rawValue === void 0 ? "" : String(rawValue);
524
+ const swatch = typeof entry.color === "string" ? entry.color : "transparent";
525
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("li", { className: "flex items-center gap-2", children: [
526
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "inline-block h-2.5 w-2.5 shrink-0 rounded-sm", style: { background: swatch } }),
527
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "text-brown-70", children: name }),
528
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "ml-auto font-semibold tabular-nums text-brown-90", children: display })
529
+ ] }, `${name}-${i}`);
530
+ }) })
531
+ ]
532
+ }
533
+ );
534
+ }
195
535
 
196
536
  // src/tokens/typography.ts
197
537
  var fontFamilies = {
@@ -488,8 +828,1010 @@ function typographyMixin(type) {
488
828
  ].join(";\n ");
489
829
  }
490
830
 
491
- // src/components/blocks/text-block/text-block-styles.ts
492
- var TextBlockWrapper = import_styled_components2.default.div`
831
+ // src/components/chart/chart-palette.ts
832
+ var CHART_PALETTE = [
833
+ colors["blue-600"],
834
+ // #2563EB — primary
835
+ colors["blue-400"],
836
+ // #60A5FA — mid
837
+ colors["blue-300"],
838
+ // #93C5FD — light
839
+ colors["blue-100"],
840
+ // #DBEAFE — very light
841
+ colors["indigo-400"],
842
+ // #818CF8
843
+ colors["indigo-500"],
844
+ // #6366F1
845
+ colors["indigo-600"],
846
+ // #4F46E5
847
+ colors["indigo-800"],
848
+ // #3730A3
849
+ colors["blue-800"]
850
+ // #1E40AF — deepest
851
+ ];
852
+ var OTHERS_SLICE_COLOR = colors["brown-60"];
853
+ var DEFAULT_OTHERS_CATEGORY_LABELS = ["others", "other"];
854
+ function isOthersCategory(value, labels) {
855
+ if (value === null || value === void 0) return false;
856
+ if (labels.length === 0) return false;
857
+ const normalized = String(value).trim().toLowerCase();
858
+ for (const label of labels) {
859
+ if (normalized === label.toLowerCase()) return true;
860
+ }
861
+ return false;
862
+ }
863
+ function colorForSeriesIndex(index, palette = CHART_PALETTE) {
864
+ return palette[index % palette.length] ?? CHART_PALETTE[0];
865
+ }
866
+
867
+ // src/components/chart/chart-styles.ts
868
+ var GRID_STROKE = colors["brown-40"];
869
+ var AXIS_STROKE = colors["brown-50"];
870
+ var AXIS_TICK_STYLE = {
871
+ fill: colors["brown-70"],
872
+ fontSize: 12,
873
+ fontWeight: 500
874
+ };
875
+ var CHART_VALUE_LABEL_STYLE = {
876
+ fill: colors["brown-70"],
877
+ fontSize: 12,
878
+ fontWeight: 500
879
+ };
880
+ var BAR_TOOLTIP_CURSOR_FILL = colors["brown-alpha-12"];
881
+ var LINE_ACTIVE_DOT_STROKE = colors["brown-10"];
882
+ var CHART_MARGIN = { top: 8, right: 16, left: 0, bottom: 4 };
883
+ var PIE_CHART_MARGIN = { top: 0, right: 24, left: 24, bottom: 4 };
884
+ var CHART_AXIS_PROPS = {
885
+ stroke: AXIS_STROKE,
886
+ tick: AXIS_TICK_STYLE,
887
+ tickLine: false,
888
+ axisLine: false
889
+ };
890
+ var CHART_GRID_PROPS = {
891
+ stroke: GRID_STROKE,
892
+ strokeDasharray: "3 3",
893
+ vertical: false
894
+ };
895
+
896
+ // src/components/chart/chart-tick-utils.ts
897
+ function maxTickCharsByCategoryCount(n) {
898
+ if (n <= 5) return 24;
899
+ if (n <= 8) return 14;
900
+ if (n <= 12) return 10;
901
+ if (n <= 16) return 7;
902
+ return 5;
903
+ }
904
+ function truncateLabel(value, maxChars) {
905
+ const s = String(value ?? "");
906
+ if (s.length <= maxChars) return s;
907
+ return s.slice(0, Math.max(1, maxChars - 1)).trimEnd() + "\u2026";
908
+ }
909
+ function evenlyDistributedVisibleIndices(n, target) {
910
+ if (n <= target) {
911
+ return new Set(Array.from({ length: n }, (_, i) => i));
912
+ }
913
+ const set = /* @__PURE__ */ new Set();
914
+ const step = (n - 1) / (target - 1);
915
+ for (let i = 0; i < target; i++) {
916
+ set.add(Math.round(i * step));
917
+ }
918
+ return set;
919
+ }
920
+ function defaultBarTickFormatter(categoryCount) {
921
+ const max = maxTickCharsByCategoryCount(categoryCount);
922
+ return (value) => truncateLabel(value, max);
923
+ }
924
+ function defaultLineChartTickFormatter(categoryCount, visibleIndices) {
925
+ const max = maxTickCharsByCategoryCount(visibleIndices.size || categoryCount);
926
+ return (value, index) => {
927
+ if (!visibleIndices.has(index)) return "";
928
+ return truncateLabel(value, max);
929
+ };
930
+ }
931
+
932
+ // src/components/chart/coerce-numeric.ts
933
+ function coerceNumericColumns(rows, numericKeys) {
934
+ return rows.map((row) => {
935
+ const out = { ...row };
936
+ for (const k of numericKeys) {
937
+ const v = out[k];
938
+ if (v == null) continue;
939
+ const n = Number(v);
940
+ out[k] = Number.isFinite(n) ? n : NaN;
941
+ }
942
+ return out;
943
+ });
944
+ }
945
+
946
+ // src/components/chart/bar-chart/bar-chart.tsx
947
+ var import_jsx_runtime9 = require("react/jsx-runtime");
948
+ function BarChart({
949
+ data,
950
+ xKey,
951
+ series,
952
+ stacked = false,
953
+ height = 320,
954
+ width = "100%",
955
+ className,
956
+ palette = CHART_PALETTE,
957
+ showTooltip = true,
958
+ showLegend,
959
+ showGrid = true,
960
+ showXAxis = true,
961
+ showYAxis = true,
962
+ showValueLabels,
963
+ xAxisInterval = 0,
964
+ xAxisAngle,
965
+ xAxisTickFormatter
966
+ }) {
967
+ const seriesKeys = (0, import_react5.useMemo)(() => series.map((s) => s.key), [series]);
968
+ const coercedData = (0, import_react5.useMemo)(
969
+ () => coerceNumericColumns(data, seriesKeys),
970
+ [data, seriesKeys]
971
+ );
972
+ const resolvedShowLegend = showLegend ?? series.length > 1;
973
+ const resolvedShowValueLabels = showValueLabels ?? (!stacked && series.length === 1);
974
+ const resolvedAngle = xAxisAngle ?? (coercedData.length >= 8 ? -30 : 0);
975
+ const resolvedTickFormatter = (0, import_react5.useMemo)(
976
+ () => xAxisTickFormatter ?? defaultBarTickFormatter(coercedData.length),
977
+ [xAxisTickFormatter, coercedData.length]
978
+ );
979
+ const angledLabelPad = resolvedAngle === 0 ? 0 : Math.min(Math.abs(resolvedAngle) * 0.7, 40);
980
+ const chartMargin = { ...CHART_MARGIN, bottom: CHART_MARGIN.bottom + angledLabelPad };
981
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ChartContainer, { height, width, className, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_recharts2.BarChart, { data: coercedData, margin: chartMargin, barCategoryGap: "22%", children: [
982
+ showGrid ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_recharts2.CartesianGrid, { ...CHART_GRID_PROPS }) : null,
983
+ showXAxis ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
984
+ import_recharts2.XAxis,
985
+ {
986
+ dataKey: xKey,
987
+ ...CHART_AXIS_PROPS,
988
+ interval: xAxisInterval,
989
+ angle: resolvedAngle,
990
+ textAnchor: resolvedAngle !== 0 ? "end" : "middle",
991
+ tickMargin: resolvedAngle === 0 ? 8 : 12,
992
+ height: resolvedAngle !== 0 ? 60 : void 0,
993
+ tickFormatter: resolvedTickFormatter
994
+ }
995
+ ) : null,
996
+ showYAxis ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_recharts2.YAxis, { ...CHART_AXIS_PROPS, width: 40 }) : null,
997
+ showTooltip ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_recharts2.Tooltip, { cursor: { fill: BAR_TOOLTIP_CURSOR_FILL }, content: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ChartTooltipContent, {}) }) : null,
998
+ resolvedShowLegend ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
999
+ import_recharts2.Legend,
1000
+ {
1001
+ verticalAlign: "top",
1002
+ align: "center",
1003
+ wrapperStyle: { top: 0 },
1004
+ itemSorter: null,
1005
+ content: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ChartLegendContent, { iconType: "square" })
1006
+ }
1007
+ ) : null,
1008
+ series.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1009
+ import_recharts2.Bar,
1010
+ {
1011
+ dataKey: s.key,
1012
+ name: s.label,
1013
+ fill: colorForSeriesIndex(i, palette),
1014
+ stackId: stacked ? "default" : s.key,
1015
+ radius: barRadius(stacked, i, series.length),
1016
+ children: resolvedShowValueLabels ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_recharts2.LabelList, { dataKey: s.key, position: "top", style: CHART_VALUE_LABEL_STYLE }) : null
1017
+ },
1018
+ s.key
1019
+ ))
1020
+ ] }) });
1021
+ }
1022
+ function barRadius(stacked, seriesIndex, seriesCount) {
1023
+ if (!stacked) return [6, 6, 0, 0];
1024
+ const isTopSegment = seriesIndex === seriesCount - 1;
1025
+ return isTopSegment ? [6, 6, 0, 0] : [0, 0, 0, 0];
1026
+ }
1027
+
1028
+ // src/components/artifacts/chart-artifact/chart-variant-services/constants.ts
1029
+ var VARIANT_CHART_HEIGHT = "100%";
1030
+
1031
+ // src/components/artifacts/chart-artifact/chart-variant-services/bar-chart-variant-component.tsx
1032
+ var import_jsx_runtime10 = require("react/jsx-runtime");
1033
+ function BarChartVariantComponent({ config, rows }) {
1034
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1035
+ BarChart,
1036
+ {
1037
+ data: rows,
1038
+ xKey: config.xKey,
1039
+ series: config.series,
1040
+ stacked: config.stacked,
1041
+ height: VARIANT_CHART_HEIGHT
1042
+ }
1043
+ );
1044
+ }
1045
+
1046
+ // src/components/artifacts/chart-artifact/chart-variant-services/shared-column-keys.ts
1047
+ function seriesBasedColumnKeys(config) {
1048
+ return [config.xKey, ...config.series.map((s) => s.key)];
1049
+ }
1050
+ function pieColumnKeys(config) {
1051
+ return [config.categoryKey, config.valueKey];
1052
+ }
1053
+
1054
+ // src/components/artifacts/chart-artifact/chart-variant-services/shared-validators.ts
1055
+ function isValidChartSeries(series) {
1056
+ if (!series || typeof series !== "object") return false;
1057
+ const candidate = series;
1058
+ if (typeof candidate.key !== "string" || candidate.key.length === 0) return false;
1059
+ if (typeof candidate.label !== "string") return false;
1060
+ return true;
1061
+ }
1062
+
1063
+ // src/components/artifacts/chart-artifact/chart-variant-services/bar-chart-variant-service.tsx
1064
+ function isValidBarChartConfig(config) {
1065
+ if (!config || typeof config !== "object") return false;
1066
+ const candidate = config;
1067
+ if (candidate.variant !== "BAR") return false;
1068
+ if (typeof candidate.xKey !== "string" || candidate.xKey.length === 0) return false;
1069
+ if (!Array.isArray(candidate.series) || candidate.series.length === 0) return false;
1070
+ if (!candidate.series.every(isValidChartSeries)) return false;
1071
+ return true;
1072
+ }
1073
+ var BarChartVariantService = {
1074
+ variant: "BAR",
1075
+ isValidConfig: isValidBarChartConfig,
1076
+ columnKeys: seriesBasedColumnKeys,
1077
+ Component: BarChartVariantComponent
1078
+ };
1079
+
1080
+ // src/components/chart/line-chart/line-chart.tsx
1081
+ var import_react6 = require("react");
1082
+ var import_recharts3 = require("recharts");
1083
+ var import_jsx_runtime11 = require("react/jsx-runtime");
1084
+ var TARGET_VISIBLE_LABELS = 5;
1085
+ function LineChart({
1086
+ data,
1087
+ xKey,
1088
+ series,
1089
+ smooth = true,
1090
+ showDots = true,
1091
+ strokeWidth = 2,
1092
+ height = 320,
1093
+ width = "100%",
1094
+ className,
1095
+ palette = CHART_PALETTE,
1096
+ showTooltip = true,
1097
+ showLegend,
1098
+ showGrid = true,
1099
+ showXAxis = true,
1100
+ showYAxis = true,
1101
+ showValueLabels = false,
1102
+ xAxisInterval = 0,
1103
+ xAxisAngle = 0,
1104
+ xAxisTickFormatter
1105
+ }) {
1106
+ const seriesKeys = (0, import_react6.useMemo)(() => series.map((s) => s.key), [series]);
1107
+ const coercedData = (0, import_react6.useMemo)(
1108
+ () => coerceNumericColumns(data, seriesKeys),
1109
+ [data, seriesKeys]
1110
+ );
1111
+ const lineType = smooth ? "monotone" : "linear";
1112
+ const resolvedShowLegend = showLegend ?? series.length > 1;
1113
+ const resolvedTickFormatter = (0, import_react6.useMemo)(() => {
1114
+ if (xAxisTickFormatter) return xAxisTickFormatter;
1115
+ const visible = evenlyDistributedVisibleIndices(coercedData.length, TARGET_VISIBLE_LABELS);
1116
+ return defaultLineChartTickFormatter(coercedData.length, visible);
1117
+ }, [xAxisTickFormatter, coercedData.length]);
1118
+ const angledLabelPad = xAxisAngle === 0 ? 0 : Math.min(Math.abs(xAxisAngle) * 0.7, 40);
1119
+ const chartMargin = { ...CHART_MARGIN, right: CHART_MARGIN.right + 16, bottom: CHART_MARGIN.bottom + angledLabelPad };
1120
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ChartContainer, { height, width, className, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_recharts3.LineChart, { data: coercedData, margin: chartMargin, children: [
1121
+ showGrid ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_recharts3.CartesianGrid, { ...CHART_GRID_PROPS }) : null,
1122
+ showXAxis ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1123
+ import_recharts3.XAxis,
1124
+ {
1125
+ dataKey: xKey,
1126
+ ...CHART_AXIS_PROPS,
1127
+ interval: xAxisInterval,
1128
+ angle: xAxisAngle,
1129
+ textAnchor: xAxisAngle !== 0 ? "end" : "middle",
1130
+ tickMargin: xAxisAngle === 0 ? 8 : 12,
1131
+ height: xAxisAngle !== 0 ? 60 : void 0,
1132
+ tickFormatter: resolvedTickFormatter
1133
+ }
1134
+ ) : null,
1135
+ showYAxis ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_recharts3.YAxis, { ...CHART_AXIS_PROPS, width: 40 }) : null,
1136
+ showTooltip ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_recharts3.Tooltip, { content: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ChartTooltipContent, {}) }) : null,
1137
+ resolvedShowLegend ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1138
+ import_recharts3.Legend,
1139
+ {
1140
+ verticalAlign: "top",
1141
+ align: "center",
1142
+ wrapperStyle: { top: 0 },
1143
+ itemSorter: null,
1144
+ content: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ChartLegendContent, { iconType: "line" })
1145
+ }
1146
+ ) : null,
1147
+ series.map((s, i) => {
1148
+ const color = colorForSeriesIndex(i, palette);
1149
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1150
+ import_recharts3.Line,
1151
+ {
1152
+ type: lineType,
1153
+ dataKey: s.key,
1154
+ name: s.label,
1155
+ stroke: color,
1156
+ strokeWidth,
1157
+ dot: showDots ? { r: 3, fill: color, stroke: color, strokeWidth: 0 } : false,
1158
+ activeDot: { r: 5, fill: color, stroke: LINE_ACTIVE_DOT_STROKE, strokeWidth: 2 },
1159
+ children: showValueLabels ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_recharts3.LabelList, { dataKey: s.key, position: "top", style: CHART_VALUE_LABEL_STYLE }) : null
1160
+ },
1161
+ s.key
1162
+ );
1163
+ })
1164
+ ] }) });
1165
+ }
1166
+
1167
+ // src/components/artifacts/chart-artifact/chart-variant-services/line-chart-variant-component.tsx
1168
+ var import_jsx_runtime12 = require("react/jsx-runtime");
1169
+ function LineChartVariantComponent({ config, rows }) {
1170
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(LineChart, { data: rows, xKey: config.xKey, series: config.series, height: VARIANT_CHART_HEIGHT });
1171
+ }
1172
+
1173
+ // src/components/artifacts/chart-artifact/chart-variant-services/line-chart-variant-service.tsx
1174
+ function isValidLineChartConfig(config) {
1175
+ if (!config || typeof config !== "object") return false;
1176
+ const candidate = config;
1177
+ if (candidate.variant !== "LINE") return false;
1178
+ if (typeof candidate.xKey !== "string" || candidate.xKey.length === 0) return false;
1179
+ if (!Array.isArray(candidate.series) || candidate.series.length === 0) return false;
1180
+ if (!candidate.series.every(isValidChartSeries)) return false;
1181
+ return true;
1182
+ }
1183
+ var LineChartVariantService = {
1184
+ variant: "LINE",
1185
+ isValidConfig: isValidLineChartConfig,
1186
+ columnKeys: seriesBasedColumnKeys,
1187
+ Component: LineChartVariantComponent
1188
+ };
1189
+
1190
+ // src/components/chart/pie-chart/pie-chart.tsx
1191
+ var import_react7 = require("react");
1192
+ var import_recharts4 = require("recharts");
1193
+ var import_jsx_runtime13 = require("react/jsx-runtime");
1194
+ function PieChart({
1195
+ data,
1196
+ categoryKey,
1197
+ valueKey,
1198
+ height = 320,
1199
+ width = "100%",
1200
+ className,
1201
+ palette = CHART_PALETTE,
1202
+ showLabels = true,
1203
+ showTooltip = true,
1204
+ showLegend = true,
1205
+ innerRadius = 0,
1206
+ outerRadius = "68%",
1207
+ othersCategoryLabels = DEFAULT_OTHERS_CATEGORY_LABELS
1208
+ }) {
1209
+ const { orderedData, sliceColors } = (0, import_react7.useMemo)(() => {
1210
+ const coerced = coerceNumericColumns(data, [valueKey]);
1211
+ const namedRows = [];
1212
+ const namedColors = [];
1213
+ const othersRows = [];
1214
+ const othersColors = [];
1215
+ for (const row of coerced) {
1216
+ if (isOthersCategory(row[categoryKey], othersCategoryLabels)) {
1217
+ othersRows.push(row);
1218
+ othersColors.push(OTHERS_SLICE_COLOR);
1219
+ } else {
1220
+ namedRows.push(row);
1221
+ namedColors.push(colorForSeriesIndex(namedRows.length - 1, palette));
1222
+ }
1223
+ }
1224
+ return {
1225
+ orderedData: [...namedRows, ...othersRows],
1226
+ sliceColors: [...namedColors, ...othersColors]
1227
+ };
1228
+ }, [data, valueKey, categoryKey, othersCategoryLabels, palette]);
1229
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ChartContainer, { height, width, className, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_recharts4.PieChart, { margin: PIE_CHART_MARGIN, children: [
1230
+ showTooltip ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_recharts4.Tooltip, { content: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ChartTooltipContent, {}) }) : null,
1231
+ showLegend ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1232
+ import_recharts4.Legend,
1233
+ {
1234
+ verticalAlign: "top",
1235
+ align: "center",
1236
+ wrapperStyle: { top: 0 },
1237
+ itemSorter: null,
1238
+ content: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ChartLegendContent, { iconType: "square" })
1239
+ }
1240
+ ) : null,
1241
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1242
+ import_recharts4.Pie,
1243
+ {
1244
+ data: orderedData,
1245
+ dataKey: valueKey,
1246
+ nameKey: categoryKey,
1247
+ cx: "50%",
1248
+ cy: "52%",
1249
+ startAngle: 90,
1250
+ endAngle: -270,
1251
+ innerRadius,
1252
+ outerRadius,
1253
+ label: showLabels ? (entry) => {
1254
+ const v = entry.value;
1255
+ if (v === void 0 || v === null) return null;
1256
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1257
+ "text",
1258
+ {
1259
+ x: entry.x,
1260
+ y: entry.y,
1261
+ fill: CHART_VALUE_LABEL_STYLE.fill,
1262
+ fontSize: CHART_VALUE_LABEL_STYLE.fontSize,
1263
+ fontWeight: CHART_VALUE_LABEL_STYLE.fontWeight,
1264
+ textAnchor: entry.textAnchor,
1265
+ dominantBaseline: "central",
1266
+ children: String(v)
1267
+ }
1268
+ );
1269
+ } : false,
1270
+ labelLine: false,
1271
+ stroke: "none",
1272
+ strokeWidth: 0,
1273
+ children: orderedData.map((_row, i) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_recharts4.Cell, { fill: sliceColors[i] }, i))
1274
+ }
1275
+ )
1276
+ ] }) });
1277
+ }
1278
+
1279
+ // src/components/artifacts/chart-artifact/chart-variant-services/pie-chart-variant-component.tsx
1280
+ var import_jsx_runtime14 = require("react/jsx-runtime");
1281
+ function PieChartVariantComponent({
1282
+ config,
1283
+ rows,
1284
+ labels
1285
+ }) {
1286
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1287
+ PieChart,
1288
+ {
1289
+ data: rows,
1290
+ categoryKey: config.categoryKey,
1291
+ valueKey: config.valueKey,
1292
+ othersCategoryLabels: labels.othersCategoryLabels,
1293
+ height: VARIANT_CHART_HEIGHT
1294
+ }
1295
+ );
1296
+ }
1297
+
1298
+ // src/components/artifacts/chart-artifact/chart-variant-services/pie-chart-variant-service.tsx
1299
+ function isValidPieChartConfig(config) {
1300
+ if (!config || typeof config !== "object") return false;
1301
+ const candidate = config;
1302
+ if (candidate.variant !== "PIE") return false;
1303
+ if (typeof candidate.categoryKey !== "string" || candidate.categoryKey.length === 0) return false;
1304
+ if (typeof candidate.valueKey !== "string" || candidate.valueKey.length === 0) return false;
1305
+ if (!Array.isArray(candidate.series)) return false;
1306
+ return true;
1307
+ }
1308
+ var PieChartVariantService = {
1309
+ variant: "PIE",
1310
+ isValidConfig: isValidPieChartConfig,
1311
+ columnKeys: pieColumnKeys,
1312
+ Component: PieChartVariantComponent
1313
+ };
1314
+
1315
+ // src/components/artifacts/chart-artifact/chart-variant-services/index.ts
1316
+ var ChartVariantServices = {
1317
+ LINE: LineChartVariantService,
1318
+ BAR: BarChartVariantService,
1319
+ PIE: PieChartVariantService
1320
+ };
1321
+
1322
+ // src/components/artifacts/chart-artifact/chart-artifact-content.tsx
1323
+ var import_jsx_runtime15 = require("react/jsx-runtime");
1324
+ function ChartArtifactContent({ config, rows, labels }) {
1325
+ const service = ChartVariantServices[config.variant];
1326
+ if (!service) {
1327
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(StatusMessage, { $tone: "info", children: labels.unavailable });
1328
+ }
1329
+ const Component = service.Component;
1330
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Component, { config, rows, labels });
1331
+ }
1332
+
1333
+ // src/components/artifacts/parse-csv.ts
1334
+ var import_papaparse = __toESM(require("papaparse"), 1);
1335
+ function parseCsv(text, columns) {
1336
+ const result = import_papaparse.default.parse(text, {
1337
+ header: true,
1338
+ skipEmptyLines: true,
1339
+ dynamicTyping: false
1340
+ });
1341
+ const csvHeaders = new Set(result.meta?.fields ?? []);
1342
+ const presentColumns = columns.filter((col) => csvHeaders.has(col.key));
1343
+ if (presentColumns.length === 0) return [];
1344
+ return result.data.map((rawRow) => {
1345
+ const row = {};
1346
+ for (const column of presentColumns) {
1347
+ const value = rawRow[column.key];
1348
+ row[column.key] = value === void 0 || value === "" ? null : value;
1349
+ }
1350
+ return row;
1351
+ });
1352
+ }
1353
+
1354
+ // src/components/artifacts/use-rows-artifact-data.ts
1355
+ var import_react8 = require("react");
1356
+ var LOADING = { status: "loading", rows: null, error: null };
1357
+ var UNAVAILABLE = { status: "unavailable", rows: null, error: null };
1358
+ var INITIAL_INTERNAL = { cacheKey: null, data: LOADING };
1359
+ function columnsFingerprint(columns) {
1360
+ return columns.map((c) => c.key).join("|");
1361
+ }
1362
+ function scrubUrl(url) {
1363
+ const q = url.indexOf("?");
1364
+ return q === -1 ? url : `${url.slice(0, q)}?<redacted>`;
1365
+ }
1366
+ function useRowsArtifactData(presignedUrl, columns, parse) {
1367
+ const colsKey = (0, import_react8.useMemo)(() => columnsFingerprint(columns), [columns]);
1368
+ const safeUrl = presignedUrl && isSafeDownloadUrl(presignedUrl) ? presignedUrl : null;
1369
+ const cacheKey = safeUrl ? `${safeUrl}::${colsKey}` : null;
1370
+ const [state, setState] = (0, import_react8.useState)(INITIAL_INTERNAL);
1371
+ (0, import_react8.useEffect)(() => {
1372
+ if (!safeUrl) return;
1373
+ const controller = new AbortController();
1374
+ fetch(safeUrl, {
1375
+ signal: controller.signal,
1376
+ headers: { Accept: "text/csv" },
1377
+ // Presigned URLs are cross-origin S3 in production and don't need credentials. Explicit
1378
+ // omit so a same-origin URL (compromised payload, local dev) can't accidentally receive
1379
+ // session cookies or auth headers.
1380
+ credentials: "omit"
1381
+ }).then(async (response) => {
1382
+ if (!response.ok) throw new Error(`Failed to load artifact data (${response.status})`);
1383
+ return response.text();
1384
+ }).then((text) => {
1385
+ if (controller.signal.aborted) return;
1386
+ try {
1387
+ const rows = parse(text, columns);
1388
+ setState({ cacheKey: `${safeUrl}::${colsKey}`, data: { status: "ready", rows, error: null } });
1389
+ } catch (parseError) {
1390
+ const err = parseError instanceof Error ? parseError : new Error(String(parseError));
1391
+ console.error("[ArtifactData] CSV parse failed:", err.message);
1392
+ setState({ cacheKey: `${safeUrl}::${colsKey}`, data: { status: "error", rows: null, error: err } });
1393
+ }
1394
+ }).catch((error) => {
1395
+ if (controller.signal.aborted) return;
1396
+ const err = error instanceof Error ? error : new Error(String(error));
1397
+ console.error(`[ArtifactData] Fetch failed [${scrubUrl(safeUrl)}]:`, err.message);
1398
+ setState({ cacheKey: `${safeUrl}::${colsKey}`, data: { status: "error", rows: null, error: err } });
1399
+ });
1400
+ return () => {
1401
+ controller.abort();
1402
+ };
1403
+ }, [safeUrl, colsKey]);
1404
+ if (!safeUrl) return UNAVAILABLE;
1405
+ if (state.cacheKey !== cacheKey) return LOADING;
1406
+ return state.data;
1407
+ }
1408
+
1409
+ // src/components/artifacts/chart-artifact/use-chart-artifact-data.ts
1410
+ function useChartArtifactData(presignedUrl, config) {
1411
+ const service = ChartVariantServices[config.variant];
1412
+ const keys = service ? service.columnKeys(config) : [];
1413
+ const columns = keys.map((key) => ({ key }));
1414
+ return useRowsArtifactData(presignedUrl, columns, parseCsv);
1415
+ }
1416
+
1417
+ // src/components/artifacts/chart-artifact/validate.ts
1418
+ function isValidChartConfig(config) {
1419
+ if (!config || typeof config !== "object") return false;
1420
+ const variant = config.variant;
1421
+ if (typeof variant !== "string") return false;
1422
+ const service = ChartVariantServices[variant];
1423
+ if (!service) return false;
1424
+ return service.isValidConfig(config);
1425
+ }
1426
+
1427
+ // src/components/artifacts/chart-artifact/chart-artifact.tsx
1428
+ var import_jsx_runtime16 = require("react/jsx-runtime");
1429
+ var TITLE_CLASSES = "text-sm font-semibold text-foreground";
1430
+ var CHART_HEIGHT = 320;
1431
+ function ChartArtifact({ artifact, labels, callbacks }) {
1432
+ const validConfig = isValidChartConfig(artifact.config);
1433
+ (0, import_react9.useEffect)(() => {
1434
+ if (validConfig) return;
1435
+ console.warn("[clay] ChartArtifact: malformed `config` \u2014 skipping render.");
1436
+ }, [validConfig]);
1437
+ const presignedUrl = artifact.protectedAsset?.presignedUrl ?? null;
1438
+ const resolvedLabels = (0, import_react9.useMemo)(
1439
+ () => ({ ...DEFAULT_CHART_ARTIFACT_LABELS, ...labels?.[artifactTypes.CHART] ?? {} }),
1440
+ [labels]
1441
+ );
1442
+ const data = useChartArtifactData(
1443
+ validConfig ? presignedUrl : null,
1444
+ validConfig ? artifact.config : { variant: "LINE", series: [], xKey: "x" }
1445
+ );
1446
+ const onDownload = callbacks?.[artifactTypes.CHART]?.onDownload;
1447
+ const handleDownloadClick = onDownload ? () => onDownload(artifact) : void 0;
1448
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(Artifact, { className: "shadow-none w-full rounded-2xl bg-background/70", children: [
1449
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(ArtifactHeader, { className: "bg-background/70", children: [
1450
+ artifact.title ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ArtifactTitle, { className: TITLE_CLASSES, children: artifact.title }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", {}),
1451
+ presignedUrl ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ArtifactActions, { children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ArtifactDownloadButton, { href: presignedUrl, label: resolvedLabels.download, onClick: handleDownloadClick }) }) : null
1452
+ ] }),
1453
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
1454
+ ArtifactContent,
1455
+ {
1456
+ className: "p-4 overflow-visible flex flex-none items-center justify-center",
1457
+ style: { height: CHART_HEIGHT },
1458
+ children: renderBody(validConfig, artifact, data, resolvedLabels)
1459
+ }
1460
+ )
1461
+ ] });
1462
+ }
1463
+ function renderBody(validConfig, artifact, data, labels) {
1464
+ if (!validConfig) {
1465
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(StatusMessage, { $tone: "info", children: labels.unavailable });
1466
+ }
1467
+ if (data.status === "error") {
1468
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(StatusMessage, { $tone: "error", children: labels.loadError });
1469
+ }
1470
+ if (data.status === "unavailable") {
1471
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(StatusMessage, { $tone: "info", children: labels.unavailable });
1472
+ }
1473
+ if (data.status === "loading") {
1474
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(StatusMessage, { $tone: "info", children: labels.loading });
1475
+ }
1476
+ if (data.rows.length === 0) {
1477
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(StatusMessage, { $tone: "info", children: labels.empty });
1478
+ }
1479
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ChartArtifactContent, { config: artifact.config, rows: data.rows, labels });
1480
+ }
1481
+
1482
+ // src/components/artifacts/chart-artifact/to-clipboard-text.ts
1483
+ function chartArtifactToClipboardText(artifact) {
1484
+ const trimmedTitle = artifact.title?.trim();
1485
+ return trimmedTitle || "Chart";
1486
+ }
1487
+
1488
+ // src/components/artifacts/artifact-services/chart-artifact-service.ts
1489
+ var ChartArtifactService = {
1490
+ type: artifactTypes.CHART,
1491
+ Component: ChartArtifact,
1492
+ toClipboardText: chartArtifactToClipboardText
1493
+ };
1494
+
1495
+ // src/components/artifacts/table-artifact/table-artifact.tsx
1496
+ var import_react11 = require("react");
1497
+
1498
+ // src/components/ui/table.tsx
1499
+ var React2 = __toESM(require("react"), 1);
1500
+ var import_jsx_runtime17 = require("react/jsx-runtime");
1501
+ var Table = React2.forwardRef(({ className, containerClassName, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: cn("relative w-full overflow-auto", containerClassName), children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("table", { ref, className: cn("w-full caption-bottom text-sm", className), ...props }) }));
1502
+ Table.displayName = "Table";
1503
+ var TableHeader = React2.forwardRef(
1504
+ ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("thead", { ref, className: cn("[&_tr]:border-b", className), ...props })
1505
+ );
1506
+ TableHeader.displayName = "TableHeader";
1507
+ var TableBody = React2.forwardRef(
1508
+ ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("tbody", { ref, className: cn("[&_tr:last-child]:border-0", className), ...props })
1509
+ );
1510
+ TableBody.displayName = "TableBody";
1511
+ var TableFooter = React2.forwardRef(
1512
+ ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("tfoot", { ref, className: cn("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", className), ...props })
1513
+ );
1514
+ TableFooter.displayName = "TableFooter";
1515
+ var TableRow = React2.forwardRef(
1516
+ ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1517
+ "tr",
1518
+ {
1519
+ ref,
1520
+ className: cn("border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted", className),
1521
+ ...props
1522
+ }
1523
+ )
1524
+ );
1525
+ TableRow.displayName = "TableRow";
1526
+ var TableHead = React2.forwardRef(
1527
+ ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1528
+ "th",
1529
+ {
1530
+ ref,
1531
+ className: cn(
1532
+ "h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
1533
+ className
1534
+ ),
1535
+ ...props
1536
+ }
1537
+ )
1538
+ );
1539
+ TableHead.displayName = "TableHead";
1540
+ var TableCell = React2.forwardRef(
1541
+ ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("td", { ref, className: cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className), ...props })
1542
+ );
1543
+ TableCell.displayName = "TableCell";
1544
+ var TableCaption = React2.forwardRef(
1545
+ ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("caption", { ref, className: cn("mt-4 text-sm text-muted-foreground", className), ...props })
1546
+ );
1547
+ TableCaption.displayName = "TableCaption";
1548
+
1549
+ // src/components/table/use-scroll-shadow.ts
1550
+ var import_react10 = require("react");
1551
+ function useScrollShadow() {
1552
+ const targetRef = (0, import_react10.useRef)(null);
1553
+ const [isScrolled, setIsScrolled] = (0, import_react10.useState)(false);
1554
+ (0, import_react10.useEffect)(() => {
1555
+ const target = targetRef.current;
1556
+ if (!target) return;
1557
+ let scrollContainer = target;
1558
+ while (scrollContainer) {
1559
+ const overflowY = getComputedStyle(scrollContainer).overflowY;
1560
+ if (overflowY === "auto" || overflowY === "scroll") break;
1561
+ scrollContainer = scrollContainer.parentElement;
1562
+ }
1563
+ if (!scrollContainer) return;
1564
+ setIsScrolled(scrollContainer.scrollTop > 0);
1565
+ const handleScroll = () => {
1566
+ if (!scrollContainer) return;
1567
+ setIsScrolled(scrollContainer.scrollTop > 0);
1568
+ };
1569
+ scrollContainer.addEventListener("scroll", handleScroll, { passive: true });
1570
+ return () => scrollContainer.removeEventListener("scroll", handleScroll);
1571
+ }, []);
1572
+ return { targetRef, isScrolled };
1573
+ }
1574
+
1575
+ // src/components/artifacts/table-artifact/table-artifact-content.constants.ts
1576
+ var TABLE_STATUS = {
1577
+ LOADING: "loading",
1578
+ READY: "ready",
1579
+ ERROR: "error",
1580
+ UNAVAILABLE: "unavailable"
1581
+ };
1582
+
1583
+ // src/components/artifacts/table-artifact/table-artifact-content.utils.ts
1584
+ function formatCell(row, column) {
1585
+ return row[column.key] ?? "";
1586
+ }
1587
+ var HEAD_CLASS = "h-auto py-2 px-4 text-xs font-medium text-foreground whitespace-nowrap text-left";
1588
+ var CELL_CLASS = "p-4 text-sm font-normal text-foreground align-top min-w-[150px] text-left";
1589
+ var ROW_CLASSES = "border-b-brown-30 bg-background/70 hover:bg-secondary";
1590
+ var DATA_ROW_CLASSES = `${ROW_CLASSES} animate-in fade-in duration-300`;
1591
+ var SCROLL_SHADOW = "0 1px 2px rgb(0 0 0 / 0.01), 0 6px 12px -2px rgb(0 0 0 / 0.03)";
1592
+
1593
+ // src/components/artifacts/table-artifact/table-artifact-content.tsx
1594
+ var import_jsx_runtime18 = require("react/jsx-runtime");
1595
+ function TableArtifactContent({
1596
+ columns,
1597
+ data,
1598
+ knownRowCount,
1599
+ maxShimmerRows,
1600
+ maxBodyHeight,
1601
+ labels
1602
+ }) {
1603
+ if (data.status === TABLE_STATUS.ERROR) {
1604
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(StatusMessage, { $tone: "error", children: labels.loadError });
1605
+ }
1606
+ if (data.status === TABLE_STATUS.UNAVAILABLE) {
1607
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(StatusMessage, { $tone: "info", children: labels.unavailable });
1608
+ }
1609
+ if (data.status === TABLE_STATUS.LOADING) {
1610
+ if (typeof knownRowCount === "number" && knownRowCount > 0) {
1611
+ const shimmerCount = Math.min(knownRowCount, maxShimmerRows);
1612
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ArtifactTable, { columns, maxBodyHeight, children: renderShimmerRows(columns, shimmerCount) });
1613
+ }
1614
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(StatusMessage, { $tone: "info", children: labels.loading });
1615
+ }
1616
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ArtifactTable, { columns, maxBodyHeight, children: data.rows.length === 0 ? renderEmptyRow(columns.length, labels.empty) : renderDataRows(columns, data.rows) });
1617
+ }
1618
+ function ArtifactTable({ columns, maxBodyHeight, children }) {
1619
+ const { targetRef, isScrolled } = useScrollShadow();
1620
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { ref: targetRef, className: "overscroll-none", style: { maxHeight: maxBodyHeight, overflowY: "auto" }, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(Table, { containerClassName: "overflow-visible", children: [
1621
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1622
+ TableHeader,
1623
+ {
1624
+ "data-scrolled": isScrolled ? "true" : void 0,
1625
+ className: "sticky top-0 z-10 bg-background/70 backdrop-blur-lg transition-shadow",
1626
+ style: isScrolled ? { boxShadow: SCROLL_SHADOW } : void 0,
1627
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(TableRow, { className: "hover:bg-transparent", children: columns.map((column) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(TableHead, { scope: "col", className: HEAD_CLASS, children: column.label }, column.key)) })
1628
+ }
1629
+ ),
1630
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(TableBody, { children })
1631
+ ] }) });
1632
+ }
1633
+ function renderShimmerRows(columns, count) {
1634
+ return Array.from({ length: count }).map((_, index) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(TableRow, { className: ROW_CLASSES, "aria-hidden": "true", children: columns.map((column) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(TableCell, { className: CELL_CLASS, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "block h-3 w-3/4 animate-pulse rounded bg-muted-foreground/20" }) }, column.key)) }, `shimmer-${index}`));
1635
+ }
1636
+ function renderDataRows(columns, rows) {
1637
+ return rows.map((row, index) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(TableRow, { className: DATA_ROW_CLASSES, children: columns.map((column) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(TableCell, { className: CELL_CLASS, children: formatCell(row, column) }, column.key)) }, index));
1638
+ }
1639
+ function renderEmptyRow(colSpan, emptyMessage) {
1640
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(TableRow, { className: "hover:bg-transparent", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(TableCell, { colSpan, className: "py-6 text-center text-sm text-muted-foreground", children: emptyMessage }) });
1641
+ }
1642
+
1643
+ // src/components/artifacts/table-artifact/use-table-artifact-data.ts
1644
+ function useTableArtifactData(presignedUrl, columns) {
1645
+ return useRowsArtifactData(presignedUrl, columns, parseCsv);
1646
+ }
1647
+
1648
+ // src/components/artifacts/table-artifact/validate.ts
1649
+ function isValidTableConfig(config) {
1650
+ if (!config || typeof config !== "object") return false;
1651
+ const candidate = config;
1652
+ if (!Array.isArray(candidate.columns)) return false;
1653
+ if (candidate.columns.length === 0) return false;
1654
+ return candidate.columns.every(isValidTableColumn);
1655
+ }
1656
+ function isValidTableColumn(column) {
1657
+ if (!column || typeof column !== "object") return false;
1658
+ const candidate = column;
1659
+ if (typeof candidate.key !== "string" || candidate.key.length === 0) return false;
1660
+ if (typeof candidate.label !== "string") return false;
1661
+ return true;
1662
+ }
1663
+
1664
+ // src/components/artifacts/table-artifact/table-artifact.tsx
1665
+ var import_jsx_runtime19 = require("react/jsx-runtime");
1666
+ var TITLE_CLASSES2 = "text-sm font-semibold text-foreground";
1667
+ var TABLE_MAX_BODY_HEIGHT = 480;
1668
+ var MAX_SHIMMER_ROWS = 10;
1669
+ function TableArtifact({ artifact, labels, callbacks }) {
1670
+ const validConfig = isValidTableConfig(artifact.config);
1671
+ const validColumns = validConfig ? artifact.config.columns : null;
1672
+ (0, import_react11.useEffect)(() => {
1673
+ if (validConfig) return;
1674
+ console.warn("[clay] TableArtifact: malformed `config` \u2014 skipping render.");
1675
+ }, [validConfig]);
1676
+ const resolvedLabels = (0, import_react11.useMemo)(
1677
+ () => ({ ...DEFAULT_TABLE_ARTIFACT_LABELS, ...labels?.[artifactTypes.TABLE] ?? {} }),
1678
+ [labels]
1679
+ );
1680
+ const data = useTableArtifactData(
1681
+ validConfig ? artifact.protectedAsset?.presignedUrl ?? null : null,
1682
+ validColumns ?? []
1683
+ );
1684
+ const presignedUrl = artifact.protectedAsset?.presignedUrl ?? null;
1685
+ const onDownload = callbacks?.[artifactTypes.TABLE]?.onDownload;
1686
+ const handleDownloadClick = onDownload ? () => onDownload(artifact) : void 0;
1687
+ if (!validConfig || !validColumns) {
1688
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(Artifact, { className: "shadow-none w-full rounded-2xl bg-background/70", children: [
1689
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(ArtifactHeader, { className: "bg-background/70", children: [
1690
+ artifact.title ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(ArtifactTitle, { className: TITLE_CLASSES2, children: artifact.title }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", {}),
1691
+ presignedUrl ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(ArtifactActions, { children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1692
+ ArtifactDownloadButton,
1693
+ {
1694
+ href: presignedUrl,
1695
+ label: resolvedLabels.download,
1696
+ onClick: handleDownloadClick
1697
+ }
1698
+ ) }) : null
1699
+ ] }),
1700
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(ArtifactContent, { className: "p-0 overflow-auto", style: { maxHeight: TABLE_MAX_BODY_HEIGHT }, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(StatusMessage, { $tone: "info", children: resolvedLabels.unavailable }) })
1701
+ ] });
1702
+ }
1703
+ return (
1704
+ // The table body owns its own scroll container (`ArtifactTable` inside `TableArtifactContent`).
1705
+ // Disable the outer overflow container so only one scroll context exists — this prevents
1706
+ // double-scroll nesting and lets `useScrollShadow` find the correct scroll ancestor.
1707
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(Artifact, { className: "shadow-none w-full rounded-2xl bg-background/70", children: [
1708
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(ArtifactHeader, { className: "bg-background/70", children: [
1709
+ artifact.title ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(ArtifactTitle, { className: TITLE_CLASSES2, children: artifact.title }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", {}),
1710
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(ArtifactActions, { children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1711
+ ArtifactDownloadButton,
1712
+ {
1713
+ href: presignedUrl ?? "",
1714
+ label: resolvedLabels.download,
1715
+ onClick: handleDownloadClick
1716
+ }
1717
+ ) })
1718
+ ] }),
1719
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(ArtifactContent, { className: "p-0 overflow-visible", style: { maxHeight: "none" }, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1720
+ TableArtifactContent,
1721
+ {
1722
+ columns: validColumns,
1723
+ data,
1724
+ knownRowCount: artifact.config.stats?.rowCount,
1725
+ maxShimmerRows: MAX_SHIMMER_ROWS,
1726
+ maxBodyHeight: TABLE_MAX_BODY_HEIGHT,
1727
+ labels: resolvedLabels
1728
+ }
1729
+ ) })
1730
+ ] })
1731
+ );
1732
+ }
1733
+
1734
+ // src/components/artifacts/table-artifact/to-clipboard-text.ts
1735
+ function tableArtifactToClipboardText(artifact) {
1736
+ const trimmedTitle = artifact.title?.trim();
1737
+ return trimmedTitle || "Table";
1738
+ }
1739
+
1740
+ // src/components/artifacts/artifact-services/table-artifact-service.ts
1741
+ var TableArtifactService = {
1742
+ type: artifactTypes.TABLE,
1743
+ Component: TableArtifact,
1744
+ toClipboardText: tableArtifactToClipboardText
1745
+ };
1746
+
1747
+ // src/components/artifacts/artifact-services/index.ts
1748
+ var ArtifactServices = {
1749
+ [artifactTypes.TABLE]: TableArtifactService,
1750
+ [artifactTypes.CHART]: ChartArtifactService
1751
+ };
1752
+ function artifactToClipboardText(artifact) {
1753
+ const entry = ArtifactServices[artifact.type];
1754
+ if (!entry) {
1755
+ return `[Artifact: ${artifact.title ?? artifact.type}]`;
1756
+ }
1757
+ return entry.toClipboardText(artifact);
1758
+ }
1759
+
1760
+ // src/components/blocks/artifact-ref-block/artifact-ref-block.tsx
1761
+ var import_jsx_runtime20 = require("react/jsx-runtime");
1762
+ function ArtifactRefBlock({ block }) {
1763
+ const { artifact, labels, callbacks } = block.payload;
1764
+ const entry = ArtifactServices[artifact.type];
1765
+ if (!entry) return null;
1766
+ const { Component } = entry;
1767
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Component, { artifact, labels, callbacks });
1768
+ }
1769
+
1770
+ // src/components/blocks/block-services/artifact-ref-block-service.ts
1771
+ var ArtifactRefBlockService = {
1772
+ type: blockTypes.ARTIFACT_REF,
1773
+ Component: ArtifactRefBlock,
1774
+ /**
1775
+ * Delegates to the registered artifact component's `toClipboardText`. Falls back to
1776
+ * `[Artifact: <title>]` via `artifactToClipboardText`'s own fallback when the artifact type
1777
+ * has no registered component, so copy never silently produces an empty string.
1778
+ */
1779
+ toClipboardText: (block) => artifactToClipboardText(block.payload.artifact)
1780
+ };
1781
+
1782
+ // src/utils/markdown-to-plain-text.ts
1783
+ var PLACEHOLDER_PREFIX = "\u200B\u200BIC";
1784
+ var PLACEHOLDER_SUFFIX = "\u200B\u200B";
1785
+ var PLACEHOLDER_REGEX = new RegExp(`${PLACEHOLDER_PREFIX}(\\d+)${PLACEHOLDER_SUFFIX}`, "g");
1786
+ function stripInlineMarks(text) {
1787
+ 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");
1788
+ }
1789
+ function markdownToPlainText(markdown) {
1790
+ if (!markdown) return "";
1791
+ const inlineCodeSnippets = [];
1792
+ let result = markdown.replace(/^<!--[\s\S]*?--!?>\n?/gm, "").replace(/```[\s\S]*?```/g, "").replace(/`([^`]+)`/g, (_match, code) => {
1793
+ inlineCodeSnippets.push(code);
1794
+ return `${PLACEHOLDER_PREFIX}${inlineCodeSnippets.length - 1}${PLACEHOLDER_SUFFIX}`;
1795
+ }).replace(/^#{1,6}\s+/gm, "").replace(/^(?:---|\*\*\*|___)\s*$/gm, "");
1796
+ 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();
1797
+ result = result.replace(PLACEHOLDER_REGEX, (_match, index) => inlineCodeSnippets[Number(index)]);
1798
+ return result;
1799
+ }
1800
+
1801
+ // src/components/blocks/text-block/text-block.tsx
1802
+ var import_react_markdown = __toESM(require("react-markdown"), 1);
1803
+ var import_remark_breaks = __toESM(require("remark-breaks"), 1);
1804
+ var import_remark_gfm = __toESM(require("remark-gfm"), 1);
1805
+
1806
+ // src/components/blocks/scrollable-table/scrollable-table-styles.ts
1807
+ var import_styled_components3 = __toESM(require("styled-components"), 1);
1808
+ var TableContainer = import_styled_components3.default.div`
1809
+ position: relative;
1810
+ margin-block: 8px 16px;
1811
+ `;
1812
+ var TableScroll = import_styled_components3.default.div`
1813
+ width: 100%;
1814
+ overflow-x: auto;
1815
+ overflow-y: hidden;
1816
+ `;
1817
+
1818
+ // src/components/blocks/scrollable-table/scrollable-table.tsx
1819
+ var import_jsx_runtime21 = require("react/jsx-runtime");
1820
+ function ScrollableTable({ node: _node, ...props }) {
1821
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TableContainer, { children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TableScroll, { children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("table", { ...props }) }) });
1822
+ }
1823
+
1824
+ // src/components/blocks/text-block/sanitize.ts
1825
+ function sanitize(text) {
1826
+ return text.split("\n").filter((line) => {
1827
+ const t = line.trim();
1828
+ return !(t.startsWith("<!-- table-title:") && t.endsWith("-->"));
1829
+ }).join("\n");
1830
+ }
1831
+
1832
+ // src/components/blocks/text-block/text-block-styles.ts
1833
+ var import_styled_components4 = __toESM(require("styled-components"), 1);
1834
+ var TextBlockWrapper = import_styled_components4.default.div`
493
1835
  ${typographyMixin(typographyTypes.GEIST_BODY_S_REGULAR)};
494
1836
  color: ${colors["brown-100"]};
495
1837
  word-break: break-word;
@@ -607,248 +1949,71 @@ var TextBlockWrapper = import_styled_components2.default.div`
607
1949
  `;
608
1950
 
609
1951
  // src/components/blocks/text-block/text-block.tsx
610
- var import_jsx_runtime2 = require("react/jsx-runtime");
1952
+ var import_jsx_runtime22 = require("react/jsx-runtime");
1953
+ var markdownComponents = {
1954
+ table: ((tableProps) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ScrollableTable, { ...tableProps }))
1955
+ };
611
1956
  function TextBlock({ block }) {
612
- const counter = { value: 0 };
613
- const markdownComponents = {
614
- table: ((tableProps) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ScrollableTable, { ...tableProps, counter }))
615
- };
616
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(TextBlockWrapper, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_markdown.default, { remarkPlugins: [import_remark_gfm.default, import_remark_breaks.default], components: markdownComponents, children: sanitize(block.payload.text) }) });
1957
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TextBlockWrapper, { children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_markdown.default, { remarkPlugins: [import_remark_gfm.default, import_remark_breaks.default], components: markdownComponents, children: sanitize(block.payload.text) }) });
617
1958
  }
618
1959
 
619
- // src/components/blocks/types.ts
620
- var blockTypes = {
621
- TEXT: "TEXT"
622
- };
623
-
624
1960
  // src/components/blocks/block-services/text-block-service.ts
625
1961
  var TextBlockService = {
626
1962
  type: blockTypes.TEXT,
627
- Component: TextBlock
1963
+ Component: TextBlock,
1964
+ /**
1965
+ * Converts the block's markdown source to plain text so the final clipboard value is clean
1966
+ * when pasted into tools that don't render markdown. Each block type owns its own clipboard
1967
+ * conversion so `useCopyToClipboard` can write text verbatim without a second pass.
1968
+ */
1969
+ toClipboardText: (block) => markdownToPlainText(block.payload.text)
628
1970
  };
629
1971
 
630
1972
  // src/components/blocks/block-services/index.ts
631
1973
  var BlockServices = {
632
- [TextBlockService.type]: TextBlockService
1974
+ [blockTypes.TEXT]: TextBlockService,
1975
+ [blockTypes.ARTIFACT_REF]: ArtifactRefBlockService
633
1976
  };
634
1977
 
635
1978
  // src/components/blocks/block/block.tsx
636
- var import_jsx_runtime3 = require("react/jsx-runtime");
1979
+ var import_jsx_runtime23 = require("react/jsx-runtime");
637
1980
  function Block({ block }) {
638
1981
  const service = BlockServices[block.type];
639
1982
  if (!service) return null;
640
1983
  const { Component } = service;
641
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Component, { block });
1984
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Component, { block });
642
1985
  }
643
1986
 
644
- // src/components/chat/chat-context/chat-context.ts
645
- var import_react2 = require("react");
646
- var ChatContext = (0, import_react2.createContext)(null);
647
- function useChatContext() {
648
- const context = (0, import_react2.useContext)(ChatContext);
649
- if (!context) {
650
- throw new Error("useChatContext must be used within a Chat.Root component");
1987
+ // src/components/chat/hooks/copy-message-text.ts
1988
+ function copyMessageText(message) {
1989
+ const parts = [];
1990
+ for (const block of message.blocks) {
1991
+ const service = BlockServices[block.type];
1992
+ if (!service) continue;
1993
+ if (!service.toClipboardText) continue;
1994
+ const text = service.toClipboardText(block);
1995
+ if (text) parts.push(text);
651
1996
  }
652
- return context;
1997
+ if (parts.length === 0) return message.fallbackText;
1998
+ return parts.join("\n\n");
653
1999
  }
654
2000
 
655
2001
  // src/components/chat/chat-message-actions/chat-message-actions.tsx
656
- var import_react6 = require("react");
657
-
658
- // node_modules/@tabler/icons-react/dist/esm/createReactComponent.mjs
659
- var import_react3 = require("react");
660
-
661
- // node_modules/@tabler/icons-react/dist/esm/defaultAttributes.mjs
662
- var defaultAttributes = {
663
- outline: {
664
- xmlns: "http://www.w3.org/2000/svg",
665
- width: 24,
666
- height: 24,
667
- viewBox: "0 0 24 24",
668
- fill: "none",
669
- stroke: "currentColor",
670
- strokeWidth: 2,
671
- strokeLinecap: "round",
672
- strokeLinejoin: "round"
673
- },
674
- filled: {
675
- xmlns: "http://www.w3.org/2000/svg",
676
- width: 24,
677
- height: 24,
678
- viewBox: "0 0 24 24",
679
- fill: "currentColor",
680
- stroke: "none"
681
- }
682
- };
683
-
684
- // node_modules/@tabler/icons-react/dist/esm/createReactComponent.mjs
685
- var createReactComponent = (type, iconName, iconNamePascal, iconNode) => {
686
- const Component = (0, import_react3.forwardRef)(
687
- ({ color = "currentColor", size = 24, stroke = 2, title, className, children, ...rest }, ref) => (0, import_react3.createElement)(
688
- "svg",
689
- {
690
- ref,
691
- ...defaultAttributes[type],
692
- width: size,
693
- height: size,
694
- className: [`tabler-icon`, `tabler-icon-${iconName}`, className].join(" "),
695
- ...type === "filled" ? {
696
- fill: color
697
- } : {
698
- strokeWidth: stroke,
699
- stroke: color
700
- },
701
- ...rest
702
- },
703
- [
704
- title && (0, import_react3.createElement)("title", { key: "svg-title" }, title),
705
- ...iconNode.map(([tag, attrs]) => (0, import_react3.createElement)(tag, attrs)),
706
- ...Array.isArray(children) ? children : [children]
707
- ]
708
- )
709
- );
710
- Component.displayName = `${iconNamePascal}`;
711
- return Component;
712
- };
713
-
714
- // node_modules/@tabler/icons-react/dist/esm/icons/IconCheck.mjs
715
- var __iconNode = [["path", { "d": "M5 12l5 5l10 -10", "key": "svg-0" }]];
716
- var IconCheck = createReactComponent("outline", "check", "Check", __iconNode);
717
-
718
- // node_modules/@tabler/icons-react/dist/esm/icons/IconCopy.mjs
719
- 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" }]];
720
- var IconCopy = createReactComponent("outline", "copy", "Copy", __iconNode2);
721
-
722
- // node_modules/@tabler/icons-react/dist/esm/icons/IconThumbDown.mjs
723
- 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" }]];
724
- var IconThumbDown = createReactComponent("outline", "thumb-down", "ThumbDown", __iconNode3);
725
-
726
- // node_modules/@tabler/icons-react/dist/esm/icons/IconThumbUp.mjs
727
- 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" }]];
728
- var IconThumbUp = createReactComponent("outline", "thumb-up", "ThumbUp", __iconNode4);
729
-
730
- // src/components/icon/icon.tsx
731
- var import_react4 = require("react");
732
-
733
- // src/components/icon/icon-styles.ts
734
- var import_styled_components3 = __toESM(require("styled-components"), 1);
735
- var STROKE_WIDTH_BY_SIZE = {
736
- 12: 1.4,
737
- 14: 1.4,
738
- 16: 1.8,
739
- 18: 1.8,
740
- 20: 1.8,
741
- 24: 1.8
742
- };
743
- var DEFAULT_STROKE_WIDTH = 1.8;
744
- function getStrokeWidth(size) {
745
- return STROKE_WIDTH_BY_SIZE[size] ?? DEFAULT_STROKE_WIDTH;
746
- }
747
- var IconWrapper = import_styled_components3.default.span`
748
- display: inline-flex;
749
- align-items: center;
750
- justify-content: center;
751
- color: ${({ $color }) => $color ?? "currentColor"};
752
- flex-shrink: 0;
753
- `;
2002
+ var import_react14 = require("react");
754
2003
 
755
- // src/components/icon/icon.tsx
756
- var import_jsx_runtime4 = require("react/jsx-runtime");
757
- var DEFAULT_SIZE = 16;
758
- var Icon = (0, import_react4.forwardRef)(function Icon2({ icon: IconComponent, size = DEFAULT_SIZE, color, className, "aria-label": ariaLabel }, ref) {
759
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
760
- IconWrapper,
761
- {
762
- ref,
763
- className,
764
- $color: color,
765
- "aria-label": ariaLabel,
766
- role: ariaLabel ? "img" : void 0,
767
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconComponent, { width: size, height: size, strokeWidth: getStrokeWidth(size) })
768
- }
769
- );
770
- });
771
-
772
- // src/components/icon-button/icon-button-styles.ts
773
- var import_styled_components4 = __toESM(require("styled-components"), 1);
774
- var PADDING = {
775
- short: {
776
- small: "4px",
777
- medium: "6px"
778
- },
779
- long: {
780
- small: "4px 6px",
781
- medium: "6px 10px"
782
- }
783
- };
784
- var BORDER_RADIUS = {
785
- small: 6,
786
- medium: 10
787
- };
788
- var Button = import_styled_components4.default.button`
789
- display: inline-flex;
790
- align-items: center;
791
- justify-content: center;
792
- padding: ${({ $size, $variant }) => PADDING[$variant][$size]};
793
- border: none;
794
- border-radius: ${({ $size }) => BORDER_RADIUS[$size]}px;
795
- background: ${({ $isSelected }) => $isSelected ? colors["brown-40"] : "transparent"};
796
- color: ${colors["brown-100"]};
797
- cursor: pointer;
798
- transition: background-color 100ms ease;
799
-
800
- &:hover {
801
- background: ${({ $isSelected }) => $isSelected ? colors["brown-40"] : colors["brown-20"]};
802
- }
803
-
804
- &:active {
805
- background: ${colors["brown-40"]};
806
- }
807
-
808
- &:disabled {
809
- color: ${colors["brown-50"]};
810
- background: transparent;
811
- cursor: not-allowed;
2004
+ // src/components/chat/chat-context/chat-context.ts
2005
+ var import_react12 = require("react");
2006
+ var ChatContext = (0, import_react12.createContext)(null);
2007
+ function useChatContext() {
2008
+ const context = (0, import_react12.useContext)(ChatContext);
2009
+ if (!context) {
2010
+ throw new Error("useChatContext must be used within a Chat.Root component");
812
2011
  }
813
- `;
814
-
815
- // src/components/icon-button/icon-button.tsx
816
- var import_jsx_runtime5 = require("react/jsx-runtime");
817
- var ICON_SIZE_BY_BUTTON_SIZE = {
818
- small: 16,
819
- medium: 20
820
- };
821
- function IconButton({
822
- icon,
823
- size = "small",
824
- variant = "short",
825
- isSelected = false,
826
- disabled = false,
827
- onClick,
828
- className,
829
- id,
830
- as,
831
- "aria-label": ariaLabel
832
- }) {
833
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
834
- Button,
835
- {
836
- className,
837
- id,
838
- as,
839
- $size: size,
840
- $variant: variant,
841
- $isSelected: isSelected,
842
- disabled,
843
- onClick: disabled ? void 0 : onClick,
844
- "aria-label": ariaLabel,
845
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Icon, { icon, size: ICON_SIZE_BY_BUTTON_SIZE[size] })
846
- }
847
- );
2012
+ return context;
848
2013
  }
849
2014
 
850
2015
  // src/components/chat/hooks/use-copy-to-clipboard.ts
851
- var import_react5 = require("react");
2016
+ var import_react13 = require("react");
852
2017
 
853
2018
  // src/utils/clipboard.ts
854
2019
  function copyToClipboard(text, options) {
@@ -858,37 +2023,18 @@ function copyToClipboard(text, options) {
858
2023
  );
859
2024
  }
860
2025
 
861
- // src/utils/markdown-to-plain-text.ts
862
- var PLACEHOLDER_PREFIX = "\u200B\u200BIC";
863
- var PLACEHOLDER_SUFFIX = "\u200B\u200B";
864
- var PLACEHOLDER_REGEX = new RegExp(`${PLACEHOLDER_PREFIX}(\\d+)${PLACEHOLDER_SUFFIX}`, "g");
865
- function stripInlineMarks(text) {
866
- 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");
867
- }
868
- function markdownToPlainText(markdown) {
869
- if (!markdown) return "";
870
- const inlineCodeSnippets = [];
871
- let result = markdown.replace(/^<!--[\s\S]*?--!?>\n?/gm, "").replace(/```[\s\S]*?```/g, "").replace(/`([^`]+)`/g, (_match, code) => {
872
- inlineCodeSnippets.push(code);
873
- return `${PLACEHOLDER_PREFIX}${inlineCodeSnippets.length - 1}${PLACEHOLDER_SUFFIX}`;
874
- }).replace(/^#{1,6}\s+/gm, "").replace(/^(?:---|\*\*\*|___)\s*$/gm, "");
875
- 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();
876
- result = result.replace(PLACEHOLDER_REGEX, (_match, index) => inlineCodeSnippets[Number(index)]);
877
- return result;
878
- }
879
-
880
2026
  // src/components/chat/hooks/use-copy-to-clipboard.ts
881
2027
  var RESET_DELAY_MS = 2e3;
882
2028
  function useCopyToClipboard() {
883
- const [isCopied, setIsCopied] = (0, import_react5.useState)(false);
884
- const timeoutRef = (0, import_react5.useRef)(null);
885
- (0, import_react5.useEffect)(() => {
2029
+ const [isCopied, setIsCopied] = (0, import_react13.useState)(false);
2030
+ const timeoutRef = (0, import_react13.useRef)(null);
2031
+ (0, import_react13.useEffect)(() => {
886
2032
  return () => {
887
2033
  if (timeoutRef.current) clearTimeout(timeoutRef.current);
888
2034
  };
889
2035
  }, []);
890
- const copy = (0, import_react5.useCallback)((text) => {
891
- copyToClipboard(markdownToPlainText(text), {
2036
+ const copy = (0, import_react13.useCallback)((text) => {
2037
+ copyToClipboard(text, {
892
2038
  onSuccess: () => {
893
2039
  setIsCopied(true);
894
2040
  if (timeoutRef.current) clearTimeout(timeoutRef.current);
@@ -927,24 +2073,24 @@ var AnimatedAction = import_styled_components5.default.div`
927
2073
  `;
928
2074
 
929
2075
  // src/components/chat/chat-message-actions/chat-message-actions.tsx
930
- var import_jsx_runtime6 = require("react/jsx-runtime");
2076
+ var import_jsx_runtime24 = require("react/jsx-runtime");
931
2077
  function ChatMessageActions({ className, messageId, content, role, isHelpful }) {
932
2078
  const { onCopyMessage, onThumbUpClick, onThumbDownClick } = useChatContext();
933
2079
  const { isCopied, copy } = useCopyToClipboard();
934
- const handleCopy = (0, import_react6.useCallback)(() => {
2080
+ const handleCopy = (0, import_react14.useCallback)(() => {
935
2081
  copy(content);
936
2082
  onCopyMessage?.(messageId);
937
2083
  }, [content, messageId, copy, onCopyMessage]);
938
- const handleThumbUp = (0, import_react6.useCallback)(() => {
2084
+ const handleThumbUp = (0, import_react14.useCallback)(() => {
939
2085
  onThumbUpClick?.(messageId, isHelpful === true ? null : true);
940
2086
  }, [messageId, isHelpful, onThumbUpClick]);
941
- const handleThumbDown = (0, import_react6.useCallback)(() => {
2087
+ const handleThumbDown = (0, import_react14.useCallback)(() => {
942
2088
  onThumbDownClick?.(messageId, isHelpful === false ? null : false);
943
2089
  }, [messageId, isHelpful, onThumbDownClick]);
944
2090
  const isAssistant = role === MESSAGE_ROLE.ASSISTANT;
945
2091
  const hasFeedback = isAssistant && (onThumbUpClick || onThumbDownClick);
946
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(ActionsContainer, { $role: role, className, children: [
947
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2092
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(ActionsContainer, { $role: role, className, children: [
2093
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
948
2094
  IconButton,
949
2095
  {
950
2096
  icon: isCopied ? IconCheck : IconCopy,
@@ -952,7 +2098,7 @@ function ChatMessageActions({ className, messageId, content, role, isHelpful })
952
2098
  "aria-label": isCopied ? "Copied" : "Copy message"
953
2099
  }
954
2100
  ),
955
- hasFeedback && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(AnimatedAction, { $visible: isHelpful !== false, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2101
+ hasFeedback && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(AnimatedAction, { $visible: isHelpful !== false, children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
956
2102
  IconButton,
957
2103
  {
958
2104
  icon: IconThumbUp,
@@ -961,7 +2107,7 @@ function ChatMessageActions({ className, messageId, content, role, isHelpful })
961
2107
  "aria-label": "Good response"
962
2108
  }
963
2109
  ) }),
964
- hasFeedback && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(AnimatedAction, { $visible: isHelpful !== true, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2110
+ hasFeedback && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(AnimatedAction, { $visible: isHelpful !== true, children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
965
2111
  IconButton,
966
2112
  {
967
2113
  icon: IconThumbDown,
@@ -999,26 +2145,28 @@ var MessageBubble = import_styled_components6.default.div`
999
2145
  padding: ${({ $role }) => $role === MESSAGE_ROLE.USER ? "12px 16px" : "0"};
1000
2146
  border-radius: ${({ $role }) => $role === MESSAGE_ROLE.USER ? "16px" : "0"};
1001
2147
  max-width: 100%;
2148
+ width: 100%;
1002
2149
  min-width: 0;
2150
+ /* Vertical rhythm between consecutive blocks — renderer-owned, not agent-emitted.
2151
+ * Industry standard (Claude, ChatGPT, Vercel AI Elements): the chat renderer applies
2152
+ * spacing via CSS; agents emit semantic blocks only. Value matches the MessageContainer
2153
+ * gap. TODO: replace 12px with a clay spacing token when a spacing scale is added. */
2154
+ display: flex;
2155
+ flex-direction: column;
2156
+ gap: 12px;
1003
2157
  `;
1004
2158
 
1005
2159
  // src/components/chat/chat-message/chat-message.tsx
1006
- var import_jsx_runtime7 = require("react/jsx-runtime");
2160
+ var import_jsx_runtime25 = require("react/jsx-runtime");
1007
2161
  function ChatMessage({ message }) {
1008
- const { TableActions: ChatTableActions } = useChatContext();
1009
- const blockContextValue = (0, import_react7.useMemo)(
1010
- () => ({
1011
- TableActions: ChatTableActions ? ({ tableIndex }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ChatTableActions, { messageId: message.id, tableIndex }) : void 0
1012
- }),
1013
- [ChatTableActions, message.id]
1014
- );
1015
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(MessageRow, { $role: message.role, role: "article", "aria-label": `${message.role} message`, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(MessageContainer, { $role: message.role, children: [
1016
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(MessageBubble, { $role: message.role, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(BlockContext.Provider, { value: blockContextValue, children: message.blocks.map((block, index) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Block, { block }, index)) }) }),
1017
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2162
+ const clipboardText = (0, import_react15.useMemo)(() => copyMessageText(message), [message]);
2163
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(MessageRow, { $role: message.role, role: "article", "aria-label": `${message.role} message`, children: /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(MessageContainer, { $role: message.role, children: [
2164
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(MessageBubble, { $role: message.role, "data-testid": "message-bubble", children: message.blocks.map((block, index) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(Block, { block }, index)) }),
2165
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1018
2166
  ChatMessageActions,
1019
2167
  {
1020
2168
  messageId: message.id,
1021
- content: message.fallbackText,
2169
+ content: clipboardText,
1022
2170
  role: message.role,
1023
2171
  isHelpful: message.isHelpful
1024
2172
  }
@@ -1030,6 +2178,7 @@ function ChatMessage({ message }) {
1030
2178
  ChatContext,
1031
2179
  ChatMessage,
1032
2180
  MESSAGE_ROLE,
2181
+ copyMessageText,
1033
2182
  useChatContext,
1034
2183
  useCopyToClipboard
1035
2184
  });
@@ -1039,6 +2188,7 @@ function ChatMessage({ message }) {
1039
2188
  @tabler/icons-react/dist/esm/createReactComponent.mjs:
1040
2189
  @tabler/icons-react/dist/esm/icons/IconCheck.mjs:
1041
2190
  @tabler/icons-react/dist/esm/icons/IconCopy.mjs:
2191
+ @tabler/icons-react/dist/esm/icons/IconDownload.mjs:
1042
2192
  @tabler/icons-react/dist/esm/icons/IconThumbDown.mjs:
1043
2193
  @tabler/icons-react/dist/esm/icons/IconThumbUp.mjs:
1044
2194
  @tabler/icons-react/dist/esm/tabler-icons-react.mjs: