diff --git a/app/actions.ts b/app/actions.ts index b8180cb..2e8110c 100644 --- a/app/actions.ts +++ b/app/actions.ts @@ -1,13 +1,9 @@ 'use server'; import { OpenAI } from 'openai'; -import { Redis } from '@upstash/redis'; -import { Ratelimit } from '@upstash/ratelimit'; -import { generateObject, generateText } from 'ai'; -import { createOpenAI } from '@ai-sdk/openai' +import { generateObject } from 'ai'; import { createOpenAI as createGroq } from '@ai-sdk/openai'; import { z } from 'zod'; -import { headers } from 'next/headers'; import { load } from 'cheerio'; const groq = createGroq({ @@ -19,7 +15,7 @@ export async function suggestQuestions(history: any[]) { 'use server'; const { object } = await generateObject({ - model: groq('llama-3.1-70b-versatile'), + model: groq('llama-3.2-90b-text-preview'), temperature: 0, system: `You are a search engine query generator. You 'have' to create only '3' questions for the search engine based on the message history which has been provided to you. @@ -42,123 +38,49 @@ Never use pronouns in the questions as they blur the context.`, }; } +const ELEVENLABS_API_KEY = process.env.ELEVENLABS_API_KEY; + export async function generateSpeech(text: string, voice: 'alloy' | 'echo' | 'fable' | 'onyx' | 'nova' | 'shimmer' = "alloy") { - if (process.env.OPENAI_PROVIDER === 'azure') { - if (!process.env.AZURE_OPENAI_API_KEY || !process.env.AZURE_OPENAI_API_URL) { - throw new Error('Azure OpenAI API key and URL are required.'); - } - const url = process.env.AZURE_OPENAI_API_URL!; - const response = await fetch(url, { - method: 'POST', - headers: { - 'api-key': process.env.AZURE_OPENAI_API_KEY!, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - model: "tts", - input: text, - voice: voice - }) - }); + const VOICE_ID = 'JBFqnCBsd6RMkjVDRZzb' // This is the ID for the "George" voice. Replace with your preferred voice ID. + const url = `https://api.elevenlabs.io/v1/text-to-speech/${VOICE_ID}` + const method = 'POST' - if (!response.ok) { - throw new Error(`Failed to generate speech: ${response.statusText}`); - } - - const arrayBuffer = await response.arrayBuffer(); - const base64Audio = Buffer.from(arrayBuffer).toString('base64'); - - return { - audio: `data:audio/mp3;base64,${base64Audio}`, - }; - } else if (process.env.OPENAI_PROVIDER === 'openai') { - const openai = new OpenAI(); - - const response = await openai.audio.speech.create({ - model: "tts-1", - voice: voice, - input: text, - }); - - const arrayBuffer = await response.arrayBuffer(); - const base64Audio = Buffer.from(arrayBuffer).toString('base64'); - - return { - audio: `data:audio/mp3;base64,${base64Audio}`, - }; - } else { - const openai = new OpenAI(); - - const response = await openai.audio.speech.create({ - model: "tts-1", - voice: voice, - input: text, - }); - - const arrayBuffer = await response.arrayBuffer(); - - const base64Audio = Buffer.from(arrayBuffer).toString('base64'); - - return { - audio: `data:audio/mp3;base64,${base64Audio}`, - }; - } -} - -const openai = createOpenAI({ - baseURL: "https://openrouter.ai/api/v1/", - apiKey: process.env.OPENROUTER_API_KEY, - headers: { - "HTTP-Referer": "https://mplx.run/search", - "X-Title": "MiniPerplx" - } -}); - -const redis = new Redis({ - url: process.env.UPSTASH_REDIS_REST_URL!, - token: process.env.UPSTASH_REDIS_REST_TOKEN!, -}); - -const ratelimit = new Ratelimit({ - redis: redis, - limiter: Ratelimit.fixedWindow(10, '4 h'), - analytics: true, - prefix: 'miniperplx', -}); - -export interface Message { - role: 'user' | 'assistant'; - content: string; -} - -export async function continueConversation(history: Message[]) { - 'use server'; - - const headersList = headers(); - const ip = headersList.get('x-forwarded-for') ?? 'unknown'; - const { success, limit, reset, remaining } = await ratelimit.limit(ip); - - if (!success) { - const resetDate = new Date(reset * 1000); // Convert seconds to milliseconds - throw new Error(`4-hour rate limit exceeded. Try again after ${resetDate.toLocaleTimeString()}.`); + if (!ELEVENLABS_API_KEY) { + throw new Error('ELEVENLABS_API_KEY is not defined'); } - const { text } = await generateText({ - model: openai('openai/o1-mini'), - messages: history, - }); + const headers = { + Accept: 'audio/mpeg', + 'xi-api-key': ELEVENLABS_API_KEY, + 'Content-Type': 'application/json', + } + + const data = { + text, + model_id: 'eleven_turbo_v2_5', + voice_settings: { + stability: 0.5, + similarity_boost: 0.5, + }, + } + + const body = JSON.stringify(data) + + const input = { + method, + headers, + body, + } + + const response = await fetch(url, input) + + const arrayBuffer = await response.arrayBuffer(); + + const base64Audio = Buffer.from(arrayBuffer).toString('base64'); return { - messages: [ - ...history, - { - role: 'assistant' as const, - content: text, - }, - ], - remaining, - reset: reset * 1000, // Convert seconds to milliseconds + audio: `data:audio/mp3;base64,${base64Audio}`, }; } diff --git a/app/layout.tsx b/app/layout.tsx index 8069a26..1ac53b9 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -4,12 +4,13 @@ import { Metadata, Viewport } from "next"; import { Toaster } from "sonner"; import { Inter, Instrument_Serif, IBM_Plex_Mono } from 'next/font/google'; import { Analytics } from "@vercel/analytics/react"; +import { Providers } from './providers' export const metadata: Metadata = { metadataBase: new URL("https://mplx.run"), title: "MiniPerplx", description: "MiniPerplx is a minimalistic AI-powered search engine that helps you find information on the internet.", - openGraph : { + openGraph: { url: "https://mplx.run", siteName: "MiniPerplx", } @@ -48,8 +49,10 @@ export default function RootLayout({ return ( - - {children} + + + {children} + diff --git a/app/page.tsx b/app/page.tsx index 779f5c6..d0dea2d 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -122,7 +122,7 @@ function GetStarted() { title="Get Started" icon={BarChart} description={"Experience the power of minimalistic AI search with MiniPerplx."} - className="col-span-full sm:col-span-1 sm:row-span-2" + className="col-span-full sm:col-span-1 sm:row-span-2 dark:text-neutral-950" gradient="from-blue-700 via-60% via-blue-600 to-cyan-600" >
@@ -146,7 +146,7 @@ function MinimalisticSearch() { icon={Search} description="We strip away the clutter to focus on what matters most - delivering accurate and relevant results." gradient="from-red-700 via-60% via-red-600 to-rose-600" - className="group col-span-full sm:col-span-1" + className="group col-span-full sm:col-span-1 dark:text-neutral-950" >
@@ -173,7 +173,7 @@ function AIPowered() { icon={Code} description="Leveraging cutting-edge AI technology to understand and respond to your queries with precision." gradient="from-emerald-700 via-60% via-emerald-600 to-green-600" - className="group col-span-full sm:col-span-1" + className="group col-span-full sm:col-span-1 dark:text-neutral-950" >
@@ -206,7 +206,7 @@ function LightningFast() { icon={Zap} description="Designed for speed, MiniPerplx provides instant answers to keep up with your pace of work." gradient="from-purple-700 via-60% via-purple-600 to-fuchsia-600" - className="col-span-full sm:col-span-2" + className="col-span-full sm:col-span-2 dark:text-neutral-950" /> ); } @@ -247,7 +247,7 @@ const MarqueeTestimonials: React.FC = () => { transition={{ repeat: Infinity, duration: 20, ease: "linear" }} > {testimonials.concat(testimonials).map((text, index) => ( - + {text} ))} @@ -697,12 +697,12 @@ const LandingPage: React.FC = () => {
-
+
{ Introducing MiniPerplx + {children} + + ) +} \ No newline at end of file diff --git a/app/search/page.tsx b/app/search/page.tsx index 50f7c77..fbe3527 100644 --- a/app/search/page.tsx +++ b/app/search/page.tsx @@ -13,10 +13,9 @@ React, memo, Suspense } from 'react'; -import ReactMarkdown, { Components } from 'react-markdown'; +import ReactMarkdown from 'react-markdown'; +import { useTheme } from 'next-themes'; import Marked, { ReactRenderer } from 'marked-react'; -import katex from 'katex'; -import Latex from 'react-latex-next'; import { track } from '@vercel/analytics'; import { useSearchParams } from 'next/navigation'; import { useChat } from 'ai/react'; @@ -25,15 +24,13 @@ import { toast } from 'sonner'; import { motion, AnimatePresence } from 'framer-motion'; import Image from 'next/image'; import { - continueConversation, fetchMetadata, generateSpeech, - Message, suggestQuestions } from '../actions'; import { Wave } from "@foobar404/wave"; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; -import { oneLight } from 'react-syntax-highlighter/dist/esm/styles/prism'; +import { oneLight, oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism'; import { SearchIcon, Sparkles, @@ -67,12 +64,8 @@ import { Zap, Edit2, ChevronUp, - Battery, - Clock, - Cpu, - Network, - ExternalLink, - Camera + Camera, + Moon } from 'lucide-react'; import { HoverCard, @@ -164,6 +157,8 @@ const HomeContent = () => { const fileInputRef = useRef(null); const inputRef = useRef(null); + const { theme } = useTheme(); + const [openChangelog, setOpenChangelog] = useState(false); const { isLoading, input, messages, setInput, handleInputChange, append, handleSubmit, setMessages, reload } = useChat({ @@ -191,6 +186,24 @@ const HomeContent = () => { }, }); + const ThemeToggle: React.FC = () => { + const { theme, setTheme } = useTheme(); + + return ( + + ); + }; + + const CopyButton = ({ text }: { text: string }) => { const [isCopied, setIsCopied] = useState(false); @@ -228,33 +241,27 @@ const HomeContent = () => { const changelogs: Changelog[] = [ { id: "1", - title: "Results Overview, Default Search Engine, and o1-mini!", + title: "Dark mode is here!", images: [ - "https://metwm7frkvew6tn1.public.blob.vercel-storage.com/mplx-changelogs/results-overview.png", - "https://metwm7frkvew6tn1.public.blob.vercel-storage.com/mplx-changelogs/default-search-engine-mplx.png", - "https://metwm7frkvew6tn1.public.blob.vercel-storage.com/mplx-changelogs/o1-mini-mplx.png" + "https://metwm7frkvew6tn1.public.blob.vercel-storage.com/mplx-changelogs/mplx-dark-mode.png", ], content: - `## **Results Overview** + `## **Dark Mode** -The new Results Overview tool provides a summary of the search results, including images, descriptions, and other relevant information. It also includes a table with additional details. - -## **Default Search Engine** - -You can know set MiniPerplx as your default search engine using the URL parameters. Just add \`?query=%s\` to the URL to set the default search query. The model can also be set using the \`model\` parameter.`, +The most requested feature is finally here! You can now toggle between light and dark mode. Default is set to your system preference.`, } ]; const ChangeLogs: React.FC<{ open: boolean; setOpen: (open: boolean) => void }> = ({ open, setOpen }) => { return ( - -
-

+ +
+

What's new

-
+
{changelogs.map((changelog) => (
{changelog.images.map((image, index) => ( @@ -285,15 +292,13 @@ You can know set MiniPerplx as your default search engine using the URL paramete
-

{changelog.title}

+

{changelog.title}

, - p: ({ node, className, ...props }) =>

, - } as Components - } - className="text-sm text-neutral-900" + components={{ + h2: ({ node, className, ...props }) =>

, + p: ({ node, className, ...props }) =>

, + }} + className="text-sm" > {changelog.content} @@ -354,10 +359,10 @@ You can know set MiniPerplx as your default search engine using the URL paramete }), []); return ( - + - Weather Forecast for {result.city.name} - + Weather Forecast for {result.city.name} + Showing min and max temperatures for the next 5 days @@ -368,14 +373,16 @@ You can know set MiniPerplx as your default search engine using the URL paramete data={chartData} margin={{ top: 10, right: 30, left: 0, bottom: 0 }} > - + new Date(value).toLocaleDateString(undefined, { month: 'short', day: 'numeric' })} + stroke="#9CA3AF" /> `${value}°C`} + stroke="#9CA3AF" /> } />

-
+
{result.city.name}, {result.city.country}
-
+
Next 5 days forecast
@@ -503,7 +510,7 @@ You can know set MiniPerplx as your default search engine using the URL paramete }, [initializeMap]); if (mapError) { - return
{mapError}
; + return
{mapError}
; } return
; @@ -512,19 +519,19 @@ You can know set MiniPerplx as your default search engine using the URL paramete MapComponent.displayName = 'MapComponent'; const MapSkeleton = () => ( - + ); const PlaceDetails = ({ place }: { place: any }) => (
-

{place.name}

-

+

{place.name}

+

{place.vicinity}

{place.rating && ( - + {place.rating} ({place.user_ratings_total}) @@ -559,21 +566,21 @@ You can know set MiniPerplx as your default search engine using the URL paramete const location = `${place.geometry.location.lat},${place.geometry.location.lng}`; return ( - + - + {place.name} -
+

Address: {place.formatted_address}

{place.rating && (
Rating: - + {place.rating} @@ -595,9 +602,9 @@ You can know set MiniPerplx as your default search engine using the URL paramete const mapLocation = centerLocation ? `${centerLocation.lat},${centerLocation.lng}` : ''; return ( - + - + Text Search Results @@ -606,19 +613,19 @@ You can know set MiniPerplx as your default search engine using the URL paramete {mapLocation && } - Place Details + Place Details
{result.results.map((place: any, index: number) => ( -
+
-

{place.name}

-

+

{place.name}

+

{place.formatted_address}

{place.rating && ( - + {place.rating} ({place.user_ratings_total}) @@ -696,7 +703,7 @@ You can know set MiniPerplx as your default search engine using the URL paramete if (!result) { return ( - +
@@ -708,11 +715,11 @@ You can know set MiniPerplx as your default search engine using the URL paramete } return ( - +
-
- +
+
@@ -721,7 +728,7 @@ You can know set MiniPerplx as your default search engine using the URL paramete disabled={isGeneratingAudio} variant="outline" size="sm" - className="text-xs sm:text-sm w-24" + className="text-xs sm:text-sm w-24 bg-neutral-100 dark:bg-neutral-700 text-neutral-800 dark:text-neutral-200" > {isGeneratingAudio ? ( "Generating..." @@ -732,9 +739,7 @@ You can know set MiniPerplx as your default search engine using the URL paramete )}
-
+
The phrase {toolInvocation.args.text} translates from {result.detectedLanguage} to {toolInvocation.args.to} as {result.translatedText} in {toolInvocation.args.to}.
@@ -761,12 +766,12 @@ You can know set MiniPerplx as your default search engine using the URL paramete const ImageCarousel = ({ images, onClose }: { images: SearchImage[], onClose: () => void }) => { return ( - + @@ -778,7 +783,7 @@ You can know set MiniPerplx as your default search engine using the URL paramete alt={image.description} className="max-w-full max-h-[70vh] object-contain mb-4" /> -

{image.description}

+

{image.description}

))} @@ -811,24 +816,24 @@ You can know set MiniPerplx as your default search engine using the URL paramete
-

Sources Found

+

Sources Found

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

{item.title}

-

{item.content}

+

{item.title}

+

{item.content}

{result && result.images && result.images.length > 0 && (
-
+
-

Images

+

Images

{result.images.slice(0, 4).map((image: SearchImage, itemIndex: number) => ( @@ -917,8 +920,8 @@ You can know set MiniPerplx as your default search engine using the URL paramete }, [showAll, result.table_data]); return ( - - + +
{result.image && (
@@ -930,8 +933,8 @@ You can know set MiniPerplx as your default search engine using the URL paramete
)}
- {result.title} -

{result.description}

+ {result.title} +

{result.description}

@@ -939,9 +942,9 @@ You can know set MiniPerplx as your default search engine using the URL paramete {visibleData.map((item, index) => ( - - {item.title} - {item.content} + + {item.title} + {item.content} ))} @@ -949,7 +952,7 @@ You can know set MiniPerplx as your default search engine using the URL paramete {result.table_data.length > 3 && ( ); @@ -1578,7 +1512,7 @@ You can know set MiniPerplx as your default search engine using the URL paramete if (isLoading) { return (
- +
); } @@ -1586,8 +1520,8 @@ You can know set MiniPerplx as your default search engine using the URL paramete const domain = new URL(href).hostname; return ( -
-
+
+
- {domain} + {domain}
-

+

{metadata?.title || "Untitled"}

{metadata?.description && ( -

+

{metadata.description}

)} @@ -1619,7 +1553,7 @@ You can know set MiniPerplx as your default search engine using the URL paramete href={href} target="_blank" rel="noopener noreferrer" - className={isCitation ? "cursor-help text-sm text-primary py-0.5 px-1.5 m-0 bg-secondary rounded-full no-underline" : "text-teal-600 no-underline hover:underline"} + className={isCitation ? "cursor-help text-sm text-primary py-0.5 px-1.5 m-0 bg-neutral-200 dark:bg-neutral-700 rounded-full no-underline" : "text-teal-600 dark:text-teal-400 no-underline hover:underline"} > {text} @@ -1637,14 +1571,12 @@ You can know set MiniPerplx as your default search engine using the URL paramete const renderer: Partial = { paragraph(children) { - return

{children}

; + return

{children}

; }, code(children, language) { return {String(children)}; }, link(href, text) { - // if (!href) return <>{text}; - const citationIndex = citationLinks.findIndex(link => link.link === href); if (citationIndex !== -1) { return ( @@ -1653,27 +1585,27 @@ You can know set MiniPerplx as your default search engine using the URL paramete ); } - return isValidUrl(href) ? renderHoverCard(href, text) : {text}; + return isValidUrl(href) ? renderHoverCard(href, text) : {text}; }, heading(children, level) { const HeadingTag = `h${level}` as keyof JSX.IntrinsicElements; - const className = `text-${4 - level}xl font-bold my-4`; + const className = `text-${4 - level}xl font-bold my-4 text-neutral-800 dark:text-neutral-100`; return {children}; }, list(children, ordered) { const ListTag = ordered ? 'ol' : 'ul'; - return {children}; + return {children}; }, listItem(children) { - return
  • {children}
  • ; + return
  • {children}
  • ; }, blockquote(children) { - return
    {children}
    ; + return
    {children}
    ; }, }; return ( -
    +
    {content}
    ); @@ -1697,7 +1629,7 @@ You can know set MiniPerplx as your default search engine using the URL paramete }, [messages, suggestedQuestions]); const handleExampleClick = useCallback(async (card: typeof suggestionCards[number]) => { - const exampleText = selectedModel === 'openai/o1-mini' ? card.o1Examples : card.text; + const exampleText = card.text; track("search example", { query: exampleText }); setLastSubmittedQuery(exampleText.trim()); setHasSubmitted(true); @@ -1709,7 +1641,7 @@ You can know set MiniPerplx as your default search engine using the URL paramete role: 'user', }); - }, [append, setLastSubmittedQuery, setHasSubmitted, setSuggestedQuestions, selectedModel]); + }, [append, setLastSubmittedQuery, setHasSubmitted, setSuggestedQuestions]); const handleSuggestedQuestionClick = useCallback(async (question: string) => { setHasSubmitted(true); @@ -1746,22 +1678,16 @@ You can know set MiniPerplx as your default search engine using the URL paramete const suggestionCards = [ { - icon: , - o1Icon: , + icon: , text: "Shah Rukh Khan", - o1Examples: "How many GPUs does it take to fill up Mars?" }, { - icon: , - o1Icon: , + icon: , text: "Weather in Doha", - o1Examples: "Dijkstra algorithm" }, { - icon: , - o1Icon: , + icon: , text: "Count the no. of r's in strawberry?", - o1Examples: "Count the no. of r's in strawberry?" }, ]; @@ -1769,12 +1695,12 @@ You can know set MiniPerplx as your default search engine using the URL paramete const Navbar: React.FC = () => { return ( -
    +
    @@ -1798,17 +1724,18 @@ You can know set MiniPerplx as your default search engine using the URL paramete - +

    Sponsor this project on GitHub

    +
    ); @@ -1845,18 +1772,18 @@ You can know set MiniPerplx as your default search engine using the URL paramete animate={{ opacity: 1, scale: 1 }} exit={{ opacity: 0, scale: 0.8 }} transition={{ duration: 0.2 }} - className="relative flex items-center bg-background border border-input rounded-2xl p-2 pr-8 gap-2 cursor-pointer shadow-sm !z-30" + className="relative flex items-center bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-2xl p-2 pr-8 gap-2 cursor-pointer shadow-sm !z-30" > {isUploading ? (
    - +
    ) : isUploadingAttachment(attachment) ? (
    - {Math.round(attachment.progress * 100)}% + {Math.round(attachment.progress * 100)}%
    @@ -1891,9 +1818,9 @@ You can know set MiniPerplx as your default search engine using the URL paramete )}
    {!isUploadingAttachment(attachment) && ( -

    {attachment.name}

    +

    {attachment.name}

    )} -

    +

    {isUploadingAttachment(attachment) ? 'Uploading...' : formatFileSize((attachment as Attachment).size)} @@ -1903,14 +1830,14 @@ You can know set MiniPerplx as your default search engine using the URL paramete whileHover={{ scale: 1.1 }} whileTap={{ scale: 0.9 }} onClick={(e) => { e.stopPropagation(); onRemove(); }} - className="absolute -top-2 -right-2 p-0.5 m-0 rounded-full bg-background border border-input shadow-sm hover:bg-muted transition-colors z-20" + className="absolute -top-2 -right-2 p-0.5 m-0 rounded-full bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 shadow-sm hover:bg-neutral-100 dark:hover:bg-neutral-700 transition-colors z-20" > - + {!isUploadingAttachment(attachment) && ( - + 0 || uploadingAttachments.length > 0 ? 'rounded-2xl' : 'rounded-full'} - w-full - bg-background border border-input + ${attachments.length > 0 || uploadingAttachments.length > 0 ? 'rounded-2xl' : 'rounded-xl'} + w-full + bg-background border border-input dark:border-neutral-700 overflow-hidden mb-4 transition-all duration-300 ease-in-out z-50 @@ -2102,11 +2029,9 @@ You can know set MiniPerplx as your default search engine using the URL paramete onChange={handleInputChange} disabled={isLoading} className={cn( - "w-full h-12 pr-12 bg-muted", - "ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", - "text-sm sm:text-base rounded-full", - { 'pl-10': selectedModel !== 'openai/o1-mini' }, - { 'pl-6': selectedModel === 'openai/o1-mini' } + "w-full min-h-[48px] max-h-[200px] h-full pr-12 bg-muted pl-10", + "ring-offset-background focus-visible:outline-none focus-visible:ring-0 border-none", + "text-sm sm:text-base rounded-md overflow-hidden", )} onKeyDown={(e) => { if (e.key === 'Enter' && !e.shiftKey) { @@ -2156,12 +2081,12 @@ You can know set MiniPerplx as your default search engine using the URL paramete @@ -2191,24 +2116,20 @@ You can know set MiniPerplx as your default search engine using the URL paramete switch (color) { case 'emerald': return isSelected - ? '!bg-emerald-500 !text-white hover:!bg-emerald-600' - : '!text-emerald-700 hover:!bg-emerald-100'; + ? '!bg-emerald-500 dark:!bg-emerald-700 !text-white hover:!bg-emerald-600 dark:hover:!bg-emerald-800' + : '!text-emerald-700 dark:!text-emerald-300 hover:!bg-emerald-100 dark:hover:!bg-emerald-800/30'; case 'indigo': return isSelected - ? '!bg-indigo-500 !text-white hover:!bg-indigo-600' - : '!text-indigo-700 hover:!bg-indigo-100'; - case 'orange': - return isSelected - ? '!bg-orange-500 !text-white hover:!bg-orange-600' - : '!text-orange-700 hover:!bg-orange-100'; + ? '!bg-indigo-500 dark:!bg-indigo-700 !text-white hover:!bg-indigo-600 dark:hover:!bg-indigo-800' + : '!text-indigo-700 dark:!text-indigo-300 hover:!bg-indigo-100 dark:hover:!bg-indigo-800/30'; case 'blue': return isSelected - ? '!bg-blue-500 !text-white hover:!bg-blue-600' - : '!text-blue-700 hover:!bg-blue-100'; + ? '!bg-blue-500 dark:!bg-blue-700 !text-white hover:!bg-blue-600 dark:hover:!bg-blue-800' + : '!text-blue-700 dark:!text-blue-300 hover:!bg-blue-100 dark:hover:!bg-blue-800/30'; default: return isSelected - ? 'bg-gray-500 text-white hover:bg-gray-600' - : 'text-gray-700 hover:bg-gray-100'; + ? 'bg-neutral-500 dark:bg-neutral-600 text-white hover:bg-neutral-600 dark:hover:bg-neutral-700' + : 'text-neutral-700 dark:text-neutral-300 hover:bg-neutral-100 dark:hover:bg-neutral-800/30'; } } @@ -2232,7 +2153,7 @@ You can know set MiniPerplx as your default search engine using the URL paramete isOpen && "transform rotate-180" )} /> - + {models.map((model) => (

    {model.label}
    {model.description}
    @@ -2284,13 +2205,13 @@ You can know set MiniPerplx as your default search engine using the URL paramete
    setOpenChangelog(true)} - className="cursor-pointer gap-1 mb-2" - variant="green" + className="cursor-pointer gap-1 mb-2 bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200" + variant="secondary" > What's new -

    MiniPerplx

    -

    +

    MiniPerplx

    +

    In search for minimalism and simplicity

    @@ -2324,7 +2245,6 @@ You can know set MiniPerplx as your default search engine using the URL paramete )} -
    {messages.map((message, index) => (
    @@ -2342,7 +2262,7 @@ You can know set MiniPerplx as your default search engine using the URL paramete setInput(e.target.value)} - className="flex-grow" + className="flex-grow bg-white dark:bg-neutral-800 text-neutral-900 dark:text-neutral-100" /> - ) : (
    -

    +

    {message.content}

    -
    +
    {message.experimental_attachments?.map((attachment, attachmentIndex) => (
    {attachment.contentType!.startsWith('image/') && ( @@ -2386,14 +2305,12 @@ You can know set MiniPerplx as your default search engine using the URL paramete
    {!isEditingMessage && index === lastUserMessageIndex && ( -
    +