A modern AI-powered search and emotion analysis platform inspired by Perplexity AI. Derplexity combines intelligent web search capabilities with emotion detection to provide comprehensive insights and answers to user queries.
- π AI-Powered Search: Get intelligent answers with cited sources from across the web
- π Emotion Analysis: Analyze emotional content and sentiment in text
- π€ Voice Input: Speech recognition for hands-free interaction
- π¬ Chat Interface: Conversational UI for natural interactions
- π Search Library: Save and organize your search history
- π User Authentication: Secure login and user management with Clerk
- π± Responsive Design: Works seamlessly across desktop and mobile devices
- π¨ Modern UI: Beautiful interface built with shadcn/ui components
Derplexity follows a modern microservices-inspired architecture built on Next.js with the following key components:
graph TB
subgraph "Client Layer"
UI[React UI Components]
Auth[Clerk Authentication]
Speech[Speech Recognition]
end
subgraph "Application Layer"
API[Next.js API Routes]
MW[Middleware]
Pages[App Router Pages]
end
subgraph "Service Layer"
Inngest[Background Jobs]
AI[Gemini AI Service]
Search[Search Service]
end
subgraph "Data Layer"
DB[(Supabase Database)]
Storage[File Storage]
end
UI --> Auth
UI --> API
API --> MW
API --> Inngest
Inngest --> AI
Inngest --> DB
API --> DB
Search --> AI
classDef client fill:#e1f5fe
classDef app fill:#f3e5f5
classDef service fill:#e8f5e8
classDef data fill:#fff3e0
class UI,Auth,Speech client
class API,MW,Pages app
class Inngest,AI,Search service
class DB,Storage data
-
Frontend (Next.js 15 + React 18)
- Server-side rendering for optimal performance
- App Router for modern routing patterns
- React Server Components for efficient data fetching
-
Authentication Layer (Clerk)
- Secure user authentication and session management
- Protected routes with middleware
- User profile management
-
API Layer (Next.js API Routes)
- RESTful endpoints for client-server communication
- Background job triggers
- Integration with external services
-
Background Processing (Inngest)
- Asynchronous AI model processing
- Reliable job execution with retries
- Event-driven architecture
-
Database Layer (Supabase)
- PostgreSQL database for data persistence
- Real-time subscriptions
- Row-level security
βββββββββββββββ ββββββββββββββββ βββββββββββββββ βββββββββββββββ
β Client βββββΆβ Next.js API βββββΆβ Inngest βββββΆβ Gemini β
β (React) β β Routes β β (Jobs) β β AI β
βββββββββββββββ ββββββββββββββββ βββββββββββββββ βββββββββββββββ
β β β β
β βΌ βΌ β
β ββββββββββββββββ βββββββββββββββ β
βββββββββββββΆβ Supabase ββββββ Database ββββββββββββββ
β Database β β Updates β
ββββββββββββββββ βββββββββββββββ
User Input β ChatInputBox β API Route β Database Insert β Inngest Job β AI Processing β Database Update β Real-time UI Update
User Input β Emotion Mode β API Route β Background Job β Sentiment Analysis β Response Generation β UI Display
-- Core Tables Structure
βββββββββββββββββββ βββββββββββββββββββ
β Library β β Chats β
βββββββββββββββββββ€ βββββββββββββββββββ€
β id (SERIAL) ββββββ β id (SERIAL) β
β libid (UUID) β β β libid (UUID) β
β searchInput β βββββΆβ aiResp (TEXT) β
β userEmail β β created_at β
β searchType β βββββββββββββββββββ
β created_at β
βββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β App Layout β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β βββββββββββββββ βββββββββββββββββββββββββββββββββββββββββββ β
β β Sidebar β β Main Content β β
β β β β βββββββββββββββββββββββββββββββββββββββ β β
β β β’ Library β β β Page Content β β β
β β β’ Discover β β β β β β
β β β’ Settings β β β βββββββββββββββββββββββββββββββββββ β β β
β β β β β β ChatInputBox β β β β
β βββββββββββββββ β β β β’ Search Input β β β β
β β β β β’ Voice Recognition β β β β
β β β β β’ Mode Selection β β β β
β β β βββββββββββββββββββββββββββββββββββ β β β
β β βββββββββββββββββββββββββββββββββββββββ β β
β βββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// Global State Architecture
βββββββββββββββββββ
β Clerk Auth β β User authentication state
βββββββββββββββββββ
βββββββββββββββββββ
β UserDetailContextβ β User profile data
βββββββββββββββββββ
βββββββββββββββββββ
β Component State β β Local UI state (React useState)
βββββββββββββββββββ
βββββββββββββββββββ
β Supabase Store β β Server state (queries & mutations)
βββββββββββββββββββ
-
Authentication Security
- JWT tokens managed by Clerk
- Secure session handling
- Protected API routes
-
API Security
- Middleware-based route protection
- User context validation
- Input sanitization
-
Database Security
- Row-level security (RLS) in Supabase
- User-scoped data access
- Prepared statements for SQL injection prevention
-
Environment Security
- Environment variables for sensitive data
- API key rotation support
- HTTPS enforcement
-
Frontend Performance
- Next.js automatic code splitting
- React Server Components
- Image optimization with Next.js Image
- Static generation where possible
-
API Performance
- Background job processing for heavy operations
- Database query optimization
- Response caching strategies
-
Database Performance
- Indexed queries on frequently accessed columns
- Connection pooling
- Query optimization
-
Horizontal Scaling
- Stateless API design
- Background job distribution
- CDN integration for static assets
-
Vertical Scaling
- Database connection pooling
- Memory-efficient React components
- Optimized bundle sizes
-
Monitoring & Observability
- Error tracking integration ready
- Performance monitoring capabilities
- Database query analysis
POST /api/search-api
Content-Type: application/json
Request Body:
{
"searchInput": string,
"searchType": "search" | "emotion",
"userEmail": string,
"libid": string
}
Response:
{
"success": boolean,
"data": SearchResult[],
"message": string
}
POST /api/llm-model
Content-Type: application/json
Request Body:
{
"searchInput": string,
"searchResultWeb": SearchResult[],
"recordId": string
}
Response:
{
"inngestRunId": string,
"status": "queued" | "running" | "completed"
}
POST /api/llm-model-emotion
Content-Type: application/json
Request Body:
{
"searchInput": string,
"recordId": string
}
Response:
{
"inngestRunId": string,
"emotionAnalysis": EmotionResult
}
GET /api/get-inngest-status?runId={inngestRunId}
Response:
{
"status": "pending" | "running" | "completed" | "failed",
"output": any,
"error": string | null
}
// Function: llm-model
Event: "llm-model"
Purpose: Process search queries with AI and generate responses
Input:
{
searchInput: string,
searchResultWeb: SearchResult[],
recordId: string
}
Steps:
1. AI inference with Gemini model
2. Response formatting
3. Database update
4. Error handling with retries
// Function: emotion-analysis
Event: "emotion-analysis"
Purpose: Analyze emotional content and sentiment
Input:
{
searchInput: string,
recordId: string
}
Steps:
1. Sentiment analysis with AI
2. Emotion classification
3. Result storage
4. Notification dispatch
CREATE TABLE Library (
id SERIAL PRIMARY KEY,
libid UUID UNIQUE NOT NULL DEFAULT gen_random_uuid(),
searchInput TEXT NOT NULL,
userEmail TEXT NOT NULL,
searchType VARCHAR(20) NOT NULL CHECK (searchType IN ('search', 'emotion')),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Indexes for performance
CREATE INDEX idx_library_user_email ON Library(userEmail);
CREATE INDEX idx_library_libid ON Library(libid);
CREATE INDEX idx_library_created_at ON Library(created_at DESC);
CREATE TABLE Chats (
id SERIAL PRIMARY KEY,
libid UUID NOT NULL REFERENCES Library(libid) ON DELETE CASCADE,
aiResp TEXT,
sources JSONB,
metadata JSONB,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Indexes for performance
CREATE INDEX idx_chats_libid ON Chats(libid);
CREATE INDEX idx_chats_created_at ON Chats(created_at DESC);
-- Enable RLS
ALTER TABLE Library ENABLE ROW LEVEL SECURITY;
ALTER TABLE Chats ENABLE ROW LEVEL SECURITY;
-- Policies for user data access
CREATE POLICY "Users can only access their own library records"
ON Library FOR ALL
USING (userEmail = auth.jwt() ->> 'email');
CREATE POLICY "Users can only access their own chats"
ON Chats FOR ALL
USING (
libid IN (
SELECT libid FROM Library
WHERE userEmail = auth.jwt() ->> 'email'
)
);
- Frontend: Next.js 15 with React 18
- UI Components: shadcn/ui with Tailwind CSS
- Authentication: Clerk
- Database: Supabase
- Background Jobs: Inngest
- AI Integration: Google Gemini API
- Speech Recognition: Web Speech API
- Icons: Lucide React
- Styling: Tailwind CSS
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Production Stack β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β Vercel β β Supabase β β Inngest β β
β β (Hosting) β β (Database) β β (Jobs) β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β β β β
β ββββββββββββββββββΌβββββββββββββββββ β
β β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β Clerk β β Gemini β β CDN β β
β β (Auth) β β (AI) β β (Assets) β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Environment: development
Database: Supabase (Development)
Authentication: Clerk (Development)
Background Jobs: Inngest (Development)
AI: Gemini API (Development quota)
Hosting: Local (localhost:3000)
Environment: staging
Database: Supabase (Staging)
Authentication: Clerk (Staging)
Background Jobs: Inngest (Staging)
AI: Gemini API (Staging quota)
Hosting: Vercel Preview
Environment: production
Database: Supabase (Production)
Authentication: Clerk (Production)
Background Jobs: Inngest (Production)
AI: Gemini API (Production quota)
Hosting: Vercel Production
CDN: Vercel Edge Network
Monitoring: Built-in Vercel Analytics
// vercel.json
{
"framework": "nextjs",
"buildCommand": "npm run build",
"devCommand": "npm run dev",
"installCommand": "npm install",
"env": {
"NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY": "@clerk_publishable_key",
"CLERK_SECRET_KEY": "@clerk_secret_key",
"NEXT_PUBLIC_SUPABASE_URL": "@supabase_url",
"NEXT_PUBLIC_SUPABASE_KEY": "@supabase_anon_key",
"NEXT_PUBLIC_GEMINI_API_KEY": "@gemini_api_key",
"INNGEST_EVENT_KEY": "@inngest_event_key",
"INNGEST_SIGNING_KEY": "@inngest_signing_key"
},
"regions": ["iad1"],
"functions": {
"app/api/inngest/route.ts": {
"maxDuration": 300
}
}
}
-- Migration script for production setup
-- migrations/001_initial_schema.sql
-- Enable UUID extension
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- Create enum for search types
CREATE TYPE search_type_enum AS ENUM ('search', 'emotion');
-- Create tables with proper constraints
CREATE TABLE Library (
id SERIAL PRIMARY KEY,
libid UUID UNIQUE NOT NULL DEFAULT uuid_generate_v4(),
searchInput TEXT NOT NULL CHECK (length(searchInput) > 0),
userEmail TEXT NOT NULL CHECK (userEmail ~ '^[^@]+@[^@]+\.[^@]+$'),
searchType search_type_enum NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE TABLE Chats (
id SERIAL PRIMARY KEY,
libid UUID NOT NULL REFERENCES Library(libid) ON DELETE CASCADE,
aiResp TEXT,
sources JSONB DEFAULT '[]'::jsonb,
metadata JSONB DEFAULT '{}'::jsonb,
processing_status VARCHAR(20) DEFAULT 'pending',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Create indexes
CREATE INDEX CONCURRENTLY idx_library_user_email_created ON Library(userEmail, created_at DESC);
CREATE INDEX CONCURRENTLY idx_library_search_type ON Library(searchType);
CREATE INDEX CONCURRENTLY idx_chats_libid_status ON Chats(libid, processing_status);
CREATE INDEX CONCURRENTLY idx_chats_created_at ON Chats(created_at DESC);
-- Enable RLS
ALTER TABLE Library ENABLE ROW LEVEL SECURITY;
ALTER TABLE Chats ENABLE ROW LEVEL SECURITY;
-- RLS Policies
CREATE POLICY "library_user_isolation" ON Library
FOR ALL USING (userEmail = current_setting('request.jwt.claims')::json->>'email');
CREATE POLICY "chats_user_isolation" ON Chats
FOR ALL USING (
libid IN (
SELECT libid FROM Library
WHERE userEmail = current_setting('request.jwt.claims')::json->>'email'
)
);
// lib/monitoring.ts
export const monitoring = {
// Error tracking
captureException: (error: Error, context?: any) => {
// Integration with error tracking service
console.error('Application Error:', error, context);
},
// Performance monitoring
trackPageView: (page: string) => {
// Analytics integration
},
// Custom metrics
trackSearchQuery: (searchType: string, duration: number) => {
// Custom metrics tracking
}
};
// app/api/health/route.ts
export async function GET() {
const healthChecks = {
database: await checkSupabaseConnection(),
ai: await checkGeminiAPI(),
inngest: await checkInngestConnection(),
timestamp: new Date().toISOString()
};
const isHealthy = Object.values(healthChecks).every(check =>
typeof check === 'boolean' ? check : true
);
return Response.json(healthChecks, {
status: isHealthy ? 200 : 503
});
}
- Node.js 18+ and npm/yarn/pnpm
- Supabase account and project
- Clerk account for authentication
- Google Gemini API key
- Inngest account (for background jobs)
-
Clone the repository
git clone https://github.com/yourusername/derplexity.git cd derplexity
-
Install dependencies
npm install # or yarn install # or pnpm install
-
Set up environment variables
Create a
.env.local
file in the root directory:# Clerk Authentication NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=your_clerk_publishable_key CLERK_SECRET_KEY=your_clerk_secret_key NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up # Supabase NEXT_PUBLIC_SUPABASE_URL=your_supabase_url NEXT_PUBLIC_SUPABASE_KEY=your_supabase_anon_key # AI Integration NEXT_PUBLIC_GEMINI_API_KEY=your_gemini_api_key # Inngest INNGEST_EVENT_KEY=your_inngest_event_key INNGEST_SIGNING_KEY=your_inngest_signing_key
-
Set up Supabase database
Create the following tables in your Supabase project:
-- Library table for storing search queries CREATE TABLE Library ( id SERIAL PRIMARY KEY, libid UUID UNIQUE NOT NULL, searchInput TEXT NOT NULL, userEmail TEXT NOT NULL, searchType TEXT NOT NULL, created_at TIMESTAMP DEFAULT NOW() ); -- Chats table for storing AI responses CREATE TABLE Chats ( id SERIAL PRIMARY KEY, libid UUID REFERENCES Library(libid), aiResp TEXT, created_at TIMESTAMP DEFAULT NOW() );
-
Run the development server
npm run dev # or yarn dev # or pnpm dev
-
Open your browser
Navigate to http://localhost:3000 to see the application.
derplexity/
βββ app/ # Next.js App Router
β βββ (auth)/ # Authentication routes
β β βββ sign-in/ # Sign in page
β β βββ sign-up/ # Sign up page
β βββ (routes)/ # Protected routes
β β βββ discover/ # Discovery page
β β βββ emotion/[libid]/ # Emotion analysis results
β β βββ library/ # Search history
β β βββ search/[libid]/ # Search results
β βββ api/ # API routes
β β βββ llm-model/ # AI model integration
β β βββ search-api/ # Search functionality
β β βββ inngest/ # Background job webhook
β βββ globals.css # Global styles
β βββ layout.tsx # Root layout
β βββ page.tsx # Home page
βββ components/ # React components
β βββ ui/ # shadcn/ui components
β βββ AppSidebar.tsx # Application sidebar
β βββ ChatInputBox.tsx # Main search interface
β βββ SpeechRecognition.tsx # Voice input component
βββ context/ # React context providers
βββ hooks/ # Custom React hooks
βββ inngest/ # Background job functions
βββ lib/ # Utility functions
βββ services/ # External service integrations
βββ public/ # Static assets
- Enter your query in the search box on the home page
- Select "Search" mode for web search or "Emotion" mode for emotion analysis
- Click the search button or press Enter
- View AI-generated responses with cited sources
- Click the microphone icon in the search box
- Speak your query clearly
- The speech will be automatically transcribed to text
- Navigate to the Library page from the sidebar
- View your search history and previous results
- Click on any previous search to view the results again
The application uses Google Gemini for AI responses. You can modify the model settings in inngest/function.ts
:
model: step.ai.models.gemini({
model: "gemini-1.5-flash", // Change model version here
apiKey: process.env.NEXT_PUBLIC_GEMINI_API_KEY,
})
The project uses shadcn/ui components. You can customize the theme in app/globals.css
and component configurations in components.json
.
- Push your code to GitHub
- Connect your repository to Vercel
- Add environment variables in Vercel dashboard
- Deploy automatically
The application can be deployed on any platform that supports Next.js:
- Netlify
- Railway
- Render
- AWS Amplify
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
- Fork the project
- Create your feature branch (
git checkout -b feature/AmazingFeature
) - Commit your changes (
git commit -m 'Add some AmazingFeature'
) - Push to the branch (
git push origin feature/AmazingFeature
) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- Perplexity AI for inspiration
- shadcn/ui for beautiful UI components
- Clerk for authentication
- Supabase for database services
- Inngest for background job processing
If you have any questions or suggestions, feel free to reach out or open an issue on GitHub.
Built with β€οΈ using Next.js and modern web technologies.