@tangle-network/ui 1.0.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 (220) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/LICENSE +21 -0
  3. package/README.md +33 -0
  4. package/dist/active-sessions-store-CeOmXgv5.d.ts +85 -0
  5. package/dist/artifact-pane-DvJyPWV4.d.ts +24 -0
  6. package/dist/auth.d.ts +74 -0
  7. package/dist/auth.js +15 -0
  8. package/dist/button-CMQuQEW_.d.ts +17 -0
  9. package/dist/chat.d.ts +232 -0
  10. package/dist/chat.js +30 -0
  11. package/dist/chunk-2NFQRQOD.js +1009 -0
  12. package/dist/chunk-2VH6PUXD.js +186 -0
  13. package/dist/chunk-34A66VBG.js +214 -0
  14. package/dist/chunk-3OI2QKFD.js +0 -0
  15. package/dist/chunk-4CLN43XT.js +45 -0
  16. package/dist/chunk-54SQQMMM.js +156 -0
  17. package/dist/chunk-5Z5ZYMOJ.js +0 -0
  18. package/dist/chunk-66BNMOVT.js +167 -0
  19. package/dist/chunk-6BGQA4BQ.js +0 -0
  20. package/dist/chunk-7UO2ZMRQ.js +133 -0
  21. package/dist/chunk-BX6AQMUS.js +183 -0
  22. package/dist/chunk-CD53GZOM.js +59 -0
  23. package/dist/chunk-CSAIKY36.js +54 -0
  24. package/dist/chunk-EEE55AVS.js +1201 -0
  25. package/dist/chunk-GYPQXTJU.js +230 -0
  26. package/dist/chunk-HFL6R6IF.js +37 -0
  27. package/dist/chunk-HJKCSXCH.js +737 -0
  28. package/dist/chunk-LISXUB4D.js +1222 -0
  29. package/dist/chunk-LQS34IGP.js +0 -0
  30. package/dist/chunk-MKTSMWVD.js +109 -0
  31. package/dist/chunk-NKDZ7GZE.js +192 -0
  32. package/dist/chunk-OEX7NZE3.js +321 -0
  33. package/dist/chunk-Q56BYXQF.js +61 -0
  34. package/dist/chunk-Q7EIIWTC.js +0 -0
  35. package/dist/chunk-REJESC5U.js +117 -0
  36. package/dist/chunk-RQGKSCEZ.js +0 -0
  37. package/dist/chunk-RQHJBTEU.js +10 -0
  38. package/dist/chunk-TMFOPHHN.js +299 -0
  39. package/dist/chunk-XGKULLYE.js +40 -0
  40. package/dist/chunk-XIHMJ7ZQ.js +614 -0
  41. package/dist/chunk-YJ2G3XO5.js +1048 -0
  42. package/dist/chunk-YNN4O57I.js +754 -0
  43. package/dist/code-block-DjXf8eOG.d.ts +19 -0
  44. package/dist/document-editor-pane-A5LT5H4N.js +12 -0
  45. package/dist/document-editor-pane-DyDEX_Zm.d.ts +124 -0
  46. package/dist/editor.d.ts +120 -0
  47. package/dist/editor.js +34 -0
  48. package/dist/files.d.ts +175 -0
  49. package/dist/files.js +20 -0
  50. package/dist/hooks.d.ts +56 -0
  51. package/dist/hooks.js +41 -0
  52. package/dist/index.d.ts +43 -0
  53. package/dist/index.js +446 -0
  54. package/dist/markdown.d.ts +15 -0
  55. package/dist/markdown.js +14 -0
  56. package/dist/message-BHWbxBtT.d.ts +15 -0
  57. package/dist/openui.d.ts +115 -0
  58. package/dist/openui.js +12 -0
  59. package/dist/parts-dj7AcUg0.d.ts +36 -0
  60. package/dist/primitives.d.ts +332 -0
  61. package/dist/primitives.js +191 -0
  62. package/dist/run-PfLmDAox.d.ts +41 -0
  63. package/dist/run.d.ts +69 -0
  64. package/dist/run.js +36 -0
  65. package/dist/sdk-hooks.d.ts +285 -0
  66. package/dist/sdk-hooks.js +31 -0
  67. package/dist/stores.d.ts +17 -0
  68. package/dist/stores.js +76 -0
  69. package/dist/tool-call-feed-Bs3MyQMT.d.ts +68 -0
  70. package/dist/tool-display-z4JcDmMQ.d.ts +32 -0
  71. package/dist/tool-previews.d.ts +48 -0
  72. package/dist/tool-previews.js +21 -0
  73. package/dist/types.d.ts +19 -0
  74. package/dist/types.js +1 -0
  75. package/dist/utils.d.ts +45 -0
  76. package/dist/utils.js +32 -0
  77. package/package.json +193 -0
  78. package/src/auth/auth.tsx +228 -0
  79. package/src/auth/index.ts +13 -0
  80. package/src/auth/login-layout.tsx +46 -0
  81. package/src/chat/agent-timeline.stories.tsx +429 -0
  82. package/src/chat/agent-timeline.tsx +360 -0
  83. package/src/chat/chat-container.tsx +486 -0
  84. package/src/chat/chat-input.stories.tsx +142 -0
  85. package/src/chat/chat-input.tsx +389 -0
  86. package/src/chat/chat-message.stories.tsx +237 -0
  87. package/src/chat/chat-message.tsx +129 -0
  88. package/src/chat/index.ts +18 -0
  89. package/src/chat/message-list.stories.tsx +336 -0
  90. package/src/chat/message-list.tsx +79 -0
  91. package/src/chat/thinking-indicator.stories.tsx +56 -0
  92. package/src/chat/thinking-indicator.tsx +30 -0
  93. package/src/chat/user-message.stories.tsx +92 -0
  94. package/src/chat/user-message.tsx +43 -0
  95. package/src/editor/document-editor-pane.tsx +351 -0
  96. package/src/editor/editor-provider.tsx +428 -0
  97. package/src/editor/editor-toolbar.tsx +130 -0
  98. package/src/editor/index.ts +31 -0
  99. package/src/editor/markdown-conversion.ts +21 -0
  100. package/src/editor/markdown-document-editor.tsx +137 -0
  101. package/src/editor/tiptap-editor.tsx +331 -0
  102. package/src/editor/use-editor.ts +221 -0
  103. package/src/files/file-artifact-pane.tsx +183 -0
  104. package/src/files/file-preview.tsx +342 -0
  105. package/src/files/file-tabs.tsx +71 -0
  106. package/src/files/file-tree.tsx +258 -0
  107. package/src/files/index.ts +17 -0
  108. package/src/files/rich-file-tree.stories.tsx +104 -0
  109. package/src/files/rich-file-tree.test.tsx +42 -0
  110. package/src/files/rich-file-tree.tsx +232 -0
  111. package/src/hooks/index.ts +10 -0
  112. package/src/hooks/use-auth.ts +153 -0
  113. package/src/hooks/use-auto-scroll.ts +59 -0
  114. package/src/hooks/use-dropdown-menu.ts +40 -0
  115. package/src/hooks/use-live-time.test.tsx +40 -0
  116. package/src/hooks/use-live-time.ts +27 -0
  117. package/src/hooks/use-realtime-session.ts +319 -0
  118. package/src/hooks/use-run-collapse-state.ts +25 -0
  119. package/src/hooks/use-run-groups.ts +111 -0
  120. package/src/hooks/use-sdk-session.ts +575 -0
  121. package/src/hooks/use-sse-stream.ts +475 -0
  122. package/src/hooks/use-tool-call-stream.ts +96 -0
  123. package/src/index.ts +14 -0
  124. package/src/lib/utils.ts +6 -0
  125. package/src/markdown/code-block.tsx +198 -0
  126. package/src/markdown/index.ts +2 -0
  127. package/src/markdown/markdown.stories.tsx +190 -0
  128. package/src/markdown/markdown.tsx +62 -0
  129. package/src/openui/index.ts +20 -0
  130. package/src/openui/openui-artifact-renderer.tsx +542 -0
  131. package/src/primitives/artifact-pane.tsx +91 -0
  132. package/src/primitives/avatar.stories.tsx +95 -0
  133. package/src/primitives/avatar.tsx +47 -0
  134. package/src/primitives/badge.stories.tsx +57 -0
  135. package/src/primitives/badge.tsx +97 -0
  136. package/src/primitives/button.stories.tsx +48 -0
  137. package/src/primitives/button.tsx +115 -0
  138. package/src/primitives/card.stories.tsx +53 -0
  139. package/src/primitives/card.tsx +98 -0
  140. package/src/primitives/code-block.stories.tsx +115 -0
  141. package/src/primitives/code-block.tsx +22 -0
  142. package/src/primitives/design-tokens.stories.tsx +162 -0
  143. package/src/primitives/dialog.stories.tsx +176 -0
  144. package/src/primitives/dialog.tsx +137 -0
  145. package/src/primitives/drop-zone.stories.tsx +123 -0
  146. package/src/primitives/drop-zone.tsx +131 -0
  147. package/src/primitives/dropdown-menu.stories.tsx +122 -0
  148. package/src/primitives/dropdown-menu.tsx +214 -0
  149. package/src/primitives/empty-state.stories.tsx +81 -0
  150. package/src/primitives/empty-state.tsx +40 -0
  151. package/src/primitives/index.ts +118 -0
  152. package/src/primitives/input.stories.tsx +113 -0
  153. package/src/primitives/input.tsx +136 -0
  154. package/src/primitives/label.stories.tsx +84 -0
  155. package/src/primitives/label.tsx +24 -0
  156. package/src/primitives/progress.stories.tsx +93 -0
  157. package/src/primitives/progress.tsx +50 -0
  158. package/src/primitives/segmented-control.test.tsx +328 -0
  159. package/src/primitives/segmented-control.tsx +154 -0
  160. package/src/primitives/select.stories.tsx +164 -0
  161. package/src/primitives/select.tsx +158 -0
  162. package/src/primitives/sidebar-drop-zone.stories.tsx +100 -0
  163. package/src/primitives/sidebar-drop-zone.tsx +149 -0
  164. package/src/primitives/skeleton.stories.tsx +79 -0
  165. package/src/primitives/skeleton.tsx +55 -0
  166. package/src/primitives/stat-card.stories.tsx +137 -0
  167. package/src/primitives/stat-card.tsx +97 -0
  168. package/src/primitives/switch.stories.tsx +85 -0
  169. package/src/primitives/switch.tsx +28 -0
  170. package/src/primitives/table.stories.tsx +170 -0
  171. package/src/primitives/table.tsx +116 -0
  172. package/src/primitives/tabs.stories.tsx +180 -0
  173. package/src/primitives/tabs.tsx +71 -0
  174. package/src/primitives/terminal-display.stories.tsx +191 -0
  175. package/src/primitives/terminal-display.tsx +189 -0
  176. package/src/primitives/theme-toggle.stories.tsx +32 -0
  177. package/src/primitives/theme-toggle.tsx +96 -0
  178. package/src/primitives/toast.stories.tsx +155 -0
  179. package/src/primitives/toast.tsx +190 -0
  180. package/src/primitives/upload-progress.stories.tsx +120 -0
  181. package/src/primitives/upload-progress.tsx +110 -0
  182. package/src/run/expanded-tool-detail.stories.tsx +182 -0
  183. package/src/run/expanded-tool-detail.tsx +186 -0
  184. package/src/run/index.ts +13 -0
  185. package/src/run/inline-thinking-item.stories.tsx +136 -0
  186. package/src/run/inline-thinking-item.tsx +120 -0
  187. package/src/run/inline-tool-item.stories.tsx +222 -0
  188. package/src/run/inline-tool-item.tsx +190 -0
  189. package/src/run/run-group.stories.tsx +322 -0
  190. package/src/run/run-group.tsx +569 -0
  191. package/src/run/run-item-primitives.tsx +17 -0
  192. package/src/run/tool-call-feed.stories.tsx +294 -0
  193. package/src/run/tool-call-feed.tsx +192 -0
  194. package/src/run/tool-call-step.stories.tsx +198 -0
  195. package/src/run/tool-call-step.tsx +240 -0
  196. package/src/sdk-hooks.ts +38 -0
  197. package/src/stores/active-sessions-store.ts +455 -0
  198. package/src/stores/chat-store.ts +43 -0
  199. package/src/stores/index.ts +2 -0
  200. package/src/tool-previews/command-preview.tsx +116 -0
  201. package/src/tool-previews/diff-preview.tsx +85 -0
  202. package/src/tool-previews/glob-results-preview.tsx +98 -0
  203. package/src/tool-previews/grep-results-preview.tsx +157 -0
  204. package/src/tool-previews/index.ts +22 -0
  205. package/src/tool-previews/preview-primitives.tsx +84 -0
  206. package/src/tool-previews/question-preview.tsx +101 -0
  207. package/src/tool-previews/web-search-preview.tsx +117 -0
  208. package/src/tool-previews/write-file-preview.tsx +80 -0
  209. package/src/types/branding.ts +11 -0
  210. package/src/types/index.ts +5 -0
  211. package/src/types/message.ts +13 -0
  212. package/src/types/parts.ts +51 -0
  213. package/src/types/run.ts +56 -0
  214. package/src/types/tool-display.ts +41 -0
  215. package/src/utils/copy-text.ts +30 -0
  216. package/src/utils/format.test.ts +43 -0
  217. package/src/utils/format.ts +56 -0
  218. package/src/utils/index.ts +10 -0
  219. package/src/utils/time-ago.ts +9 -0
  220. package/src/utils/tool-display.ts +238 -0
@@ -0,0 +1,542 @@
1
+ import { Fragment, type ReactNode } from "react";
2
+ import { Minus } from "lucide-react";
3
+ import { cn } from "../lib/utils";
4
+ import { Badge } from "../primitives/badge";
5
+ import { Button } from "../primitives/button";
6
+ import {
7
+ Card,
8
+ CardContent,
9
+ CardDescription,
10
+ CardHeader,
11
+ CardTitle,
12
+ } from "../primitives/card";
13
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../primitives/table";
14
+ import { CodeBlock } from "../primitives/code-block";
15
+ import { Markdown } from "../markdown/markdown";
16
+
17
+ export type OpenUIPrimitive = string | number | boolean | null | undefined;
18
+
19
+ export interface OpenUIAction {
20
+ id: string;
21
+ label: string;
22
+ tone?: "default" | "secondary" | "outline" | "ghost" | "destructive";
23
+ disabled?: boolean;
24
+ onPress?: () => void;
25
+ }
26
+
27
+ interface OpenUIBaseNode {
28
+ id?: string;
29
+ }
30
+
31
+ export interface OpenUIHeadingNode extends OpenUIBaseNode {
32
+ type: "heading";
33
+ text: string;
34
+ level?: 1 | 2 | 3 | 4;
35
+ kicker?: string;
36
+ meta?: string;
37
+ }
38
+
39
+ export interface OpenUITextNode extends OpenUIBaseNode {
40
+ type: "text";
41
+ text: string;
42
+ tone?: "default" | "muted" | "success" | "warning" | "error";
43
+ mono?: boolean;
44
+ }
45
+
46
+ export interface OpenUIBadgeNode extends OpenUIBaseNode {
47
+ type: "badge";
48
+ label: string;
49
+ tone?: "default" | "secondary" | "success" | "warning" | "error" | "info" | "sandbox";
50
+ }
51
+
52
+ export interface OpenUIStatNode extends OpenUIBaseNode {
53
+ type: "stat";
54
+ label: string;
55
+ value: string;
56
+ change?: string;
57
+ tone?: "default" | "success" | "warning" | "error" | "info";
58
+ }
59
+
60
+ export interface OpenUIKeyValueNode extends OpenUIBaseNode {
61
+ type: "key_value";
62
+ items: Array<{
63
+ id?: string;
64
+ label: string;
65
+ value: ReactNode | OpenUIPrimitive;
66
+ tone?: "default" | "muted";
67
+ }>;
68
+ }
69
+
70
+ export interface OpenUICodeNode extends OpenUIBaseNode {
71
+ type: "code";
72
+ code: string;
73
+ language?: string;
74
+ title?: string;
75
+ showLineNumbers?: boolean;
76
+ }
77
+
78
+ export interface OpenUIMarkdownNode extends OpenUIBaseNode {
79
+ type: "markdown";
80
+ content: string;
81
+ }
82
+
83
+ export interface OpenUITableNode extends OpenUIBaseNode {
84
+ type: "table";
85
+ columns: Array<{
86
+ key: string;
87
+ header: string;
88
+ align?: "left" | "right";
89
+ }>;
90
+ rows: Array<Record<string, ReactNode | OpenUIPrimitive>>;
91
+ caption?: string;
92
+ }
93
+
94
+ export interface OpenUIActionsNode extends OpenUIBaseNode {
95
+ type: "actions";
96
+ actions: OpenUIAction[];
97
+ }
98
+
99
+ export interface OpenUISeparatorNode extends OpenUIBaseNode {
100
+ type: "separator";
101
+ label?: string;
102
+ }
103
+
104
+ export interface OpenUIStackNode extends OpenUIBaseNode {
105
+ type: "stack";
106
+ direction?: "row" | "column";
107
+ gap?: "sm" | "md" | "lg";
108
+ align?: "start" | "center" | "end" | "stretch";
109
+ wrap?: boolean;
110
+ children: OpenUIComponentNode[];
111
+ }
112
+
113
+ export interface OpenUIGridNode extends OpenUIBaseNode {
114
+ type: "grid";
115
+ columns?: 1 | 2 | 3 | 4;
116
+ gap?: "sm" | "md" | "lg";
117
+ children: OpenUIComponentNode[];
118
+ }
119
+
120
+ export interface OpenUICardNode extends OpenUIBaseNode {
121
+ type: "card";
122
+ title?: string;
123
+ description?: string;
124
+ eyebrow?: string;
125
+ badge?: OpenUIBadgeNode;
126
+ actions?: OpenUIAction[];
127
+ children?: OpenUIComponentNode[];
128
+ }
129
+
130
+ export type OpenUIComponentNode =
131
+ | OpenUIActionsNode
132
+ | OpenUIBadgeNode
133
+ | OpenUICardNode
134
+ | OpenUICodeNode
135
+ | OpenUIGridNode
136
+ | OpenUIHeadingNode
137
+ | OpenUIKeyValueNode
138
+ | OpenUIMarkdownNode
139
+ | OpenUISeparatorNode
140
+ | OpenUIStackNode
141
+ | OpenUIStatNode
142
+ | OpenUITableNode
143
+ | OpenUITextNode;
144
+
145
+ export interface OpenUIArtifactRendererProps {
146
+ schema: OpenUIComponentNode | OpenUIComponentNode[];
147
+ onAction?: (action: OpenUIAction) => void;
148
+ className?: string;
149
+ }
150
+
151
+ const NODE_TYPES = new Set([
152
+ "heading",
153
+ "text",
154
+ "badge",
155
+ "stat",
156
+ "key_value",
157
+ "code",
158
+ "markdown",
159
+ "table",
160
+ "actions",
161
+ "separator",
162
+ "stack",
163
+ "grid",
164
+ "card",
165
+ ]);
166
+
167
+ const GAP_STYLES = {
168
+ sm: "gap-2",
169
+ md: "gap-4",
170
+ lg: "gap-6",
171
+ } as const;
172
+
173
+ const GRID_STYLES = {
174
+ 1: "grid-cols-1",
175
+ 2: "grid-cols-1 md:grid-cols-2",
176
+ 3: "grid-cols-1 md:grid-cols-3",
177
+ 4: "grid-cols-1 md:grid-cols-2 xl:grid-cols-4",
178
+ } as const;
179
+
180
+ const ALIGN_STYLES = {
181
+ start: "items-start",
182
+ center: "items-center",
183
+ end: "items-end",
184
+ stretch: "items-stretch",
185
+ } as const;
186
+
187
+ function formatValue(value: ReactNode | OpenUIPrimitive) {
188
+ if (value === null || value === undefined) {
189
+ return <span className="text-muted-foreground">—</span>;
190
+ }
191
+
192
+ if (typeof value === "boolean") {
193
+ return value ? "Yes" : "No";
194
+ }
195
+
196
+ return value;
197
+ }
198
+
199
+ function asArray<T>(value: unknown): T[] {
200
+ return Array.isArray(value) ? (value as T[]) : [];
201
+ }
202
+
203
+ function isOpenUIComponentNode(value: unknown): value is OpenUIComponentNode {
204
+ return (
205
+ typeof value === "object" &&
206
+ value !== null &&
207
+ "type" in value &&
208
+ typeof (value as { type?: unknown }).type === "string" &&
209
+ NODE_TYPES.has((value as { type: string }).type)
210
+ );
211
+ }
212
+
213
+ function renderActions(actions: OpenUIAction[], onAction?: (action: OpenUIAction) => void) {
214
+ if (actions.length === 0) return null;
215
+
216
+ return (
217
+ <div className="flex flex-wrap items-center gap-2">
218
+ {actions.map((action) => (
219
+ <Button
220
+ key={action.id}
221
+ type="button"
222
+ size="sm"
223
+ variant={action.tone ?? "outline"}
224
+ disabled={action.disabled}
225
+ onClick={() => {
226
+ action.onPress?.();
227
+ onAction?.(action);
228
+ }}
229
+ >
230
+ {action.label}
231
+ </Button>
232
+ ))}
233
+ </div>
234
+ );
235
+ }
236
+
237
+ function renderNode(node: OpenUIComponentNode, onAction?: (action: OpenUIAction) => void): ReactNode {
238
+ switch (node.type) {
239
+ case "heading": {
240
+ const level = node.level ?? 2;
241
+ const HeadingTag = `h${level}` as const;
242
+
243
+ return (
244
+ <div className="space-y-1">
245
+ {node.kicker && (
246
+ <div className="text-[10px] font-semibold uppercase tracking-[0.16em] text-muted-foreground">
247
+ {node.kicker}
248
+ </div>
249
+ )}
250
+ <HeadingTag
251
+ className={cn(
252
+ "font-semibold tracking-tight text-foreground",
253
+ level === 1 && "text-2xl",
254
+ level === 2 && "text-xl",
255
+ level === 3 && "text-lg",
256
+ level === 4 && "text-base",
257
+ )}
258
+ >
259
+ {node.text}
260
+ </HeadingTag>
261
+ {node.meta && <p className="text-sm text-muted-foreground">{node.meta}</p>}
262
+ </div>
263
+ );
264
+ }
265
+
266
+ case "text":
267
+ return (
268
+ <p
269
+ className={cn(
270
+ "text-sm leading-6 text-foreground",
271
+ node.tone === "muted" && "text-muted-foreground",
272
+ node.tone === "success" && "text-[var(--surface-success-text)]",
273
+ node.tone === "warning" && "text-[var(--surface-warning-text)]",
274
+ node.tone === "error" && "text-[var(--surface-danger-text)]",
275
+ node.mono && "font-mono text-[13px]",
276
+ )}
277
+ >
278
+ {node.text}
279
+ </p>
280
+ );
281
+
282
+ case "badge":
283
+ return <Badge variant={node.tone ?? "outline"}>{node.label}</Badge>;
284
+
285
+ case "stat":
286
+ return (
287
+ <Card variant="glass" className="border-border shadow-[var(--shadow-card)]">
288
+ <CardContent className="space-y-2 p-4">
289
+ <div className="text-xs uppercase tracking-[0.12em] text-muted-foreground">
290
+ {node.label}
291
+ </div>
292
+ <div className="text-2xl font-semibold tracking-tight text-foreground">
293
+ {node.value}
294
+ </div>
295
+ {node.change && (
296
+ <div
297
+ className={cn(
298
+ "text-xs",
299
+ node.tone === "success" && "text-[var(--surface-success-text)]",
300
+ node.tone === "warning" && "text-[var(--surface-warning-text)]",
301
+ node.tone === "error" && "text-[var(--surface-danger-text)]",
302
+ node.tone === "info" && "text-[var(--surface-info-text)]",
303
+ !node.tone || node.tone === "default" ? "text-muted-foreground" : undefined,
304
+ )}
305
+ >
306
+ {node.change}
307
+ </div>
308
+ )}
309
+ </CardContent>
310
+ </Card>
311
+ );
312
+
313
+ case "key_value":
314
+ if (asArray(node.items).length === 0) {
315
+ return null;
316
+ }
317
+ return (
318
+ <dl className="grid gap-3 sm:grid-cols-2">
319
+ {asArray<OpenUIKeyValueNode["items"][number]>(node.items).map((item, index) => (
320
+ <div
321
+ key={item.id ?? `${item.label}-${index}`}
322
+ className="rounded-[var(--radius-lg)] border border-border bg-card px-4 py-3"
323
+ >
324
+ <dt className="text-[11px] uppercase tracking-[0.12em] text-muted-foreground">
325
+ {item.label}
326
+ </dt>
327
+ <dd
328
+ className={cn(
329
+ "mt-1 text-sm font-medium text-foreground",
330
+ item.tone === "muted" && "text-foreground",
331
+ )}
332
+ >
333
+ {formatValue(item.value)}
334
+ </dd>
335
+ </div>
336
+ ))}
337
+ </dl>
338
+ );
339
+
340
+ case "code":
341
+ return (
342
+ <div className="space-y-2">
343
+ {node.title && (
344
+ <div className="text-xs uppercase tracking-[0.12em] text-muted-foreground">
345
+ {node.title}
346
+ </div>
347
+ )}
348
+ <CodeBlock
349
+ code={node.code}
350
+ language={node.language}
351
+ showLineNumbers={node.showLineNumbers}
352
+ className="border-border bg-background"
353
+ />
354
+ </div>
355
+ );
356
+
357
+ case "markdown":
358
+ return (
359
+ <div className="rounded-[var(--radius-lg)] border border-border bg-card p-5">
360
+ <Markdown className="prose-sm max-w-none">{node.content}</Markdown>
361
+ </div>
362
+ );
363
+
364
+ case "table":
365
+ if (asArray(node.columns).length === 0) {
366
+ return null;
367
+ }
368
+ return (
369
+ <div className="overflow-hidden rounded-[var(--radius-lg)] border border-border bg-card">
370
+ <Table>
371
+ <TableHeader>
372
+ <TableRow className="border-border">
373
+ {asArray<OpenUITableNode["columns"][number]>(node.columns).map((column) => (
374
+ <TableHead
375
+ key={column.key}
376
+ className={cn(
377
+ "h-10 text-[11px] uppercase tracking-[0.1em] text-muted-foreground",
378
+ column.align === "right" && "text-right",
379
+ )}
380
+ >
381
+ {column.header}
382
+ </TableHead>
383
+ ))}
384
+ </TableRow>
385
+ </TableHeader>
386
+ <TableBody>
387
+ {asArray<OpenUITableNode["rows"][number]>(node.rows).map((row, rowIndex) => (
388
+ <TableRow key={rowIndex} className="border-border">
389
+ {asArray<OpenUITableNode["columns"][number]>(node.columns).map((column) => (
390
+ <TableCell
391
+ key={column.key}
392
+ className={cn(
393
+ "py-3 text-sm text-foreground",
394
+ column.align === "right" && "text-right tabular-nums",
395
+ )}
396
+ >
397
+ {formatValue(row[column.key])}
398
+ </TableCell>
399
+ ))}
400
+ </TableRow>
401
+ ))}
402
+ </TableBody>
403
+ </Table>
404
+ {node.caption && (
405
+ <div className="border-t border-border px-4 py-2 text-xs text-muted-foreground">
406
+ {node.caption}
407
+ </div>
408
+ )}
409
+ </div>
410
+ );
411
+
412
+ case "actions":
413
+ return renderActions(asArray<OpenUIAction>(node.actions), onAction);
414
+
415
+ case "separator":
416
+ return (
417
+ <div className="flex items-center gap-3">
418
+ <div className="h-px flex-1 bg-border" />
419
+ {node.label && (
420
+ <span className="text-[10px] uppercase tracking-[0.14em] text-muted-foreground">
421
+ {node.label}
422
+ </span>
423
+ )}
424
+ <div className="h-px flex-1 bg-border" />
425
+ </div>
426
+ );
427
+
428
+ case "stack":
429
+ if (asArray(node.children).length === 0) {
430
+ return null;
431
+ }
432
+ return (
433
+ <div
434
+ className={cn(
435
+ "flex",
436
+ node.direction === "row" ? "flex-row" : "flex-col",
437
+ GAP_STYLES[node.gap ?? "md"],
438
+ ALIGN_STYLES[node.align ?? "stretch"],
439
+ node.wrap && "flex-wrap",
440
+ )}
441
+ >
442
+ {asArray<OpenUIComponentNode>(node.children).map((child, index) => (
443
+ <Fragment key={child.id ?? `${child.type}-${index}`}>
444
+ {renderNode(child, onAction)}
445
+ </Fragment>
446
+ ))}
447
+ </div>
448
+ );
449
+
450
+ case "grid":
451
+ if (asArray(node.children).length === 0) {
452
+ return null;
453
+ }
454
+ return (
455
+ <div className={cn("grid", GRID_STYLES[node.columns ?? 2], GAP_STYLES[node.gap ?? "md"])}>
456
+ {asArray<OpenUIComponentNode>(node.children).map((child, index) => (
457
+ <Fragment key={child.id ?? `${child.type}-${index}`}>
458
+ {renderNode(child, onAction)}
459
+ </Fragment>
460
+ ))}
461
+ </div>
462
+ );
463
+
464
+ case "card":
465
+ return (
466
+ <Card variant="glass" className="border-border shadow-[var(--shadow-card)]">
467
+ {(node.eyebrow || node.title || node.description || node.badge || asArray(node.actions).length > 0) && (
468
+ <CardHeader className="gap-2 p-4 pb-0">
469
+ <div className="flex items-start justify-between gap-3">
470
+ <div className="min-w-0 flex-1 space-y-1">
471
+ {node.eyebrow && (
472
+ <div className="text-[10px] font-semibold uppercase tracking-[0.16em] text-muted-foreground">
473
+ {node.eyebrow}
474
+ </div>
475
+ )}
476
+ {node.title && <CardTitle className="text-base text-foreground">{node.title}</CardTitle>}
477
+ {node.description && (
478
+ <CardDescription className="text-muted-foreground">
479
+ {node.description}
480
+ </CardDescription>
481
+ )}
482
+ </div>
483
+ {node.badge && <Badge variant={node.badge.tone ?? "outline"}>{node.badge.label}</Badge>}
484
+ </div>
485
+ {asArray(node.actions).length > 0 && renderActions(asArray<OpenUIAction>(node.actions), onAction)}
486
+ </CardHeader>
487
+ )}
488
+ {asArray(node.children).length > 0 && (
489
+ <CardContent className="space-y-4 p-4">
490
+ {asArray<OpenUIComponentNode>(node.children).map((child, index) => (
491
+ <Fragment key={child.id ?? `${child.type}-${index}`}>
492
+ {renderNode(child, onAction)}
493
+ </Fragment>
494
+ ))}
495
+ </CardContent>
496
+ )}
497
+ </Card>
498
+ );
499
+ }
500
+ }
501
+
502
+ /**
503
+ * OpenUIArtifactRenderer — contained renderer for OpenUI-like structured
504
+ * artifact payloads using sandbox-ui primitives and theme tokens. This is an
505
+ * extension surface for generated inspectors/results, not a replacement for
506
+ * the sandbox shell.
507
+ */
508
+ export function OpenUIArtifactRenderer({
509
+ schema,
510
+ onAction,
511
+ className,
512
+ }: OpenUIArtifactRendererProps) {
513
+ const nodes = (Array.isArray(schema) ? schema : [schema]).filter(isOpenUIComponentNode);
514
+
515
+ if (nodes.length === 0) {
516
+ return (
517
+ <div
518
+ className={cn(
519
+ "flex min-h-[6rem] items-center justify-center rounded-[var(--radius-xl)] border border-dashed border-border bg-card p-5 text-center",
520
+ className,
521
+ )}
522
+ >
523
+ <div className="space-y-2">
524
+ <div className="mx-auto flex h-10 w-10 items-center justify-center rounded-full bg-muted/50 text-muted-foreground">
525
+ <Minus className="h-4 w-4" />
526
+ </div>
527
+ <div className="text-sm font-medium text-foreground">No view was generated.</div>
528
+ </div>
529
+ </div>
530
+ );
531
+ }
532
+
533
+ return (
534
+ <div className={cn("space-y-4", className)}>
535
+ {nodes.map((node, index) => (
536
+ <Fragment key={node.id ?? `${node.type}-${index}`}>
537
+ {renderNode(node, onAction)}
538
+ </Fragment>
539
+ ))}
540
+ </div>
541
+ );
542
+ }
@@ -0,0 +1,91 @@
1
+ import { type ReactNode } from "react";
2
+ import { cn } from "../lib/utils";
3
+
4
+ export interface ArtifactPaneProps {
5
+ eyebrow?: ReactNode;
6
+ title: ReactNode;
7
+ subtitle?: ReactNode;
8
+ meta?: ReactNode;
9
+ headerActions?: ReactNode;
10
+ tabs?: ReactNode;
11
+ toolbar?: ReactNode;
12
+ footer?: ReactNode;
13
+ emptyState?: ReactNode;
14
+ children?: ReactNode;
15
+ className?: string;
16
+ contentClassName?: string;
17
+ }
18
+
19
+ /**
20
+ * ArtifactPane — shared frame for files, previews, documents, inspectors, and
21
+ * other artifact-like surfaces inside sandbox applications.
22
+ */
23
+ export function ArtifactPane({
24
+ eyebrow,
25
+ title,
26
+ subtitle,
27
+ meta,
28
+ headerActions,
29
+ tabs,
30
+ toolbar,
31
+ footer,
32
+ emptyState,
33
+ children,
34
+ className,
35
+ contentClassName,
36
+ }: ArtifactPaneProps) {
37
+ const hasContent = children !== undefined && children !== null;
38
+
39
+ return (
40
+ <section
41
+ className={cn(
42
+ "flex h-full min-h-0 flex-col overflow-hidden bg-background text-foreground",
43
+ className,
44
+ )}
45
+ >
46
+ <header className="border-b border-border bg-muted/10">
47
+ <div className="flex items-start justify-between gap-3 px-3 py-2">
48
+ <div className="min-w-0 flex-1">
49
+ {eyebrow && (
50
+ <div className="mb-1 inline-flex max-w-full items-center px-1 py-0.5 text-[10px] font-bold uppercase tracking-wider text-muted-foreground">
51
+ {eyebrow}
52
+ </div>
53
+ )}
54
+ <div className="min-w-0 text-[13px] font-medium text-foreground">
55
+ {title}
56
+ </div>
57
+ {(subtitle || meta) && (
58
+ <div className="mt-1 flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 text-xs leading-relaxed text-muted-foreground">
59
+ {subtitle && <span className="truncate">{subtitle}</span>}
60
+ {meta && <span className="flex items-center gap-2">{meta}</span>}
61
+ </div>
62
+ )}
63
+ </div>
64
+ {headerActions && (
65
+ <div className="flex shrink-0 items-center gap-1.5">{headerActions}</div>
66
+ )}
67
+ </div>
68
+ {tabs}
69
+ {toolbar && (
70
+ <div className="border-t border-border px-3 py-2">
71
+ {toolbar}
72
+ </div>
73
+ )}
74
+ </header>
75
+
76
+ <div className={cn("min-h-0 flex-1 overflow-auto", contentClassName)}>
77
+ {hasContent ? (
78
+ children
79
+ ) : emptyState ? (
80
+ <div className="flex h-full items-center justify-center p-6">{emptyState}</div>
81
+ ) : null}
82
+ </div>
83
+
84
+ {footer && (
85
+ <footer className="shrink-0 border-t border-border bg-card px-3 py-2">
86
+ {footer}
87
+ </footer>
88
+ )}
89
+ </section>
90
+ );
91
+ }