/* eslint-disable @next/next/no-img-element */ "use client"; import React, { useRef, useCallback, useState, useEffect, ReactNode, useMemo } from 'react'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import { useChat } from 'ai/react'; import { ToolInvocation } from 'ai'; import { toast } from 'sonner'; import { motion, AnimatePresence } from 'framer-motion'; import Image from 'next/image'; import { suggestQuestions, Message } from './actions'; import { SearchIcon, ChevronDown, FastForward, Sparkles, ArrowRight, Globe, AlignLeft } from 'lucide-react'; import { HoverCard, HoverCardContent, HoverCardTrigger, } from "@/components/ui/hover-card"; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, } from "@/components/ui/accordion"; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; export default function Home() { const inputRef = useRef(null); const [lastSubmittedQuery, setLastSubmittedQuery] = useState(""); const [hasSubmitted, setHasSubmitted] = useState(false); const [isAnimating, setIsAnimating] = useState(false); const bottomRef = useRef(null); const [suggestedQuestions, setSuggestedQuestions] = useState([]); const [isModelSelectorOpen, setIsModelSelectorOpen] = useState(false); const [selectedModel, setSelectedModel] = useState('Speed'); const [showExamples, setShowExamples] = useState(false) const { isLoading, input, messages, setInput, append, handleSubmit, setMessages } = useChat({ api: '/api/chat', body: { model: selectedModel === 'Speed' ? 'gpt-4o-mini' : selectedModel === 'Quality (GPT)' ? 'gpt-4o' : 'claude-3-5-sonnet-20240620', }, maxToolRoundtrips: 1, onFinish: async (message, { finishReason }) => { if (finishReason === 'stop') { const newHistory: Message[] = [{ role: "user", content: lastSubmittedQuery, }, { role: "assistant", content: message.content }]; const { questions } = await suggestQuestions(newHistory); setSuggestedQuestions(questions); } setIsAnimating(false); }, onError: (error) => { console.error("Chat error:", error); toast.error("An error occurred. Please try again."); }, }); const models = [ { name: 'Speed', description: 'High speed, but lower quality.', details: '(OpenAI/GPT-4o-mini)', icon: FastForward }, { name: 'Quality (GPT)', description: 'Speed and quality, balanced.', details: '(OpenAI/GPT | Optimized)', icon: Sparkles }, { name: 'Quality (Claude)', description: 'High quality generation.', details: '(Anthropic/Claude-3.5-Sonnet)', icon: Sparkles }, ]; const renderToolInvocation = (toolInvocation: ToolInvocation, index: number) => { const args = JSON.parse(JSON.stringify(toolInvocation.args)); const result = 'result' in toolInvocation ? JSON.parse(JSON.stringify(toolInvocation.result)) : null; return (
{!result ? (
Searching the web...
{[0, 1, 2].map((index) => ( ))}
) :

Web Search Results Found

{result && ( {result.results.length} results )}
{args?.query && ( {args.query} )} {result && (
{result.results.map((item: any, itemIndex: number) => ( Favicon {item.title}

{item.content}

))}
)}
}
); }; const renderCitation = (citationText: string, citationLink: string, index: number) => { const faviconUrl = `https://www.google.com/s2/favicons?domain=${new URL(citationLink).hostname}`; return ( {index + 1} Favicon {citationLink} ); }; const CitationComponent: React.FC<{ href: string; children: ReactNode; index: number }> = React.memo(({ href, children, index }) => { const citationText = Array.isArray(children) ? children[0] : children; return renderCitation(citationText as string, href, index); }); CitationComponent.displayName = "CitationComponent"; const MarkdownRenderer: React.FC<{ content: string }> = React.memo(({ content }) => { const citationLinks = useMemo(() => { return [...content.matchAll(/\[([^\]]+)\]\(([^)]+)\)/g)].map(([_, text, link]) => ({ text, link, })); }, [content]); return ( { const index = citationLinks.findIndex((link: { link: string | undefined; }) => link.link === href); return index !== -1 ? ( {children} ) : ( {children} ); }, }} > {content} ); }); MarkdownRenderer.displayName = "MarkdownRenderer"; useEffect(() => { if (bottomRef.current) { bottomRef.current.scrollIntoView({ behavior: "smooth" }); } }, [messages, suggestedQuestions]); const handleExampleClick = useCallback(async (query: string) => { setLastSubmittedQuery(query.trim()); setHasSubmitted(true); setSuggestedQuestions([]); setIsAnimating(true); await append({ content: query.trim(), role: 'user' }); }, [append]); const handleFormSubmit = useCallback((e: React.FormEvent) => { e.preventDefault(); if (input.trim()) { setMessages([]); setLastSubmittedQuery(input.trim()); setHasSubmitted(true); setIsAnimating(true); setSuggestedQuestions([]); handleSubmit(e); } else { toast.error("Please enter a search query."); } }, [input, setMessages, handleSubmit]); const handleSuggestedQuestionClick = useCallback(async (question: string) => { setMessages([]); setLastSubmittedQuery(question.trim()); setHasSubmitted(true); setSuggestedQuestions([]); setIsAnimating(true); await append({ content: question.trim(), role: 'user' }); }, [append, setMessages]); const exampleQueries = [ "Meta Llama 3.1 405B", "Latest on Paris Olympics", "What is Github Models?", "OpenAI GPT-4o mini" ]; return (

MiniPerplx

{!hasSubmitted &&

A minimalistic AI-powered search engine that helps you find information on the internet.

}
{!hasSubmitted && (
{isModelSelectorOpen && (
{models.map((model) => ( ))}
)}
setInput(e.target.value)} disabled={isLoading} className="w-full min-h-12 py-3 px-4 bg-muted border border-input rounded-full pr-12 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-200 focus-visible:ring-offset-2 text-sm sm:text-base" onFocus={() => setShowExamples(true)} onBlur={() => setShowExamples(false)} />
{exampleQueries.map((message, index) => ( ))}
)}
{hasSubmitted && ( setIsAnimating(false)} >
{lastSubmittedQuery} {selectedModel === 'Speed' && } {selectedModel === 'Quality (GPT)' && } {selectedModel === 'Quality (Claude)' && } {selectedModel}
)}
{messages.map((message, index) => (
{message.role === 'assistant' && message.content && (

Answer

)} {message.toolInvocations?.map((toolInvocation: ToolInvocation, toolIndex: number) => (
{renderToolInvocation(toolInvocation, toolIndex)}
))}
))} {suggestedQuestions.length > 0 && (

Suggested questions

{suggestedQuestions.map((question, index) => ( ))}
)}
{hasSubmitted && !isAnimating && (
setInput(e.target.value)} disabled={isLoading} className="w-full min-h-12 py-3 px-4 bg-muted border border-input rounded-full pr-12 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-200 focus-visible:ring-offset-2 text-sm sm:text-base" />
)}
); }