import {
ActionBarPrimitive,
AssistantIf,
BranchPickerPrimitive,
ComposerPrimitive,
ErrorPrimitive,
MessagePrimitive,
ThreadPrimitive,
useAssistantState,
} from "@assistant-ui/react";
import {
ArrowDownIcon,
ArrowUpIcon,
CheckIcon,
ChevronLeftIcon,
ChevronRightIcon,
CopyIcon,
DownloadIcon,
Loader2,
PencilIcon,
RefreshCwIcon,
SquareIcon,
} from "lucide-react";
import Image from "next/image";
import type { FC } from "react";
import { useAtomValue } from "jotai";
import {
ComposerAddAttachment,
ComposerAttachments,
UserMessageAttachments,
} from "@/components/assistant-ui/attachment";
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 { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { currentUserAtom } from "@/atoms/user/user-query.atoms";
export const Thread: FC = () => {
return (
thread.isEmpty}>
!thread.isEmpty}>
);
};
const ThreadScrollToBottom: FC = () => {
return (
);
};
const getTimeBasedGreeting = (userEmail?: string): string => {
const hour = new Date().getHours();
// Extract first name from email if available
const firstName = userEmail
? userEmail.split("@")[0].split(".")[0].charAt(0).toUpperCase() +
userEmail.split("@")[0].split(".")[0].slice(1)
: null;
// Array of greeting variations for each time period
const morningGreetings = [
"Good morning",
"Rise and shine",
"Morning",
"Hey there",
"Welcome back",
];
const afternoonGreetings = [
"Good afternoon",
"Afternoon",
"Hey there",
"Welcome back",
"Hope you're having a great day",
];
const eveningGreetings = [
"Good evening",
"Evening",
"Hey there",
"Welcome back",
"Hope you had a great day",
];
const nightGreetings = [
"Late night",
"Still up",
"Hey there",
"Welcome back",
"Burning the midnight oil",
];
// Select a random greeting based on time
let greeting: string;
if (hour < 12) {
greeting = morningGreetings[Math.floor(Math.random() * morningGreetings.length)];
} else if (hour < 17) {
greeting = afternoonGreetings[Math.floor(Math.random() * afternoonGreetings.length)];
} else if (hour < 21) {
greeting = eveningGreetings[Math.floor(Math.random() * eveningGreetings.length)];
} else {
greeting = nightGreetings[Math.floor(Math.random() * nightGreetings.length)];
}
// Add personalization with first name if available
if (firstName) {
return `${greeting}, ${firstName}!`;
}
return `${greeting}!`;
};
const ThreadWelcome: FC = () => {
const { data: user } = useAtomValue(currentUserAtom);
return (
{/* Greeting positioned near the composer */}
{/** biome-ignore lint/a11y/noStaticElementInteractions: wrong lint error, this is a workaround to fix the lint error */}
{
const rect = e.currentTarget.getBoundingClientRect();
const x = (e.clientX - rect.left - rect.width / 2) / 3;
const y = (e.clientY - rect.top - rect.height / 2) / 3;
e.currentTarget.style.setProperty("--mag-x", `${x}px`);
e.currentTarget.style.setProperty("--mag-y", `${y}px`);
}}
onMouseLeave={(e) => {
e.currentTarget.style.setProperty("--mag-x", "0px");
e.currentTarget.style.setProperty("--mag-y", "0px");
}}
>
{getTimeBasedGreeting(user?.email)}
{/* Composer centered in the middle of the screen */}
);
};
const Composer: FC = () => {
return (
);
};
const ComposerAction: FC = () => {
// Check if any attachments are still being processed (running AND progress < 100)
// When progress is 100, processing is done but waiting for send()
const hasProcessingAttachments = useAssistantState(({ composer }) =>
composer.attachments?.some((att) => {
const status = att.status;
if (status?.type !== "running") return false;
const progress = (status as { type: "running"; progress?: number }).progress;
return progress === undefined || progress < 100;
})
);
// Check if composer text is empty
const isComposerEmpty = useAssistantState(({ composer }) => {
const text = composer.text?.trim() || "";
return text.length === 0;
});
const isSendDisabled = hasProcessingAttachments || isComposerEmpty;
return (
{/* Show processing indicator when attachments are being processed */}
{hasProcessingAttachments && (
Processing...
)}
!thread.isRunning}>
thread.isRunning}>
);
};
const MessageError: FC = () => {
return (
);
};
const AssistantMessage: FC = () => {
return (
);
};
const AssistantActionBar: FC = () => {
return (
message.isCopied}>
!message.isCopied}>
);
};
const UserMessage: FC = () => {
return (
);
};
const UserActionBar: FC = () => {
return (
);
};
const EditComposer: FC = () => {
return (
);
};
const BranchPicker: FC = ({ className, ...rest }) => {
return (
/
);
};