ai / components /assistant-ui /thread.tsx
Hamed744's picture
Update components/assistant-ui/thread.tsx
cc076c9 verified
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>
);
};
// ==========================================================
// START OF THE SECTION THAT WAS MODIFIED
// ==========================================================
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>
);
};
// ==========================================================
// END OF THE SECTION THAT WAS MODIFIED
// ==========================================================
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>
);
};