@aomi-labs/widget-lib 1.0.0 → 1.1.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 (76) hide show
  1. package/SHADCN-FETCH-GUIDE.md +105 -0
  2. package/components.json +10 -0
  3. package/dist/accordion.json +18 -0
  4. package/dist/alert.json +17 -0
  5. package/dist/aomi-frame.json +24 -0
  6. package/dist/assistant-thread-list.json +22 -0
  7. package/dist/assistant-thread.json +27 -0
  8. package/dist/assistant-threadlist-collapsible.json +23 -0
  9. package/dist/assistant-threadlist-sidebar.json +20 -0
  10. package/dist/assistant-tool-fallback.json +20 -0
  11. package/dist/avatar.json +17 -0
  12. package/dist/badge.json +17 -0
  13. package/dist/breadcrumb.json +17 -0
  14. package/dist/button.json +18 -0
  15. package/dist/card.json +15 -0
  16. package/dist/collapsible.json +17 -0
  17. package/dist/command.json +21 -0
  18. package/dist/dialog.json +18 -0
  19. package/dist/drawer.json +17 -0
  20. package/dist/input.json +15 -0
  21. package/dist/label.json +15 -0
  22. package/dist/notification.json +20 -0
  23. package/dist/popover.json +17 -0
  24. package/dist/registry.json +429 -0
  25. package/dist/separator.json +17 -0
  26. package/dist/sheet.json +18 -0
  27. package/dist/sidebar.json +18 -0
  28. package/dist/skeleton.json +15 -0
  29. package/dist/sonner.json +17 -0
  30. package/dist/tooltip.json +17 -0
  31. package/package.json +24 -88
  32. package/scripts/build-registry.js +74 -0
  33. package/src/components/aomi-frame.tsx +128 -0
  34. package/src/components/assistant-ui/attachment.tsx +235 -0
  35. package/src/components/assistant-ui/markdown-text.tsx +228 -0
  36. package/src/components/assistant-ui/thread-list.tsx +106 -0
  37. package/src/components/assistant-ui/thread.tsx +457 -0
  38. package/src/components/assistant-ui/threadlist-sidebar.tsx +71 -0
  39. package/src/components/assistant-ui/tool-fallback.tsx +48 -0
  40. package/src/components/assistant-ui/tooltip-icon-button.tsx +42 -0
  41. package/src/components/test/ThreadContextTest.tsx +204 -0
  42. package/src/components/tools/example-tool/ExampleTool.tsx +102 -0
  43. package/src/components/ui/accordion.tsx +58 -0
  44. package/src/components/ui/alert.tsx +62 -0
  45. package/src/components/ui/avatar.tsx +53 -0
  46. package/src/components/ui/badge.tsx +37 -0
  47. package/src/components/ui/breadcrumb.tsx +109 -0
  48. package/src/components/ui/button.tsx +59 -0
  49. package/src/components/ui/card.tsx +86 -0
  50. package/src/components/ui/collapsible.tsx +12 -0
  51. package/src/components/ui/command.tsx +156 -0
  52. package/src/components/ui/dialog.tsx +143 -0
  53. package/src/components/ui/drawer.tsx +118 -0
  54. package/src/components/ui/input.tsx +21 -0
  55. package/src/components/ui/label.tsx +20 -0
  56. package/src/components/ui/notification.tsx +57 -0
  57. package/src/components/ui/popover.tsx +33 -0
  58. package/src/components/ui/separator.tsx +28 -0
  59. package/src/components/ui/sheet.tsx +139 -0
  60. package/src/components/ui/sidebar.tsx +820 -0
  61. package/src/components/ui/skeleton.tsx +15 -0
  62. package/src/components/ui/sonner.tsx +29 -0
  63. package/src/components/ui/tooltip.tsx +61 -0
  64. package/src/hooks/use-mobile.ts +21 -0
  65. package/src/index.ts +26 -0
  66. package/src/registry.ts +218 -0
  67. package/{dist/styles.css → src/themes/default.css} +21 -3
  68. package/src/themes/tokens.config.ts +39 -0
  69. package/tsconfig.json +19 -0
  70. package/README.md +0 -41
  71. package/dist/index.cjs +0 -3780
  72. package/dist/index.cjs.map +0 -1
  73. package/dist/index.d.cts +0 -302
  74. package/dist/index.d.ts +0 -302
  75. package/dist/index.js +0 -3696
  76. package/dist/index.js.map +0 -1
@@ -0,0 +1,457 @@
1
+ "use client";
2
+
3
+ import {
4
+ ArrowDownIcon,
5
+ ArrowUpIcon,
6
+ CheckIcon,
7
+ ChevronLeftIcon,
8
+ ChevronRightIcon,
9
+ CopyIcon,
10
+ PencilIcon,
11
+ RefreshCwIcon,
12
+ Square,
13
+ } from "lucide-react";
14
+
15
+ import {
16
+ ActionBarPrimitive,
17
+ BranchPickerPrimitive,
18
+ ComposerPrimitive,
19
+ ErrorPrimitive,
20
+ MessagePrimitive,
21
+ ThreadPrimitive,
22
+ } from "@assistant-ui/react";
23
+
24
+ import type { FC } from "react";
25
+ import { useEffect } from "react";
26
+ import { LazyMotion, MotionConfig, domAnimation } from "motion/react";
27
+ import * as m from "motion/react-m";
28
+
29
+ import { Button } from "@/components/ui/button";
30
+ import { MarkdownText } from "@/components/assistant-ui/markdown-text";
31
+ import { ToolFallback } from "@/components/assistant-ui/tool-fallback";
32
+ import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
33
+ import {
34
+ ComposerAddAttachment,
35
+ ComposerAttachments,
36
+ UserMessageAttachments,
37
+ } from "@/components/assistant-ui/attachment";
38
+
39
+ import { cn, useNotification, useThreadContext } from "@aomi-labs/react";
40
+ import { useAssistantApi, useMessage } from "@assistant-ui/react";
41
+
42
+ const seenSystemMessages = new Set<string>();
43
+
44
+ export const Thread: FC = () => {
45
+ const api = useAssistantApi();
46
+ const { threadViewKey } = useThreadContext();
47
+
48
+ useEffect(() => {
49
+ try {
50
+ const composer = api.composer();
51
+ composer.setText("");
52
+ void composer.clearAttachments?.();
53
+ } catch (error) {
54
+ console.error("Failed to reset composer input:", error);
55
+ }
56
+ }, [api, threadViewKey]);
57
+
58
+ return (
59
+ <LazyMotion features={domAnimation}>
60
+ <MotionConfig reducedMotion="user">
61
+ <ThreadPrimitive.Root
62
+ className="aui-root aui-thread-root @container bg-background flex h-full flex-col"
63
+ style={{
64
+ ["--thread-max-width" as string]: "44rem",
65
+ }}
66
+ >
67
+ <ThreadPrimitive.Viewport className="aui-thread-viewport relative flex flex-1 flex-col overflow-x-auto overflow-y-scroll px-4">
68
+ <ThreadPrimitive.If empty>
69
+ <ThreadWelcome />
70
+ </ThreadPrimitive.If>
71
+
72
+ <ThreadPrimitive.Messages
73
+ components={{
74
+ UserMessage,
75
+ EditComposer,
76
+ AssistantMessage,
77
+ SystemMessage,
78
+ }}
79
+ />
80
+
81
+ <ThreadPrimitive.If empty={false}>
82
+ <div className="aui-thread-viewport-spacer min-h-8 grow" />
83
+ </ThreadPrimitive.If>
84
+
85
+ <Composer />
86
+ </ThreadPrimitive.Viewport>
87
+ </ThreadPrimitive.Root>
88
+ </MotionConfig>
89
+ </LazyMotion>
90
+ );
91
+ };
92
+
93
+ const ThreadScrollToBottom: FC = () => {
94
+ return (
95
+ <ThreadPrimitive.ScrollToBottom asChild>
96
+ <TooltipIconButton
97
+ tooltip="Scroll to bottom"
98
+ variant="outline"
99
+ className="aui-thread-scroll-to-bottom dark:bg-background dark:hover:bg-accent absolute -top-12 z-10 self-center rounded-full p-4 disabled:invisible"
100
+ >
101
+ <ArrowDownIcon />
102
+ </TooltipIconButton>
103
+ </ThreadPrimitive.ScrollToBottom>
104
+ );
105
+ };
106
+
107
+ const ThreadWelcome: FC = () => {
108
+ return (
109
+ <div className="aui-thread-welcome-root mx-auto my-auto flex w-full max-w-[var(--thread-max-width)] flex-grow flex-col">
110
+ <div className="aui-thread-welcome-center flex w-full flex-grow flex-col items-center justify-center">
111
+ <div className="aui-thread-welcome-message flex size-full flex-col justify-center px-8">
112
+ <m.div
113
+ initial={{ opacity: 0, y: 10 }}
114
+ animate={{ opacity: 1, y: 0 }}
115
+ exit={{ opacity: 0, y: 10 }}
116
+ className="aui-thread-welcome-message-motion-1 text-2xl font-semibold"
117
+ >
118
+ Hello there!
119
+ </m.div>
120
+ <m.div
121
+ initial={{ opacity: 0, y: 10 }}
122
+ animate={{ opacity: 1, y: 0 }}
123
+ exit={{ opacity: 0, y: 10 }}
124
+ transition={{ delay: 0.1 }}
125
+ className="aui-thread-welcome-message-motion-2 text-muted-foreground/65 text-2xl"
126
+ >
127
+ How can I help you today?
128
+ </m.div>
129
+ </div>
130
+ </div>
131
+ <ThreadSuggestions />
132
+ </div>
133
+ );
134
+ };
135
+
136
+ const ThreadSuggestions: FC = () => {
137
+ return (
138
+ <div className="aui-thread-welcome-suggestions @md:grid-cols-2 grid w-full gap-2 pb-4">
139
+ {[
140
+ {
141
+ title: "Show my wallet balances",
142
+ label: "and positions",
143
+ action: "Show my wallet balances and positions",
144
+ },
145
+ {
146
+ title: "Swap 1 ETH to USDC",
147
+ label: "with the best price",
148
+ action: "Swap 1 ETH to USDC with the best price",
149
+ },
150
+ {
151
+ title: "Stake half of my ETH",
152
+ label: "in the highest yield pool",
153
+ action: "Stake half of my ETH in the highest yield pool",
154
+ },
155
+ {
156
+ title: "Bridge 100 USDC",
157
+ label: "from Ethereum to Arbitrum",
158
+ action: "Bridge 100 USDC from Ethereum to Arbitrum",
159
+ },
160
+ ].map((suggestedAction, index) => (
161
+ <m.div
162
+ initial={{ opacity: 0, y: 20 }}
163
+ animate={{ opacity: 1, y: 0 }}
164
+ exit={{ opacity: 0, y: 20 }}
165
+ transition={{ delay: 0.05 * index }}
166
+ key={`suggested-action-${suggestedAction.title}-${index}`}
167
+ className="aui-thread-welcome-suggestion-display @md:[&:nth-child(n+3)]:block [&:nth-child(n+3)]:hidden"
168
+ >
169
+ <ThreadPrimitive.Suggestion
170
+ prompt={suggestedAction.action}
171
+ send
172
+ asChild
173
+ >
174
+ <Button
175
+ variant="ghost"
176
+ className="aui-thread-welcome-suggestion @md:flex-col dark:hover:bg-accent/60 h-auto w-full flex-1 flex-wrap items-start justify-start gap-1 rounded-3xl border px-5 py-4 text-left text-sm"
177
+ aria-label={suggestedAction.action}
178
+ >
179
+ <span className="aui-thread-welcome-suggestion-text-1 font-medium">
180
+ {suggestedAction.title}
181
+ </span>
182
+ <span className="aui-thread-welcome-suggestion-text-2 text-muted-foreground">
183
+ {suggestedAction.label}
184
+ </span>
185
+ </Button>
186
+ </ThreadPrimitive.Suggestion>
187
+ </m.div>
188
+ ))}
189
+ </div>
190
+ );
191
+ };
192
+
193
+ const Composer: FC = () => {
194
+ return (
195
+ <div className="aui-composer-wrapper bg-background sticky bottom-0 mx-auto flex w-full max-w-[var(--thread-max-width)] flex-col gap-4 overflow-visible rounded-t-3xl pb-4 md:pb-6">
196
+ <ThreadScrollToBottom />
197
+ <ComposerPrimitive.Root className="aui-composer-root rounded-4xl dark:border-muted-foreground/15 relative flex w-full flex-col border bg-white px-1 pt-2 shadow-[0_9px_9px_0px_rgba(0,0,0,0.01),0_2px_5px_0px_rgba(0,0,0,0.06)]">
198
+ <ComposerAttachments />
199
+ <ComposerPrimitive.Input
200
+ placeholder="Send a message..."
201
+ className="aui-composer-input placeholder:text-muted-foreground focus:outline-primary ml-3 mt-2 max-h-32 min-h-16 w-full resize-none bg-transparent px-3.5 pb-3 pt-1.5 text-sm text-stone-800 outline-none"
202
+ rows={1}
203
+ autoFocus
204
+ aria-label="Message input"
205
+ />
206
+ <ComposerAction />
207
+ </ComposerPrimitive.Root>
208
+ </div>
209
+ );
210
+ };
211
+
212
+ const ComposerAction: FC = () => {
213
+ return (
214
+ <div className="aui-composer-action-wrapper relative mx-1 mb-2 mt-2 flex items-center justify-between">
215
+ <ComposerAddAttachment />
216
+
217
+ <ThreadPrimitive.If running={false}>
218
+ <ComposerPrimitive.Send asChild>
219
+ <TooltipIconButton
220
+ tooltip="Send message"
221
+ side="bottom"
222
+ type="submit"
223
+ variant="default"
224
+ size="icon"
225
+ className="aui-composer-send mb-3 mr-3 size-[34px] rounded-full p-1"
226
+ aria-label="Send message"
227
+ >
228
+ <ArrowUpIcon className="aui-composer-send-icon size-5" />
229
+ </TooltipIconButton>
230
+ </ComposerPrimitive.Send>
231
+ </ThreadPrimitive.If>
232
+
233
+ <ThreadPrimitive.If running>
234
+ <ComposerPrimitive.Cancel asChild>
235
+ <Button
236
+ type="button"
237
+ variant="default"
238
+ size="icon"
239
+ className="aui-composer-cancel border-muted-foreground/60 hover:bg-primary/75 dark:border-muted-foreground/90 size-[34px] rounded-full border"
240
+ aria-label="Stop generating"
241
+ >
242
+ <Square className="aui-composer-cancel-icon size-3.5 fill-white dark:fill-black" />
243
+ </Button>
244
+ </ComposerPrimitive.Cancel>
245
+ </ThreadPrimitive.If>
246
+ </div>
247
+ );
248
+ };
249
+
250
+ const MessageError: FC = () => {
251
+ return (
252
+ <MessagePrimitive.Error>
253
+ <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">
254
+ <ErrorPrimitive.Message className="aui-message-error-message line-clamp-2" />
255
+ </ErrorPrimitive.Root>
256
+ </MessagePrimitive.Error>
257
+ );
258
+ };
259
+
260
+ const AssistantMessage: FC = () => {
261
+ return (
262
+ <MessagePrimitive.Root asChild>
263
+ <div
264
+ className="aui-assistant-message-root animate-in fade-in slide-in-from-bottom-1 relative mx-auto w-full max-w-[var(--thread-max-width)] py-4 duration-150 ease-out last:mb-24"
265
+ data-role="assistant"
266
+ >
267
+ <div className="aui-assistant-message-content text-foreground mx-2 break-words text-sm leading-5">
268
+ <MessagePrimitive.Parts
269
+ components={{
270
+ Text: MarkdownText,
271
+ tools: { Fallback: ToolFallback },
272
+ }}
273
+ />
274
+ <MessageError />
275
+ </div>
276
+
277
+ <div className="aui-assistant-message-footer ml-2 mt-2 flex">
278
+ <BranchPicker />
279
+ <AssistantActionBar />
280
+ </div>
281
+ </div>
282
+ </MessagePrimitive.Root>
283
+ );
284
+ };
285
+
286
+ const AssistantActionBar: FC = () => {
287
+ return (
288
+ <ActionBarPrimitive.Root
289
+ hideWhenRunning
290
+ autohide="not-last"
291
+ autohideFloat="single-branch"
292
+ className="aui-assistant-action-bar-root text-muted-foreground data-floating:absolute data-floating:rounded-md data-floating:border data-floating:bg-background data-floating:p-1 data-floating:shadow-sm col-start-3 row-start-2 -ml-1 flex gap-1"
293
+ >
294
+ <ActionBarPrimitive.Copy asChild>
295
+ <TooltipIconButton tooltip="Copy">
296
+ <MessagePrimitive.If copied>
297
+ <CheckIcon />
298
+ </MessagePrimitive.If>
299
+ <MessagePrimitive.If copied={false}>
300
+ <CopyIcon />
301
+ </MessagePrimitive.If>
302
+ </TooltipIconButton>
303
+ </ActionBarPrimitive.Copy>
304
+ <ActionBarPrimitive.Reload asChild>
305
+ <TooltipIconButton tooltip="Refresh">
306
+ <RefreshCwIcon />
307
+ </TooltipIconButton>
308
+ </ActionBarPrimitive.Reload>
309
+ </ActionBarPrimitive.Root>
310
+ );
311
+ };
312
+
313
+ const UserMessage: FC = () => {
314
+ return (
315
+ <MessagePrimitive.Root asChild>
316
+ <div
317
+ className="aui-user-message-root animate-in fade-in slide-in-from-bottom-1 mx-auto grid w-full max-w-[var(--thread-max-width)] auto-rows-auto grid-cols-[minmax(72px,1fr)_auto] gap-y-2 px-2 py-4 duration-150 ease-out first:mt-3 last:mb-5 [&:where(>*)]:col-start-2"
318
+ data-role="user"
319
+ >
320
+ <UserMessageAttachments />
321
+
322
+ <div className="aui-user-message-content-wrapper relative col-start-2 min-w-0">
323
+ <div className="aui-user-message-content bg-muted text-foreground break-words rounded-3xl px-5 py-2.5 text-sm">
324
+ <MessagePrimitive.Parts />
325
+ </div>
326
+ <div className="aui-user-action-bar-wrapper absolute left-0 top-1/2 -translate-x-full -translate-y-1/2 pr-2">
327
+ <UserActionBar />
328
+ </div>
329
+ </div>
330
+
331
+ <BranchPicker className="aui-user-branch-picker col-span-full col-start-1 row-start-3 -mr-1 justify-end" />
332
+ </div>
333
+ </MessagePrimitive.Root>
334
+ );
335
+ };
336
+
337
+ const UserActionBar: FC = () => {
338
+ return (
339
+ <ActionBarPrimitive.Root
340
+ hideWhenRunning
341
+ autohide="not-last"
342
+ className="aui-user-action-bar-root flex flex-col items-end"
343
+ >
344
+ <ActionBarPrimitive.Edit asChild>
345
+ <TooltipIconButton tooltip="Edit" className="aui-user-action-edit p-4">
346
+ <PencilIcon />
347
+ </TooltipIconButton>
348
+ </ActionBarPrimitive.Edit>
349
+ </ActionBarPrimitive.Root>
350
+ );
351
+ };
352
+
353
+ const EditComposer: FC = () => {
354
+ return (
355
+ <div className="aui-edit-composer-wrapper mx-auto flex w-full max-w-[var(--thread-max-width)] flex-col gap-4 px-2 first:mt-4">
356
+ <ComposerPrimitive.Root className="aui-edit-composer-root max-w-7/8 bg-muted ml-auto flex w-full flex-col rounded-xl">
357
+ <ComposerPrimitive.Input
358
+ className="aui-edit-composer-input text-foreground flex min-h-[60px] w-full resize-none bg-transparent p-4 outline-none"
359
+ autoFocus
360
+ />
361
+
362
+ <div className="aui-edit-composer-footer mx-3 mb-3 flex items-center justify-center gap-2 self-end">
363
+ <ComposerPrimitive.Cancel asChild>
364
+ <Button variant="ghost" size="sm" aria-label="Cancel edit">
365
+ Cancel
366
+ </Button>
367
+ </ComposerPrimitive.Cancel>
368
+ <ComposerPrimitive.Send asChild>
369
+ <Button size="sm" aria-label="Update message">
370
+ Update
371
+ </Button>
372
+ </ComposerPrimitive.Send>
373
+ </div>
374
+ </ComposerPrimitive.Root>
375
+ </div>
376
+ );
377
+ };
378
+
379
+ const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
380
+ className,
381
+ ...rest
382
+ }) => {
383
+ return (
384
+ <BranchPickerPrimitive.Root
385
+ hideWhenSingleBranch
386
+ className={cn(
387
+ "aui-branch-picker-root text-muted-foreground -ml-2 mr-2 inline-flex items-center text-xs",
388
+ className,
389
+ )}
390
+ {...rest}
391
+ >
392
+ <BranchPickerPrimitive.Previous asChild>
393
+ <TooltipIconButton tooltip="Previous">
394
+ <ChevronLeftIcon />
395
+ </TooltipIconButton>
396
+ </BranchPickerPrimitive.Previous>
397
+ <span className="aui-branch-picker-state font-medium">
398
+ <BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count />
399
+ </span>
400
+ <BranchPickerPrimitive.Next asChild>
401
+ <TooltipIconButton tooltip="Next">
402
+ <ChevronRightIcon />
403
+ </TooltipIconButton>
404
+ </BranchPickerPrimitive.Next>
405
+ </BranchPickerPrimitive.Root>
406
+ );
407
+ };
408
+
409
+ const SystemMessage: FC = () => {
410
+ const { showNotification } = useNotification();
411
+ const messageId = useMessage((state) => state.id);
412
+ const content = useMessage((state) => state.content) as Array<{
413
+ type: string;
414
+ text?: string;
415
+ }>;
416
+ const custom = useMessage((state) => state.metadata?.custom) as
417
+ | { kind?: string; title?: string }
418
+ | undefined;
419
+ useEffect(() => {
420
+ const text = content
421
+ .filter((part) => part.type === "text")
422
+ .map((part) => part.text ?? "")
423
+ .join("")
424
+ .trim();
425
+
426
+ if (!text) return;
427
+
428
+ const key = messageId ?? text;
429
+ if (seenSystemMessages.has(key)) return;
430
+ seenSystemMessages.add(key);
431
+
432
+ const inferredKind =
433
+ custom?.kind ??
434
+ (text.startsWith("Wallet transaction request:")
435
+ ? "wallet_tx_request"
436
+ : "system_notice");
437
+
438
+ const type =
439
+ inferredKind === "system_error"
440
+ ? "error"
441
+ : inferredKind === "system_success"
442
+ ? "success"
443
+ : "notice";
444
+
445
+ const title =
446
+ custom?.title ??
447
+ (inferredKind === "wallet_tx_request"
448
+ ? "Wallet transaction request"
449
+ : inferredKind === "system_error"
450
+ ? "Error"
451
+ : "System notice");
452
+
453
+ showNotification({ type, title, message: text });
454
+ }, [content, custom, showNotification, messageId]);
455
+
456
+ return null;
457
+ };
@@ -0,0 +1,71 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import Link from "next/link";
5
+ import Image from "next/image";
6
+ import {
7
+ Sidebar,
8
+ SidebarContent,
9
+ SidebarFooter,
10
+ SidebarHeader,
11
+ SidebarMenu,
12
+ SidebarMenuButton,
13
+ SidebarMenuItem,
14
+ SidebarRail,
15
+ } from "@/components/ui/sidebar";
16
+ import { ThreadList } from "@/components/assistant-ui/thread-list";
17
+
18
+ type ThreadListSidebarProps = React.ComponentProps<typeof Sidebar> & {
19
+ /** Optional footer component (e.g., WalletFooter from consumer app) */
20
+ footer?: React.ReactNode;
21
+ };
22
+
23
+ export function ThreadListSidebar({
24
+ footer,
25
+ ...props
26
+ }: ThreadListSidebarProps) {
27
+ return (
28
+ <Sidebar
29
+ collapsible="offcanvas"
30
+ variant="inset"
31
+ className="relative"
32
+ {...props}
33
+ >
34
+ <SidebarHeader className="aomi-sidebar-header">
35
+ <div className="aomi-sidebar-header-content flex items-center justify-between">
36
+ <SidebarMenu>
37
+ <SidebarMenuItem>
38
+ <SidebarMenuButton size="lg" asChild>
39
+ <Link
40
+ href="https://aomi.dev"
41
+ target="_blank"
42
+ rel="noopener noreferrer"
43
+ >
44
+ <div className="aomi-sidebar-header-icon-wrapper flex aspect-square size-8 items-center justify-center rounded-lg bg-white">
45
+ <Image
46
+ src="/assets/images/bubble.svg"
47
+ alt="Logo"
48
+ width={28}
49
+ height={28}
50
+ className="aomi-sidebar-header-icon ml-3 size-7"
51
+ priority
52
+ />
53
+ </div>
54
+ </Link>
55
+ </SidebarMenuButton>
56
+ </SidebarMenuItem>
57
+ </SidebarMenu>
58
+ </div>
59
+ </SidebarHeader>
60
+ <SidebarContent className="aomi-sidebar-content">
61
+ <ThreadList />
62
+ </SidebarContent>
63
+ <SidebarRail />
64
+ {footer && (
65
+ <SidebarFooter className="aomi-sidebar-footer border-sm border-t py-4">
66
+ {footer}
67
+ </SidebarFooter>
68
+ )}
69
+ </Sidebar>
70
+ );
71
+ }
@@ -0,0 +1,48 @@
1
+ "use client";
2
+
3
+ import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
4
+ import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
5
+ import { useState } from "react";
6
+ import { Button } from "@/components/ui/button";
7
+
8
+ export const ToolFallback: ToolCallMessagePartComponent = ({
9
+ toolName,
10
+ argsText,
11
+ result,
12
+ }) => {
13
+ const [isCollapsed, setIsCollapsed] = useState(true);
14
+ return (
15
+ <div className="aui-tool-fallback-root mb-4 flex w-full flex-col gap-3 rounded-lg border py-3">
16
+ <div className="aui-tool-fallback-header flex items-center gap-2 px-4">
17
+ <CheckIcon className="aui-tool-fallback-icon size-4" />
18
+ <p className="aui-tool-fallback-title flex-grow">
19
+ Used tool: <b>{toolName}</b>
20
+ </p>
21
+ <Button onClick={() => setIsCollapsed(!isCollapsed)}>
22
+ {isCollapsed ? <ChevronUpIcon /> : <ChevronDownIcon />}
23
+ </Button>
24
+ </div>
25
+ {!isCollapsed && (
26
+ <div className="aui-tool-fallback-content bg-muted flex flex-col gap-2 border-t pt-2">
27
+ <div className="aui-tool-fallback-args-root px-4">
28
+ <pre className="aui-tool-fallback-args-value whitespace-pre-wrap">
29
+ {argsText}
30
+ </pre>
31
+ </div>
32
+ {result !== undefined && (
33
+ <div className="aui-tool-fallback-result-root border-t border-dashed px-4 pt-2">
34
+ <p className="aui-tool-fallback-result-header font-semibold">
35
+ Result:
36
+ </p>
37
+ <pre className="aui-tool-fallback-result-content whitespace-pre-wrap text-[012px]">
38
+ {typeof result === "string"
39
+ ? result
40
+ : JSON.stringify(result, null, 2)}
41
+ </pre>
42
+ </div>
43
+ )}
44
+ </div>
45
+ )}
46
+ </div>
47
+ );
48
+ };
@@ -0,0 +1,42 @@
1
+ "use client";
2
+
3
+ import { ComponentPropsWithRef, forwardRef } from "react";
4
+ import { Slottable } from "@radix-ui/react-slot";
5
+
6
+ import {
7
+ Tooltip,
8
+ TooltipContent,
9
+ TooltipTrigger,
10
+ } from "@/components/ui/tooltip";
11
+ import { Button } from "@/components/ui/button";
12
+ import { cn } from "@aomi-labs/react";
13
+
14
+ export type TooltipIconButtonProps = ComponentPropsWithRef<typeof Button> & {
15
+ tooltip: string;
16
+ side?: "top" | "bottom" | "left" | "right";
17
+ };
18
+
19
+ export const TooltipIconButton = forwardRef<
20
+ HTMLButtonElement,
21
+ TooltipIconButtonProps
22
+ >(({ children, tooltip, side = "bottom", className, ...rest }, ref) => {
23
+ return (
24
+ <Tooltip>
25
+ <TooltipTrigger asChild>
26
+ <Button
27
+ variant="ghost"
28
+ size="icon"
29
+ {...rest}
30
+ className={cn("aui-button-icon size-6 p-1", className)}
31
+ ref={ref}
32
+ >
33
+ <Slottable>{children}</Slottable>
34
+ <span className="aui-sr-only sr-only">{tooltip}</span>
35
+ </Button>
36
+ </TooltipTrigger>
37
+ <TooltipContent side={side}>{tooltip}</TooltipContent>
38
+ </Tooltip>
39
+ );
40
+ });
41
+
42
+ TooltipIconButton.displayName = "TooltipIconButton";