@aomi-labs/widget-lib 1.1.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.
@@ -7,7 +7,7 @@
7
7
  {
8
8
  "type": "registry:component",
9
9
  "path": "components/aomi-frame.tsx",
10
- "content": "\"use client\";\n\nimport { type CSSProperties, type ReactNode } from \"react\";\nimport {\n AomiRuntimeProvider,\n cn,\n useAomiRuntime,\n type UserConfig,\n} from \"@aomi-labs/react\";\nimport { Thread } from \"@/components/assistant-ui/thread\";\nimport { ThreadListSidebar } from \"@/components/assistant-ui/threadlist-sidebar\";\nimport {\n SidebarInset,\n SidebarProvider,\n SidebarTrigger,\n} from \"@/components/ui/sidebar\";\nimport { Separator } from \"@/components/ui/separator\";\nimport {\n Breadcrumb,\n BreadcrumbItem,\n BreadcrumbList,\n BreadcrumbSeparator,\n} from \"@/components/ui/breadcrumb\";\n\n// =============================================================================\n// Types\n// =============================================================================\n\ntype AomiFrameProps = {\n width?: CSSProperties[\"width\"];\n height?: CSSProperties[\"height\"];\n className?: string;\n style?: CSSProperties;\n /** Render prop for wallet footer - receives user state and setter from UserContext */\n walletFooter?: (props: UserConfig) => ReactNode;\n /** Additional content to render inside the frame */\n children?: ReactNode;\n};\n\n// =============================================================================\n// AomiFrame Component\n// =============================================================================\n\nexport const AomiFrame = ({\n width = \"100%\",\n height = \"80vh\",\n className,\n style,\n walletFooter,\n children,\n}: AomiFrameProps) => {\n const backendUrl =\n process.env.NEXT_PUBLIC_BACKEND_URL ?? \"http://localhost:8080\";\n\n return (\n <AomiRuntimeProvider backendUrl={backendUrl}>\n <AomiFrameShell\n width={width}\n height={height}\n className={className}\n style={style}\n walletFooter={walletFooter}\n >\n {children}\n </AomiFrameShell>\n </AomiRuntimeProvider>\n );\n};\n\n// =============================================================================\n// Internal Shell Component (uses hooks from providers)\n// =============================================================================\n\ntype AomiFrameShellProps = {\n width: CSSProperties[\"width\"];\n height: CSSProperties[\"height\"];\n className?: string;\n style?: CSSProperties;\n walletFooter?: (props: UserConfig) => ReactNode;\n children?: ReactNode;\n};\n\nconst AomiFrameShell = ({\n width,\n height,\n className,\n style,\n walletFooter,\n children,\n}: AomiFrameShellProps) => {\n const { user, setUser, currentThreadId, threadViewKey, getThreadMetadata } =\n useAomiRuntime();\n const currentTitle = getThreadMetadata(currentThreadId)?.title ?? \"New Chat\";\n\n const frameStyle: CSSProperties = { width, height, ...style };\n\n return (\n <SidebarProvider>\n {children}\n <div\n className={cn(\n \"flex h-full w-full overflow-hidden rounded-2xl bg-white shadow-2xl dark:bg-neutral-950\",\n className,\n )}\n style={frameStyle}\n >\n <ThreadListSidebar footer={walletFooter?.({ user, setUser })} />\n <SidebarInset className=\"relative\">\n <header className=\"mt-1 flex h-14 shrink-0 items-center gap-2 border-b px-3\">\n <SidebarTrigger />\n <Separator orientation=\"vertical\" className=\"mr-2 h-4\" />\n <Breadcrumb>\n <BreadcrumbList>\n <BreadcrumbItem className=\"hidden md:block\">\n {currentTitle}\n </BreadcrumbItem>\n <BreadcrumbSeparator className=\"hidden md:block\" />\n </BreadcrumbList>\n </Breadcrumb>\n </header>\n <div className=\"flex-1 overflow-hidden\">\n <Thread key={`${currentThreadId}-${threadViewKey}`} />\n </div>\n </SidebarInset>\n </div>\n </SidebarProvider>\n );\n};\n"
10
+ "content": "\"use client\";\n\nimport {\n type CSSProperties,\n type ReactNode,\n type FC,\n createContext,\n useContext,\n} from \"react\";\nimport { AomiRuntimeProvider, cn, useAomiRuntime } from \"@aomi-labs/react\";\nimport { Thread } from \"@/components/assistant-ui/thread\";\nimport { ThreadListSidebar } from \"@/components/assistant-ui/threadlist-sidebar\";\nimport { NotificationToaster } from \"@/components/ui/notification\";\nimport {\n SidebarInset,\n SidebarProvider,\n SidebarTrigger,\n} from \"@/components/ui/sidebar\";\nimport { Separator } from \"@/components/ui/separator\";\nimport {\n Breadcrumb,\n BreadcrumbItem,\n BreadcrumbList,\n} from \"@/components/ui/breadcrumb\";\nimport { ControlBar, type ControlBarProps } from \"@/components/control-bar\";\n\n// =============================================================================\n// Composer Control Context - signals Thread to show inline controls\n// =============================================================================\n\nconst ComposerControlContext = createContext<boolean>(false);\n\nexport const useComposerControl = () => useContext(ComposerControlContext);\n\n// =============================================================================\n// Types\n// =============================================================================\n\ntype RootProps = {\n children?: ReactNode;\n width?: CSSProperties[\"width\"];\n height?: CSSProperties[\"height\"];\n className?: string;\n style?: CSSProperties;\n /** Position of the wallet button in the sidebar */\n walletPosition?: \"header\" | \"footer\" | null;\n /** Backend URL for the Aomi runtime */\n backendUrl?: string;\n};\n\ntype HeaderProps = {\n children?: ReactNode;\n /** Show the control bar in the header */\n withControl?: boolean;\n /** Props to pass to the ControlBar when withControl is true */\n controlBarProps?: Omit<ControlBarProps, \"children\">;\n className?: string;\n};\n\ntype ComposerProps = {\n children?: ReactNode;\n /** Show inline controls in the composer input area */\n withControl?: boolean;\n className?: string;\n};\n\ntype FrameControlBarProps = ControlBarProps;\n\n// =============================================================================\n// Compound Components\n// =============================================================================\n\n/**\n * Root component - provides all context and layout container\n */\nconst Root: FC<RootProps> = ({\n children,\n width = \"100%\",\n height = \"80vh\",\n className,\n style,\n walletPosition = \"footer\",\n backendUrl,\n}) => {\n const resolvedBackendUrl =\n backendUrl ??\n process.env.NEXT_PUBLIC_BACKEND_URL ??\n \"http://localhost:8080\";\n const frameStyle: CSSProperties = { width, height, ...style };\n\n return (\n <AomiRuntimeProvider backendUrl={resolvedBackendUrl}>\n <SidebarProvider>\n <div\n className={cn(\n \"rounded-4xl flex h-full w-full overflow-hidden bg-white shadow-2xl dark:bg-neutral-950\",\n className,\n )}\n style={frameStyle}\n >\n <ThreadListSidebar walletPosition={walletPosition} />\n <SidebarInset className=\"relative flex flex-col\">\n {children}\n </SidebarInset>\n <NotificationToaster />\n </div>\n </SidebarProvider>\n </AomiRuntimeProvider>\n );\n};\n\n/**\n * Header component - renders the header with optional control bar\n */\nconst Header: FC<HeaderProps> = ({\n children,\n withControl,\n controlBarProps,\n className,\n}) => {\n const { currentThreadId, getThreadMetadata } = useAomiRuntime();\n const currentTitle = getThreadMetadata(currentThreadId)?.title ?? \"New Chat\";\n\n return (\n <header\n className={cn(\n \"mt-1 flex h-14 shrink-0 items-center gap-2 px-3\",\n className,\n )}\n >\n <SidebarTrigger />\n <Separator orientation=\"vertical\" className=\"mr-2 h-4\" />\n <Breadcrumb>\n <BreadcrumbList>\n <BreadcrumbItem className=\"hidden md:block\">\n {currentTitle}\n </BreadcrumbItem>\n </BreadcrumbList>\n </Breadcrumb>\n <div className=\"ml-auto flex items-center gap-2\">\n {withControl && <ControlBar {...controlBarProps} />}\n {children}\n </div>\n </header>\n );\n};\n\n/**\n * Composer component - renders the thread with optional inline controls\n * When withControl={true}, controls appear inline in the composer input area\n */\nconst Composer: FC<ComposerProps> = ({\n children,\n withControl = false,\n className,\n}) => {\n const { currentThreadId, threadViewKey } = useAomiRuntime();\n\n return (\n <ComposerControlContext.Provider value={withControl}>\n <div className={cn(\"flex flex-1 flex-col overflow-hidden\", className)}>\n <Thread key={`${currentThreadId}-${threadViewKey}`} />\n {children}\n </div>\n </ComposerControlContext.Provider>\n );\n};\n\n/**\n * ControlBar component - wrapper for the control bar with frame styling\n */\nconst FrameControlBar: FC<FrameControlBarProps> = (props) => {\n return <ControlBar {...props} />;\n};\n\n// =============================================================================\n// Default Layout Component (Simple API)\n// =============================================================================\n\ntype DefaultLayoutProps = Omit<RootProps, \"children\">;\n\n/**\n * Default layout - controls are inline in the composer input area\n * Usage: <AomiFrame /> or <AomiFrame walletPosition=\"header\" />\n */\nconst DefaultLayout: FC<DefaultLayoutProps> = ({\n walletPosition = \"footer\",\n ...props\n}) => {\n // Hide wallet in ControlBar when it's shown in sidebar\n const hideWalletInControlBar = walletPosition !== null;\n\n return (\n <Root walletPosition={walletPosition} {...props}>\n <Header\n withControl\n controlBarProps={{ hideWallet: hideWalletInControlBar }}\n />\n <Composer />\n </Root>\n );\n};\n\n// =============================================================================\n// Export Compound Component\n// =============================================================================\n\nexport const AomiFrame = Object.assign(DefaultLayout, {\n Root,\n Header,\n Composer,\n ControlBar: FrameControlBar,\n});\n\n// Re-export types for consumers\nexport type {\n RootProps as AomiFrameRootProps,\n HeaderProps as AomiFrameHeaderProps,\n ComposerProps as AomiFrameComposerProps,\n FrameControlBarProps as AomiFrameControlBarProps,\n};\n"
11
11
  }
12
12
  ],
13
13
  "dependencies": [
@@ -7,7 +7,7 @@
7
7
  {
8
8
  "type": "registry:component",
9
9
  "path": "components/assistant-ui/thread-list.tsx",
10
- "content": "\"use client\";\n\nimport type { FC } from \"react\";\nimport {\n ThreadListItemPrimitive,\n ThreadListPrimitive,\n useAssistantState,\n} from \"@assistant-ui/react\";\nimport { PlusIcon, TrashIcon } from \"lucide-react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { TooltipIconButton } from \"@/components/assistant-ui/tooltip-icon-button\";\nimport { Skeleton } from \"@/components/ui/skeleton\";\n\nexport const ThreadList: FC = () => {\n return (\n <ThreadListPrimitive.Root className=\"aui-root aui-thread-list-root flex flex-col items-stretch gap-1.5\">\n <ThreadListNew />\n <ThreadListItems />\n </ThreadListPrimitive.Root>\n );\n};\n\nconst ThreadListNew: FC = () => {\n return (\n <ThreadListPrimitive.New asChild>\n <Button\n className=\"aui-thread-list-new hover:bg-muted data-active:bg-muted flex items-center justify-start gap-1 rounded-lg px-2.5 py-2 text-start\"\n variant=\"ghost\"\n >\n <PlusIcon />\n New Chat\n </Button>\n </ThreadListPrimitive.New>\n );\n};\n\nconst ThreadListItems: FC = () => {\n const isLoading = useAssistantState(({ threads }) => threads.isLoading);\n\n if (isLoading) {\n return <ThreadListSkeleton />;\n }\n\n return <ThreadListPrimitive.Items components={{ ThreadListItem }} />;\n};\n\nconst ThreadListSkeleton: FC = () => {\n return (\n <>\n {Array.from({ length: 5 }, (_, i) => (\n <div\n key={i}\n role=\"status\"\n aria-label=\"Loading threads\"\n aria-live=\"polite\"\n className=\"aui-thread-list-skeleton-wrapper flex items-center gap-2 rounded-md px-3 py-2\"\n >\n <Skeleton className=\"aui-thread-list-skeleton h-[22px] flex-grow\" />\n </div>\n ))}\n </>\n );\n};\n\nconst ThreadListItem: FC = () => {\n return (\n <ThreadListItemPrimitive.Root className=\"aui-thread-list-item hover:bg-muted focus-visible:bg-muted focus-visible:ring-ring data-active:bg-muted flex items-center gap-2 rounded-lg transition-all focus-visible:outline-none focus-visible:ring-2\">\n <ThreadListItemPrimitive.Trigger className=\"aui-thread-list-item-trigger flex-grow px-3 py-2 text-start\">\n <ThreadListItemTitle />\n </ThreadListItemPrimitive.Trigger>\n <ThreadListItemDelete />\n </ThreadListItemPrimitive.Root>\n );\n};\n\nconst ThreadListItemTitle: FC = () => {\n return (\n <span className=\"aui-thread-list-item-title text-sm\">\n <ThreadListItemPrimitive.Title fallback=\"New Chat\" />\n </span>\n );\n};\n\nconst ThreadListItemDelete: FC = () => {\n return (\n <ThreadListItemPrimitive.Delete asChild>\n <TooltipIconButton\n className=\"aui-thread-list-item-delete text-foreground hover:text-primary ml-auto mr-3 size-4 p-0\"\n variant=\"ghost\"\n tooltip=\"Delete thread\"\n onClick={(event) => {\n const confirmed = window.confirm(\n \"Delete this chat? This action cannot be undone.\",\n );\n if (!confirmed) {\n event.preventDefault();\n event.stopPropagation();\n }\n }}\n >\n <TrashIcon />\n </TooltipIconButton>\n </ThreadListItemPrimitive.Delete>\n );\n};\n"
10
+ "content": "\"use client\";\n\nimport type { FC } from \"react\";\nimport {\n ThreadListItemPrimitive,\n ThreadListPrimitive,\n useAssistantState,\n} from \"@assistant-ui/react\";\nimport { PlusIcon, TrashIcon } from \"lucide-react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { TooltipIconButton } from \"@/components/assistant-ui/tooltip-icon-button\";\nimport { Skeleton } from \"@/components/ui/skeleton\";\n\nexport const ThreadList: FC = () => {\n return (\n <ThreadListPrimitive.Root className=\"aui-root aui-thread-list-root flex list-none flex-col items-stretch gap-1 pl-2\">\n <ThreadListNew />\n <ThreadListItems />\n </ThreadListPrimitive.Root>\n );\n};\n\nconst ThreadListNew: FC = () => {\n return (\n <ThreadListPrimitive.New asChild>\n <Button\n className=\"aui-thread-list-new hover:bg-accent data-active:bg-accent flex items-center justify-start gap-2 rounded-full px-4 py-2 text-start\"\n variant=\"ghost\"\n >\n <PlusIcon className=\"size-4\" />\n New Chat\n </Button>\n </ThreadListPrimitive.New>\n );\n};\n\nconst ThreadListItems: FC = () => {\n const isLoading = useAssistantState(({ threads }) => threads.isLoading);\n\n if (isLoading) {\n return <ThreadListSkeleton />;\n }\n\n return <ThreadListPrimitive.Items components={{ ThreadListItem }} />;\n};\n\nconst ThreadListSkeleton: FC = () => {\n return (\n <>\n {Array.from({ length: 5 }, (_, i) => (\n <div\n key={i}\n role=\"status\"\n aria-label=\"Loading threads\"\n aria-live=\"polite\"\n className=\"aui-thread-list-skeleton-wrapper flex items-center gap-2 rounded-md px-3 py-2\"\n >\n <Skeleton className=\"aui-thread-list-skeleton h-[22px] flex-grow\" />\n </div>\n ))}\n </>\n );\n};\n\nconst ThreadListItem: FC = () => {\n return (\n <ThreadListItemPrimitive.Root className=\"aui-thread-list-item hover:bg-accent focus-visible:bg-accent data-active:bg-accent flex items-center gap-2 rounded-full pl-4 transition-all focus-visible:outline-none\">\n <ThreadListItemPrimitive.Trigger className=\"aui-thread-list-item-trigger flex-grow py-2 text-start\">\n <ThreadListItemTitle />\n </ThreadListItemPrimitive.Trigger>\n <ThreadListItemDelete />\n </ThreadListItemPrimitive.Root>\n );\n};\n\nconst ThreadListItemTitle: FC = () => {\n return (\n <span className=\"aui-thread-list-item-title text-sm\">\n <ThreadListItemPrimitive.Title fallback=\"New Chat\" />\n </span>\n );\n};\n\nconst ThreadListItemDelete: FC = () => {\n return (\n <ThreadListItemPrimitive.Delete asChild>\n <TooltipIconButton\n className=\"aui-thread-list-item-delete text-foreground hover:text-primary ml-auto mr-3 size-4 p-0\"\n variant=\"ghost\"\n tooltip=\"Delete thread\"\n onClick={(event) => {\n const confirmed = window.confirm(\n \"Delete this chat? This action cannot be undone.\",\n );\n if (!confirmed) {\n event.preventDefault();\n event.stopPropagation();\n }\n }}\n >\n <TrashIcon />\n </TooltipIconButton>\n </ThreadListItemPrimitive.Delete>\n );\n};\n"
11
11
  }
12
12
  ],
13
13
  "dependencies": [
@@ -7,7 +7,7 @@
7
7
  {
8
8
  "type": "registry:component",
9
9
  "path": "components/assistant-ui/thread.tsx",
10
- "content": "\"use client\";\n\nimport {\n ArrowDownIcon,\n ArrowUpIcon,\n CheckIcon,\n ChevronLeftIcon,\n ChevronRightIcon,\n CopyIcon,\n PencilIcon,\n RefreshCwIcon,\n Square,\n} from \"lucide-react\";\n\nimport {\n ActionBarPrimitive,\n BranchPickerPrimitive,\n ComposerPrimitive,\n ErrorPrimitive,\n MessagePrimitive,\n ThreadPrimitive,\n} from \"@assistant-ui/react\";\n\nimport type { FC } from \"react\";\nimport { useEffect } from \"react\";\nimport { LazyMotion, MotionConfig, domAnimation } from \"motion/react\";\nimport * as m from \"motion/react-m\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { MarkdownText } from \"@/components/assistant-ui/markdown-text\";\nimport { ToolFallback } from \"@/components/assistant-ui/tool-fallback\";\nimport { TooltipIconButton } from \"@/components/assistant-ui/tooltip-icon-button\";\nimport {\n ComposerAddAttachment,\n ComposerAttachments,\n UserMessageAttachments,\n} from \"@/components/assistant-ui/attachment\";\n\nimport { cn, useNotification, useThreadContext } from \"@aomi-labs/react\";\nimport { useAssistantApi, useMessage } from \"@assistant-ui/react\";\n\nconst seenSystemMessages = new Set<string>();\n\nexport const Thread: FC = () => {\n const api = useAssistantApi();\n const { threadViewKey } = useThreadContext();\n\n useEffect(() => {\n try {\n const composer = api.composer();\n composer.setText(\"\");\n void composer.clearAttachments?.();\n } catch (error) {\n console.error(\"Failed to reset composer input:\", error);\n }\n }, [api, threadViewKey]);\n\n return (\n <LazyMotion features={domAnimation}>\n <MotionConfig reducedMotion=\"user\">\n <ThreadPrimitive.Root\n className=\"aui-root aui-thread-root @container bg-background flex h-full flex-col\"\n style={{\n [\"--thread-max-width\" as string]: \"44rem\",\n }}\n >\n <ThreadPrimitive.Viewport className=\"aui-thread-viewport relative flex flex-1 flex-col overflow-x-auto overflow-y-scroll px-4\">\n <ThreadPrimitive.If empty>\n <ThreadWelcome />\n </ThreadPrimitive.If>\n\n <ThreadPrimitive.Messages\n components={{\n UserMessage,\n EditComposer,\n AssistantMessage,\n SystemMessage,\n }}\n />\n\n <ThreadPrimitive.If empty={false}>\n <div className=\"aui-thread-viewport-spacer min-h-8 grow\" />\n </ThreadPrimitive.If>\n\n <Composer />\n </ThreadPrimitive.Viewport>\n </ThreadPrimitive.Root>\n </MotionConfig>\n </LazyMotion>\n );\n};\n\nconst ThreadScrollToBottom: FC = () => {\n return (\n <ThreadPrimitive.ScrollToBottom asChild>\n <TooltipIconButton\n tooltip=\"Scroll to bottom\"\n variant=\"outline\"\n 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\"\n >\n <ArrowDownIcon />\n </TooltipIconButton>\n </ThreadPrimitive.ScrollToBottom>\n );\n};\n\nconst ThreadWelcome: FC = () => {\n return (\n <div className=\"aui-thread-welcome-root mx-auto my-auto flex w-full max-w-[var(--thread-max-width)] flex-grow flex-col\">\n <div className=\"aui-thread-welcome-center flex w-full flex-grow flex-col items-center justify-center\">\n <div className=\"aui-thread-welcome-message flex size-full flex-col justify-center px-8\">\n <m.div\n initial={{ opacity: 0, y: 10 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: 10 }}\n className=\"aui-thread-welcome-message-motion-1 text-2xl font-semibold\"\n >\n Hello there!\n </m.div>\n <m.div\n initial={{ opacity: 0, y: 10 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: 10 }}\n transition={{ delay: 0.1 }}\n className=\"aui-thread-welcome-message-motion-2 text-muted-foreground/65 text-2xl\"\n >\n How can I help you today?\n </m.div>\n </div>\n </div>\n <ThreadSuggestions />\n </div>\n );\n};\n\nconst ThreadSuggestions: FC = () => {\n return (\n <div className=\"aui-thread-welcome-suggestions @md:grid-cols-2 grid w-full gap-2 pb-4\">\n {[\n {\n title: \"Show my wallet balances\",\n label: \"and positions\",\n action: \"Show my wallet balances and positions\",\n },\n {\n title: \"Swap 1 ETH to USDC\",\n label: \"with the best price\",\n action: \"Swap 1 ETH to USDC with the best price\",\n },\n {\n title: \"Stake half of my ETH\",\n label: \"in the highest yield pool\",\n action: \"Stake half of my ETH in the highest yield pool\",\n },\n {\n title: \"Bridge 100 USDC\",\n label: \"from Ethereum to Arbitrum\",\n action: \"Bridge 100 USDC from Ethereum to Arbitrum\",\n },\n ].map((suggestedAction, index) => (\n <m.div\n initial={{ opacity: 0, y: 20 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: 20 }}\n transition={{ delay: 0.05 * index }}\n key={`suggested-action-${suggestedAction.title}-${index}`}\n className=\"aui-thread-welcome-suggestion-display @md:[&:nth-child(n+3)]:block [&:nth-child(n+3)]:hidden\"\n >\n <ThreadPrimitive.Suggestion\n prompt={suggestedAction.action}\n send\n asChild\n >\n <Button\n variant=\"ghost\"\n 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\"\n aria-label={suggestedAction.action}\n >\n <span className=\"aui-thread-welcome-suggestion-text-1 font-medium\">\n {suggestedAction.title}\n </span>\n <span className=\"aui-thread-welcome-suggestion-text-2 text-muted-foreground\">\n {suggestedAction.label}\n </span>\n </Button>\n </ThreadPrimitive.Suggestion>\n </m.div>\n ))}\n </div>\n );\n};\n\nconst Composer: FC = () => {\n return (\n <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\">\n <ThreadScrollToBottom />\n <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)]\">\n <ComposerAttachments />\n <ComposerPrimitive.Input\n placeholder=\"Send a message...\"\n 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\"\n rows={1}\n autoFocus\n aria-label=\"Message input\"\n />\n <ComposerAction />\n </ComposerPrimitive.Root>\n </div>\n );\n};\n\nconst ComposerAction: FC = () => {\n return (\n <div className=\"aui-composer-action-wrapper relative mx-1 mb-2 mt-2 flex items-center justify-between\">\n <ComposerAddAttachment />\n\n <ThreadPrimitive.If running={false}>\n <ComposerPrimitive.Send asChild>\n <TooltipIconButton\n tooltip=\"Send message\"\n side=\"bottom\"\n type=\"submit\"\n variant=\"default\"\n size=\"icon\"\n className=\"aui-composer-send mb-3 mr-3 size-[34px] rounded-full p-1\"\n aria-label=\"Send message\"\n >\n <ArrowUpIcon className=\"aui-composer-send-icon size-5\" />\n </TooltipIconButton>\n </ComposerPrimitive.Send>\n </ThreadPrimitive.If>\n\n <ThreadPrimitive.If running>\n <ComposerPrimitive.Cancel asChild>\n <Button\n type=\"button\"\n variant=\"default\"\n size=\"icon\"\n className=\"aui-composer-cancel border-muted-foreground/60 hover:bg-primary/75 dark:border-muted-foreground/90 size-[34px] rounded-full border\"\n aria-label=\"Stop generating\"\n >\n <Square className=\"aui-composer-cancel-icon size-3.5 fill-white dark:fill-black\" />\n </Button>\n </ComposerPrimitive.Cancel>\n </ThreadPrimitive.If>\n </div>\n );\n};\n\nconst MessageError: FC = () => {\n return (\n <MessagePrimitive.Error>\n <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\">\n <ErrorPrimitive.Message className=\"aui-message-error-message line-clamp-2\" />\n </ErrorPrimitive.Root>\n </MessagePrimitive.Error>\n );\n};\n\nconst AssistantMessage: FC = () => {\n return (\n <MessagePrimitive.Root asChild>\n <div\n 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\"\n data-role=\"assistant\"\n >\n <div className=\"aui-assistant-message-content text-foreground mx-2 break-words text-sm leading-5\">\n <MessagePrimitive.Parts\n components={{\n Text: MarkdownText,\n tools: { Fallback: ToolFallback },\n }}\n />\n <MessageError />\n </div>\n\n <div className=\"aui-assistant-message-footer ml-2 mt-2 flex\">\n <BranchPicker />\n <AssistantActionBar />\n </div>\n </div>\n </MessagePrimitive.Root>\n );\n};\n\nconst AssistantActionBar: FC = () => {\n return (\n <ActionBarPrimitive.Root\n hideWhenRunning\n autohide=\"not-last\"\n autohideFloat=\"single-branch\"\n 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\"\n >\n <ActionBarPrimitive.Copy asChild>\n <TooltipIconButton tooltip=\"Copy\">\n <MessagePrimitive.If copied>\n <CheckIcon />\n </MessagePrimitive.If>\n <MessagePrimitive.If copied={false}>\n <CopyIcon />\n </MessagePrimitive.If>\n </TooltipIconButton>\n </ActionBarPrimitive.Copy>\n <ActionBarPrimitive.Reload asChild>\n <TooltipIconButton tooltip=\"Refresh\">\n <RefreshCwIcon />\n </TooltipIconButton>\n </ActionBarPrimitive.Reload>\n </ActionBarPrimitive.Root>\n );\n};\n\nconst UserMessage: FC = () => {\n return (\n <MessagePrimitive.Root asChild>\n <div\n 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\"\n data-role=\"user\"\n >\n <UserMessageAttachments />\n\n <div className=\"aui-user-message-content-wrapper relative col-start-2 min-w-0\">\n <div className=\"aui-user-message-content bg-muted text-foreground break-words rounded-3xl px-5 py-2.5 text-sm\">\n <MessagePrimitive.Parts />\n </div>\n <div className=\"aui-user-action-bar-wrapper absolute left-0 top-1/2 -translate-x-full -translate-y-1/2 pr-2\">\n <UserActionBar />\n </div>\n </div>\n\n <BranchPicker className=\"aui-user-branch-picker col-span-full col-start-1 row-start-3 -mr-1 justify-end\" />\n </div>\n </MessagePrimitive.Root>\n );\n};\n\nconst UserActionBar: FC = () => {\n return (\n <ActionBarPrimitive.Root\n hideWhenRunning\n autohide=\"not-last\"\n className=\"aui-user-action-bar-root flex flex-col items-end\"\n >\n <ActionBarPrimitive.Edit asChild>\n <TooltipIconButton tooltip=\"Edit\" className=\"aui-user-action-edit p-4\">\n <PencilIcon />\n </TooltipIconButton>\n </ActionBarPrimitive.Edit>\n </ActionBarPrimitive.Root>\n );\n};\n\nconst EditComposer: FC = () => {\n return (\n <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\">\n <ComposerPrimitive.Root className=\"aui-edit-composer-root max-w-7/8 bg-muted ml-auto flex w-full flex-col rounded-xl\">\n <ComposerPrimitive.Input\n className=\"aui-edit-composer-input text-foreground flex min-h-[60px] w-full resize-none bg-transparent p-4 outline-none\"\n autoFocus\n />\n\n <div className=\"aui-edit-composer-footer mx-3 mb-3 flex items-center justify-center gap-2 self-end\">\n <ComposerPrimitive.Cancel asChild>\n <Button variant=\"ghost\" size=\"sm\" aria-label=\"Cancel edit\">\n Cancel\n </Button>\n </ComposerPrimitive.Cancel>\n <ComposerPrimitive.Send asChild>\n <Button size=\"sm\" aria-label=\"Update message\">\n Update\n </Button>\n </ComposerPrimitive.Send>\n </div>\n </ComposerPrimitive.Root>\n </div>\n );\n};\n\nconst BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({\n className,\n ...rest\n}) => {\n return (\n <BranchPickerPrimitive.Root\n hideWhenSingleBranch\n className={cn(\n \"aui-branch-picker-root text-muted-foreground -ml-2 mr-2 inline-flex items-center text-xs\",\n className,\n )}\n {...rest}\n >\n <BranchPickerPrimitive.Previous asChild>\n <TooltipIconButton tooltip=\"Previous\">\n <ChevronLeftIcon />\n </TooltipIconButton>\n </BranchPickerPrimitive.Previous>\n <span className=\"aui-branch-picker-state font-medium\">\n <BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count />\n </span>\n <BranchPickerPrimitive.Next asChild>\n <TooltipIconButton tooltip=\"Next\">\n <ChevronRightIcon />\n </TooltipIconButton>\n </BranchPickerPrimitive.Next>\n </BranchPickerPrimitive.Root>\n );\n};\n\nconst SystemMessage: FC = () => {\n const { showNotification } = useNotification();\n const messageId = useMessage((state) => state.id);\n const content = useMessage((state) => state.content) as Array<{\n type: string;\n text?: string;\n }>;\n const custom = useMessage((state) => state.metadata?.custom) as\n | { kind?: string; title?: string }\n | undefined;\n useEffect(() => {\n const text = content\n .filter((part) => part.type === \"text\")\n .map((part) => part.text ?? \"\")\n .join(\"\")\n .trim();\n\n if (!text) return;\n\n const key = messageId ?? text;\n if (seenSystemMessages.has(key)) return;\n seenSystemMessages.add(key);\n\n const inferredKind =\n custom?.kind ??\n (text.startsWith(\"Wallet transaction request:\")\n ? \"wallet_tx_request\"\n : \"system_notice\");\n\n const type =\n inferredKind === \"system_error\"\n ? \"error\"\n : inferredKind === \"system_success\"\n ? \"success\"\n : \"notice\";\n\n const title =\n custom?.title ??\n (inferredKind === \"wallet_tx_request\"\n ? \"Wallet transaction request\"\n : inferredKind === \"system_error\"\n ? \"Error\"\n : \"System notice\");\n\n showNotification({ type, title, message: text });\n }, [content, custom, showNotification, messageId]);\n\n return null;\n};\n"
10
+ "content": "\"use client\";\n\nimport {\n ArrowDownIcon,\n ArrowUpIcon,\n CheckIcon,\n ChevronLeftIcon,\n ChevronRightIcon,\n CopyIcon,\n PencilIcon,\n RefreshCwIcon,\n Square,\n} from \"lucide-react\";\n\nimport {\n ActionBarPrimitive,\n BranchPickerPrimitive,\n ComposerPrimitive,\n ErrorPrimitive,\n MessagePrimitive,\n ThreadPrimitive,\n} from \"@assistant-ui/react\";\n\nimport type { FC } from \"react\";\nimport { useEffect } from \"react\";\nimport { LazyMotion, MotionConfig, domAnimation } from \"motion/react\";\nimport * as m from \"motion/react-m\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { MarkdownText } from \"@/components/assistant-ui/markdown-text\";\nimport { ToolFallback } from \"@/components/assistant-ui/tool-fallback\";\nimport { TooltipIconButton } from \"@/components/assistant-ui/tooltip-icon-button\";\nimport {\n ComposerAddAttachment,\n ComposerAttachments,\n UserMessageAttachments,\n} from \"@/components/assistant-ui/attachment\";\n\nimport { cn, useNotification, useThreadContext } from \"@aomi-labs/react\";\nimport { useComposerControl } from \"@/components/aomi-frame\";\nimport { ModelSelect } from \"@/components/control-bar/model-select\";\nimport { NamespaceSelect } from \"@/components/control-bar/namespace-select\";\nimport { ApiKeyInput } from \"@/components/control-bar/api-key-input\";\nimport { useAssistantApi, useMessage } from \"@assistant-ui/react\";\n\nconst seenSystemMessages = new Set<string>();\n\nexport const Thread: FC = () => {\n const api = useAssistantApi();\n const { threadViewKey } = useThreadContext();\n\n useEffect(() => {\n try {\n const composer = api.composer();\n composer.setText(\"\");\n void composer.clearAttachments?.();\n } catch (error) {\n console.error(\"Failed to reset composer input:\", error);\n }\n }, [api, threadViewKey]);\n\n return (\n <LazyMotion features={domAnimation}>\n <MotionConfig reducedMotion=\"user\">\n <ThreadPrimitive.Root\n className=\"aui-root aui-thread-root @container bg-background flex h-full flex-col\"\n style={{\n [\"--thread-max-width\" as string]: \"44rem\",\n }}\n >\n <ThreadPrimitive.Viewport className=\"aui-thread-viewport relative flex flex-1 flex-col overflow-x-auto overflow-y-scroll px-2\">\n <ThreadPrimitive.If empty>\n <ThreadWelcome />\n </ThreadPrimitive.If>\n\n <ThreadPrimitive.Messages\n components={{\n UserMessage,\n EditComposer,\n AssistantMessage,\n SystemMessage,\n }}\n />\n\n <ThreadPrimitive.If empty={false}>\n <div className=\"aui-thread-viewport-spacer min-h-8 grow\" />\n </ThreadPrimitive.If>\n\n <Composer />\n </ThreadPrimitive.Viewport>\n </ThreadPrimitive.Root>\n </MotionConfig>\n </LazyMotion>\n );\n};\n\nconst ThreadScrollToBottom: FC = () => {\n return (\n <ThreadPrimitive.ScrollToBottom asChild>\n <TooltipIconButton\n tooltip=\"Scroll to bottom\"\n variant=\"outline\"\n 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\"\n >\n <ArrowDownIcon />\n </TooltipIconButton>\n </ThreadPrimitive.ScrollToBottom>\n );\n};\n\nconst ThreadWelcome: FC = () => {\n return (\n <div className=\"aui-thread-welcome-root mx-auto my-auto flex w-full max-w-[var(--thread-max-width)] flex-grow flex-col\">\n <div className=\"aui-thread-welcome-center flex w-full flex-grow flex-col items-center justify-center\">\n <div className=\"aui-thread-welcome-message flex size-full flex-col justify-center px-8\">\n <m.div\n initial={{ opacity: 0, y: 10 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: 10 }}\n className=\"aui-thread-welcome-message-motion-1 text-2xl font-semibold\"\n >\n Hello there!\n </m.div>\n <m.div\n initial={{ opacity: 0, y: 10 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: 10 }}\n transition={{ delay: 0.1 }}\n className=\"aui-thread-welcome-message-motion-2 text-muted-foreground/65 text-2xl\"\n >\n How can I help you today?\n </m.div>\n </div>\n </div>\n <ThreadSuggestions />\n </div>\n );\n};\n\nconst ThreadSuggestions: FC = () => {\n return (\n <div className=\"aui-thread-welcome-suggestions @md:grid-cols-2 grid w-full gap-2 pb-4\">\n {[\n {\n title: \"Show my wallet balances\",\n label: \"and positions\",\n action: \"Show my wallet balances and positions\",\n },\n {\n title: \"Swap 1 ETH to USDC\",\n label: \"with the best price\",\n action: \"Swap 1 ETH to USDC with the best price\",\n },\n {\n title: \"Stake half of my ETH\",\n label: \"in the highest yield pool\",\n action: \"Stake half of my ETH in the highest yield pool\",\n },\n {\n title: \"Bridge 100 USDC\",\n label: \"from Ethereum to Arbitrum\",\n action: \"Bridge 100 USDC from Ethereum to Arbitrum\",\n },\n ].map((suggestedAction, index) => (\n <m.div\n initial={{ opacity: 0, y: 20 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: 20 }}\n transition={{ delay: 0.05 * index }}\n key={`suggested-action-${suggestedAction.title}-${index}`}\n className=\"aui-thread-welcome-suggestion-display @md:[&:nth-child(n+3)]:block [&:nth-child(n+3)]:hidden\"\n >\n <ThreadPrimitive.Suggestion\n prompt={suggestedAction.action}\n send\n asChild\n >\n <Button\n variant=\"ghost\"\n 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\"\n aria-label={suggestedAction.action}\n >\n <span className=\"aui-thread-welcome-suggestion-text-1\">\n {suggestedAction.title}\n </span>\n <span className=\"aui-thread-welcome-suggestion-text-2 text-muted-foreground\">\n {suggestedAction.label}\n </span>\n </Button>\n </ThreadPrimitive.Suggestion>\n </m.div>\n ))}\n </div>\n );\n};\n\nconst Composer: FC = () => {\n return (\n <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\">\n <ThreadScrollToBottom />\n <ComposerPrimitive.Root className=\"aui-composer-root rounded-4xl bg-sidebar text-card-foreground relative flex w-full flex-col px-1 pt-2\">\n <ComposerAttachments />\n <ComposerPrimitive.Input\n placeholder=\"Send a message...\"\n 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\"\n rows={1}\n autoFocus\n aria-label=\"Message input\"\n />\n <ComposerAction />\n </ComposerPrimitive.Root>\n </div>\n );\n};\n\nconst ComposerAction: FC = () => {\n const showInlineControls = useComposerControl();\n\n return (\n <div className=\"aui-composer-action-wrapper relative mx-1 mb-2 mt-2 flex items-center\">\n {/* Show attachment button only when inline controls are hidden */}\n {!showInlineControls && <ComposerAddAttachment />}\n\n {/* Inline controls: [Model ▾] [Agent ▾] [🔑] */}\n {showInlineControls && (\n <div className=\"ml-2 flex items-center gap-2\">\n <ModelSelect />\n <NamespaceSelect />\n <ApiKeyInput />\n </div>\n )}\n\n {/* Spacer */}\n <div className=\"flex-1\" />\n\n <ThreadPrimitive.If running={false}>\n <ComposerPrimitive.Send asChild>\n <TooltipIconButton\n tooltip=\"Send message\"\n side=\"bottom\"\n type=\"submit\"\n variant=\"default\"\n size=\"icon\"\n className=\"aui-composer-send mb-3 mr-3 size-[34px] rounded-full p-1\"\n aria-label=\"Send message\"\n >\n <ArrowUpIcon className=\"aui-composer-send-icon size-5\" />\n </TooltipIconButton>\n </ComposerPrimitive.Send>\n </ThreadPrimitive.If>\n\n <ThreadPrimitive.If running>\n <ComposerPrimitive.Cancel asChild>\n <Button\n type=\"button\"\n variant=\"default\"\n size=\"icon\"\n className=\"aui-composer-cancel border-muted-foreground/60 hover:bg-primary/75 dark:border-muted-foreground/90 size-[34px] rounded-full border\"\n aria-label=\"Stop generating\"\n >\n <Square className=\"aui-composer-cancel-icon size-3.5 fill-white dark:fill-black\" />\n </Button>\n </ComposerPrimitive.Cancel>\n </ThreadPrimitive.If>\n </div>\n );\n};\n\nconst MessageError: FC = () => {\n return (\n <MessagePrimitive.Error>\n <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\">\n <ErrorPrimitive.Message className=\"aui-message-error-message line-clamp-2\" />\n </ErrorPrimitive.Root>\n </MessagePrimitive.Error>\n );\n};\n\nconst AssistantMessage: FC = () => {\n return (\n <MessagePrimitive.Root asChild>\n <div\n 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\"\n data-role=\"assistant\"\n >\n <div className=\"aui-assistant-message-content text-foreground mx-2 break-words text-sm leading-5\">\n <MessagePrimitive.Parts\n components={{\n Text: MarkdownText,\n tools: { Fallback: ToolFallback },\n }}\n />\n <MessageError />\n </div>\n\n <div className=\"aui-assistant-message-footer ml-2 mt-2 flex\">\n <BranchPicker />\n <AssistantActionBar />\n </div>\n </div>\n </MessagePrimitive.Root>\n );\n};\n\nconst AssistantActionBar: FC = () => {\n return (\n <ActionBarPrimitive.Root\n hideWhenRunning\n autohide=\"not-last\"\n autohideFloat=\"single-branch\"\n 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\"\n >\n <ActionBarPrimitive.Copy asChild>\n <TooltipIconButton tooltip=\"Copy\">\n <MessagePrimitive.If copied>\n <CheckIcon />\n </MessagePrimitive.If>\n <MessagePrimitive.If copied={false}>\n <CopyIcon />\n </MessagePrimitive.If>\n </TooltipIconButton>\n </ActionBarPrimitive.Copy>\n <ActionBarPrimitive.Reload asChild>\n <TooltipIconButton tooltip=\"Refresh\">\n <RefreshCwIcon />\n </TooltipIconButton>\n </ActionBarPrimitive.Reload>\n </ActionBarPrimitive.Root>\n );\n};\n\nconst UserMessage: FC = () => {\n return (\n <MessagePrimitive.Root asChild>\n <div\n 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\"\n data-role=\"user\"\n >\n <UserMessageAttachments />\n\n <div className=\"aui-user-message-content-wrapper relative col-start-2 min-w-0\">\n <div className=\"aui-user-message-content bg-muted text-foreground break-words rounded-3xl px-5 py-2.5 text-sm\">\n <MessagePrimitive.Parts />\n </div>\n <div className=\"aui-user-action-bar-wrapper absolute left-0 top-1/2 -translate-x-full -translate-y-1/2 pr-2\">\n <UserActionBar />\n </div>\n </div>\n\n <BranchPicker className=\"aui-user-branch-picker col-span-full col-start-1 row-start-3 -mr-1 justify-end\" />\n </div>\n </MessagePrimitive.Root>\n );\n};\n\nconst UserActionBar: FC = () => {\n return (\n <ActionBarPrimitive.Root\n hideWhenRunning\n autohide=\"not-last\"\n className=\"aui-user-action-bar-root flex flex-col items-end\"\n >\n <ActionBarPrimitive.Edit asChild>\n <TooltipIconButton tooltip=\"Edit\" className=\"aui-user-action-edit p-4\">\n <PencilIcon />\n </TooltipIconButton>\n </ActionBarPrimitive.Edit>\n </ActionBarPrimitive.Root>\n );\n};\n\nconst EditComposer: FC = () => {\n return (\n <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\">\n <ComposerPrimitive.Root className=\"aui-edit-composer-root max-w-7/8 bg-muted ml-auto flex w-full flex-col rounded-xl\">\n <ComposerPrimitive.Input\n className=\"aui-edit-composer-input text-foreground dark:text-white flex min-h-[60px] w-full resize-none bg-transparent p-4 outline-none\"\n autoFocus\n />\n\n <div className=\"aui-edit-composer-footer mx-3 mb-3 flex items-center justify-center gap-2 self-end\">\n <ComposerPrimitive.Cancel asChild>\n <Button variant=\"ghost\" size=\"sm\" aria-label=\"Cancel edit\">\n Cancel\n </Button>\n </ComposerPrimitive.Cancel>\n <ComposerPrimitive.Send asChild>\n <Button size=\"sm\" aria-label=\"Update message\">\n Update\n </Button>\n </ComposerPrimitive.Send>\n </div>\n </ComposerPrimitive.Root>\n </div>\n );\n};\n\nconst BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({\n className,\n ...rest\n}) => {\n return (\n <BranchPickerPrimitive.Root\n hideWhenSingleBranch\n className={cn(\n \"aui-branch-picker-root text-muted-foreground -ml-2 mr-2 inline-flex items-center text-xs\",\n className,\n )}\n {...rest}\n >\n <BranchPickerPrimitive.Previous asChild>\n <TooltipIconButton tooltip=\"Previous\">\n <ChevronLeftIcon />\n </TooltipIconButton>\n </BranchPickerPrimitive.Previous>\n <span className=\"aui-branch-picker-state font-medium\">\n <BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count />\n </span>\n <BranchPickerPrimitive.Next asChild>\n <TooltipIconButton tooltip=\"Next\">\n <ChevronRightIcon />\n </TooltipIconButton>\n </BranchPickerPrimitive.Next>\n </BranchPickerPrimitive.Root>\n );\n};\n\nconst SystemMessage: FC = () => {\n const { showNotification } = useNotification();\n const messageId = useMessage((state) => state.id);\n const content = useMessage((state) => state.content) as Array<{\n type: string;\n text?: string;\n }>;\n const custom = useMessage((state) => state.metadata?.custom) as\n | { kind?: string; title?: string }\n | undefined;\n useEffect(() => {\n const text = content\n .filter((part) => part.type === \"text\")\n .map((part) => part.text ?? \"\")\n .join(\"\")\n .trim();\n\n if (!text) return;\n\n const key = messageId ?? text;\n if (seenSystemMessages.has(key)) return;\n seenSystemMessages.add(key);\n\n const inferredKind =\n custom?.kind ??\n (text.startsWith(\"Wallet transaction request:\")\n ? \"wallet_tx_request\"\n : \"system_notice\");\n\n const type =\n inferredKind === \"system_error\"\n ? \"error\"\n : inferredKind === \"system_success\"\n ? \"success\"\n : \"notice\";\n\n const title =\n custom?.title ??\n (inferredKind === \"wallet_tx_request\"\n ? \"Wallet transaction request\"\n : inferredKind === \"system_error\"\n ? \"Error\"\n : \"System notice\");\n\n showNotification({ type, title, message: text });\n }, [content, custom, showNotification, messageId]);\n\n return null;\n};\n"
11
11
  }
12
12
  ],
13
13
  "dependencies": [
@@ -7,7 +7,7 @@
7
7
  {
8
8
  "type": "registry:component",
9
9
  "path": "components/assistant-ui/threadlist-sidebar.tsx",
10
- "content": "\"use client\";\n\nimport * as React from \"react\";\nimport Link from \"next/link\";\nimport Image from \"next/image\";\nimport {\n Sidebar,\n SidebarContent,\n SidebarFooter,\n SidebarHeader,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarRail,\n} from \"@/components/ui/sidebar\";\nimport { ThreadList } from \"@/components/assistant-ui/thread-list\";\n\ntype ThreadListSidebarProps = React.ComponentProps<typeof Sidebar> & {\n /** Optional footer component (e.g., WalletFooter from consumer app) */\n footer?: React.ReactNode;\n};\n\nexport function ThreadListSidebar({\n footer,\n ...props\n}: ThreadListSidebarProps) {\n return (\n <Sidebar\n collapsible=\"offcanvas\"\n variant=\"inset\"\n className=\"relative\"\n {...props}\n >\n <SidebarHeader className=\"aomi-sidebar-header\">\n <div className=\"aomi-sidebar-header-content flex items-center justify-between\">\n <SidebarMenu>\n <SidebarMenuItem>\n <SidebarMenuButton size=\"lg\" asChild>\n <Link\n href=\"https://aomi.dev\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n <div className=\"aomi-sidebar-header-icon-wrapper flex aspect-square size-8 items-center justify-center rounded-lg bg-white\">\n <Image\n src=\"/assets/images/bubble.svg\"\n alt=\"Logo\"\n width={28}\n height={28}\n className=\"aomi-sidebar-header-icon ml-3 size-7\"\n priority\n />\n </div>\n </Link>\n </SidebarMenuButton>\n </SidebarMenuItem>\n </SidebarMenu>\n </div>\n </SidebarHeader>\n <SidebarContent className=\"aomi-sidebar-content\">\n <ThreadList />\n </SidebarContent>\n <SidebarRail />\n {footer && (\n <SidebarFooter className=\"aomi-sidebar-footer border-sm border-t py-4\">\n {footer}\n </SidebarFooter>\n )}\n </Sidebar>\n );\n}\n"
10
+ "content": "\"use client\";\n\nimport * as React from \"react\";\nimport Link from \"next/link\";\nimport Image from \"next/image\";\nimport {\n Sidebar,\n SidebarContent,\n SidebarFooter,\n SidebarHeader,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarRail,\n} from \"@/components/ui/sidebar\";\nimport { ThreadList } from \"@/components/assistant-ui/thread-list\";\nimport { WalletConnect } from \"@/components/control-bar/wallet-connect\";\n\ntype ThreadListSidebarProps = React.ComponentProps<typeof Sidebar> & {\n /** Position of the wallet button: \"header\" (top), \"footer\" (bottom), or null (hidden) */\n walletPosition?: \"header\" | \"footer\" | null;\n};\n\nexport function ThreadListSidebar({\n walletPosition = \"footer\",\n ...props\n}: ThreadListSidebarProps) {\n return (\n <Sidebar\n collapsible=\"offcanvas\"\n variant=\"inset\"\n className=\"relative\"\n {...props}\n >\n <SidebarHeader className=\"aomi-sidebar-header\">\n <div className=\"aomi-sidebar-header-content mt-5 mb-5 ml-5 flex items-center justify-between\">\n <Link\n href=\"https://aomi.dev\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"flex items-center justify-center\"\n >\n <Image\n src=\"/assets/images/bubble.svg\"\n alt=\"Logo\"\n width={25}\n height={25}\n className=\"aomi-sidebar-header-icon size-6\"\n priority\n />\n </Link>\n {walletPosition === \"header\" && <WalletConnect />}\n </div>\n </SidebarHeader>\n <SidebarContent className=\"aomi-sidebar-content\">\n <ThreadList />\n </SidebarContent>\n <SidebarRail />\n {walletPosition === \"footer\" && (\n <SidebarFooter className=\"aomi-sidebar-footer border-0 mx-5 mb-5\">\n <WalletConnect className=\"w-full\" />\n </SidebarFooter>\n )}\n </Sidebar>\n );\n}\n"
11
11
  }
12
12
  ],
13
13
  "dependencies": [
package/dist/sidebar.json CHANGED
@@ -7,7 +7,7 @@
7
7
  {
8
8
  "type": "registry:component",
9
9
  "path": "components/ui/sidebar.tsx",
10
- "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, VariantProps } from \"class-variance-authority\";\nimport { PanelLeftIcon } from \"lucide-react\";\n\nimport { useIsMobile } from \"@/hooks/use-mobile\";\nimport { cn } from \"@aomi-labs/react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Separator } from \"@/components/ui/separator\";\nimport {\n Sheet,\n SheetContent,\n SheetDescription,\n SheetHeader,\n SheetTitle,\n} from \"@/components/ui/sheet\";\nimport { Skeleton } from \"@/components/ui/skeleton\";\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from \"@/components/ui/tooltip\";\n\nconst SIDEBAR_COOKIE_NAME = \"sidebar_state\";\nconst SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;\nconst SIDEBAR_WIDTH = \"16rem\";\nconst SIDEBAR_WIDTH_MOBILE = \"18rem\";\nconst SIDEBAR_WIDTH_ICON = \"3rem\";\nconst SIDEBAR_KEYBOARD_SHORTCUT = \"b\";\nconst SIDEBAR_MIN_WIDTH = 100; // px\nconst SIDEBAR_MAX_WIDTH = 200; // px\n\ntype SidebarContextProps = {\n state: \"expanded\" | \"collapsed\";\n open: boolean;\n setOpen: (open: boolean) => void;\n openMobile: boolean;\n setOpenMobile: (open: boolean) => void;\n isMobile: boolean;\n toggleSidebar: () => void;\n sidebarWidth: number;\n setSidebarWidth: (width: number) => void;\n};\n\nconst SidebarContext = React.createContext<SidebarContextProps | null>(null);\n\nfunction useSidebar() {\n const context = React.useContext(SidebarContext);\n if (!context) {\n throw new Error(\"useSidebar must be used within a SidebarProvider.\");\n }\n\n return context;\n}\n\nfunction SidebarProvider({\n defaultOpen = true,\n open: openProp,\n onOpenChange: setOpenProp,\n className,\n style,\n children,\n ...props\n}: React.ComponentProps<\"div\"> & {\n defaultOpen?: boolean;\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n}) {\n const isMobile = useIsMobile();\n const [openMobile, setOpenMobile] = React.useState(false);\n\n // This is the internal state of the sidebar.\n // We use openProp and setOpenProp for control from outside the component.\n const [_open, _setOpen] = React.useState(defaultOpen);\n const open = openProp ?? _open;\n\n // Sidebar width state (in pixels)\n const [sidebarWidth, setSidebarWidth] = React.useState(256); // 16rem = 256px\n const setOpen = React.useCallback(\n (value: boolean | ((value: boolean) => boolean)) => {\n const openState = typeof value === \"function\" ? value(open) : value;\n if (setOpenProp) {\n setOpenProp(openState);\n } else {\n _setOpen(openState);\n }\n\n // This sets the cookie to keep the sidebar state.\n document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;\n },\n [setOpenProp, open],\n );\n\n // Helper to toggle the sidebar.\n const toggleSidebar = React.useCallback(() => {\n return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);\n }, [isMobile, setOpen, setOpenMobile]);\n\n // Adds a keyboard shortcut to toggle the sidebar.\n React.useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (\n event.key === SIDEBAR_KEYBOARD_SHORTCUT &&\n (event.metaKey || event.ctrlKey)\n ) {\n event.preventDefault();\n toggleSidebar();\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [toggleSidebar]);\n\n // We add a state so that we can do data-state=\"expanded\" or \"collapsed\".\n // This makes it easier to style the sidebar with Tailwind classes.\n const state = open ? \"expanded\" : \"collapsed\";\n\n const contextValue = React.useMemo<SidebarContextProps>(\n () => ({\n state,\n open,\n setOpen,\n isMobile,\n openMobile,\n setOpenMobile,\n toggleSidebar,\n sidebarWidth,\n setSidebarWidth,\n }),\n [\n state,\n open,\n setOpen,\n isMobile,\n openMobile,\n setOpenMobile,\n toggleSidebar,\n sidebarWidth,\n ],\n );\n\n return (\n <SidebarContext.Provider value={contextValue}>\n <TooltipProvider delayDuration={0}>\n <div\n data-slot=\"sidebar-wrapper\"\n style={\n {\n \"--sidebar-width\": `${sidebarWidth}px`,\n \"--sidebar-width-icon\": SIDEBAR_WIDTH_ICON,\n ...style,\n } as React.CSSProperties\n }\n className={cn(\n \"group/sidebar-wrapper has-data-[variant=offcanvas]:bg-sidebar flex h-full w-full\",\n className,\n )}\n {...props}\n >\n {children}\n </div>\n </TooltipProvider>\n </SidebarContext.Provider>\n );\n}\n\nfunction Sidebar({\n side = \"left\",\n variant = \"sidebar\",\n collapsible = \"offcanvas\",\n className,\n children,\n ...props\n}: React.ComponentProps<\"div\"> & {\n side?: \"left\" | \"right\";\n variant?: \"sidebar\" | \"floating\" | \"inset\";\n collapsible?: \"offcanvas\" | \"icon\" | \"none\";\n}) {\n const { isMobile, state, openMobile, setOpenMobile } = useSidebar();\n\n if (collapsible === \"none\") {\n return (\n <div\n data-slot=\"sidebar\"\n className={cn(\n \"w-(--sidebar-width) bg-sidebar text-sidebar-foreground flex h-full flex-col\",\n className,\n )}\n {...props}\n >\n {children}\n </div>\n );\n }\n\n if (isMobile) {\n return (\n <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>\n <SheetContent\n data-sidebar=\"sidebar\"\n data-slot=\"sidebar\"\n data-mobile=\"true\"\n className=\"w-(--sidebar-width) bg-sidebar text-sidebar-foreground p-0 [&>button]:hidden\"\n style={\n {\n \"--sidebar-width\": SIDEBAR_WIDTH_MOBILE,\n } as React.CSSProperties\n }\n side={side}\n >\n <SheetHeader className=\"sr-only\">\n <SheetTitle>Sidebar</SheetTitle>\n <SheetDescription>Displays the mobile sidebar.</SheetDescription>\n </SheetHeader>\n <div className=\"flex h-full w-full flex-col\">{children}</div>\n </SheetContent>\n </Sheet>\n );\n }\n\n return (\n <div\n className={cn(\n \"text-sidebar-foreground group peer relative hidden md:block\",\n \"w-[var(--sidebar-width)]\",\n \"data-[collapsible=offcanvas]:w-0\",\n \"transition-[width] duration-200 ease-linear\",\n )}\n data-state={state}\n data-collapsible={state === \"collapsed\" ? collapsible : \"\"}\n data-variant={variant}\n data-side={side}\n data-slot=\"sidebar\"\n >\n {/* This is what handles the sidebar gap on desktop */}\n <div\n data-slot=\"sidebar-gap\"\n className={cn(\n \"w-(--sidebar-width) relative bg-transparent transition-[width] duration-200 ease-linear\",\n \"group-data-[collapsible=offcanvas]:w-0\",\n \"group-data-[side=right]:rotate-180\",\n variant === \"floating\" || variant === \"inset\"\n ? \"group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]\"\n : \"group-data-[collapsible=icon]:w-(--sidebar-width-icon)\",\n )}\n />\n <div\n data-slot=\"sidebar-container\"\n className={cn(\n \"w-(--sidebar-width) fixed inset-y-0 z-10 hidden h-full transition-[left,right,width] duration-200 ease-linear md:flex\",\n side === \"left\"\n ? \"left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]\"\n : \"right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]\",\n // Adjust the padding for floating and inset variants.\n variant === \"floating\"\n ? \"p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]\"\n : variant === \"inset\"\n ? \"group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]\"\n : \"group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l\",\n className,\n )}\n {...props}\n >\n <div\n data-sidebar=\"sidebar\"\n data-slot=\"sidebar-inner\"\n className=\"bg-sidebar group-data-[variant=floating]:border-sidebar-border flex h-full w-full flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm\"\n >\n {children}\n </div>\n </div>\n </div>\n );\n}\n\nfunction SidebarTrigger({\n className,\n onClick,\n ...props\n}: React.ComponentProps<typeof Button>) {\n const { toggleSidebar } = useSidebar();\n\n return (\n <Button\n data-sidebar=\"trigger\"\n data-slot=\"sidebar-trigger\"\n variant=\"ghost\"\n size=\"icon\"\n className={cn(\"size-7\", className)}\n onClick={(event) => {\n onClick?.(event);\n toggleSidebar();\n }}\n {...props}\n >\n <PanelLeftIcon />\n <span className=\"sr-only\">Toggle Sidebar</span>\n </Button>\n );\n}\n\nfunction SidebarRail({ className, ...props }: React.ComponentProps<\"button\">) {\n const { toggleSidebar, sidebarWidth, setSidebarWidth, setOpen } =\n useSidebar();\n const isDraggingRef = React.useRef(false);\n const startXRef = React.useRef(0);\n const startWidthRef = React.useRef(0);\n const hasDraggedRef = React.useRef(false);\n const rafRef = React.useRef<number | null>(null);\n\n const handleMouseDown = React.useCallback(\n (e: React.MouseEvent) => {\n e.preventDefault();\n isDraggingRef.current = true;\n hasDraggedRef.current = false;\n startXRef.current = e.clientX;\n startWidthRef.current = sidebarWidth;\n document.body.style.cursor = \"ew-resize\";\n document.body.style.userSelect = \"none\";\n },\n [sidebarWidth],\n );\n\n React.useEffect(() => {\n const handleMouseMove = (e: MouseEvent) => {\n if (!isDraggingRef.current) return;\n\n // Cancel any pending RAF\n if (rafRef.current) {\n cancelAnimationFrame(rafRef.current);\n }\n\n rafRef.current = requestAnimationFrame(() => {\n const deltaX = e.clientX - startXRef.current;\n if (Math.abs(deltaX) > 5) {\n hasDraggedRef.current = true;\n }\n\n const rawWidth = startWidthRef.current + deltaX;\n\n if (rawWidth < SIDEBAR_MIN_WIDTH) {\n setOpen(false);\n } else {\n setOpen(true);\n setSidebarWidth(Math.min(SIDEBAR_MAX_WIDTH, rawWidth));\n }\n });\n };\n\n const handleMouseUp = () => {\n if (!isDraggingRef.current) return;\n isDraggingRef.current = false;\n document.body.style.cursor = \"\";\n document.body.style.userSelect = \"\";\n if (rafRef.current) {\n cancelAnimationFrame(rafRef.current);\n }\n };\n\n document.addEventListener(\"mousemove\", handleMouseMove);\n document.addEventListener(\"mouseup\", handleMouseUp);\n\n return () => {\n document.removeEventListener(\"mousemove\", handleMouseMove);\n document.removeEventListener(\"mouseup\", handleMouseUp);\n };\n }, [setSidebarWidth, setOpen]);\n\n const handleClick = React.useCallback(() => {\n if (!hasDraggedRef.current) {\n toggleSidebar();\n }\n }, [toggleSidebar]);\n\n return (\n <button\n data-sidebar=\"rail\"\n data-slot=\"sidebar-rail\"\n aria-label=\"Toggle Sidebar\"\n tabIndex={-1}\n onMouseDown={handleMouseDown}\n onClick={handleClick}\n title=\"Drag to resize, click to toggle\"\n className={cn(\n \"hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex\",\n \"cursor-ew-resize\",\n \"hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full\",\n \"[[data-side=left][data-collapsible=offcanvas]_&]:-right-2\",\n \"[[data-side=right][data-collapsible=offcanvas]_&]:-left-2\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction SidebarInset({ className, ...props }: React.ComponentProps<\"main\">) {\n return (\n <main\n data-slot=\"sidebar-inset\"\n className={cn(\n \"bg-background relative flex w-full flex-1 flex-col\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction SidebarInput({\n className,\n ...props\n}: React.ComponentProps<typeof Input>) {\n return (\n <Input\n data-slot=\"sidebar-input\"\n data-sidebar=\"input\"\n className={cn(\"bg-background h-8 w-full shadow-none\", className)}\n {...props}\n />\n );\n}\n\nfunction SidebarHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"sidebar-header\"\n data-sidebar=\"header\"\n className={cn(\"flex flex-col gap-2 p-2\", className)}\n {...props}\n />\n );\n}\n\nfunction SidebarFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"sidebar-footer\"\n data-sidebar=\"footer\"\n className={cn(\"mr-2 flex flex-col gap-2 p-2\", className)}\n {...props}\n />\n );\n}\n\nfunction SidebarSeparator({\n className,\n ...props\n}: React.ComponentProps<typeof Separator>) {\n return (\n <Separator\n data-slot=\"sidebar-separator\"\n data-sidebar=\"separator\"\n className={cn(\"bg-sidebar-border mx-2 w-auto\", className)}\n {...props}\n />\n );\n}\n\nfunction SidebarContent({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"sidebar-content\"\n data-sidebar=\"content\"\n className={cn(\n \"mr-2 flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction SidebarGroup({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"sidebar-group\"\n data-sidebar=\"group\"\n className={cn(\"relative flex w-full min-w-0 flex-col p-2\", className)}\n {...props}\n />\n );\n}\n\nfunction SidebarGroupLabel({\n className,\n asChild = false,\n ...props\n}: React.ComponentProps<\"div\"> & { asChild?: boolean }) {\n const Comp = asChild ? Slot : \"div\";\n\n return (\n <Comp\n data-slot=\"sidebar-group-label\"\n data-sidebar=\"group-label\"\n className={cn(\n \"text-sidebar-foreground/70 ring-sidebar-ring outline-hidden flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0\",\n \"group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction SidebarGroupAction({\n className,\n asChild = false,\n ...props\n}: React.ComponentProps<\"button\"> & { asChild?: boolean }) {\n const Comp = asChild ? Slot : \"button\";\n\n return (\n <Comp\n data-slot=\"sidebar-group-action\"\n data-sidebar=\"group-action\"\n className={cn(\n \"text-sidebar-foreground ring-sidebar-ring outline-hidden hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0\",\n // Increases the hit area of the button on mobile.\n \"after:absolute after:-inset-2 md:after:hidden\",\n \"group-data-[collapsible=icon]:hidden\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction SidebarGroupContent({\n className,\n ...props\n}: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"sidebar-group-content\"\n data-sidebar=\"group-content\"\n className={cn(\"w-full text-sm\", className)}\n {...props}\n />\n );\n}\n\nfunction SidebarMenu({ className, ...props }: React.ComponentProps<\"ul\">) {\n return (\n <ul\n data-slot=\"sidebar-menu\"\n data-sidebar=\"menu\"\n className={cn(\"flex w-full min-w-0 flex-col gap-1\", className)}\n {...props}\n />\n );\n}\n\nfunction SidebarMenuItem({ className, ...props }: React.ComponentProps<\"li\">) {\n return (\n <li\n data-slot=\"sidebar-menu-item\"\n data-sidebar=\"menu-item\"\n className={cn(\"group/menu-item relative\", className)}\n {...props}\n />\n );\n}\n\nconst sidebarMenuButtonVariants = cva(\n \"peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0\",\n {\n variants: {\n variant: {\n default: \"hover:bg-sidebar-accent hover:text-sidebar-accent-foreground\",\n outline:\n \"bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]\",\n },\n size: {\n default: \"h-8 text-sm\",\n sm: \"h-7 text-xs\",\n lg: \"h-12 text-sm group-data-[collapsible=icon]:p-0!\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n },\n);\n\nfunction SidebarMenuButton({\n asChild = false,\n isActive = false,\n variant = \"default\",\n size = \"default\",\n tooltip,\n className,\n ...props\n}: React.ComponentProps<\"button\"> & {\n asChild?: boolean;\n isActive?: boolean;\n tooltip?: string | React.ComponentProps<typeof TooltipContent>;\n} & VariantProps<typeof sidebarMenuButtonVariants>) {\n const Comp = asChild ? Slot : \"button\";\n const { isMobile, state } = useSidebar();\n\n const button = (\n <Comp\n data-slot=\"sidebar-menu-button\"\n data-sidebar=\"menu-button\"\n data-size={size}\n data-active={isActive}\n className={cn(sidebarMenuButtonVariants({ variant, size }), className)}\n {...props}\n />\n );\n\n if (!tooltip) {\n return button;\n }\n\n if (typeof tooltip === \"string\") {\n tooltip = {\n children: tooltip,\n };\n }\n\n return (\n <Tooltip>\n <TooltipTrigger asChild>{button}</TooltipTrigger>\n <TooltipContent\n side=\"right\"\n align=\"center\"\n hidden={state !== \"collapsed\" || isMobile}\n {...tooltip}\n />\n </Tooltip>\n );\n}\n\nfunction SidebarMenuAction({\n className,\n asChild = false,\n showOnHover = false,\n ...props\n}: React.ComponentProps<\"button\"> & {\n asChild?: boolean;\n showOnHover?: boolean;\n}) {\n const Comp = asChild ? Slot : \"button\";\n\n return (\n <Comp\n data-slot=\"sidebar-menu-action\"\n data-sidebar=\"menu-action\"\n className={cn(\n \"text-sidebar-foreground ring-sidebar-ring outline-hidden peer-hover/menu-button:text-sidebar-accent-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0\",\n // Increases the hit area of the button on mobile.\n \"after:absolute after:-inset-2 md:after:hidden\",\n \"peer-data-[size=sm]/menu-button:top-1\",\n \"peer-data-[size=default]/menu-button:top-1.5\",\n \"peer-data-[size=lg]/menu-button:top-2.5\",\n \"group-data-[collapsible=icon]:hidden\",\n showOnHover &&\n \"peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction SidebarMenuBadge({\n className,\n ...props\n}: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"sidebar-menu-badge\"\n data-sidebar=\"menu-badge\"\n className={cn(\n \"text-sidebar-foreground pointer-events-none absolute right-1 flex h-5 min-w-5 select-none items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums\",\n \"peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground\",\n \"peer-data-[size=sm]/menu-button:top-1\",\n \"peer-data-[size=default]/menu-button:top-1.5\",\n \"peer-data-[size=lg]/menu-button:top-2.5\",\n \"group-data-[collapsible=icon]:hidden\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction SidebarMenuSkeleton({\n className,\n showIcon = false,\n ...props\n}: React.ComponentProps<\"div\"> & {\n showIcon?: boolean;\n}) {\n // Random width between 50 to 90%.\n const width = React.useMemo(() => {\n return `${Math.floor(Math.random() * 40) + 50}%`;\n }, []);\n\n return (\n <div\n data-slot=\"sidebar-menu-skeleton\"\n data-sidebar=\"menu-skeleton\"\n className={cn(\"flex h-8 items-center gap-2 rounded-md px-2\", className)}\n {...props}\n >\n {showIcon && (\n <Skeleton\n className=\"size-4 rounded-md\"\n data-sidebar=\"menu-skeleton-icon\"\n />\n )}\n <Skeleton\n className=\"max-w-(--skeleton-width) h-4 flex-1\"\n data-sidebar=\"menu-skeleton-text\"\n style={\n {\n \"--skeleton-width\": width,\n } as React.CSSProperties\n }\n />\n </div>\n );\n}\n\nfunction SidebarMenuSub({ className, ...props }: React.ComponentProps<\"ul\">) {\n return (\n <ul\n data-slot=\"sidebar-menu-sub\"\n data-sidebar=\"menu-sub\"\n className={cn(\n \"border-sidebar-border mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l px-2.5 py-0.5\",\n \"group-data-[collapsible=icon]:hidden\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction SidebarMenuSubItem({\n className,\n ...props\n}: React.ComponentProps<\"li\">) {\n return (\n <li\n data-slot=\"sidebar-menu-sub-item\"\n data-sidebar=\"menu-sub-item\"\n className={cn(\"group/menu-sub-item relative\", className)}\n {...props}\n />\n );\n}\n\nfunction SidebarMenuSubButton({\n asChild = false,\n size = \"md\",\n isActive = false,\n className,\n ...props\n}: React.ComponentProps<\"a\"> & {\n asChild?: boolean;\n size?: \"sm\" | \"md\";\n isActive?: boolean;\n}) {\n const Comp = asChild ? Slot : \"a\";\n\n return (\n <Comp\n data-slot=\"sidebar-menu-sub-button\"\n data-sidebar=\"menu-sub-button\"\n data-size={size}\n data-active={isActive}\n className={cn(\n \"text-sidebar-foreground ring-sidebar-ring outline-hidden hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0\",\n \"data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground\",\n size === \"sm\" && \"text-xs\",\n size === \"md\" && \"text-sm\",\n \"group-data-[collapsible=icon]:hidden\",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport {\n Sidebar,\n SidebarContent,\n SidebarFooter,\n SidebarGroup,\n SidebarGroupAction,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarHeader,\n SidebarInput,\n SidebarInset,\n SidebarMenu,\n SidebarMenuAction,\n SidebarMenuBadge,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarMenuSkeleton,\n SidebarMenuSub,\n SidebarMenuSubButton,\n SidebarMenuSubItem,\n SidebarProvider,\n SidebarRail,\n SidebarSeparator,\n SidebarTrigger,\n useSidebar,\n};\n"
10
+ "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, VariantProps } from \"class-variance-authority\";\nimport { PanelLeftIcon } from \"lucide-react\";\n\nimport { useIsMobile } from \"@/hooks/use-mobile\";\nimport { cn } from \"@aomi-labs/react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Separator } from \"@/components/ui/separator\";\nimport {\n Sheet,\n SheetContent,\n SheetDescription,\n SheetHeader,\n SheetTitle,\n} from \"@/components/ui/sheet\";\nimport { Skeleton } from \"@/components/ui/skeleton\";\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from \"@/components/ui/tooltip\";\n\nconst SIDEBAR_COOKIE_NAME = \"sidebar_state\";\nconst SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;\nconst SIDEBAR_WIDTH = \"16rem\";\nconst SIDEBAR_WIDTH_MOBILE = \"18rem\";\nconst SIDEBAR_WIDTH_ICON = \"3rem\";\nconst SIDEBAR_KEYBOARD_SHORTCUT = \"b\";\nconst SIDEBAR_MIN_WIDTH = 100; // px\nconst SIDEBAR_MAX_WIDTH = 200; // px\n\ntype SidebarContextProps = {\n state: \"expanded\" | \"collapsed\";\n open: boolean;\n setOpen: (open: boolean) => void;\n openMobile: boolean;\n setOpenMobile: (open: boolean) => void;\n isMobile: boolean;\n toggleSidebar: () => void;\n sidebarWidth: number;\n setSidebarWidth: (width: number) => void;\n};\n\nconst SidebarContext = React.createContext<SidebarContextProps | null>(null);\n\nfunction useSidebar() {\n const context = React.useContext(SidebarContext);\n if (!context) {\n throw new Error(\"useSidebar must be used within a SidebarProvider.\");\n }\n\n return context;\n}\n\nfunction SidebarProvider({\n defaultOpen = true,\n open: openProp,\n onOpenChange: setOpenProp,\n className,\n style,\n children,\n ...props\n}: React.ComponentProps<\"div\"> & {\n defaultOpen?: boolean;\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n}) {\n const isMobile = useIsMobile();\n const [openMobile, setOpenMobile] = React.useState(false);\n\n // This is the internal state of the sidebar.\n // We use openProp and setOpenProp for control from outside the component.\n const [_open, _setOpen] = React.useState(defaultOpen);\n const open = openProp ?? _open;\n\n // Sidebar width state (in pixels)\n const [sidebarWidth, setSidebarWidth] = React.useState(256); // 16rem = 256px\n const setOpen = React.useCallback(\n (value: boolean | ((value: boolean) => boolean)) => {\n const openState = typeof value === \"function\" ? value(open) : value;\n if (setOpenProp) {\n setOpenProp(openState);\n } else {\n _setOpen(openState);\n }\n\n // This sets the cookie to keep the sidebar state.\n document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;\n },\n [setOpenProp, open],\n );\n\n // Helper to toggle the sidebar.\n const toggleSidebar = React.useCallback(() => {\n return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);\n }, [isMobile, setOpen, setOpenMobile]);\n\n // Adds a keyboard shortcut to toggle the sidebar.\n React.useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (\n event.key === SIDEBAR_KEYBOARD_SHORTCUT &&\n (event.metaKey || event.ctrlKey)\n ) {\n event.preventDefault();\n toggleSidebar();\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [toggleSidebar]);\n\n // We add a state so that we can do data-state=\"expanded\" or \"collapsed\".\n // This makes it easier to style the sidebar with Tailwind classes.\n const state = open ? \"expanded\" : \"collapsed\";\n\n const contextValue = React.useMemo<SidebarContextProps>(\n () => ({\n state,\n open,\n setOpen,\n isMobile,\n openMobile,\n setOpenMobile,\n toggleSidebar,\n sidebarWidth,\n setSidebarWidth,\n }),\n [\n state,\n open,\n setOpen,\n isMobile,\n openMobile,\n setOpenMobile,\n toggleSidebar,\n sidebarWidth,\n ],\n );\n\n return (\n <SidebarContext.Provider value={contextValue}>\n <TooltipProvider delayDuration={0}>\n <div\n data-slot=\"sidebar-wrapper\"\n style={\n {\n \"--sidebar-width\": `${sidebarWidth}px`,\n \"--sidebar-width-icon\": SIDEBAR_WIDTH_ICON,\n ...style,\n } as React.CSSProperties\n }\n className={cn(\n \"group/sidebar-wrapper has-data-[variant=offcanvas]:bg-sidebar flex h-full w-full\",\n className,\n )}\n {...props}\n >\n {children}\n </div>\n </TooltipProvider>\n </SidebarContext.Provider>\n );\n}\n\nfunction Sidebar({\n side = \"left\",\n variant = \"sidebar\",\n collapsible = \"offcanvas\",\n className,\n children,\n ...props\n}: React.ComponentProps<\"div\"> & {\n side?: \"left\" | \"right\";\n variant?: \"sidebar\" | \"floating\" | \"inset\";\n collapsible?: \"offcanvas\" | \"icon\" | \"none\";\n}) {\n const { isMobile, state, openMobile, setOpenMobile } = useSidebar();\n\n if (collapsible === \"none\") {\n return (\n <div\n data-slot=\"sidebar\"\n className={cn(\n \"w-(--sidebar-width) bg-sidebar text-sidebar-foreground flex h-full flex-col\",\n className,\n )}\n {...props}\n >\n {children}\n </div>\n );\n }\n\n if (isMobile) {\n return (\n <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>\n <SheetContent\n data-sidebar=\"sidebar\"\n data-slot=\"sidebar\"\n data-mobile=\"true\"\n className=\"w-(--sidebar-width) bg-sidebar text-sidebar-foreground p-0 [&>button]:hidden\"\n style={\n {\n \"--sidebar-width\": SIDEBAR_WIDTH_MOBILE,\n } as React.CSSProperties\n }\n side={side}\n >\n <SheetHeader className=\"sr-only\">\n <SheetTitle>Sidebar</SheetTitle>\n <SheetDescription>Displays the mobile sidebar.</SheetDescription>\n </SheetHeader>\n <div className=\"flex h-full w-full flex-col\">{children}</div>\n </SheetContent>\n </Sheet>\n );\n }\n\n return (\n <div\n className={cn(\n \"text-sidebar-foreground group peer relative hidden md:block\",\n \"w-[var(--sidebar-width)]\",\n \"data-[collapsible=offcanvas]:w-0\",\n \"transition-[width] duration-200 ease-linear\",\n )}\n data-state={state}\n data-collapsible={state === \"collapsed\" ? collapsible : \"\"}\n data-variant={variant}\n data-side={side}\n data-slot=\"sidebar\"\n >\n {/* This is what handles the sidebar gap on desktop */}\n <div\n data-slot=\"sidebar-gap\"\n className={cn(\n \"w-(--sidebar-width) relative bg-transparent transition-[width] duration-200 ease-linear\",\n \"group-data-[collapsible=offcanvas]:w-0\",\n \"group-data-[side=right]:rotate-180\",\n variant === \"floating\" || variant === \"inset\"\n ? \"group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]\"\n : \"group-data-[collapsible=icon]:w-(--sidebar-width-icon)\",\n )}\n />\n <div\n data-slot=\"sidebar-container\"\n className={cn(\n \"w-(--sidebar-width) fixed inset-y-0 z-10 hidden h-full transition-[left,right,width] duration-200 ease-linear md:flex\",\n side === \"left\"\n ? \"left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]\"\n : \"right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]\",\n // Adjust the padding for floating and inset variants.\n variant === \"floating\"\n ? \"p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]\"\n : variant === \"inset\"\n ? \"group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]\"\n : \"group-data-[collapsible=icon]:w-(--sidebar-width-icon)\",\n className,\n )}\n {...props}\n >\n <div\n data-sidebar=\"sidebar\"\n data-slot=\"sidebar-inner\"\n className=\"\n bg-sidebar\n dark:bg-sidebar \n group-data-[variant=floating]:border-sidebar-border \n flex h-full w-full flex-col \n group-data-[variant=floating]:rounded-lg \n group-data-[variant=floating]:border \n group-data-[variant=floating]:shadow-sm\"\n >\n {children}\n </div>\n </div>\n </div>\n );\n}\n\nfunction SidebarTrigger({\n className,\n onClick,\n ...props\n}: React.ComponentProps<typeof Button>) {\n const { toggleSidebar } = useSidebar();\n\n return (\n <Button\n data-sidebar=\"trigger\"\n data-slot=\"sidebar-trigger\"\n variant=\"ghost\"\n size=\"icon\"\n className={cn(\"size-7\", className)}\n onClick={(event) => {\n onClick?.(event);\n toggleSidebar();\n }}\n {...props}\n >\n <PanelLeftIcon />\n <span className=\"sr-only\">Toggle Sidebar</span>\n </Button>\n );\n}\n\nfunction SidebarRail({ className, ...props }: React.ComponentProps<\"button\">) {\n const { toggleSidebar, sidebarWidth, setSidebarWidth, setOpen } =\n useSidebar();\n const isDraggingRef = React.useRef(false);\n const startXRef = React.useRef(0);\n const startWidthRef = React.useRef(0);\n const hasDraggedRef = React.useRef(false);\n const rafRef = React.useRef<number | null>(null);\n\n const handleMouseDown = React.useCallback(\n (e: React.MouseEvent) => {\n e.preventDefault();\n isDraggingRef.current = true;\n hasDraggedRef.current = false;\n startXRef.current = e.clientX;\n startWidthRef.current = sidebarWidth;\n document.body.style.cursor = \"ew-resize\";\n document.body.style.userSelect = \"none\";\n },\n [sidebarWidth],\n );\n\n React.useEffect(() => {\n const handleMouseMove = (e: MouseEvent) => {\n if (!isDraggingRef.current) return;\n\n // Cancel any pending RAF\n if (rafRef.current) {\n cancelAnimationFrame(rafRef.current);\n }\n\n rafRef.current = requestAnimationFrame(() => {\n const deltaX = e.clientX - startXRef.current;\n if (Math.abs(deltaX) > 5) {\n hasDraggedRef.current = true;\n }\n\n const rawWidth = startWidthRef.current + deltaX;\n\n if (rawWidth < SIDEBAR_MIN_WIDTH) {\n setOpen(false);\n } else {\n setOpen(true);\n setSidebarWidth(Math.min(SIDEBAR_MAX_WIDTH, rawWidth));\n }\n });\n };\n\n const handleMouseUp = () => {\n if (!isDraggingRef.current) return;\n isDraggingRef.current = false;\n document.body.style.cursor = \"\";\n document.body.style.userSelect = \"\";\n if (rafRef.current) {\n cancelAnimationFrame(rafRef.current);\n }\n };\n\n document.addEventListener(\"mousemove\", handleMouseMove);\n document.addEventListener(\"mouseup\", handleMouseUp);\n\n return () => {\n document.removeEventListener(\"mousemove\", handleMouseMove);\n document.removeEventListener(\"mouseup\", handleMouseUp);\n };\n }, [setSidebarWidth, setOpen]);\n\n const handleClick = React.useCallback(() => {\n if (!hasDraggedRef.current) {\n toggleSidebar();\n }\n }, [toggleSidebar]);\n\n return (\n <button\n data-sidebar=\"rail\"\n data-slot=\"sidebar-rail\"\n aria-label=\"Toggle Sidebar\"\n tabIndex={-1}\n onMouseDown={handleMouseDown}\n onClick={handleClick}\n title=\"Drag to resize, click to toggle\"\n className={cn(\n \"hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex\",\n \"cursor-ew-resize\",\n \"hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full\",\n \"[[data-side=left][data-collapsible=offcanvas]_&]:-right-2\",\n \"[[data-side=right][data-collapsible=offcanvas]_&]:-left-2\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction SidebarInset({ className, ...props }: React.ComponentProps<\"main\">) {\n return (\n <main\n data-slot=\"sidebar-inset\"\n className={cn(\n \"bg-background relative flex w-full flex-1 flex-col\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction SidebarInput({\n className,\n ...props\n}: React.ComponentProps<typeof Input>) {\n return (\n <Input\n data-slot=\"sidebar-input\"\n data-sidebar=\"input\"\n className={cn(\"bg-background h-8 w-full shadow-none\", className)}\n {...props}\n />\n );\n}\n\nfunction SidebarHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"sidebar-header\"\n data-sidebar=\"header\"\n className={cn(\"flex flex-col gap-2 p-2\", className)}\n {...props}\n />\n );\n}\n\nfunction SidebarFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"sidebar-footer\"\n data-sidebar=\"footer\"\n className={cn(\"mr-2 flex flex-col gap-2 p-2\", className)}\n {...props}\n />\n );\n}\n\nfunction SidebarSeparator({\n className,\n ...props\n}: React.ComponentProps<typeof Separator>) {\n return (\n <Separator\n data-slot=\"sidebar-separator\"\n data-sidebar=\"separator\"\n className={cn(\"bg-sidebar-border mx-2 w-auto\", className)}\n {...props}\n />\n );\n}\n\nfunction SidebarContent({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"sidebar-content\"\n data-sidebar=\"content\"\n className={cn(\n \"mr-2 flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction SidebarGroup({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"sidebar-group\"\n data-sidebar=\"group\"\n className={cn(\"relative flex w-full min-w-0 flex-col p-2\", className)}\n {...props}\n />\n );\n}\n\nfunction SidebarGroupLabel({\n className,\n asChild = false,\n ...props\n}: React.ComponentProps<\"div\"> & { asChild?: boolean }) {\n const Comp = asChild ? Slot : \"div\";\n\n return (\n <Comp\n data-slot=\"sidebar-group-label\"\n data-sidebar=\"group-label\"\n className={cn(\n \"text-sidebar-foreground/70 ring-sidebar-ring outline-hidden flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0\",\n \"group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction SidebarGroupAction({\n className,\n asChild = false,\n ...props\n}: React.ComponentProps<\"button\"> & { asChild?: boolean }) {\n const Comp = asChild ? Slot : \"button\";\n\n return (\n <Comp\n data-slot=\"sidebar-group-action\"\n data-sidebar=\"group-action\"\n className={cn(\n \"text-sidebar-foreground ring-sidebar-ring outline-hidden hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0\",\n // Increases the hit area of the button on mobile.\n \"after:absolute after:-inset-2 md:after:hidden\",\n \"group-data-[collapsible=icon]:hidden\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction SidebarGroupContent({\n className,\n ...props\n}: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"sidebar-group-content\"\n data-sidebar=\"group-content\"\n className={cn(\"w-full text-sm\", className)}\n {...props}\n />\n );\n}\n\nfunction SidebarMenu({ className, ...props }: React.ComponentProps<\"ul\">) {\n return (\n <ul\n data-slot=\"sidebar-menu\"\n data-sidebar=\"menu\"\n className={cn(\"flex w-full min-w-0 list-none flex-col gap-1\", className)}\n {...props}\n />\n );\n}\n\nfunction SidebarMenuItem({ className, ...props }: React.ComponentProps<\"li\">) {\n return (\n <li\n data-slot=\"sidebar-menu-item\"\n data-sidebar=\"menu-item\"\n className={cn(\"group/menu-item relative\", className)}\n {...props}\n />\n );\n}\n\nconst sidebarMenuButtonVariants = cva(\n \"peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0\",\n {\n variants: {\n variant: {\n default: \"hover:bg-sidebar-accent hover:text-sidebar-accent-foreground\",\n outline:\n \"bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]\",\n },\n size: {\n default: \"h-8 text-sm\",\n sm: \"h-7 text-xs\",\n lg: \"h-12 text-sm group-data-[collapsible=icon]:p-0!\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n },\n);\n\nfunction SidebarMenuButton({\n asChild = false,\n isActive = false,\n variant = \"default\",\n size = \"default\",\n tooltip,\n className,\n ...props\n}: React.ComponentProps<\"button\"> & {\n asChild?: boolean;\n isActive?: boolean;\n tooltip?: string | React.ComponentProps<typeof TooltipContent>;\n} & VariantProps<typeof sidebarMenuButtonVariants>) {\n const Comp = asChild ? Slot : \"button\";\n const { isMobile, state } = useSidebar();\n\n const button = (\n <Comp\n data-slot=\"sidebar-menu-button\"\n data-sidebar=\"menu-button\"\n data-size={size}\n data-active={isActive}\n className={cn(sidebarMenuButtonVariants({ variant, size }), className)}\n {...props}\n />\n );\n\n if (!tooltip) {\n return button;\n }\n\n if (typeof tooltip === \"string\") {\n tooltip = {\n children: tooltip,\n };\n }\n\n return (\n <Tooltip>\n <TooltipTrigger asChild>{button}</TooltipTrigger>\n <TooltipContent\n side=\"right\"\n align=\"center\"\n hidden={state !== \"collapsed\" || isMobile}\n {...tooltip}\n />\n </Tooltip>\n );\n}\n\nfunction SidebarMenuAction({\n className,\n asChild = false,\n showOnHover = false,\n ...props\n}: React.ComponentProps<\"button\"> & {\n asChild?: boolean;\n showOnHover?: boolean;\n}) {\n const Comp = asChild ? Slot : \"button\";\n\n return (\n <Comp\n data-slot=\"sidebar-menu-action\"\n data-sidebar=\"menu-action\"\n className={cn(\n \"text-sidebar-foreground ring-sidebar-ring outline-hidden peer-hover/menu-button:text-sidebar-accent-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0\",\n // Increases the hit area of the button on mobile.\n \"after:absolute after:-inset-2 md:after:hidden\",\n \"peer-data-[size=sm]/menu-button:top-1\",\n \"peer-data-[size=default]/menu-button:top-1.5\",\n \"peer-data-[size=lg]/menu-button:top-2.5\",\n \"group-data-[collapsible=icon]:hidden\",\n showOnHover &&\n \"peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction SidebarMenuBadge({\n className,\n ...props\n}: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"sidebar-menu-badge\"\n data-sidebar=\"menu-badge\"\n className={cn(\n \"text-sidebar-foreground pointer-events-none absolute right-1 flex h-5 min-w-5 select-none items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums\",\n \"peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground\",\n \"peer-data-[size=sm]/menu-button:top-1\",\n \"peer-data-[size=default]/menu-button:top-1.5\",\n \"peer-data-[size=lg]/menu-button:top-2.5\",\n \"group-data-[collapsible=icon]:hidden\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction SidebarMenuSkeleton({\n className,\n showIcon = false,\n ...props\n}: React.ComponentProps<\"div\"> & {\n showIcon?: boolean;\n}) {\n // Random width between 50 to 90%.\n const width = React.useMemo(() => {\n return `${Math.floor(Math.random() * 40) + 50}%`;\n }, []);\n\n return (\n <div\n data-slot=\"sidebar-menu-skeleton\"\n data-sidebar=\"menu-skeleton\"\n className={cn(\"flex h-8 items-center gap-2 rounded-md px-2\", className)}\n {...props}\n >\n {showIcon && (\n <Skeleton\n className=\"size-4 rounded-md\"\n data-sidebar=\"menu-skeleton-icon\"\n />\n )}\n <Skeleton\n className=\"max-w-(--skeleton-width) h-4 flex-1\"\n data-sidebar=\"menu-skeleton-text\"\n style={\n {\n \"--skeleton-width\": width,\n } as React.CSSProperties\n }\n />\n </div>\n );\n}\n\nfunction SidebarMenuSub({ className, ...props }: React.ComponentProps<\"ul\">) {\n return (\n <ul\n data-slot=\"sidebar-menu-sub\"\n data-sidebar=\"menu-sub\"\n className={cn(\n \"border-sidebar-border mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l px-2.5 py-0.5\",\n \"group-data-[collapsible=icon]:hidden\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction SidebarMenuSubItem({\n className,\n ...props\n}: React.ComponentProps<\"li\">) {\n return (\n <li\n data-slot=\"sidebar-menu-sub-item\"\n data-sidebar=\"menu-sub-item\"\n className={cn(\"group/menu-sub-item relative\", className)}\n {...props}\n />\n );\n}\n\nfunction SidebarMenuSubButton({\n asChild = false,\n size = \"md\",\n isActive = false,\n className,\n ...props\n}: React.ComponentProps<\"a\"> & {\n asChild?: boolean;\n size?: \"sm\" | \"md\";\n isActive?: boolean;\n}) {\n const Comp = asChild ? Slot : \"a\";\n\n return (\n <Comp\n data-slot=\"sidebar-menu-sub-button\"\n data-sidebar=\"menu-sub-button\"\n data-size={size}\n data-active={isActive}\n className={cn(\n \"text-sidebar-foreground ring-sidebar-ring outline-hidden hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0\",\n \"data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground\",\n size === \"sm\" && \"text-xs\",\n size === \"md\" && \"text-sm\",\n \"group-data-[collapsible=icon]:hidden\",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport {\n Sidebar,\n SidebarContent,\n SidebarFooter,\n SidebarGroup,\n SidebarGroupAction,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarHeader,\n SidebarInput,\n SidebarInset,\n SidebarMenu,\n SidebarMenuAction,\n SidebarMenuBadge,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarMenuSkeleton,\n SidebarMenuSub,\n SidebarMenuSubButton,\n SidebarMenuSubItem,\n SidebarProvider,\n SidebarRail,\n SidebarSeparator,\n SidebarTrigger,\n useSidebar,\n};\n"
11
11
  }
12
12
  ],
13
13
  "dependencies": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aomi-labs/widget-lib",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Shadcn registry for the Aomi widget.",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -8,12 +8,18 @@
8
8
  ".": "./src/index.ts",
9
9
  "./aomi-frame": "./src/components/aomi-frame.tsx",
10
10
  "./aomi-frame-collapsible": "./src/components/aomi-frame-collapsible.tsx",
11
+ "./control-bar": "./src/components/control-bar/index.tsx",
11
12
  "./components/ui/*": "./src/components/ui/*.tsx",
12
13
  "./components/assistant-ui/*": "./src/components/assistant-ui/*.tsx",
14
+ "./components/control-bar/*": "./src/components/control-bar/*.tsx",
13
15
  "./hooks/*": "./src/hooks/*.ts",
14
16
  "./styles.css": "./src/themes/default.css",
15
17
  "./themes/*.css": "./src/themes/*.css"
16
18
  },
19
+ "files": [
20
+ "src",
21
+ "dist"
22
+ ],
17
23
  "dependencies": {
18
24
  "@assistant-ui/react": "^0.11.28",
19
25
  "@assistant-ui/react-markdown": "^0.11.1",
@@ -33,7 +39,7 @@
33
39
  "sonner": "^1.7.4",
34
40
  "vaul": "^1.1.2",
35
41
  "zustand": "^5.0.8",
36
- "@aomi-labs/react": "0.2.0"
42
+ "@aomi-labs/react": "0.2.1"
37
43
  },
38
44
  "devDependencies": {
39
45
  "tsx": "^4.21.0"