@aomi-labs/widget-lib 1.0.0 → 1.1.1

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