/* eslint-disable @next/next/no-img-element */ "use client"; import React, { useRef, useCallback, useState, useEffect, ReactNode } from 'react'; import { useChat } from 'ai/react'; import { ToolInvocation } from 'ai'; import { toast } from 'sonner'; import { motion, AnimatePresence } from 'framer-motion'; import { SearchIcon, LinkIcon, Check, Loader2, ChevronDown, ChevronUp, FastForward, Sparkles, ArrowRight } from 'lucide-react'; import { HoverCard, HoverCardContent, HoverCardTrigger, } from "@/components/ui/hover-card"; 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'; import { ScrollArea } from '@/components/ui/scroll-area'; export default function Home() { const inputRef = useRef(null); const [lastSubmittedQuery, setLastSubmittedQuery] = useState(""); const [hasSubmitted, setHasSubmitted] = useState(false); const bottomRef = useRef(null); const [showToolResults, setShowToolResults] = useState<{ [key: number]: boolean }>({}); const [isModelSelectorOpen, setIsModelSelectorOpen] = useState(false); const [selectedModel, setSelectedModel] = useState('Speed'); 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, 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 ? : } {result ? 'Used' : 'Using'} {toolInvocation.toolName === 'web_search' ? 'Web Search' : toolInvocation.toolName}
{args?.query && ( {args.query} )} {showToolResults[index] && result && ( {result.results.map((item: any, itemIndex: number) => (

{item.title}

{item.content}

{item.url}
))}
)}
); }; const renderCitation = (citationText: string, citationLink: string, index: number) => { const faviconUrl = `https://www.google.com/s2/favicons?domain=${new URL(citationLink).hostname}`; return ( {citationText} [{index + 1}] Favicon {citationLink} ); }; const renderMarkdown = (content: string) => { const citationRegex = /\[([^\]]+)\]\(([^)]+)\)/g; const boldRegex = /\*\*(.*?)\*\*/g; // Bold const italicRegex = /\*(.*?)\*/g; // Italic const unorderedListRegex = /^-\s+(.*)$/gm; // Unordered list const orderedListRegex = /^\d+\.\s+(.*)$/gm; // Ordered list const headingRegex = /^(#{1,6})\s+(.*)$/gm; // Headings const parts: (string | ReactNode)[] = []; let lastIndex = 0; let match; // Replace bold and italic content = content .replace(boldRegex, '$1') .replace(italicRegex, '$1'); // Replace unordered and ordered lists content = content .replace(unorderedListRegex, '
  • $1
  • ') .replace(orderedListRegex, '
  • $1
  • '); // Replace headings content = content.replace(headingRegex, (match, hashes, headingText) => { const level = hashes.length; // Determine heading level return `${headingText}`; }); // Add list wrapping const wrappedContent = content.split(/()/g).map((item, index) => { if (item.startsWith('${item}`; } return item; }).join(''); // Parse citations and add to parts while ((match = citationRegex.exec(wrappedContent)) !== null) { // Add text before the citation if (match.index > lastIndex) { parts.push(wrappedContent.slice(lastIndex, match.index)); } const citationText = match[1]; const citationLink = match[2]; parts.push(renderCitation(citationText, citationLink, parts.length)); // Adjusting index for key lastIndex = match.index + match[0].length; } // Add any remaining text after the last citation if (lastIndex < wrappedContent.length) { parts.push(wrappedContent.slice(lastIndex)); } return ( {parts.map((part, index) => { if (typeof part === 'string') { const lines = part.split('\n'); return lines.map((line, lineIndex) => ( {lineIndex < lines.length - 1 &&
    }
    )); } return {part}; // Render citations })}
    ); }; useEffect(() => { if (bottomRef.current) { bottomRef.current.scrollIntoView({ behavior: "smooth" }); } }, [messages]); const handleExampleClick = useCallback(async (query: string) => { setLastSubmittedQuery(query.trim()); setHasSubmitted(true); await append({ content: query.trim(), role: 'user' }); }, [append]); const handleFormSubmit = useCallback((e: React.FormEvent) => { e.preventDefault(); if (input.trim()) { setMessages([]); setLastSubmittedQuery(input.trim()); handleSubmit(e); setHasSubmitted(true); setShowToolResults({}); } else { toast.error("Please enter a search query."); } }, [input, setMessages, handleSubmit]); const exampleQueries = [ "Best programming languages in 2024", "How to build a responsive website", "Latest trends in AI technology", "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 h-10 py-3 px-4 bg-gray-100 rounded-full pr-12 focus:outline-none focus:ring-2 focus:ring-green-500 text-sm sm:text-base" />
    {exampleQueries.map((query, index) => ( ))}
    )}
    {hasSubmitted && (

    {lastSubmittedQuery}

    {selectedModel === 'Speed' && } {selectedModel === 'Quality (GPT)' && } {selectedModel === 'Quality (Claude)' && } {selectedModel}
    )}
    {messages.length > 0 && (
    {messages.map((message, index) => ( {message.role === 'assistant' && message.content && (

    Answer

    {renderMarkdown(message.content)}
    )} {message.toolInvocations?.map((toolInvocation: ToolInvocation, toolIndex: number) => ( {renderToolInvocation(toolInvocation, toolIndex)} ))}
    ))}
    )}
    {hasSubmitted && (
    setInput(e.target.value)} disabled={isLoading} className="w-full h-10 py-3 px-4 bg-gray-100 rounded-full pr-12 focus:outline-none focus:ring-2 focus:ring-green-500 text-sm" />
    )}
    ); }