introducing dark mode, we are so back!

This commit is contained in:
zaidmukaddam 2024-10-13 23:54:07 +05:30
parent 59b0da7710
commit 40de2e9415
10 changed files with 1107 additions and 1109 deletions

View File

@ -1,13 +1,9 @@
'use server'; 'use server';
import { OpenAI } from 'openai'; import { OpenAI } from 'openai';
import { Redis } from '@upstash/redis'; import { generateObject } from 'ai';
import { Ratelimit } from '@upstash/ratelimit';
import { generateObject, generateText } from 'ai';
import { createOpenAI } from '@ai-sdk/openai'
import { createOpenAI as createGroq } from '@ai-sdk/openai'; import { createOpenAI as createGroq } from '@ai-sdk/openai';
import { z } from 'zod'; import { z } from 'zod';
import { headers } from 'next/headers';
import { load } from 'cheerio'; import { load } from 'cheerio';
const groq = createGroq({ const groq = createGroq({
@ -19,7 +15,7 @@ export async function suggestQuestions(history: any[]) {
'use server'; 'use server';
const { object } = await generateObject({ const { object } = await generateObject({
model: groq('llama-3.1-70b-versatile'), model: groq('llama-3.2-90b-text-preview'),
temperature: 0, temperature: 0,
system: 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. `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,59 +38,42 @@ 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") { 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, { const VOICE_ID = 'JBFqnCBsd6RMkjVDRZzb' // This is the ID for the "George" voice. Replace with your preferred voice ID.
method: 'POST', const url = `https://api.elevenlabs.io/v1/text-to-speech/${VOICE_ID}`
headers: { const method = 'POST'
'api-key': process.env.AZURE_OPENAI_API_KEY!,
'Content-Type': 'application/json' if (!ELEVENLABS_API_KEY) {
throw new Error('ELEVENLABS_API_KEY is not defined');
}
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,
}, },
body: JSON.stringify({
model: "tts",
input: text,
voice: voice
})
});
if (!response.ok) {
throw new Error(`Failed to generate speech: ${response.statusText}`);
} }
const arrayBuffer = await response.arrayBuffer(); const body = JSON.stringify(data)
const base64Audio = Buffer.from(arrayBuffer).toString('base64');
return { const input = {
audio: `data:audio/mp3;base64,${base64Audio}`, method,
}; headers,
} else if (process.env.OPENAI_PROVIDER === 'openai') { body,
const openai = new OpenAI(); }
const response = await openai.audio.speech.create({ const response = await fetch(url, input)
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 arrayBuffer = await response.arrayBuffer();
@ -104,63 +83,6 @@ export async function generateSpeech(text: string, voice: 'alloy' | 'echo' | 'fa
audio: `data:audio/mp3;base64,${base64Audio}`, 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()}.`);
}
const { text } = await generateText({
model: openai('openai/o1-mini'),
messages: history,
});
return {
messages: [
...history,
{
role: 'assistant' as const,
content: text,
},
],
remaining,
reset: reset * 1000, // Convert seconds to milliseconds
};
}
export async function fetchMetadata(url: string) { export async function fetchMetadata(url: string) {
try { try {

View File

@ -4,6 +4,7 @@ import { Metadata, Viewport } from "next";
import { Toaster } from "sonner"; import { Toaster } from "sonner";
import { Inter, Instrument_Serif, IBM_Plex_Mono } from 'next/font/google'; import { Inter, Instrument_Serif, IBM_Plex_Mono } from 'next/font/google';
import { Analytics } from "@vercel/analytics/react"; import { Analytics } from "@vercel/analytics/react";
import { Providers } from './providers'
export const metadata: Metadata = { export const metadata: Metadata = {
metadataBase: new URL("https://mplx.run"), metadataBase: new URL("https://mplx.run"),
@ -48,8 +49,10 @@ export default function RootLayout({
return ( return (
<html lang="en"> <html lang="en">
<body className={`${inter.className} ${instrumentSerif.className} ${plexMono.className}`}> <body className={`${inter.className} ${instrumentSerif.className} ${plexMono.className}`}>
<Providers>
<Toaster position="top-center" richColors /> <Toaster position="top-center" richColors />
{children} {children}
</Providers>
<Analytics /> <Analytics />
</body> </body>
</html> </html>

View File

@ -122,7 +122,7 @@ function GetStarted() {
title="Get Started" title="Get Started"
icon={BarChart} icon={BarChart}
description={"Experience the power of minimalistic AI search with MiniPerplx."} 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" gradient="from-blue-700 via-60% via-blue-600 to-cyan-600"
> >
<div className="group relative flex cursor-pointer flex-col justify-end rounded-md bg-zinc-900 p-2 text-xl sm:text-2xl md:text-4xl tracking-tight text-gray-100"> <div className="group relative flex cursor-pointer flex-col justify-end rounded-md bg-zinc-900 p-2 text-xl sm:text-2xl md:text-4xl tracking-tight text-gray-100">
@ -146,7 +146,7 @@ function MinimalisticSearch() {
icon={Search} icon={Search}
description="We strip away the clutter to focus on what matters most - delivering accurate and relevant results." 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" 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"
> >
<div className="mt-2 sm:mt-4 space-y-1 sm:space-y-2"> <div className="mt-2 sm:mt-4 space-y-1 sm:space-y-2">
<div className="flex items-center"> <div className="flex items-center">
@ -173,7 +173,7 @@ function AIPowered() {
icon={Code} icon={Code}
description="Leveraging cutting-edge AI technology to understand and respond to your queries with precision." 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" 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"
> >
<div className="mt-2 sm:mt-4 space-y-1 sm:space-y-2"> <div className="mt-2 sm:mt-4 space-y-1 sm:space-y-2">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
@ -206,7 +206,7 @@ function LightningFast() {
icon={Zap} icon={Zap}
description="Designed for speed, MiniPerplx provides instant answers to keep up with your pace of work." 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" 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" }} transition={{ repeat: Infinity, duration: 20, ease: "linear" }}
> >
{testimonials.concat(testimonials).map((text, index) => ( {testimonials.concat(testimonials).map((text, index) => (
<span key={index} className="text-white text-xl font-bold mx-8"> <span key={index} className="text-white dark:text-black text-xl font-bold mx-8">
{text} {text}
</span> </span>
))} ))}
@ -697,12 +697,12 @@ const LandingPage: React.FC = () => {
</AnimatePresence> </AnimatePresence>
<main className="flex-1"> <main className="flex-1">
<section className="w-full py-48 bg-gradient-to-b from-background to-muted relative overflow-hidden"> <section className="w-full py-48 bg-gradient-to-b from-background f to-muted relative overflow-hidden">
<FloatingIcons /> <FloatingIcons />
<div className="container px-4 md:px-6 relative z-10"> <div className="container px-4 md:px-6 relative z-10">
<div className="text-center space-y-4"> <div className="text-center space-y-4">
<motion.h1 <motion.h1
className="font-serif font-bold text-6xl md:text-7xl lg:text-8xl bg-clip-text text-transparent bg-black leading-[1.1] tracking-tight pb-2" className="font-serif font-bold text-6xl md:text-7xl lg:text-8xl bg-clip-text text-transparent bg-black dark:bg-white leading-[1.1] tracking-tight pb-2"
variants={itemVariants} variants={itemVariants}
initial="hidden" initial="hidden"
animate="visible" animate="visible"
@ -710,7 +710,7 @@ const LandingPage: React.FC = () => {
Introducing MiniPerplx Introducing MiniPerplx
</motion.h1> </motion.h1>
<motion.p <motion.p
className="mx-auto max-w-[700px] text-muted-foreground text-xl md:text-2xl text-balance font-serif tracking-normal" className="mx-auto max-w-[700px] text-muted-foreground dark:text-neutral-200 text-xl md:text-2xl text-balance font-serif tracking-normal"
variants={itemVariants} variants={itemVariants}
initial="hidden" initial="hidden"
animate="visible" animate="visible"

15
app/providers.tsx Normal file
View File

@ -0,0 +1,15 @@
import { ThemeProvider } from "next-themes"
import { ReactNode } from "react"
export function Providers({ children }: { children: ReactNode }) {
return (
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
{children}
</ThemeProvider>
)
}

File diff suppressed because it is too large Load Diff

29
components/ui/switch.tsx Normal file
View File

@ -0,0 +1,29 @@
"use client"
import * as React from "react"
import * as SwitchPrimitives from "@radix-ui/react-switch"
import { cn } from "@/lib/utils"
const Switch = React.forwardRef<
React.ElementRef<typeof SwitchPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
>(({ className, ...props }, ref) => (
<SwitchPrimitives.Root
className={cn(
"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
className
)}
{...props}
ref={ref}
>
<SwitchPrimitives.Thumb
className={cn(
"pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0"
)}
/>
</SwitchPrimitives.Root>
))
Switch.displayName = SwitchPrimitives.Root.displayName
export { Switch }

View File

@ -0,0 +1,25 @@
import * as React from "react"
import { cn } from "@/lib/utils"
export interface TextareaProps
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
({ className, ...props }, ref) => {
return (
<textarea
className={cn(
"flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
dir="ltr"
{...props}
/>
)
}
)
Textarea.displayName = "Textarea"
export { Textarea }

View File

@ -26,6 +26,7 @@
"@radix-ui/react-popover": "^1.1.1", "@radix-ui/react-popover": "^1.1.1",
"@radix-ui/react-scroll-area": "^1.1.0", "@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-switch": "^1.1.1",
"@radix-ui/react-tabs": "^1.1.0", "@radix-ui/react-tabs": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.2", "@radix-ui/react-tooltip": "^1.1.2",
"@tailwindcss/typography": "^0.5.13", "@tailwindcss/typography": "^0.5.13",
@ -51,6 +52,7 @@
"lucide-react": "^0.424.0", "lucide-react": "^0.424.0",
"marked-react": "^2.0.0", "marked-react": "^2.0.0",
"next": "^14.2.10", "next": "^14.2.10",
"next-themes": "^0.3.0",
"openai": "^4.56.0", "openai": "^4.56.0",
"react": "^18", "react": "^18",
"react-dom": "^18", "react-dom": "^18",

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
import type { Config } from "tailwindcss" import type { Config } from "tailwindcss";
const config = { const config = {
darkMode: ["class"], darkMode: ["class"],