"use client"; import "./globals.css"; import styles from "./page.module.css"; import "katex/dist/katex.min.css"; import React, { useEffect, useRef, useState } from "react"; import useSWR from "swr"; import { ArrowCounterClockwise } from "@phosphor-icons/react"; import { Card, CardTitle } from "@/components/ui/card"; import SuggestionCard from "@/app/components/suggestions/suggestionCard"; import SidePanel from "@/app/components/sidePanel/chatHistorySidePanel"; import Loading from "@/app/components/loading/loading"; import { ChatInputArea, ChatOptions } from "@/app/components/chatInputArea/chatInputArea"; import { Suggestion, suggestionsData } from "@/app/components/suggestions/suggestionsData"; import LoginPrompt from "@/app/components/loginPrompt/loginPrompt"; import { isUserSubscribed, useAuthenticatedData, UserConfig, useUserConfig, } from "@/app/common/auth"; import { convertColorToBorderClass } from "@/app/common/colorUtils"; import { getIconFromIconName } from "@/app/common/iconUtils"; import { AgentData } from "@/app/agents/page"; import { createNewConversation } from "./common/chatFunctions"; import { useDebounce, useIsMobileWidth } from "./common/utils"; import { useRouter, useSearchParams } from "next/navigation"; import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; import { AgentCard } from "@/app/components/agentCard/agentCard"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; interface ChatBodyDataProps { chatOptionsData: ChatOptions | null; onConversationIdChange?: (conversationId: string) => void; setUploadedFiles: (files: string[]) => void; isMobileWidth?: boolean; isLoggedIn: boolean; userConfig: UserConfig | null; isLoadingUserConfig: boolean; } function FisherYatesShuffle(array: any[]) { for (let i = array.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; } return array; } function ChatBodyData(props: ChatBodyDataProps) { const [message, setMessage] = useState(""); const [images, setImages] = useState([]); const [processingMessage, setProcessingMessage] = useState(false); const [greeting, setGreeting] = useState(""); const [shuffledOptions, setShuffledOptions] = useState([]); const [hoveredAgent, setHoveredAgent] = useState(null); const debouncedHoveredAgent = useDebounce(hoveredAgent, 500); const [isPopoverOpen, setIsPopoverOpen] = useState(false); const [selectedAgent, setSelectedAgent] = useState("khoj"); const [agentIcons, setAgentIcons] = useState([]); const [agents, setAgents] = useState([]); const chatInputRef = useRef(null); const [showLoginPrompt, setShowLoginPrompt] = useState(false); const router = useRouter(); const searchParams = useSearchParams(); const queryParam = searchParams.get("q"); useEffect(() => { if (queryParam) { setMessage(decodeURIComponent(queryParam)); } }, [queryParam]); useEffect(() => { if (debouncedHoveredAgent) { setIsPopoverOpen(true); } }, [debouncedHoveredAgent]); const onConversationIdChange = props.onConversationIdChange; const agentsFetcher = () => window .fetch("/api/agents") .then((res) => res.json()) .catch((err) => console.log(err)); const { data: agentsData, error } = useSWR("agents", agentsFetcher, { revalidateOnFocus: false, }); const openAgentEditCard = (agentSlug: string) => { router.push(`/agents?agent=${agentSlug}`); }; function shuffleAndSetOptions() { const shuffled = FisherYatesShuffle(suggestionsData); setShuffledOptions(shuffled.slice(0, 3)); } useEffect(() => { if (props.isLoadingUserConfig) return; // Get today's day const today = new Date(); const day = today.getDay(); const timeOfDay = today.getHours() >= 17 || today.getHours() < 4 ? "evening" : today.getHours() >= 12 ? "afternoon" : "morning"; const nameSuffix = props.userConfig?.given_name ? `, ${props.userConfig?.given_name}` : ""; const greetings = [ `What would you like to get done${nameSuffix}?`, `Hey${nameSuffix}! How can I help?`, `Good ${timeOfDay}${nameSuffix}! What's on your mind?`, `Ready to breeze through your ${["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"][day]}?`, `Want help navigating your ${["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"][day]} workload?`, ]; const greeting = greetings[Math.floor(Math.random() * greetings.length)]; setGreeting(greeting); }, [props.isLoadingUserConfig, props.userConfig]); useEffect(() => { if (props.chatOptionsData) { shuffleAndSetOptions(); } }, [props.chatOptionsData]); useEffect(() => { const agents = (agentsData || []).filter((agent) => agent !== null && agent !== undefined); setAgents(agents); // set the first agent, which is always the default agent, as the default for chat setSelectedAgent(agents.length > 1 ? agents[0].slug : "khoj"); // generate colored icons for the available agents const agentIcons = agents.map((agent) => getIconFromIconName(agent.icon, agent.color)!); setAgentIcons(agentIcons); }, [agentsData, props.isMobileWidth]); function shuffleSuggestionsCards() { shuffleAndSetOptions(); } useEffect(() => { const processMessage = async () => { if (message && !processingMessage) { setProcessingMessage(true); try { const newConversationId = await createNewConversation(selectedAgent || "khoj"); onConversationIdChange?.(newConversationId); localStorage.setItem("message", message); if (images.length > 0) { localStorage.setItem("images", JSON.stringify(images)); } window.location.href = `/chat?conversationId=${newConversationId}`; } catch (error) { console.error("Error creating new conversation:", error); setProcessingMessage(false); } setMessage(""); setImages([]); } }; processMessage(); if (message || images.length > 0) { setProcessingMessage(true); } }, [selectedAgent, message, processingMessage, onConversationIdChange]); // Close the agent detail hover card when scroll on agent pane useEffect(() => { const scrollAreaSelector = "[data-radix-scroll-area-viewport]"; const scrollAreaEl = document.querySelector(scrollAreaSelector); const handleScroll = () => { setHoveredAgent(null); setIsPopoverOpen(false); }; scrollAreaEl?.addEventListener("scroll", handleScroll); return () => scrollAreaEl?.removeEventListener("scroll", handleScroll); }, []); function fillArea(link: string, type: string, prompt: string) { if (!link) { let message_str = ""; prompt = prompt.charAt(0).toLowerCase() + prompt.slice(1); if (type === "Online Search") { message_str = "/online " + prompt; } else if (type === "Paint") { message_str = "/image " + prompt; } else { message_str = prompt; } // Get the textarea element const message_area = document.getElementById("message") as HTMLTextAreaElement; if (message_area) { // Update the value directly message_area.value = message_str; setMessage(message_str); } } } return (
{showLoginPrompt && ( )}

{greeting}

{!props.isMobileWidth && (
{agents.map((agent, index) => ( { if (!open) { setHoveredAgent(null); setIsPopoverOpen(false); } }} > openAgentEditCard(agent.slug)} onClick={() => { setSelectedAgent(agent.slug); chatInputRef.current?.focus(); setHoveredAgent(null); setIsPopoverOpen(false); }} onMouseEnter={() => setHoveredAgent(agent.slug)} onMouseLeave={() => { setHoveredAgent(null); setIsPopoverOpen(false); }} > {agentIcons[index]} {agent.name} { setHoveredAgent(null); setIsPopoverOpen(false); }} > {}} modelOptions={[]} inputToolOptions={{}} outputModeOptions={{}} /> ))}
)}
{!props.isMobileWidth && (
setMessage(message)} sendImage={(image) => setImages((prevImages) => [...prevImages, image])} sendDisabled={processingMessage} chatOptionsData={props.chatOptionsData} conversationId={null} isMobileWidth={props.isMobileWidth} setUploadedFiles={props.setUploadedFiles} agentColor={agents.find((agent) => agent.slug === selectedAgent)?.color} ref={chatInputRef} />
)}
{shuffledOptions.map((suggestion, index) => (
{ if (props.isLoggedIn) { fillArea( suggestion.link, suggestion.type, suggestion.description, ); } else { event.preventDefault(); event.stopPropagation(); setShowLoginPrompt(true); } }} >
))}
{props.isMobileWidth && ( <>
{agentIcons.map((icon, index) => ( openAgentEditCard(agents[index].slug) } onClick={() => { setSelectedAgent(agents[index].slug); chatInputRef.current?.focus(); }} > {icon} {agents[index].name} ))}
setMessage(message)} sendImage={(image) => setImages((prevImages) => [...prevImages, image])} sendDisabled={processingMessage} chatOptionsData={props.chatOptionsData} conversationId={null} isMobileWidth={props.isMobileWidth} setUploadedFiles={props.setUploadedFiles} agentColor={agents.find((agent) => agent.slug === selectedAgent)?.color} ref={chatInputRef} />
)}
); } export default function Home() { const [chatOptionsData, setChatOptionsData] = useState(null); const [isLoading, setLoading] = useState(true); const [conversationId, setConversationID] = useState(null); const [uploadedFiles, setUploadedFiles] = useState([]); const isMobileWidth = useIsMobileWidth(); const { userConfig: initialUserConfig, isLoadingUserConfig } = useUserConfig(true); const [userConfig, setUserConfig] = useState(null); const authenticatedData = useAuthenticatedData(); const handleConversationIdChange = (newConversationId: string) => { setConversationID(newConversationId); }; useEffect(() => { setUserConfig(initialUserConfig); }, [initialUserConfig]); useEffect(() => { fetch("/api/chat/options") .then((response) => response.json()) .then((data: ChatOptions) => { setLoading(false); if (data) { setChatOptionsData(data); } }) .catch((err) => { console.error(err); return; }); }, []); if (isLoading) { return ; } return (
Khoj AI - Your Second Brain
); }