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 (
-