From 2b4a4b527e592810a825ae93b23b9a1bd6cfdddb Mon Sep 17 00:00:00 2001 From: cfngc4594 Date: Mon, 24 Mar 2025 11:38:00 +0800 Subject: [PATCH] feat(cli): add all components with shadcn-chat-cli --- src/components/ui/chat/chat-bubble.tsx | 202 ++++++++++++++++++ src/components/ui/chat/chat-input.tsx | 23 ++ src/components/ui/chat/chat-message-list.tsx | 55 +++++ src/components/ui/chat/expandable-chat.tsx | 153 +++++++++++++ .../ui/chat/hooks/useAutoScroll.tsx | 135 ++++++++++++ src/components/ui/chat/message-loading.tsx | 45 ++++ 6 files changed, 613 insertions(+) create mode 100644 src/components/ui/chat/chat-bubble.tsx create mode 100644 src/components/ui/chat/chat-input.tsx create mode 100644 src/components/ui/chat/chat-message-list.tsx create mode 100644 src/components/ui/chat/expandable-chat.tsx create mode 100644 src/components/ui/chat/hooks/useAutoScroll.tsx create mode 100644 src/components/ui/chat/message-loading.tsx diff --git a/src/components/ui/chat/chat-bubble.tsx b/src/components/ui/chat/chat-bubble.tsx new file mode 100644 index 0000000..e22a1e2 --- /dev/null +++ b/src/components/ui/chat/chat-bubble.tsx @@ -0,0 +1,202 @@ +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; +import { cn } from "@/lib/utils"; +import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"; +import MessageLoading from "./message-loading"; +import { Button, ButtonProps } from "../button"; + +// ChatBubble +const chatBubbleVariant = cva( + "flex gap-2 max-w-[60%] items-end relative group", + { + variants: { + variant: { + received: "self-start", + sent: "self-end flex-row-reverse", + }, + layout: { + default: "", + ai: "max-w-full w-full items-center", + }, + }, + defaultVariants: { + variant: "received", + layout: "default", + }, + }, +); + +interface ChatBubbleProps + extends React.HTMLAttributes, + VariantProps {} + +const ChatBubble = React.forwardRef( + ({ className, variant, layout, children, ...props }, ref) => ( +
+ {React.Children.map(children, (child) => + React.isValidElement(child) && typeof child.type !== "string" + ? React.cloneElement(child, { + variant, + layout, + } as React.ComponentProps) + : child, + )} +
+ ), +); +ChatBubble.displayName = "ChatBubble"; + +// ChatBubbleAvatar +interface ChatBubbleAvatarProps { + src?: string; + fallback?: string; + className?: string; +} + +const ChatBubbleAvatar: React.FC = ({ + src, + fallback, + className, +}) => ( + + + {fallback} + +); + +// ChatBubbleMessage +const chatBubbleMessageVariants = cva("p-4", { + variants: { + variant: { + received: + "bg-secondary text-secondary-foreground rounded-r-lg rounded-tl-lg", + sent: "bg-primary text-primary-foreground rounded-l-lg rounded-tr-lg", + }, + layout: { + default: "", + ai: "border-t w-full rounded-none bg-transparent", + }, + }, + defaultVariants: { + variant: "received", + layout: "default", + }, +}); + +interface ChatBubbleMessageProps + extends React.HTMLAttributes, + VariantProps { + isLoading?: boolean; +} + +const ChatBubbleMessage = React.forwardRef< + HTMLDivElement, + ChatBubbleMessageProps +>( + ( + { className, variant, layout, isLoading = false, children, ...props }, + ref, + ) => ( +
+ {isLoading ? ( +
+ +
+ ) : ( + children + )} +
+ ), +); +ChatBubbleMessage.displayName = "ChatBubbleMessage"; + +// ChatBubbleTimestamp +interface ChatBubbleTimestampProps + extends React.HTMLAttributes { + timestamp: string; +} + +const ChatBubbleTimestamp: React.FC = ({ + timestamp, + className, + ...props +}) => ( +
+ {timestamp} +
+); + +// ChatBubbleAction +type ChatBubbleActionProps = ButtonProps & { + icon: React.ReactNode; +}; + +const ChatBubbleAction: React.FC = ({ + icon, + onClick, + className, + variant = "ghost", + size = "icon", + ...props +}) => ( + +); + +interface ChatBubbleActionWrapperProps + extends React.HTMLAttributes { + variant?: "sent" | "received"; + className?: string; +} + +const ChatBubbleActionWrapper = React.forwardRef< + HTMLDivElement, + ChatBubbleActionWrapperProps +>(({ variant, className, children, ...props }, ref) => ( +
+ {children} +
+)); +ChatBubbleActionWrapper.displayName = "ChatBubbleActionWrapper"; + +export { + ChatBubble, + ChatBubbleAvatar, + ChatBubbleMessage, + ChatBubbleTimestamp, + chatBubbleVariant, + chatBubbleMessageVariants, + ChatBubbleAction, + ChatBubbleActionWrapper, +}; diff --git a/src/components/ui/chat/chat-input.tsx b/src/components/ui/chat/chat-input.tsx new file mode 100644 index 0000000..fd7ea16 --- /dev/null +++ b/src/components/ui/chat/chat-input.tsx @@ -0,0 +1,23 @@ +import * as React from "react"; +import { Textarea } from "@/components/ui/textarea"; +import { cn } from "@/lib/utils"; + +interface ChatInputProps extends React.TextareaHTMLAttributes{} + +const ChatInput = React.forwardRef( + ({ className, ...props }, ref) => ( +