| import { |
| ArrowDownIcon, |
| ArrowUpIcon, |
| CheckIcon, |
| ChevronLeftIcon, |
| ChevronRightIcon, |
| CopyIcon, |
| PencilIcon, |
| RefreshCwIcon, |
| Square, |
| } from "lucide-react"; |
|
|
| import { |
| ActionBarPrimitive, |
| BranchPickerPrimitive, |
| ComposerPrimitive, |
| ErrorPrimitive, |
| MessagePrimitive, |
| ThreadPrimitive, |
| } from "@assistant-ui/react"; |
|
|
| import type { FC } from "react"; |
| import { LazyMotion, MotionConfig, domAnimation } from "motion/react"; |
| import * as m from "motion/react-m"; |
|
|
| import { Button } from "@/components/ui/button"; |
| import { MarkdownText } from "@/components/assistant-ui/markdown-text"; |
| import { ToolFallback } from "@/components/assistant-ui/tool-fallback"; |
| import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button"; |
| import { |
| ComposerAddAttachment, |
| ComposerAttachments, |
| UserMessageAttachments, |
| } from "@/components/assistant-ui/attachment"; |
|
|
| import { cn } from "@/lib/utils"; |
|
|
| export const Thread: FC = () => { |
| return ( |
| <LazyMotion features={domAnimation}> |
| <MotionConfig reducedMotion="user"> |
| <ThreadPrimitive.Root |
| className="aui-root aui-thread-root @container flex h-full flex-col bg-background" |
| style={{ |
| ["--thread-max-width" as string]: "44rem", |
| }} |
| > |
| <ThreadPrimitive.Viewport className="aui-thread-viewport relative flex flex-1 flex-col overflow-x-auto overflow-y-scroll px-4"> |
| <ThreadPrimitive.If empty> |
| <ThreadWelcome /> |
| </ThreadPrimitive.If> |
| |
| <ThreadPrimitive.Messages |
| components={{ |
| UserMessage, |
| EditComposer, |
| AssistantMessage, |
| }} |
| /> |
| |
| <ThreadPrimitive.If empty={false}> |
| <div className="aui-thread-viewport-spacer min-h-8 grow" /> |
| </ThreadPrimitive.If> |
| |
| <Composer /> |
| </ThreadPrimitive.Viewport> |
| </ThreadPrimitive.Root> |
| </MotionConfig> |
| </LazyMotion> |
| ); |
| }; |
|
|
| const ThreadScrollToBottom: FC = () => { |
| return ( |
| <ThreadPrimitive.ScrollToBottom asChild> |
| <TooltipIconButton |
| tooltip="رفتن به پایین" |
| variant="outline" |
| className="aui-thread-scroll-to-bottom absolute -top-12 z-10 self-center rounded-full p-4 disabled:invisible dark:bg-background dark:hover:bg-accent" |
| > |
| <ArrowDownIcon /> |
| </TooltipIconButton> |
| </ThreadPrimitive.ScrollToBottom> |
| ); |
| }; |
|
|
| const ThreadWelcome: FC = () => { |
| return ( |
| <div className="aui-thread-welcome-root mx-auto my-auto flex w-full max-w-[var(--thread-max-width)] flex-grow flex-col"> |
| <div className="aui-thread-welcome-center flex w-full flex-grow flex-col items-center justify-center"> |
| <div className="aui-thread-welcome-message flex size-full flex-col justify-center px-8"> |
| <m.div |
| initial={{ opacity: 0, y: 10 }} |
| animate={{ opacity: 1, y: 0 }} |
| exit={{ opacity: 0, y: 10 }} |
| className="aui-thread-welcome-message-motion-1 text-2xl font-semibold" |
| > |
| سلام! |
| </m.div> |
| <m.div |
| initial={{ opacity: 0, y: 10 }} |
| animate={{ opacity: 1, y: 0 }} |
| exit={{ opacity: 0, y: 10 }} |
| transition={{ delay: 0.1 }} |
| className="aui-thread-welcome-message-motion-2 text-2xl text-muted-foreground/65" |
| > |
| چطور میتوانم کمکتان کنم؟ |
| </m.div> |
| </div> |
| </div> |
| <ThreadSuggestions /> |
| </div> |
| ); |
| }; |
|
|
| const ThreadSuggestions: FC = () => { |
| return ( |
| <div className="aui-thread-welcome-suggestions grid w-full gap-2 pb-4 @md:grid-cols-2"> |
| {[ |
| { |
| title: "درباره خودت بگو", |
| label: "", |
| action: "درباره خودت برای من توضیح بده", |
| }, |
| { |
| title: "قلابهای ریاکت را توضیح بده", |
| label: "مانند useState و useEffect", |
| action: "قلابهای ریاکت مانند useState و useEffect را توضیح بده", |
| }, |
| { |
| title: "یک کوئری SQL بنویس", |
| label: "برای پیدا کردن بهترین مشتریان", |
| action: "یک کوئری SQL برای پیدا کردن بهترین مشتریان بنویس", |
| }, |
| { |
| title: "یک برنامه غذایی بساز", |
| label: "برای کاهش وزن سالم", |
| action: "یک برنامه غذایی برای کاهش وزن سالم ایجاد کن", |
| }, |
| ].map((suggestedAction, index) => ( |
| <m.div |
| initial={{ opacity: 0, y: 20 }} |
| animate={{ opacity: 1, y: 0 }} |
| exit={{ opacity: 0, y: 20 }} |
| transition={{ delay: 0.05 * index }} |
| key={`suggested-action-${suggestedAction.title}-${index}`} |
| className="aui-thread-welcome-suggestion-display [&:nth-child(n+3)]:hidden @md:[&:nth-child(n+3)]:block" |
| > |
| <ThreadPrimitive.Suggestion |
| prompt={suggestedAction.action} |
| send |
| asChild |
| > |
| <Button |
| variant="ghost" |
| className="aui-thread-welcome-suggestion h-auto w-full flex-1 flex-wrap items-start justify-start gap-1 rounded-3xl border px-5 py-4 text-right text-sm @md:flex-col dark:hover:bg-accent/60" |
| aria-label={suggestedAction.action} |
| > |
| <span className="aui-thread-welcome-suggestion-text-1 font-medium"> |
| {suggestedAction.title} |
| </span> |
| <span className="aui-thread-welcome-suggestion-text-2 text-muted-foreground"> |
| {suggestedAction.label} |
| </span> |
| </Button> |
| </ThreadPrimitive.Suggestion> |
| </m.div> |
| ))} |
| </div> |
| ); |
| }; |
|
|
| const Composer: FC = () => { |
| return ( |
| <div className="aui-composer-wrapper sticky bottom-0 mx-auto flex w-full max-w-[var(--thread-max-width)] flex-col gap-4 overflow-visible rounded-t-3xl bg-background pb-4 md:pb-6"> |
| <ThreadScrollToBottom /> |
| <ComposerPrimitive.Root className="aui-composer-root relative flex w-full flex-col rounded-3xl border border-border bg-muted 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)] dark:border-muted-foreground/15"> |
| <ComposerAttachments /> |
| <ComposerPrimitive.Input |
| placeholder="پیام خود را بنویسید..." |
| className="aui-composer-input mb-1 max-h-32 min-h-16 w-full resize-none bg-transparent px-3.5 pt-1.5 pb-3 text-base outline-none placeholder:text-muted-foreground focus:outline-primary" |
| rows={1} |
| autoFocus |
| aria-label="ورودی پیام" |
| /> |
| <ComposerAction /> |
| </ComposerPrimitive.Root> |
| </div> |
| ); |
| }; |
|
|
| const ComposerAction: FC = () => { |
| return ( |
| <div className="aui-composer-action-wrapper relative mx-1 mt-2 mb-2 flex items-center justify-between"> |
| <ComposerAddAttachment /> |
| |
| <ThreadPrimitive.If running={false}> |
| <ComposerPrimitive.Send asChild> |
| <TooltipIconButton |
| tooltip="ارسال پیام" |
| side="bottom" |
| type="submit" |
| variant="default" |
| size="icon" |
| className="aui-composer-send size-[34px] rounded-full p-1" |
| aria-label="ارسال پیام" |
| > |
| <ArrowUpIcon className="aui-composer-send-icon size-5" /> |
| </TooltipIconButton> |
| </ComposerPrimitive.Send> |
| </ThreadPrimitive.If> |
| |
| <ThreadPrimitive.If running> |
| <ComposerPrimitive.Cancel asChild> |
| <Button |
| type="button" |
| variant="default" |
| size="icon" |
| className="aui-composer-cancel size-[34px] rounded-full border border-muted-foreground/60 hover:bg-primary/75 dark:border-muted-foreground/90" |
| aria-label="توقف تولید پاسخ" |
| > |
| <Square className="aui-composer-cancel-icon size-3.5 fill-white dark:fill-black" /> |
| </Button> |
| </ComposerPrimitive.Cancel> |
| </ThreadPrimitive.If> |
| </div> |
| ); |
| }; |
|
|
| const MessageError: FC = () => { |
| return ( |
| <MessagePrimitive.Error> |
| <ErrorPrimitive.Root className="aui-message-error-root mt-2 rounded-md border border-destructive bg-destructive/10 p-3 text-sm text-destructive dark:bg-destructive/5 dark:text-red-200"> |
| <ErrorPrimitive.Message className="aui-message-error-message line-clamp-2" /> |
| </ErrorPrimitive.Root> |
| </MessagePrimitive.Error> |
| ); |
| }; |
|
|
| const AssistantMessage: FC = () => { |
| return ( |
| <MessagePrimitive.Root asChild> |
| <div |
| className="aui-assistant-message-root relative mx-auto w-full max-w-[var(--thread-max-width)] animate-in py-4 duration-150 ease-out fade-in slide-in-from-bottom-1 last:mb-24" |
| data-role="assistant" |
| > |
| <div className="aui-assistant-message-content mx-2 leading-7 break-words text-foreground"> |
| <MessagePrimitive.Parts |
| components={{ |
| Text: MarkdownText, |
| tools: { Fallback: ToolFallback }, |
| }} |
| /> |
| <MessageError /> |
| </div> |
| |
| <div className="aui-assistant-message-footer mt-2 ms-2 flex"> |
| <BranchPicker /> |
| <AssistantActionBar /> |
| </div> |
| </div> |
| </MessagePrimitive.Root> |
| ); |
| }; |
|
|
| const AssistantActionBar: FC = () => { |
| return ( |
| <ActionBarPrimitive.Root |
| hideWhenRunning |
| autohide="not-last" |
| autohideFloat="single-branch" |
| className="aui-assistant-action-bar-root col-start-3 row-start-2 -me-1 flex gap-1 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" |
| > |
| <ActionBarPrimitive.Copy asChild> |
| <TooltipIconButton tooltip="کپی"> |
| <MessagePrimitive.If copied> |
| <CheckIcon /> |
| </MessagePrimitive.If> |
| <MessagePrimitive.If copied={false}> |
| <CopyIcon /> |
| </MessagePrimitive.If> |
| </TooltipIconButton> |
| </ActionBarPrimitive.Copy> |
| <ActionBarPrimitive.Reload asChild> |
| <TooltipIconButton tooltip="تلاش مجدد"> |
| <RefreshCwIcon /> |
| </TooltipIconButton> |
| </ActionBarPrimitive.Reload> |
| </ActionBarPrimitive.Root> |
| ); |
| }; |
|
|
| |
| |
| |
| const UserMessage: FC = () => { |
| return ( |
| <MessagePrimitive.Root asChild> |
| <div |
| className="aui-user-message-root mx-auto grid w-full max-w-[var(--thread-max-width)] animate-in auto-rows-auto grid-cols-[auto_auto] justify-end gap-y-2 px-2 py-4 duration-150 ease-out fade-in slide-in-from-bottom-1 first:mt-3 last:mb-5 [&:where(>*)]:col-start-2" |
| data-role="user" |
| > |
| <UserMessageAttachments /> |
| |
| <div className="aui-user-message-content-wrapper relative col-start-2 min-w-0"> |
| <div className="aui-user-message-content rounded-3xl bg-muted px-5 py-2.5 break-words text-foreground"> |
| <MessagePrimitive.Parts /> |
| </div> |
| <div className="aui-user-action-bar-wrapper absolute top-1/2 right-0 translate-x-full -translate-y-1/2 ps-2"> |
| <UserActionBar /> |
| </div> |
| </div> |
| |
| <BranchPicker className="aui-user-branch-picker col-span-full col-start-1 row-start-3 -ms-1 justify-end" /> |
| </div> |
| </MessagePrimitive.Root> |
| ); |
| }; |
| |
| |
| |
|
|
| const UserActionBar: FC = () => { |
| return ( |
| <ActionBarPrimitive.Root |
| hideWhenRunning |
| autohide="not-last" |
| className="aui-user-action-bar-root flex flex-col items-start" |
| > |
| <ActionBarPrimitive.Edit asChild> |
| <TooltipIconButton tooltip="ویرایش" className="aui-user-action-edit p-4"> |
| <PencilIcon /> |
| </TooltipIconButton> |
| </ActionBarPrimitive.Edit> |
| </ActionBarPrimitive.Root> |
| ); |
| }; |
|
|
| const EditComposer: FC = () => { |
| return ( |
| <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"> |
| <ComposerPrimitive.Root className="aui-edit-composer-root me-auto flex w-full max-w-7/8 flex-col rounded-xl bg-muted"> |
| <ComposerPrimitive.Input |
| className="aui-edit-composer-input flex min-h-[60px] w-full resize-none bg-transparent p-4 text-foreground outline-none" |
| autoFocus |
| /> |
| |
| <div className="aui-edit-composer-footer mx-3 mb-3 flex items-center justify-center gap-2 self-end"> |
| <ComposerPrimitive.Cancel asChild> |
| <Button variant="ghost" size="sm" aria-label="لغو ویرایش"> |
| انصراف |
| </Button> |
| </ComposerPrimitive.Cancel> |
| <ComposerPrimitive.Send asChild> |
| <Button size="sm" aria-label="بهروزرسانی پیام"> |
| بهروزرسانی |
| </Button> |
| </ComposerPrimitive.Send> |
| </div> |
| </ComposerPrimitive.Root> |
| </div> |
| ); |
| }; |
|
|
| const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({ |
| className, |
| ...rest |
| }) => { |
| return ( |
| <BranchPickerPrimitive.Root |
| hideWhenSingleBranch |
| className={cn( |
| "aui-branch-picker-root ms-2 -me-2 inline-flex items-center text-xs text-muted-foreground", |
| className, |
| )} |
| {...rest} |
| > |
| <BranchPickerPrimitive.Previous asChild> |
| <TooltipIconButton tooltip="قبلی"> |
| <ChevronRightIcon /> |
| </TooltipIconButton> |
| </BranchPickerPrimitive.Previous> |
| <span className="aui-branch-picker-state font-medium"> |
| <BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count /> |
| </span> |
| <BranchPickerPrimitive.Next asChild> |
| <TooltipIconButton tooltip="بعدی"> |
| <ChevronLeftIcon /> |
| </TooltipIconButton> |
| </BranchPickerPrimitive.Next> |
| </BranchPickerPrimitive.Root> |
| ); |
| }; |