assistant-ui 0.0.120 → 0.0.121

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 (92) hide show
  1. package/README.md +60 -1
  2. package/dist/commands/cloud.d.ts +25 -0
  3. package/dist/commands/cloud.d.ts.map +1 -0
  4. package/dist/commands/cloud.js +194 -0
  5. package/dist/commands/cloud.js.map +1 -0
  6. package/dist/commands/create.d.ts.map +1 -1
  7. package/dist/commands/create.js +8 -0
  8. package/dist/commands/create.js.map +1 -1
  9. package/dist/commands/info.js +1 -1
  10. package/dist/lib/agent-skill.d.ts +2 -2
  11. package/dist/lib/agent-skill.js +1 -1
  12. package/dist/lib/agent-skill.js.map +1 -1
  13. package/dist/lib/cloud-abort.d.ts +2 -0
  14. package/dist/lib/cloud-abort.d.ts.map +1 -0
  15. package/dist/lib/cloud-abort.js +13 -0
  16. package/dist/lib/cloud-abort.js.map +1 -0
  17. package/dist/lib/cloud-auth.d.ts +28 -0
  18. package/dist/lib/cloud-auth.d.ts.map +1 -0
  19. package/dist/lib/cloud-auth.js +169 -0
  20. package/dist/lib/cloud-auth.js.map +1 -0
  21. package/dist/lib/cloud-config.d.ts +21 -0
  22. package/dist/lib/cloud-config.d.ts.map +1 -0
  23. package/dist/lib/cloud-config.js +76 -0
  24. package/dist/lib/cloud-config.js.map +1 -0
  25. package/dist/lib/cloud-provision.d.ts +20 -0
  26. package/dist/lib/cloud-provision.d.ts.map +1 -0
  27. package/dist/lib/cloud-provision.js +55 -0
  28. package/dist/lib/cloud-provision.js.map +1 -0
  29. package/dist/lib/cloud-setup-login.d.ts +9 -0
  30. package/dist/lib/cloud-setup-login.d.ts.map +1 -0
  31. package/dist/lib/cloud-setup-login.js +137 -0
  32. package/dist/lib/cloud-setup-login.js.map +1 -0
  33. package/dist/lib/cloud-url.d.ts +2 -0
  34. package/dist/lib/cloud-url.d.ts.map +1 -0
  35. package/dist/lib/cloud-url.js +14 -0
  36. package/dist/lib/cloud-url.js.map +1 -0
  37. package/dist/lib/transform.js +1 -1
  38. package/dist/program.d.ts.map +1 -1
  39. package/dist/program.js +2 -1
  40. package/dist/program.js.map +1 -1
  41. package/package.json +11 -7
  42. package/src/commands/cloud.test.ts +238 -0
  43. package/src/commands/cloud.ts +455 -0
  44. package/src/commands/create.ts +8 -0
  45. package/src/lib/agent-skill.ts +1 -1
  46. package/src/lib/cloud-abort.ts +12 -0
  47. package/src/lib/cloud-auth.test.ts +500 -0
  48. package/src/lib/cloud-auth.ts +273 -0
  49. package/src/lib/cloud-config.test.ts +82 -0
  50. package/src/lib/cloud-config.ts +138 -0
  51. package/src/lib/cloud-provision.test.ts +132 -0
  52. package/src/lib/cloud-provision.ts +93 -0
  53. package/src/lib/cloud-setup-login.test.ts +361 -0
  54. package/src/lib/cloud-setup-login.ts +240 -0
  55. package/src/lib/cloud-url.ts +19 -0
  56. package/src/program.ts +2 -0
  57. package/templates/cloud-harness/.assistant-ui/template.json +3 -0
  58. package/templates/cloud-harness/.env.example +1 -0
  59. package/templates/cloud-harness/.gitignore +42 -0
  60. package/templates/cloud-harness/README.md +28 -0
  61. package/templates/cloud-harness/app/api/chat/route.ts +13 -0
  62. package/templates/cloud-harness/app/api/credential/route.ts +14 -0
  63. package/templates/cloud-harness/app/assistant.tsx +83 -0
  64. package/templates/cloud-harness/app/favicon.ico +0 -0
  65. package/templates/cloud-harness/app/globals.css +139 -0
  66. package/templates/cloud-harness/app/layout.tsx +37 -0
  67. package/templates/cloud-harness/app/page.tsx +5 -0
  68. package/templates/cloud-harness/components/assistant-ui/elements/attachment.aui.tsx +279 -0
  69. package/templates/cloud-harness/components/assistant-ui/elements/markdown-text.tsx +238 -0
  70. package/templates/cloud-harness/components/assistant-ui/elements/thread.aui.tsx +521 -0
  71. package/templates/cloud-harness/components/assistant-ui/elements/tool-fallback.aui.tsx +1153 -0
  72. package/templates/cloud-harness/components/assistant-ui/elements/tooltip-icon-button.tsx +49 -0
  73. package/templates/cloud-harness/components/ui/avatar.tsx +109 -0
  74. package/templates/cloud-harness/components/ui/button.tsx +58 -0
  75. package/templates/cloud-harness/components/ui/collapsible.tsx +21 -0
  76. package/templates/cloud-harness/components/ui/dialog.tsx +159 -0
  77. package/templates/cloud-harness/components/ui/textarea.tsx +18 -0
  78. package/templates/cloud-harness/components/ui/tooltip.tsx +69 -0
  79. package/templates/cloud-harness/components.json +24 -0
  80. package/templates/cloud-harness/hooks/use-attachment-src.ts +46 -0
  81. package/templates/cloud-harness/hooks/use-copy-to-clipboard.ts +54 -0
  82. package/templates/cloud-harness/lib/utils.ts +1 -0
  83. package/templates/cloud-harness/next.config.ts +6 -0
  84. package/templates/cloud-harness/package.json +51 -0
  85. package/templates/cloud-harness/postcss.config.mjs +5 -0
  86. package/templates/cloud-harness/tsconfig.json +33 -0
  87. package/templates/cloud-harness/vendor/README.md +13 -0
  88. package/templates/cloud-harness/vendor/assistant-ui-react-harness-sdk-0.0.2.tgz +0 -0
  89. package/templates/cloud-harness/vendor/harness-sdk-0.3.1.tgz +0 -0
  90. package/templates/cloud-harness/vendor/provenance.json +38 -0
  91. package/templates/cloud-harness/vendor/statewire-0.19.3.tgz +0 -0
  92. /package/{LICENSE → templates/cloud-harness/vendor/LICENSE} +0 -0
@@ -0,0 +1,521 @@
1
+ import {
2
+ ComposerAddAttachment,
3
+ ComposerAttachments,
4
+ UserMessageAttachments,
5
+ } from "@/components/assistant-ui/elements/attachment.aui";
6
+ import { MarkdownText } from "@/components/assistant-ui/elements/markdown-text";
7
+ import { ToolFallback } from "@/components/assistant-ui/elements/tool-fallback.aui";
8
+ import { TooltipIconButton } from "@/components/assistant-ui/elements/tooltip-icon-button";
9
+ import { Button } from "@/components/ui/button";
10
+ import { cn } from "@/lib/utils";
11
+ import {
12
+ ActionBarMorePrimitive,
13
+ ActionBarPrimitive,
14
+ AuiIf,
15
+ type AssistantState,
16
+ BranchPickerPrimitive,
17
+ ComposerPrimitive,
18
+ ErrorPrimitive,
19
+ MessagePrimitive,
20
+ SuggestionPrimitive,
21
+ ThreadPrimitive,
22
+ useAuiState,
23
+ } from "@assistant-ui/react";
24
+ import {
25
+ ArrowDownIcon,
26
+ ArrowUpIcon,
27
+ CheckIcon,
28
+ ChevronLeftIcon,
29
+ ChevronRightIcon,
30
+ CopyIcon,
31
+ DownloadIcon,
32
+ MicIcon,
33
+ MoreHorizontalIcon,
34
+ PencilIcon,
35
+ RefreshCwIcon,
36
+ SquareIcon,
37
+ } from "lucide-react";
38
+ import type { FC } from "react";
39
+
40
+ // Startup exposes a loading placeholder thread; treat it as a new chat so
41
+ // the composer mounts centered. Loads after startup keep the docked layout.
42
+ const isNewChatView = (s: AssistantState) =>
43
+ s.thread.messages.length === 0 &&
44
+ (!s.thread.isLoading || s.threads.isLoading);
45
+
46
+ // A switched thread that is still fetching its history: skeleton, not welcome.
47
+ const isHistoryLoadingView = (s: AssistantState) =>
48
+ s.thread.messages.length === 0 &&
49
+ s.thread.isLoading &&
50
+ !s.thread.isDisabled &&
51
+ !s.threads.isLoading;
52
+
53
+ const ThreadHistorySkeleton: FC = () => (
54
+ <div
55
+ data-slot="aui_thread-history-skeleton"
56
+ role="status"
57
+ className="animate-in fade-in fill-mode-both flex flex-col [animation-delay:150ms] [animation-duration:200ms]"
58
+ >
59
+ <span className="sr-only">Loading conversation</span>
60
+ <div className="flex animate-pulse flex-col gap-y-6 motion-reduce:animate-none">
61
+ <div className="bg-muted ml-auto h-9 w-2/5 rounded-xl" />
62
+ <div className="flex flex-col gap-y-2">
63
+ <div className="bg-muted h-4 w-11/12 rounded-md" />
64
+ <div className="bg-muted h-4 w-4/5 rounded-md" />
65
+ <div className="bg-muted h-4 w-3/5 rounded-md" />
66
+ </div>
67
+ <div className="bg-muted ml-auto h-9 w-1/3 rounded-xl" />
68
+ <div className="flex flex-col gap-y-2">
69
+ <div className="bg-muted h-4 w-10/12 rounded-md" />
70
+ <div className="bg-muted h-4 w-2/3 rounded-md" />
71
+ </div>
72
+ </div>
73
+ </div>
74
+ );
75
+
76
+ export const Thread: FC = () => {
77
+ const isEmpty = useAuiState(isNewChatView);
78
+
79
+ return (
80
+ <ThreadPrimitive.Root
81
+ className="aui-root aui-thread-root bg-background @container flex h-full flex-col"
82
+ style={{
83
+ ["--thread-max-width" as string]: "44rem",
84
+ ["--composer-bg" as string]:
85
+ "color-mix(in oklab, var(--color-muted) 30%, transparent)",
86
+ ["--composer-radius" as string]: "1rem",
87
+ ["--composer-padding" as string]: "8px",
88
+ }}
89
+ >
90
+ <ThreadPrimitive.Viewport
91
+ turnAnchor="top"
92
+ data-slot="aui_thread-viewport"
93
+ className="relative flex flex-1 flex-col overflow-x-auto overflow-y-scroll scroll-smooth"
94
+ >
95
+ <div
96
+ className={cn(
97
+ "mx-auto flex w-full max-w-(--thread-max-width) flex-1 flex-col px-4 pt-4",
98
+ isEmpty && "justify-center",
99
+ )}
100
+ >
101
+ <AuiIf condition={isNewChatView}>
102
+ <ThreadWelcome />
103
+ </AuiIf>
104
+ <AuiIf condition={isHistoryLoadingView}>
105
+ <ThreadHistorySkeleton />
106
+ </AuiIf>
107
+
108
+ <div
109
+ data-slot="aui_message-group"
110
+ className="mb-14 flex flex-col gap-y-6 empty:hidden"
111
+ >
112
+ <ThreadPrimitive.Messages>
113
+ {() => <ThreadMessage />}
114
+ </ThreadPrimitive.Messages>
115
+ </div>
116
+
117
+ <ThreadPrimitive.ViewportFooter
118
+ className={cn(
119
+ "aui-thread-viewport-footer bg-background flex flex-col gap-4 overflow-visible pb-4 md:pb-6",
120
+ !isEmpty &&
121
+ "sticky bottom-0 mt-auto rounded-t-(--composer-radius)",
122
+ )}
123
+ >
124
+ <ThreadScrollToBottom />
125
+ <Composer />
126
+ <AuiIf condition={(s) => isNewChatView(s) && s.composer.isEmpty}>
127
+ <ThreadSuggestions />
128
+ </AuiIf>
129
+ </ThreadPrimitive.ViewportFooter>
130
+ </div>
131
+ </ThreadPrimitive.Viewport>
132
+ </ThreadPrimitive.Root>
133
+ );
134
+ };
135
+
136
+ const ThreadMessage: FC = () => {
137
+ const role = useAuiState((s) => s.message.role);
138
+ const isEditing = useAuiState((s) => s.message.composer.isEditing);
139
+
140
+ if (isEditing) return <EditComposer />;
141
+ if (role === "user") return <UserMessage />;
142
+ return <AssistantMessage />;
143
+ };
144
+
145
+ const ThreadScrollToBottom: FC = () => {
146
+ return (
147
+ <ThreadPrimitive.ScrollToBottom asChild>
148
+ <TooltipIconButton
149
+ tooltip="Scroll to bottom"
150
+ variant="outline"
151
+ className="aui-thread-scroll-to-bottom dark:border-border dark:bg-background dark:hover:bg-accent absolute -top-12 z-10 self-center rounded-full p-4 disabled:invisible"
152
+ >
153
+ <ArrowDownIcon />
154
+ </TooltipIconButton>
155
+ </ThreadPrimitive.ScrollToBottom>
156
+ );
157
+ };
158
+
159
+ const ThreadWelcome: FC = () => {
160
+ return (
161
+ <div className="aui-thread-welcome-root mb-6 flex flex-col px-2">
162
+ <p className="aui-thread-welcome-message-inner fade-in slide-in-from-bottom-1 animate-in fill-mode-both text-2xl font-medium tracking-tight duration-200">
163
+ How can I help you today?
164
+ </p>
165
+ </div>
166
+ );
167
+ };
168
+
169
+ const ThreadSuggestions: FC = () => {
170
+ return (
171
+ <div className="aui-thread-welcome-suggestions flex w-full flex-col">
172
+ <ThreadPrimitive.Suggestions>
173
+ {() => <ThreadSuggestionItem />}
174
+ </ThreadPrimitive.Suggestions>
175
+ </div>
176
+ );
177
+ };
178
+
179
+ const ThreadSuggestionItem: FC = () => {
180
+ return (
181
+ <div className="aui-thread-welcome-suggestion-display fade-in slide-in-from-bottom-2 animate-in fill-mode-both duration-200">
182
+ <SuggestionPrimitive.Trigger send asChild>
183
+ <button
184
+ type="button"
185
+ className="aui-thread-welcome-suggestion group hover:bg-foreground/[0.03] focus-visible:ring-ring/50 flex w-full items-baseline gap-2.5 rounded-md px-2 py-2 text-start text-sm transition-colors outline-none focus-visible:ring-1 motion-reduce:transition-none"
186
+ >
187
+ <span
188
+ aria-hidden
189
+ className="text-muted-foreground/60 group-hover:text-foreground font-mono text-xs transition-colors motion-reduce:transition-none"
190
+ >
191
+ {">"}
192
+ </span>
193
+ <span className="min-w-0 flex-1 truncate">
194
+ <SuggestionPrimitive.Title className="aui-thread-welcome-suggestion-text-1 text-foreground" />{" "}
195
+ <SuggestionPrimitive.Description className="aui-thread-welcome-suggestion-text-2 text-muted-foreground empty:hidden" />
196
+ </span>
197
+ </button>
198
+ </SuggestionPrimitive.Trigger>
199
+ </div>
200
+ );
201
+ };
202
+
203
+ const Composer: FC = () => {
204
+ return (
205
+ <ComposerPrimitive.Root className="aui-composer-root relative flex w-full flex-col">
206
+ <ComposerPrimitive.AttachmentDropzone asChild>
207
+ <div
208
+ data-slot="aui_composer-shell"
209
+ className="border-foreground/10 focus-within:border-foreground/25 data-[dragging=true]:border-ring flex w-full cursor-text flex-col gap-2 rounded-(--composer-radius) border bg-(--composer-bg) p-(--composer-padding) transition-[border-color] data-[dragging=true]:border-dashed data-[dragging=true]:bg-[color-mix(in_oklab,var(--color-accent)_50%,var(--color-background))]"
210
+ >
211
+ <ComposerAttachments />
212
+ <ComposerPrimitive.Input
213
+ placeholder="Send a message..."
214
+ className="aui-composer-input placeholder:text-muted-foreground/60 max-h-48 min-h-10 w-full resize-none bg-transparent px-2.5 py-1 text-base leading-6 outline-none"
215
+ rows={1}
216
+ autoFocus
217
+ aria-label="Message input"
218
+ />
219
+ <ComposerAction />
220
+ </div>
221
+ </ComposerPrimitive.AttachmentDropzone>
222
+ </ComposerPrimitive.Root>
223
+ );
224
+ };
225
+
226
+ const ComposerAction: FC = () => {
227
+ // The stop control only cancels the send while no run it could stop is going.
228
+ const isSending = useAuiState(
229
+ (s) =>
230
+ s.composer.submission !== undefined &&
231
+ !(s.thread.isRunning && s.thread.capabilities.cancel),
232
+ );
233
+
234
+ return (
235
+ <div className="aui-composer-action-wrapper relative flex items-center justify-between">
236
+ <ComposerAddAttachment />
237
+ <div className="flex items-center gap-1.5">
238
+ <AuiIf condition={(s) => s.thread.capabilities.dictation}>
239
+ <AuiIf condition={(s) => s.composer.dictation == null}>
240
+ <ComposerPrimitive.Dictate asChild>
241
+ <TooltipIconButton
242
+ tooltip="Voice input"
243
+ side="bottom"
244
+ type="button"
245
+ variant="ghost"
246
+ size="icon"
247
+ className="aui-composer-dictate text-muted-foreground hover:text-foreground size-7 rounded-full"
248
+ aria-label="Start voice input"
249
+ >
250
+ <MicIcon className="aui-composer-dictate-icon size-4" />
251
+ </TooltipIconButton>
252
+ </ComposerPrimitive.Dictate>
253
+ </AuiIf>
254
+ <AuiIf condition={(s) => s.composer.dictation != null}>
255
+ <ComposerPrimitive.StopDictation asChild>
256
+ <TooltipIconButton
257
+ tooltip="Stop dictation"
258
+ side="bottom"
259
+ type="button"
260
+ variant="ghost"
261
+ size="icon"
262
+ className="aui-composer-stop-dictation text-destructive size-7 rounded-full"
263
+ aria-label="Stop voice input"
264
+ >
265
+ <SquareIcon className="aui-composer-stop-dictation-icon size-3.5 animate-pulse fill-current" />
266
+ </TooltipIconButton>
267
+ </ComposerPrimitive.StopDictation>
268
+ </AuiIf>
269
+ </AuiIf>
270
+ <AuiIf
271
+ condition={(s) =>
272
+ !s.composer.canCancel ||
273
+ (s.thread.voice !== undefined &&
274
+ s.composer.submission === undefined)
275
+ }
276
+ >
277
+ <ComposerPrimitive.Send asChild>
278
+ <TooltipIconButton
279
+ tooltip="Send message"
280
+ side="bottom"
281
+ type="button"
282
+ variant="default"
283
+ size="icon"
284
+ className="aui-composer-send size-7 rounded-full"
285
+ aria-label="Send message"
286
+ >
287
+ <ArrowUpIcon className="aui-composer-send-icon size-4" />
288
+ </TooltipIconButton>
289
+ </ComposerPrimitive.Send>
290
+ </AuiIf>
291
+ <AuiIf
292
+ condition={(s) =>
293
+ s.composer.canCancel &&
294
+ (s.thread.voice === undefined ||
295
+ s.composer.submission !== undefined)
296
+ }
297
+ >
298
+ <ComposerPrimitive.Cancel asChild>
299
+ <Button
300
+ type="button"
301
+ variant="default"
302
+ size="icon"
303
+ className="aui-composer-cancel size-7 rounded-full"
304
+ aria-label={isSending ? "Cancel sending" : "Stop generating"}
305
+ >
306
+ <SquareIcon className="aui-composer-cancel-icon size-3.5 fill-current" />
307
+ </Button>
308
+ </ComposerPrimitive.Cancel>
309
+ </AuiIf>
310
+ </div>
311
+ </div>
312
+ );
313
+ };
314
+
315
+ const MessageError: FC = () => {
316
+ return (
317
+ <MessagePrimitive.Error>
318
+ <ErrorPrimitive.Root className="aui-message-error-root border-destructive bg-destructive/10 text-destructive dark:bg-destructive/5 mt-2 rounded-md border p-3 text-sm dark:text-red-200">
319
+ <ErrorPrimitive.Message className="aui-message-error-message line-clamp-2" />
320
+ </ErrorPrimitive.Root>
321
+ </MessagePrimitive.Error>
322
+ );
323
+ };
324
+
325
+ const AssistantMessage: FC = () => {
326
+ const ACTION_BAR_PT = "pt-1.5";
327
+ const ACTION_BAR_HEIGHT = `min-h-7.5 ${ACTION_BAR_PT}`;
328
+
329
+ return (
330
+ <MessagePrimitive.Root
331
+ data-slot="aui_assistant-message-root"
332
+ data-role="assistant"
333
+ className="fade-in slide-in-from-bottom-1 animate-in relative -mb-7.5 pb-7.5 duration-150 [contain-intrinsic-size:auto_200px] [content-visibility:auto]"
334
+ >
335
+ <div
336
+ data-slot="aui_assistant-message-content"
337
+ className="text-foreground px-2 leading-relaxed wrap-break-word"
338
+ >
339
+ <MessagePrimitive.Parts>
340
+ {({ part }) => {
341
+ if (part.type === "text") return <MarkdownText />;
342
+ if (part.type === "tool-call")
343
+ return part.toolUI ?? <ToolFallback {...part} />;
344
+ return null;
345
+ }}
346
+ </MessagePrimitive.Parts>
347
+ <AuiIf
348
+ condition={(s) =>
349
+ s.message.status?.type === "running" && s.message.parts.length === 0
350
+ }
351
+ >
352
+ <span
353
+ data-slot="aui_assistant-message-indicator"
354
+ className="animate-pulse font-sans"
355
+ aria-label="Assistant is working"
356
+ >
357
+ {"●"}
358
+ </span>
359
+ </AuiIf>
360
+ <MessageError />
361
+ </div>
362
+
363
+ <div
364
+ data-slot="aui_assistant-message-footer"
365
+ className={cn("ms-2 flex items-center", ACTION_BAR_HEIGHT)}
366
+ >
367
+ <BranchPicker />
368
+ <AssistantActionBar />
369
+ </div>
370
+ </MessagePrimitive.Root>
371
+ );
372
+ };
373
+
374
+ const AssistantActionBar: FC = () => {
375
+ return (
376
+ <ActionBarPrimitive.Root
377
+ hideWhenRunning
378
+ autohide="not-last"
379
+ className="aui-assistant-action-bar-root text-muted-foreground animate-in fade-in col-start-3 row-start-2 -ms-1 flex gap-1 duration-200"
380
+ >
381
+ <ActionBarPrimitive.Copy asChild>
382
+ <TooltipIconButton tooltip="Copy">
383
+ <AuiIf condition={(s) => s.message.isCopied}>
384
+ <CheckIcon className="animate-in zoom-in-50 fade-in duration-200 ease-out" />
385
+ </AuiIf>
386
+ <AuiIf condition={(s) => !s.message.isCopied}>
387
+ <CopyIcon className="animate-in zoom-in-75 fade-in duration-150" />
388
+ </AuiIf>
389
+ </TooltipIconButton>
390
+ </ActionBarPrimitive.Copy>
391
+ <ActionBarPrimitive.Reload asChild>
392
+ <TooltipIconButton tooltip="Refresh">
393
+ <RefreshCwIcon />
394
+ </TooltipIconButton>
395
+ </ActionBarPrimitive.Reload>
396
+ <ActionBarMorePrimitive.Root>
397
+ <ActionBarMorePrimitive.Trigger asChild>
398
+ <TooltipIconButton
399
+ tooltip="More"
400
+ className="data-[state=open]:bg-accent"
401
+ >
402
+ <MoreHorizontalIcon />
403
+ </TooltipIconButton>
404
+ </ActionBarMorePrimitive.Trigger>
405
+ <ActionBarMorePrimitive.Content
406
+ side="bottom"
407
+ align="start"
408
+ sideOffset={6}
409
+ className="aui-action-bar-more-content bg-popover/95 text-popover-foreground data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=closed]:animate-out 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 z-50 min-w-[8rem] overflow-hidden rounded-xl border p-1.5 shadow-lg backdrop-blur-sm"
410
+ >
411
+ <ActionBarPrimitive.ExportMarkdown asChild>
412
+ <ActionBarMorePrimitive.Item className="aui-action-bar-more-item hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground flex cursor-pointer items-center gap-2 rounded-lg px-2.5 py-1.5 text-sm outline-none select-none">
413
+ <DownloadIcon className="size-4" />
414
+ Export as Markdown
415
+ </ActionBarMorePrimitive.Item>
416
+ </ActionBarPrimitive.ExportMarkdown>
417
+ </ActionBarMorePrimitive.Content>
418
+ </ActionBarMorePrimitive.Root>
419
+ </ActionBarPrimitive.Root>
420
+ );
421
+ };
422
+
423
+ const UserMessage: FC = () => {
424
+ return (
425
+ <MessagePrimitive.Root
426
+ data-slot="aui_user-message-root"
427
+ className="fade-in slide-in-from-bottom-1 animate-in grid auto-rows-auto grid-cols-[minmax(72px,1fr)_auto] content-start gap-y-2 px-2 duration-150 [contain-intrinsic-size:auto_200px] [content-visibility:auto] [&:where(>*)]:col-start-2"
428
+ data-role="user"
429
+ >
430
+ <UserMessageAttachments />
431
+
432
+ <div className="aui-user-message-content-wrapper relative col-start-2 min-w-0">
433
+ <div className="aui-user-message-content peer bg-muted text-foreground rounded-(--composer-radius) px-4 py-2 wrap-break-word empty:hidden">
434
+ <MessagePrimitive.Parts />
435
+ </div>
436
+ <div className="aui-user-action-bar-wrapper absolute start-0 top-1/2 -translate-x-full -translate-y-1/2 pe-2 peer-empty:hidden rtl:translate-x-full">
437
+ <UserActionBar />
438
+ </div>
439
+ </div>
440
+
441
+ <BranchPicker
442
+ data-slot="aui_user-branch-picker"
443
+ className="col-span-full col-start-1 -me-1 justify-end"
444
+ />
445
+ </MessagePrimitive.Root>
446
+ );
447
+ };
448
+
449
+ const UserActionBar: FC = () => {
450
+ return (
451
+ <ActionBarPrimitive.Root
452
+ hideWhenRunning
453
+ autohide="not-last"
454
+ className="aui-user-action-bar-root flex flex-col items-end"
455
+ >
456
+ <ActionBarPrimitive.Edit asChild>
457
+ <TooltipIconButton tooltip="Edit" className="aui-user-action-edit">
458
+ <PencilIcon />
459
+ </TooltipIconButton>
460
+ </ActionBarPrimitive.Edit>
461
+ </ActionBarPrimitive.Root>
462
+ );
463
+ };
464
+
465
+ const EditComposer: FC = () => {
466
+ return (
467
+ <MessagePrimitive.Root
468
+ data-slot="aui_edit-composer-wrapper"
469
+ className="flex flex-col px-2 [contain-intrinsic-size:auto_200px] [content-visibility:auto]"
470
+ >
471
+ <ComposerPrimitive.Root className="aui-edit-composer-root border-foreground/10 focus-within:border-foreground/25 ms-auto flex w-full max-w-[85%] cursor-text flex-col rounded-(--composer-radius) border bg-(--composer-bg) shadow-[0_4px_16px_-8px_rgba(0,0,0,0.08),0_1px_2px_rgba(0,0,0,0.04)] transition-[border-color] dark:shadow-none">
472
+ <ComposerPrimitive.Input
473
+ className="aui-edit-composer-input text-foreground min-h-14 w-full resize-none bg-transparent px-4 pt-3 pb-1 text-base outline-none"
474
+ autoFocus
475
+ />
476
+ <div className="aui-edit-composer-footer mx-2.5 mb-2.5 flex items-center gap-1.5 self-end">
477
+ <ComposerPrimitive.Cancel asChild>
478
+ <Button variant="ghost" size="sm" className="h-8 px-3">
479
+ Cancel
480
+ </Button>
481
+ </ComposerPrimitive.Cancel>
482
+ <ComposerPrimitive.Send asChild>
483
+ <Button size="sm" className="h-8 px-3">
484
+ Update
485
+ </Button>
486
+ </ComposerPrimitive.Send>
487
+ </div>
488
+ </ComposerPrimitive.Root>
489
+ </MessagePrimitive.Root>
490
+ );
491
+ };
492
+
493
+ const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
494
+ className,
495
+ ...rest
496
+ }) => {
497
+ return (
498
+ <BranchPickerPrimitive.Root
499
+ hideWhenSingleBranch
500
+ className={cn(
501
+ "aui-branch-picker-root text-muted-foreground -ms-2 me-2 inline-flex items-center text-xs",
502
+ className,
503
+ )}
504
+ {...rest}
505
+ >
506
+ <BranchPickerPrimitive.Previous asChild>
507
+ <TooltipIconButton tooltip="Previous">
508
+ <ChevronLeftIcon />
509
+ </TooltipIconButton>
510
+ </BranchPickerPrimitive.Previous>
511
+ <span className="aui-branch-picker-state font-medium">
512
+ <BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count />
513
+ </span>
514
+ <BranchPickerPrimitive.Next asChild>
515
+ <TooltipIconButton tooltip="Next">
516
+ <ChevronRightIcon />
517
+ </TooltipIconButton>
518
+ </BranchPickerPrimitive.Next>
519
+ </BranchPickerPrimitive.Root>
520
+ );
521
+ };