import { ActionBarPrimitive, AssistantIf, BranchPickerPrimitive, ComposerPrimitive, ErrorPrimitive, MessagePrimitive, ThreadPrimitive, useAssistantState, useMessage, } from "@assistant-ui/react"; import { ArrowDownIcon, ArrowUpIcon, Brain, CheckCircle2, CheckIcon, ChevronLeftIcon, ChevronRightIcon, CopyIcon, DownloadIcon, Loader2, PencilIcon, RefreshCwIcon, Search, Sparkles, 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 { ChainOfThought, ChainOfThoughtContent, ChainOfThoughtItem, ChainOfThoughtStep, ChainOfThoughtTrigger, } from "@/components/prompt-kit/chain-of-thought"; import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; import { currentUserAtom } from "@/atoms/user/user-query.atoms"; import type { ThinkingStep } from "@/components/tool-ui/deepagent-thinking"; /** * Props for the Thread component */ interface ThreadProps { messageThinkingSteps?: Map; } // Context to pass thinking steps to AssistantMessage import { createContext, useContext } from "react"; const ThinkingStepsContext = createContext>(new Map()); /** * Get icon based on step status and title */ function getStepIcon(status: "pending" | "in_progress" | "completed", title: string) { const titleLower = title.toLowerCase(); if (status === "in_progress") { return ; } if (status === "completed") { return ; } if (titleLower.includes("search") || titleLower.includes("knowledge")) { return ; } if (titleLower.includes("analy") || titleLower.includes("understand")) { return ; } return ; } /** * Chain of thought display component */ const ThinkingStepsDisplay: FC<{ steps: ThinkingStep[] }> = ({ steps }) => { if (steps.length === 0) return null; return (
{steps.map((step) => { const icon = getStepIcon(step.status, step.title); return ( {step.title} {step.items && step.items.length > 0 && ( {step.items.map((item, index) => ( {item} ))} )} ); })}
); }; export const Thread: FC = ({ messageThinkingSteps = new Map() }) => { 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"); }} > SurfSense
{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 AssistantMessageInner: FC = () => { const thinkingStepsMap = useContext(ThinkingStepsContext); // Get the current message ID to look up thinking steps const messageId = useMessage((m) => m.id); const thinkingSteps = thinkingStepsMap.get(messageId) || []; return ( <> {/* Show thinking steps BEFORE the text response */} {thinkingSteps.length > 0 && (
)}
); }; 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 ( / ); };