Friday is an AI-powered conversational search engine inspired by Perplexity AI. It searches the live web, extracts context, synthesizes information using GPT-4o, and streams interactive answers in real-time. It features secure user registration, session persistent conversation history, and contextual follow-up questions.
Friday is built as a unified, full-stack Next.js 16 application running on Vercel and Edge-compatible runtimes:
- Framework: Next.js 16 (App Router + React 19)
- Runtime & Bundler: Bun / Node.js (Vercel Edge & Serverless compatible)
- Styling: Tailwind CSS 4, Shadcn UI / Radix UI, Framer Motion
- ORM: Prisma ORM 7.8 (
@prisma/adapter-pgPostgreSQL adapter with custom generated client) - Database: Supabase PostgreSQL (Managed DB with connection pooling & pgBouncer)
- Search Engine: Tavily Search AI (Real-time advanced web search and scraping)
- LLM Orchestration: Vercel AI SDK (
ai&@ai-sdk/openairunning GPT-4o via Web Streams API) - Auth: Supabase Auth (JWT verification & auto user persistence)
friday/
βββ app/ # Next.js App Router Pages & API Route Handlers
β βββ api/
β β βββ ask/ # POST /api/ask β AI streaming endpoint (Web Streams API)
β β βββ conversations/ # GET /api/conversations, GET/PATCH/DELETE [id], export
β β βββ followups/ # POST /api/followups β Contextual follow-up streaming
β βββ auth/ # Authentication & Callback routes
β βββ conversation/[id]/ # Historical conversation view
β βββ search/[id]/ # Active search view
β βββ globals.css # Global Tailwind CSS & custom design tokens
β βββ layout.tsx # Global App Layout
βββ components/ # Reusable UI components (SearchBar, Sidebar, MetaballBackground)
βββ generated/ # Automatically generated Prisma Client (`generated/client`)
βββ lib/
β βββ db/ # Singleton Prisma DB connection instance
β βββ services/ # Clean server service layer (auth, ai, conversation, search)
β βββ stream/ # Web Streams API (`ReadableStream`) helper utilities
β βββ api.ts # Axios API Client with TTL caching & request deduplication
βββ prisma/ # Prisma schema & database migrations
βββ public/ # Static public assets
βββ .env # Prisma CLI environment variables (Database & API Keys)
βββ .env.local # Next.js local development overrides
βββ package.json # Full-stack dependencies & build scripts
βββ README.md # Project documentation
The PostgreSQL database contains the following models managed via Prisma (prisma/schema.prisma):
erDiagram
User ||--o{ Conversation : owns
Conversation ||--o{ Message : contains
User {
string id PK "UUID"
string email UK
enum provider "Github | Google"
string name
string supabaseId
}
Conversation {
string id PK "UUID"
string title
string slug
string userId FK
}
Message {
int id PK "Autoincrement"
string content
enum role "User | Assistance"
string conversationId FK
DateTime createdAt
}
Set up your credentials inside .env (loaded automatically by the Prisma CLI) and/or .env.local (loaded automatically by Next.js during local development):
# Supabase Client & Service Keys
NEXT_PUBLIC_SUPABASE_URL=https://[db_ref].supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
VITE_SUPABASE_URL=https://[db_ref].supabase.co
VITE_SUPABASE_SECRET_KEY=your_supabase_service_role_key
# API Target URL (Leave empty when deployed or running locally with internal App Router)
NEXT_PUBLIC_API_URL=
# Tavily API Search Key
TAVILY_API_KEY=your_tavily_api_key
# OpenAI / AI Gateway API Key
AI_GATEWAY_API_KEY=your_openai_api_key
# Database Connection (Supabase PostgreSQL)
DATABASE_URL="postgresql://postgres.[db_ref]:[pass]@aws-1-ap-northeast-2.pooler.supabase.com:6543/postgres?pgbouncer=true"
DIRECT_DATABASE_URL="postgresql://postgres.[db_ref]:[pass]@aws-1-ap-northeast-2.pooler.supabase.com:5432/postgres"
# Optional GitHub OAuth
GITHUB_OAUTH_CLIENT_ID=your_github_client_id
GITHUB_OAUTH_CLIENT_SECRET=your_github_client_secretEnsure you have Bun installed locally on your system.
- Configure your database URLs in
.envor.env.local. - Generate the custom Prisma Client (
generated/client):bun run db:generate
Start the unified full-stack Next.js dev server on port 3000:
bun install
bun devVerify full production readiness (bun --bun run prisma generate && next build):
bun run buildTo ensure instantaneous typing responsiveness and minimal network overhead, Friday incorporates a specialized multi-layer optimization strategy:
- Adaptive 3D Raymarching Throttling (
MetaballBackground): The interactive 3D metaball canvas dynamically detects when the user is typing into an<input>or<textarea>element (document.activeElement). While typing, raymarching loops throttle to ~15 FPS to yield 100% of main thread priority to UI rendering, instantly restoring buttery 60 FPS when typing stops. Max shader iterations are bounded andpixelRatiois capped at1.25for optimal GPU performance. - Client-Side Request Deduplication & TTL Caching (
lib/api.ts):fetchConversations()implements an in-memoryPromisededuplication layer and a5000msTTL cache. Simultaneous requests across sidebar and layout components share a single network call, eliminating database polling overhead while automatically invalidating ondeleteConversationorrenameConversation. - Database Bounding (
take: 50): Database queries togetUserConversationslimit historical retrieval to the top 50 recent sessions, minimizing JSON serialization latency and serverless connection pool consumption.
All internal requests are authenticated via Supabase JWT attached to the Authorization header by the frontend API interceptor (lib/api.ts).
Authorization: Bearer <JWT_Token_From_Supabase>Streams an AI answer based on real-time web search results via standard Web Streams API.
- URL:
/api/ask - Method:
POST - Headers:
Content-Type: application/json - Request Body:
{ "query": "What is the best way to learn Rust in 2026?" }
- Raw Text: Streams markdown chunks generated by GPT-4o.
- Sources Tag: Appended at the end of the text stream:
<SOURCES> [{"url": "https://example.com/rust", "title": "Learn Rust in 2026"}] <SOURCES> - Conversation ID Tag: Appended at the very end of the stream for tracking follow-ups:
<CONVERSATION_ID> 550e8400-e29b-41d4-a716-446655440000 <CONVERSATION_ID>
Continues a search session by appending conversation history (chatHistory) to the prompt.
- URL:
/api/followups - Method:
POST - Headers:
Content-Type: application/json - Request Body:
{ "query": "Can you give me a code example of a web server in Rust?", "conversationId": "550e8400-e29b-41d4-a716-446655440000" } - Response Format: Same streaming format with
<SOURCES>blocks (closes cleanly after<SOURCES>).
Fetches the list of previous conversations for the current authenticated user.
- URL:
/api/conversations - Method:
GET - Success Response:
200 OK{ "conversations": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "title": "What is the best way to learn Rust in 2026?", "slug": "what-is-the-best-way-to-learn-rust-in-2026", "messages": [ { "content": "What is the best way to learn Rust in 2026?", "createdAt": "2026-06-25T18:16:08.000Z" } ] } ] }
Fetches all messages for a specific conversation session.
- URL:
/api/conversations/:id - Method:
GET - Success Response:
200 OK{ "conversation": { "id": "550e8400-e29b-41d4-a716-446655440000", "title": "What is the best way to learn Rust in 2026?", "slug": "what-is-the-best-way-to-learn-rust-in-2026", "userId": "usr_abc123", "messages": [ { "id": 1, "content": "What is the best way to learn Rust in 2026?", "role": "User", "conversationId": "550e8400-e29b-41d4-a716-446655440000", "createdAt": "2026-06-25T18:16:08.000Z" }, { "id": 2, "content": "To learn Rust in 2026, start with...", "role": "Assistance", "conversationId": "550e8400-e29b-41d4-a716-446655440000", "createdAt": "2026-06-25T18:16:15.000Z" } ] } }
Updates the title of an existing conversation.
- URL:
/api/conversations/:id - Method:
PATCH - Headers:
Content-Type: application/json - Request Body:
{ "title": "Learning Rust β Best Resources" }
Deletes a conversation and all its associated messages.
- URL:
/api/conversations/:id - Method:
DELETE
Downloads the full transcript of a conversation as a formatted .txt file.
- URL:
/api/conversations/:id/export - Method:
GET - Headers Returned:
Content-Type: text/plain,Content-Disposition: attachment; filename="conversation-<id>.txt"