Imported from djpatel20/renly-ai (
AGENTS.md). Install upstream withnpx skills add djpatel20/renly-ai. Copyright stays with the author.
AGENTS.md - Renly AI Agent Configuration & Guidelines
This document provides comprehensive guidelines for AI agents (like Cursor AI, GitHub Copilot, Gemini, etc.) working with the Renly AI codebase. It serves as a complete reference for understanding the project structure, coding standards, and best practices.
📋 Table of Contents
- Project Overview
- Architecture & Tech Stack
- Code Organization
- Coding Standards
- API Design Patterns
- Database Schema & Models
- Authentication & Authorization
- Credits & Payment System
- Daily Rate Limiting
- Video Generation (Veo API)
- Error Handling & Logging
- Security Guidelines
- Testing & Quality Assurance
- Common Tasks & Patterns
- UI Components & Design System
- Deployment & Infrastructure
- Performance Optimizations
Project Overview
Renly AI (formerly Pixi AI Web) is a sophisticated, production-ready Next.js application for AI-powered image and video generation and editing. The platform provides users with multiple AI-powered tools for creating and manipulating images and videos through a comprehensive web interface.
Core Features
- AI Image Generation: Text-to-image using Google Gemini AI (
gemini-3-pro-image-previewwith fallback togemini-2.5-flash-image) - AI Video Generation: Text-to-video using Google Veo API (
veo-3.1-generate-previewandveo-3.1-fast-generate-preview) - Advanced Image Editing: Masks, prompts, and AI-powered transformations
- Virtual Try-On: Cloth checker for clothing items on photos
- Logo & Sticker Generation: Professional branding materials with background removal
- Workshop Chat: Interactive AI generation with chat interface
- Video Workshop: Dedicated video generation interface with polling status
- Image-to-Image: Transform existing images using AI
- Image-to-Video: Animate images using Veo API
- Packs: Collection of specialized tools (Wedding Photo, Cooking, YouTube Thumbnail, Cloth Checker, Image-Image)
- Showcase: Public gallery featuring community-generated images
- Gallery System: Personal image/video gallery with pagination and visibility controls
- Credits System: Unlimited credits with daily rate limiting
- Daily Rate Limiting: 10 images/day, 1 video/day per user
- Payment Processing: Razorpay integration for subscriptions and one-time purchases
Key Characteristics
- Framework: Next.js 16 with App Router
- Language: TypeScript with strict mode enabled
- Database: Neon Postgres with Prisma ORM v7
- Authentication: Clerk (primary) with database user sync
- State Management: Redux Toolkit v2 with typed hooks
- Styling: Tailwind CSS 4 with custom design system
- Package Manager: Bun (latest version)
- AI/ML:
- Images: Google Gemini AI (
gemini-3-pro-image-previewprimary,gemini-2.5-flash-imagefallback) - Videos: Google Veo API (
veo-3.1-generate-preview,veo-3.1-fast-generate-preview)
- Images: Google Gemini AI (
- Deployment: Vercel (serverless) with Neon database and AWS S3 storage
Architecture & Tech Stack
Frontend Architecture
src/app/
├── (auth)/ # Authentication routes (login, register)
├── (console)/ # Protected application routes
│ ├── packs/ # Specialized tool collection
│ │ ├── cloth-checker/
│ │ ├── cooking/
│ │ ├── image-edit/
│ │ ├── image-image/
│ │ ├── wedding-photo/
│ │ └── youtube-thumbnail/
│ ├── branding/ # Branding tools (logo, sticker)
│ ├── gallery/ # User gallery (images + videos)
│ ├── showcase/ # Public showcase gallery
│ ├── workshop/ # Chat-based image generation
│ ├── video-workshop/ # Video generation workspace
│ ├── trending/ # Trending content
│ └── account/ # User account management
├── api/ # API route handlers
└── layout.tsx # Root layout
Backend Architecture
- API Routes: Next.js API routes in
src/app/api/ - Server Actions: Not used (prefer API routes for all server logic)
- Middleware: Authentication, validation, security wrappers in
src/lib/middleware/ - Security Wrapper:
withSecurity()provides CSRF, rate limiting, auth, and request validation
External Services & Integrations
| Service | Purpose | Integration Method |
|---|---|---|
| Google Gemini AI | Image generation | REST API (gemini-3-pro-image-preview) |
| Google Veo API | Video generation | REST API (veo-3.1-generate-preview) |
| AWS S3 | Image/video storage and global CDN | SDK (@aws-sdk/client-s3) |
| Supabase | Edge Functions for background removal | HTTP calls |
| Razorpay | Payment processing (India) | SDK (razorpay) |
| Neon | Managed PostgreSQL database | Connection string |
| Clerk | Primary authentication provider | SDK (@clerk/nextjs) |
Key Libraries & Dependencies
- AI/ML: Google Gemini AI, Google Veo API, Fabric.js for client-side image editing
- Database: Prisma ORM v7 with PostgreSQL adapter
- Authentication: Clerk with database user synchronization
- State Management: Redux Toolkit v2 with React-Redux v9
- UI Components: Radix UI primitives with custom Tailwind styling
- HTTP Client: Axios v1.13 with custom interceptors
- Forms: React Hook Form v7 with validation
- Image Processing: Sharp for server-side processing
- Animations: Framer Motion v12 for smooth transitions
- Icons: Lucide React for consistent iconography
- Toast Notifications: Sonner for user feedback
- Date Utilities: date-fns v4 for date manipulation
Code Organization
Directory Structure
├── src/app/ # Next.js App Router
│ ├── (auth)/ # Auth routes (public)
│ │ ├── login/
│ │ ├── register/
│ │ └── layout.tsx
│ ├── (console)/ # Protected routes
│ │ ├── packs/ # Specialized tools collection
│ │ ├── branding/ # Branding tools
│ │ ├── gallery/ # User gallery
│ │ ├── showcase/ # Public showcase
│ │ ├── workshop/ # Image generation
│ │ ├── video-workshop/ # Video generation
│ │ ├── trending/ # Trending content
│ │ └── account/ # Account management
│ └── api/ # API route handlers
│ ├── account/ # Account endpoints
│ ├── analytics/ # Analytics endpoints
│ ├── cloth-checker/ # Cloth checker endpoint
│ ├── edit-image/ # Image editing endpoint
│ ├── gallery/ # Gallery CRUD endpoints
│ ├── generate-image/ # Image generation endpoint
│ ├── generate-logo/ # Logo generation endpoint
│ ├── generate-sticker/ # Sticker generation endpoint
│ ├── generate-video/ # Video generation endpoints
│ │ ├── route.ts # Start generation
│ │ └── status/ # Poll status
│ ├── health/ # Health check endpoint
│ ├── image-image/ # Image-to-image endpoint
│ ├── payments/ # Payment endpoints
│ ├── showcase/ # Showcase endpoints
│ ├── video-download/ # Video download proxy
│ ├── wedding-photo/ # Wedding photo endpoint
│ └── workshop/ # Workshop endpoints
├── src/components/ # React components
│ ├── ui/ # Base UI components (Radix UI)
│ ├── account/ # Account components
│ ├── analytics/ # Analytics components
│ ├── auth/ # Auth components
│ ├── console/ # Console/dashboard components
│ ├── error-handling/ # Error boundary components
│ ├── gallery/ # Gallery components
│ ├── image/ # Image editing components
│ ├── landing/ # Landing page components
│ ├── navigation/ # Navigation components
│ ├── payments/ # Payment components
│ ├── providers/ # React providers
│ ├── trending/ # Trending components
│ ├── video/ # Video components
│ └── workshop/ # Workshop components
├── src/lib/ # Core logic
│ ├── api/ # API client utilities
│ ├── config/ # Configuration (env)
│ ├── constants/ # Constants and HTTP status codes
│ ├── db/ # Database (Prisma)
│ ├── errors/ # Error handling utilities
│ ├── events/ # Event system
│ ├── middleware/ # Middleware functions
│ ├── payments/ # Payment utilities
│ ├── store/ # Redux stores
│ └── utils/ # Utility functions
│ ├── analytics.ts # Analytics logging
│ ├── credits.ts # Credit management
│ ├── daily-rate-limit.ts # Daily limits
│ ├── gallery.ts # Gallery utilities
│ ├── gemini-api.ts # Gemini API wrapper
│ ├── logger.ts # Logging utilities
│ ├── s3.ts # S3 utilities
│ ├── security.ts # Security utilities
│ ├── seo.ts # SEO utilities
│ └── veo-api.ts # Veo API wrapper
├── src/types/ # TypeScript type definitions
├── src/hooks/ # Custom React hooks
└── public/ # Static assets
File Naming Conventions
- Components: PascalCase (e.g.,
GalleryImageCard.tsx,VideoWorkshop.tsx) - Utilities: camelCase (e.g.,
imageUtils.ts,veo-api.ts) - Types: camelCase (e.g.,
api.ts,gallery.ts) - API Routes:
route.ts(Next.js convention) - Pages:
page.tsx(Next.js convention) - Layouts:
layout.tsx(Next.js convention)
Coding Standards
TypeScript
- Strict Mode: Always enabled
- No
any: Useunknownand type guards instead - Explicit Types: Prefer explicit types over inference for public APIs
- Type Imports: Use
import typefor type-only imports
// ✅ Good
import type { ApiError } from '@/types/api';
import { clientApi } from '@/lib/api';
// ❌ Bad
import { ApiError, clientApi } from '@/lib/api';
React Components
- Functional Components Only: No class components
- Client Components: Mark with
'use client'directive - Server Components: Default (no directive needed)
- Props Interface: Define props interfaces above component
// ✅ Good
interface GalleryImageCardProps {
image: GalleryImage;
onDelete?: (id: string) => void;
}
export function GalleryImageCard({ image, onDelete }: GalleryImageCardProps) {
// ...
}
Code Style
- Formatting: Prettier (run
bun formatbefore committing) - Linting: ESLint with TypeScript rules
- Imports: Group imports: external → internal → types
- Comments: Use JSDoc for public functions
/**
* Uploads a base64 image to S3
* @param imageData - Base64 encoded image data
* @param userId - User ID for organizing files
* @param mimeType - MIME type of the image
* @returns Object containing S3 key and URL
*/
export async function uploadImageToS3(
imageData: string,
userId: string,
mimeType: string = 'image/png'
): Promise<{ s3Key: string; s3Url: string }> {
// ...
}
API Design Patterns
API Route Structure
All API routes follow this pattern using the withSecurity wrapper:
import { NextRequest, NextResponse } from 'next/server';
import { withSecurity } from '@/lib/middleware/security-wrapper';
import { validatePromptInput } from '@/lib/middleware/validation';
import { chargeUserCredits, refundChargeIfNeeded } from '@/lib/utils/credits';
import { checkDailyLimit } from '@/lib/utils/daily-rate-limit';
import { getLogger } from '@/lib/utils/logger';
const log = getLogger({ module: 'api:generate-image' });
async function handleGenerateImage(
request: NextRequest,
context?: { user?: { id: string; email?: string | null } }
): Promise<NextResponse> {
const userId = context?.user?.id;
if (!userId) {
return NextResponse.json(
{ success: false, error: 'Authentication required' },
{ status: 401 }
);
}
// 1. Check Daily Limit
const limitCheck = await checkDailyLimit(userId);
if (limitCheck.exceeded) {
return NextResponse.json(
{
success: false,
error: 'Daily generation limit reached',
used: limitCheck.used,
limit: limitCheck.limit,
},
{ status: 429 }
);
}
let creditCharge: CreditChargeResult | null = null;
try {
// 2. Validation
const body = await request.json();
const promptValidation = validatePromptInput(body.prompt, 2000);
if (!promptValidation.isValid) {
return NextResponse.json(
{ success: false, error: promptValidation.error },
{ status: 400 }
);
}
// 3. Credit Check & Charge
creditCharge = await chargeUserCredits({
userId,
amount: 1,
reason: 'generate-image',
metadata: { endpoint: 'generate-image' },
});
// 4. Business Logic
const result = await performOperation(body);
// 5. Log for daily limit tracking
await logImageGeneration({ userId, success: true, ... });
// 6. Response
return NextResponse.json({ success: true, data: result });
} catch (error) {
// 7. Refund on error
await refundChargeIfNeeded(creditCharge, 'operation_failed');
log.error('Request failed', { error });
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}
export const POST = withSecurity(handleGenerateImage, {
requireAuthentication: true,
rateLimit: 60,
csrf: true,
});
Security Wrapper Options
interface SecurityOptions {
requireAuthentication?: boolean; // Require auth (default: false)
maxRequestSize?: number; // Max request size in bytes (default: 10MB)
rateLimit?: number; // Requests per minute (default: 60, 0 to disable)
csrf?: boolean; // Enable CSRF protection (default: true)
authMode?: 'required' | 'optional' | 'none'; // Auth mode override
}
Error Handling
- 400: Validation errors
- 401: Unauthorized (not authenticated)
- 402: Payment required (insufficient credits)
- 403: Forbidden (CSRF/invalid origin)
- 404: Not found
- 429: Rate limit exceeded (daily limit or per-minute)
- 500: Internal server error
Response Format
// Success
{
success: true,
data: { ... },
creditsRemaining?: number,
hasUnlimitedCredits?: boolean
}
// Error
{
success: false,
error: "Error message",
code?: "ERROR_CODE",
details?: { ... },
creditsRemaining?: number,
used?: number,
limit?: number,
resetsAt?: string
}
Database Schema & Models
Prisma ORM Configuration
- Version: Prisma v7 with PostgreSQL adapter
- Client: Singleton pattern in
@/lib/db/prisma - Migrations: Version-controlled schema changes in
prisma/migrations/ - Seed: Initial data population via
prisma/seed.ts
Database Usage Patterns
import { prisma } from '@/lib/db/prisma';
// ✅ Type-safe queries with proper error handling
const user = await prisma.user.findUnique({
where: { id: userId },
select: {
id: true,
email: true,
credits: { select: { balance: true, unlimited: true } },
},
});
// ✅ Database transactions for multi-step operations
const result = await prisma.$transaction(async tx => {
const user = await tx.user.findUnique({ where: { id: userId } });
const creditCharge = await tx.userCredits.update({
where: { userId },
data: { balance: { decrement: 1 } },
});
return { user, creditCharge };
});
Core Database Models
User Management
**User**: Authentication data, profile information,isActiveflag for soft delete**Account**: OAuth provider connections (legacy, migrated to Clerk)**GalleryImage**: Generated images/videos with metadata, visibility controls,mediaTypefield
Video Generation
**VideoGeneration**: Active video generation jobs with Veo operation trackingoperationId: Gemini/Veo operation ID for status pollingstatus:pending|processing|completed|faileddurationSeconds: Video duration (4, 6, or 8 seconds)aspectRatio:16:9or9:16resolution:720p,1080p, or4k
**VideoGenerationLog**: Analytics and daily limit tracking for video generations
Credits & Billing
**UserCredits**: Credit balance and lifetime tracking (unlimited flag for all users)**CreditTransaction**: Detailed transaction history**SubscriptionPlan**: Available pricing plans**UserSubscription**: Active user subscriptions**PaymentOrder**: Payment processing records**PaymentTransaction**: Transaction details and webhooks
Content & Assets
**GalleryImage**: Generated images/videos with metadata, public/featured flagsmediaType:imageorvideodurationSeconds: For videos
**ImageGenerationLog**: AI image generation tracking and daily limit counting**VideoGenerationLog**: AI video generation tracking and daily limit counting**ApiUsageLog**: API usage statistics and monitoring**UserEvent**: User behavior analytics
Security & Audit
**AuditLog**: Comprehensive security audit trail
Database Relationships
-- Key Relationships
User (1) ↔ (many) GalleryImages
User (1) ↔ (many) VideoGenerations
User (1) ↔ (1) UserCredits
User (1) ↔ (many) CreditTransactions
User (1) ↔ (1) UserSubscription
User (1) ↔ (many) ImageGenerationLogs
User (1) ↔ (many) VideoGenerationLogs
SubscriptionPlan (1) ↔ (many) PaymentOrders
PaymentOrder (1) ↔ (many) PaymentTransactions
VideoGeneration (1) ↔ (many) VideoGenerationLogs
Database Indexing Strategy
- User lookups: Indexed on
email,authSessionVersion - Credits: Indexed on
userIdfor fast balance queries - Images: Indexed on
userId,createdAt,isPublic,isFeatured,deletedAt,mediaType - Video Generations: Indexed on
userId,operationId,status,createdAt - Generation Logs: Indexed on
userId,success,createdAtfor daily limit calculation - Audit logs: Indexed on
userId,action,createdAt
Authentication & Authorization
Clerk Authentication
- Primary Provider: Clerk (
@clerk/nextjs) - Session Management: Clerk handles sessions, database syncs user data
- All Users: Automatically granted unlimited credits on signup
- Database Sync: Clerk users are synced to database via
getOrCreateDbUser()
Authentication Flow
// In API routes - using security wrapper
import { withSecurity } from '@/lib/middleware/security-wrapper';
async function handleRequest(
request: NextRequest,
context?: { user?: { id: string; email?: string | null } }
): Promise<NextResponse> {
const userId = context?.user?.id; // Provided by withSecurity
if (!userId) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// Use userId for operations
}
export const POST = withSecurity(handleRequest, {
requireAuthentication: true,
});
Manual Authentication (if needed)
// In API routes without security wrapper
import { requireAuth, optionalAuth } from '@/lib/middleware/auth';
export async function POST(req: NextRequest) {
const authResult = await requireAuth(req);
if (authResult instanceof NextResponse) {
return authResult; // Error response
}
const { user } = authResult;
// Use user.id for operations
}
Client-Side Authentication
// In client components
import { useAuth } from '@/hooks/use-auth';
function MyComponent() {
const { isLoaded, isSignedIn, user } = useAuth();
if (!isLoaded) return <Loading />;
if (!isSignedIn) return <SignInPrompt />;
// Use user data
}
Authorization
- Route Protection: Console layout checks authentication
- API Protection: Use
withSecuritywrapper withrequireAuthentication: true - Resource Ownership: Verify user owns the resource before operations
- Soft Delete: Check
isActiveflag for deactivated accounts
Credits & Payment System
Credit Management Architecture
The credits system is designed with unlimited credits for all users but enforces daily rate limits.
Core Functions
import {
chargeUserCredits,
fetchUserCredits,
grantCredits,
refundCredits,
grantUnlimitedCredits,
} from '@/lib/utils/credits';
// Charge credits with automatic refund on failure
const chargeResult = await chargeUserCredits({
userId,
amount: 1, // 1 credit per image/video generation
reason: 'generate-image',
metadata: {
endpoint: 'generate-image',
model: 'gemini-3-pro-image-preview',
},
});
// Refund on error
await chargeResult.refund('operation_failed', { error: 'API timeout' });
// Fetch user credit balance
const credits = await fetchUserCredits(userId);
// Returns: { balance: 100, lifetimeEarned: 150, lifetimeSpent: 50, unlimited: true }
// Grant credits (admin operation)
await grantCredits(userId, 50, 'Referral bonus', { source: 'referral' });
// Grant unlimited credits
await grantUnlimitedCredits(userId, 'Development testing');
Welcome Credits System
- Automatic Grant: All users get unlimited credits
- Idempotent: Only granted once per user
- Database Transaction: Ensures consistency
- Audit Trail: Tracked in credit transactions
Payment Processing (Razorpay)
Payment Endpoints
/api/payments/create-order: Create payment order/api/payments/verify: Verify payment/api/payments/webhook: Webhook handler/api/payments/custom-credits: Custom credit purchase/api/payments/mark-failed: Mark failed orders
Subscription Plans
- Free Tier: Unlimited credits with daily limits
- Premium Monthly: Higher daily limits, all features
- Premium Yearly: Higher daily limits with discount
- Custom Credits: One-time credit purchases
Daily Rate Limiting
Overview
The system implements per-user daily limits for both image and video generation. These are tracked separately and configured via environment variables.
Configuration
# In .env.local
DAILY_IMAGE_LIMIT="10" # 10 images per day per user
DAILY_VIDEO_LIMIT="1" # 1 video per day per user
Usage in API Routes
import {
checkDailyLimit,
checkDailyVideoLimit,
getDailyGenerationUsage,
getCombinedDailyUsage,
DAILY_IMAGE_LIMIT,
DAILY_VIDEO_LIMIT,
} from '@/lib/utils/daily-rate-limit';
// Check image generation limit
const imageLimitCheck = await checkDailyLimit(userId);
if (imageLimitCheck.exceeded) {
return NextResponse.json(
{
success: false,
error: 'DAILY_LIMIT_EXCEEDED',
message: `Daily image generation limit reached (${DAILY_IMAGE_LIMIT}/day).`,
used: imageLimitCheck.used,
limit: imageLimitCheck.limit,
remaining: 0,
resetsAt: imageLimitCheck.resetsAt.toISOString(),
},
{ status: 429 }
);
}
// Check video generation limit
const videoLimitCheck = await checkDailyVideoLimit(userId);
if (videoLimitCheck.exceeded) {
return NextResponse.json(
{
success: false,
error: 'DAILY_VIDEO_LIMIT_EXCEEDED',
message: `Daily video generation limit reached (${DAILY_VIDEO_LIMIT}/day).`,
used: videoLimitCheck.used,
limit: videoLimitCheck.limit,
remaining: 0,
resetsAt: videoLimitCheck.resetsAt.toISOString(),
},
{ status: 429 }
);
}
// Get combined usage for UI display
const usage = await getCombinedDailyUsage(userId);
// Returns: { image: { used, limit, remaining, resetsAt }, video: { used, limit, remaining, resetsAt } }
How Limits Are Tracked
- Image Limits: Counted from
ImageGenerationLogtable (successful generations only) - Video Limits: Counted from
VideoGenerationLogtable (successful generations only) - Reset Time: Daily at midnight UTC
- Fail-Open: If database query fails, request proceeds (to avoid blocking users)
Logging for Rate Limits
After successful generation, you MUST log to the appropriate table:
// For images
await logImageGeneration({
userId,
endpointType: 'generate-image',
prompt,
model: 'gemini-3-pro-image-preview',
success: true,
galleryImageId: image.id,
s3Key: image.s3Key,
mimeType: 'image/png',
processingTimeMs: Date.now() - startTime,
});
// For videos
await logVideoGeneration({
userId,
endpointType: 'generate-video',
prompt,
model: 'veo-2.0-generate-001',
success: true,
videoGenerationId: videoGen.id,
durationSeconds: 8,
processingTimeMs: Date.now() - startTime,
});
Video Generation (Veo API)
Overview
Video generation uses Google's Veo API for text-to-video and image-to-video generation. This is an asynchronous process requiring status polling.
Veo Models
// Available models
export const VEO_MODEL = 'veo-3.1-generate-preview'; // High quality
export const VEO_FAST_MODEL = 'veo-3.1-fast-generate-preview'; // Faster
Video Generation Options
interface VideoGenerationOptions {
prompt: string;
negativePrompt?: string;
aspectRatio?: '16:9' | '9:16'; // Default: '16:9'
resolution?: '720p' | '1080p' | '4k'; // Default: '720p'
durationSeconds?: 4 | 6 | 8; // Default: 8
personGeneration?: 'allow_adult' | 'dont_allow';
seed?: number;
image?: {
// For image-to-video
imageBytes: string; // Base64
mimeType: string;
};
}
API Endpoints
Start Video Generation
POST /api/generate-video
// Request
{
prompt: "A cat walking through a garden",
aspectRatio: "16:9",
resolution: "720p",
durationSeconds: 8,
negativePrompt: "blurry, low quality",
image: "data:image/png;base64,..." // Optional for image-to-video
}
// Response (202 Accepted)
{
success: true,
operationId: "operations/xxx",
message: "Video generation started. Poll the status endpoint for updates.",
estimatedWaitTimeSeconds: 60,
creditsCharged: 1,
hasUnlimitedCredits: true
}
Poll Video Status
GET /api/generate-video/status?operationId=xxx
// Response - Processing
{
success: true,
status: "processing",
message: "Video is still being generated"
}
// Response - Completed
{
success: true,
status: "completed",
videoUrl: "https://s3.../video.mp4",
galleryImageId: "uuid"
}
// Response - Failed
{
success: false,
status: "failed",
error: "Generation failed: reason"
}
Download Video (Proxy)
GET /api/video-download?id=xxx
Proxies video download from S3 to avoid CORS issues.
Video Generation Flow
import {
startVideoGeneration,
checkVideoOperationStatus,
downloadGeneratedVideo,
parseVeoError,
} from '@/lib/utils/veo-api';
// 1. Start generation
const { operationId, usedFallback } = await startVideoGeneration({
prompt: 'A beautiful sunset over the ocean',
aspectRatio: '16:9',
durationSeconds: 8,
});
// 2. Store in database
await prisma.videoGeneration.create({
data: {
userId,
operationId,
prompt,
status: 'processing',
},
});
// 3. Poll for completion (done by frontend)
const status = await checkVideoOperationStatus(operationId);
if (status.done) {
if (status.error) {
// Handle error
} else {
// 4. Download and save video
const { videoData, mimeType } = await downloadGeneratedVideo(videoUri);
const { s3Key, s3Url } = await uploadVideoToS3(videoData, userId, mimeType);
// 5. Update database and gallery
await prisma.videoGeneration.update({
where: { operationId },
data: { status: 'completed', s3Key, s3Url },
});
}
}
VideoWorkshop Component
The src/components/video/VideoWorkshop.tsx component provides:
- Text prompt input for video generation
- Image upload for image-to-video
- Aspect ratio selection (16:9, 9:16)
- Duration selection (4s, 6s, 8s)
- Resolution selection (720p, 1080p, 4k)
- Status polling with visual feedback
- Video playback and download
Error Handling & Logging
Structured Logging System
The application uses Winston for comprehensive, structured logging across all components.
Logger Configuration
import { getLogger } from '@/lib/utils/logger';
// Create scoped logger with context
const log = getLogger({
module: 'api:generate-video',
userId: context?.user?.id,
});
// Structured logging with context
log.info('Video generation started', {
userId,
prompt: prompt.substring(0, 100),
model: 'veo-3.1-generate-preview',
aspectRatio: '16:9',
durationSeconds: 8,
});
log.error('Video generation failed', {
error: error.message,
stack: error.stack,
userId,
operationId,
processingTimeMs: Date.now() - startTime,
});
User-Friendly Error Messages
import { getUserFriendlyError } from '@/lib/errors/user-messages';
import { showErrorToast } from '@/lib/errors/toast';
// Get user-friendly message based on error type
const friendlyError = getUserFriendlyError(error, 'gallery');
showErrorToast(error, { context: 'gallery' });
Error Recovery Strategies
- Automatic Retries: Failed API calls with exponential backoff (via
src/lib/utils/retry.ts) - Credit Refunds: Automatic refund on service failures
- API Key Fallback: Automatic fallback to secondary API key on auth errors
- Fallback UI: Graceful degradation for component failures
Security Guidelines
Input Validation
- All Inputs: Validate and sanitize before use
- Schema Validation: Use
validateRequest(),validatePromptInput(),validateImageInput()from@/lib/middleware/validation - Base64 Images: Validate format, size, and MIME type
API Security
- Rate Limiting: Implemented via
withSecuritywrapper (default: 60 req/min) + daily limits - Request Size Limits: 10MB maximum (configurable)
- CSRF Protection: Enabled by default in
withSecuritywrapper - Origin Validation: Checks request origin against allowed origins
- Security Headers: Set in
next.config.mjsandwithSecuritywrapper
Secrets Management
- Environment Variables: Never commit secrets
- API Keys: Store in environment variables only
- Fallback Keys: Support for
GEMINI_API_KEY_FALLBACKfor redundancy - Token Encryption: Uses
TOKEN_ENCRYPTION_KEYor fallback toNEXTAUTH_SECRET(legacy)
Testing & Quality Assurance
Pre-commit Checks
bun lint # ESLint
bun type-check # TypeScript type checking
bun format:check # Prettier formatting check
Build Verification
bun build # Production build
bun run check-all-errors # Full check: lint + type-check + build
Test Commands
bun test # Run tests with Vitest
bun test:ui # Run tests with UI
bun test:coverage # Run tests with coverage
Code Quality
- Type Safety: TypeScript strict mode
- Linting: ESLint with TypeScript rules
- Formatting: Prettier
- Unused Code: Remove unused imports, variables, and files
Common Tasks & Patterns
Adding a New API Route
- Create
src/app/api/[route-name]/route.ts - Use
withSecuritywrapper with appropriate options - Implement authentication check (if required)
- Check daily limit (for generation endpoints)
- Add input validation
- Implement credit charging (if applicable)
- Add business logic
- Log to appropriate table for daily limit tracking
- Return standardized response
- Add error handling, logging, and credit refund on error
Adding a New Video Generation Endpoint
- Follow standard API route pattern
- Check
checkDailyVideoLimit(userId)before proceeding - Use
startVideoGeneration()fromsrc/lib/utils/veo-api.ts - Store
VideoGenerationrecord in database - Call
logVideoGeneration()for daily limit tracking - Create status polling endpoint in
status/route.ts
Adding a New Feature Component
- Create component in
src/components/[feature]/ - Use TypeScript interfaces for props
- Mark as
'use client'if needed - Use UI components from
src/components/ui/ - Handle loading and error states
- Add accessibility attributes
- Use React performance patterns (memo, useMemo, useCallback)
Working with Credits
import { chargeUserCredits, refundChargeIfNeeded } from '@/lib/utils/credits';
// Charge credits (with automatic refund on error)
let charge: CreditChargeResult | null = null;
try {
charge = await chargeUserCredits({
userId,
amount: 1,
reason: 'generate-image',
});
// Perform operation
const result = await performOperation();
return NextResponse.json({ success: true, data: result });
} catch (error) {
// Refund on error
await refundChargeIfNeeded(charge, 'operation_failed');
throw error;
}
Image/Video Upload to S3
import { uploadImageToS3, uploadVideoToS3 } from '@/lib/utils/s3';
// Upload image
const { s3Key, s3Url } = await uploadImageToS3(
base64ImageData,
userId,
'image/png'
);
// Upload video
const { s3Key, s3Url } = await uploadVideoToS3(
base64VideoData,
userId,
'video/mp4'
);
Using Redux Store
import { useAppDispatch, useAppSelector } from '@/lib/store/hooks';
import { stickerSlice } from '@/lib/store/slices/stickerSlice';
function StickerGenerator() {
const dispatch = useAppDispatch();
const { isGenerating, error } = useAppSelector(state => state.sticker);
const handleGenerate = async () => {
dispatch(stickerSlice.actions.setGenerating(true));
try {
const result = await generateSticker(prompt);
dispatch(stickerSlice.actions.setResult(result));
} catch (error) {
dispatch(stickerSlice.actions.setError(error.message));
} finally {
dispatch(stickerSlice.actions.setGenerating(false));
}
};
}
UI Components & Design System
Component Architecture
Base UI Components (Radix UI)
Located in src/components/ui/ - these are the foundation components:
// Button component with variants
import { Button } from '@/components/ui/button';
<Button variant="default" size="lg" onClick={handleClick}>
Generate Image
</Button>
// Dialog/Modal components
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
// Form components
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Slider } from '@/components/ui/slider';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
Feature-Specific Components
// src/components/
├── gallery/ # Gallery-related components
│ ├── GalleryGrid.tsx
│ └── GalleryImageCard.tsx
├── workshop/ # AI workshop components
│ ├── WorkshopChat.tsx
│ ├── WorkshopChatInput.tsx
│ └── WorkshopImagesArea.tsx
├── video/ # Video components
│ └── VideoWorkshop.tsx
├── payments/ # Payment-related components
│ ├── PricingModal.tsx
│ ├── CreditsExhaustedModal.tsx
│ └── PaymentForm.tsx
├── console/ # Console/dashboard components
│ ├── ConsoleSidebar.tsx
│ ├── ConsoleImageUpload.tsx
│ └── ConsoleBottomBar.tsx
└── trending/ # Trending content components
└── trending-card.tsx
Design System Principles
Visual Design
- Typography: Space Mono font family for technical aesthetic
- Colors: Yellow primary (#ffde00) with dark/light theme support
- Shadows: Hard shadows for distinctive visual style
- Spacing: Consistent spacing scale using Tailwind classes
Responsive Design Patterns
// Mobile-first responsive classes
<div className="grid grid-cols-1 xs:grid-cols-2 sm:grid-cols-3 md:grid-cols-3 lg:grid-cols-4 gap-2 xs:gap-3 sm:gap-4 md:gap-5">
{/* Grid items */}
</div>
// Minimum touch target size (44px)
<Button className="min-h-[44px] min-w-[44px] px-4">
Click Me
</Button>
Deployment & Infrastructure
Environment Variables
# ===========================================
# RENLY AI - Environment Configuration
# ===========================================
# Database (Neon PostgreSQL) - Required
DATABASE_URL="postgresql://user:password@host/database?sslmode=require"
# Authentication (Clerk) - Required
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY="pk_test_xxx"
CLERK_SECRET_KEY="sk_test_xxx"
# Token Encryption (fallback key)
NEXTAUTH_SECRET="your-secret-key-for-token-encryption"
# AI Services (Google Gemini) - Required
GEMINI_API_KEY="your-google-gemini-api-key"
GEMINI_API_KEY_FALLBACK="your-fallback-gemini-api-key" # Optional redundancy
# AWS S3 Storage - Required for image/video storage
AWS_REGION="ap-south-1"
AWS_ACCESS_KEY_ID="your-aws-access-key"
AWS_SECRET_ACCESS_KEY="your-aws-secret-key"
AWS_S3_BUCKET_NAME="your-s3-bucket-name"
# Payment Processing (Razorpay) - Optional
RAZORPAY_KEY_ID="rzp_test_xxx"
RAZORPAY_KEY_SECRET="your-razorpay-secret"
RAZORPAY_WEBHOOK_SECRET="your-razorpay-webhook-secret"
# Daily Generation Limits
DAILY_IMAGE_LIMIT="10" # Images per day per user
DAILY_VIDEO_LIMIT="1" # Videos per day per user
# Supabase (optional - for background removal)
SUPABASE_URL="https://your-project.supabase.co"
SUPABASE_ANON_KEY="your-supabase-anon-key"
# Development Settings
DEV_UNLIMITED_CREDITS_EMAILS="your-email@example.com"
Deployment Checklist
# 1. Environment Setup
cp .env.example .env.local
# 2. Install dependencies
bun install
# 3. Generate Prisma client
bun prisma:generate
# 4. Database Migration
bun prisma:migrate
# 5. Build Verification
bun run check-all-errors # lint + type-check + build
# 6. Vercel Deployment
vercel --prod
Performance Optimizations
Frontend Optimizations
React Performance Patterns
// ✅ Memoization for expensive components
const ImageCard = memo(function ImageCard({ image, onDelete }) {
return (
<Card>
<Image src={image.s3Url} alt={image.prompt} />
<Button onClick={() => onDelete(image.id)}>Delete</Button>
</Card>
);
});
// ✅ useMemo for expensive calculations
const filteredImages = useMemo(() => {
return images.filter(img =>
img.prompt.toLowerCase().includes(searchTerm.toLowerCase())
);
}, [images, searchTerm]);
// ✅ useCallback for event handlers
const handleDelete = useCallback((id: string) => {
deleteImage(id).catch(error => {
log.error('Failed to delete image', { id, error });
});
}, []);
Backend Optimizations
Database Query Optimization
// ✅ Efficient queries with select
const userWithCredits = await prisma.user.findUnique({
where: { id: userId },
select: {
id: true,
email: true,
credits: {
select: { balance: true, unlimited: true },
},
},
});
// ✅ Pagination for large datasets
const images = await prisma.galleryImage.findMany({
where: { userId, deletedAt: null },
select: { id: true, s3Url: true, prompt: true, createdAt: true },
orderBy: { createdAt: 'desc' },
take: 20,
skip: offset,
});
API Reference
Core API Endpoints
| Endpoint | Method | Purpose | Credits | Daily Limit | Auth Required |
|---|---|---|---|---|---|
/api/generate-image |
POST | Text-to-image generation | 1 | 10/day | Yes |
/api/generate-video |
POST | Text-to-video generation | 1 | 1/day | Yes |
/api/generate-video/status |
GET | Poll video status | 0 | N/A | Yes |
/api/generate-logo |
POST | Professional logo creation | 1 | 10/day | Yes |
/api/generate-sticker |
POST | Sticker with background removal | 1 | 10/day | Yes |
/api/edit-image |
POST | Advanced image editing | 1 | 10/day | Yes |
/api/cloth-checker |
POST | Virtual clothing try-on | 1 | 10/day | Yes |
/api/image-image |
POST | Image-to-image transformation | 1 | 10/day | Yes |
/api/wedding-photo |
POST | Wedding photo editing | 1 | 10/day | Yes |
/api/workshop |
POST | Chat-based image generation | 1 | 10/day | Yes |
/api/gallery |
GET | Get user gallery images | 0 | N/A | Yes |
/api/gallery/delete |
DELETE | Delete gallery image | 0 | N/A | Yes |
/api/gallery/visibility |
PATCH | Update image visibility | 0 | N/A | Yes |
/api/gallery/download |
GET | Download gallery image | 0 | N/A | Yes |
/api/video-download |
GET | Download video (S3 proxy) | 0 | N/A | Yes |
/api/showcase |
GET | Get public showcase images | 0 | N/A | No |
/api/account/credits |
GET | Credit balance inquiry | 0 | N/A | Yes |
/api/payments/create-order |
POST | Create payment order | 0 | N/A | Yes |
/api/payments/verify |
POST | Verify payment | 0 | N/A | Yes |
/api/payments/webhook |
POST | Razorpay webhook handler | 0 | N/A | No |
/api/health |
GET | Health check endpoint | 0 | N/A | No |
Contributing Guidelines
Code Contribution Process
- Fork & Branch: Create feature branch from
main - Development: Follow coding standards and best practices
- Testing: Add tests for new functionality
- Documentation: Update AGENTS.md and inline docs
- Quality Checks: Run
bun run check-all-errors - Pull Request: Create PR with detailed description
- Code Review: Address review feedback
- Merge: Squash merge to main branch
Commit Message Convention
# Format: type(scope): description
feat(video): add Veo 3.1 video generation
fix(credits): resolve daily limit calculation
docs(agents): update API reference
refactor(gallery): optimize image loading
test(api): add video generation tests
Code Review Checklist
- TypeScript strict mode compliance
- ESLint and Prettier passing
- Unit tests added/updated
- Documentation updated
- Security considerations addressed
- Performance implications reviewed
- Database queries optimized
- Error handling implemented
- Logging added for operations
- Daily limit tracking implemented (for generation endpoints)
Additional Resources
Official Documentation
- Next.js 16: https://nextjs.org/docs
- React 19: https://react.dev
- TypeScript 5: https://www.typescriptlang.org/docs
- Prisma ORM: https://www.prisma.io/docs
- Clerk: https://clerk.com/docs
- Redux Toolkit: https://redux-toolkit.js.org
- Tailwind CSS 4: https://tailwindcss.com/docs
- Radix UI: https://www.radix-ui.com
- Google Gemini AI: https://ai.google.dev/docs
- Google Veo API: https://ai.google.dev/docs
Development Tools
- Bun: https://bun.sh/docs
- Vitest: https://vitest.dev
- ESLint: https://eslint.org/docs
- Prettier: https://prettier.io/docs
- Winston: https://github.com/winstonjs/winston
Infrastructure & Services
- Vercel: https://vercel.com/docs
- Neon Postgres: https://neon.tech/docs
- AWS S3: https://docs.aws.amazon.com/s3
- Supabase: https://supabase.com/docs
- Razorpay: https://razorpay.com/docs
Version History
- v3.0.0 (January 2026): Added Veo 3.1 video generation, daily rate limiting, image-to-video support
- v2.0.0 (2025): Complete redesign with Next.js 16, React 19, Clerk authentication, Google Gemini AI integration
- v1.5.0 (2024): Added workshop chat interface, payment processing
- v1.0.0 (2024): Initial release with core AI image generation features
Last Updated: January 2026 Version: 3.0.0 Maintainer: Renly AI Team Repository: [GitHub Repository] Documentation: [Live Documentation]
