Wardrobe AI Closet technical architecture and data flow diagrams.
┌─────────────────┐
│ Web Browser │
│ (React/Next) │
└────────┬────────┘
│ HTTPS
▼
┌─────────────────────────────────────┐
│ Next.js App Router │
│ ┌──────────┐ ┌──────────────┐ │
│ │ Pages │ │ API Routes │ │
│ │ (UI/UX) │◄───┤ (REST API) │ │
│ └──────────┘ └───────┬──────┘ │
│ │ │
└──────────────────────────┼──────────┘
│
┌─────────────────┼─────────────────┐
│ │ │
▼ ▼ ▼
┌───────────┐ ┌────────────┐ ┌──────────┐
│ PostgreSQL│ │ Redis │ │ Disk │
│ (Data) │ │ (Queue) │ │ (Images) │
└───────────┘ └─────┬──────┘ └──────────┘
│
▼
┌──────────────┐
│ BullMQ │
│ AI Workers │
└──────┬───────┘
│
▼
┌──────────────┐
│ OpenRouter │
│ AI API │
└──────────────┘
┌──────────────────────────────────────┐
│ Pages (Routes) │
│ / - Home │
│ /items/[id] - Item Detail │
│ /items/new - Add Item │
│ /outfits - Outfit History │
│ /outfits/gen - Outfit Generator │
│ /analytics - Analytics │
│ /settings - Settings │
│ /closet-rail - 3D View │
└──────────────┬───────────────────────┘
│
┌──────────────▼───────────────────────┐
│ Components (Reusable) │
│ - ItemCard - FilterPanel │
│ - ItemGrid - CategoryTabs │
│ - BulkEditTable - EnhancedSearch │
│ - AddItemBtn - NotificationPrompt│
└──────────────┬───────────────────────┘
│
┌──────────────▼───────────────────────┐
│ Library (Utilities) │
│ - hooks/ - types/ │
│ - utils/ - theme │
│ - notifications - prisma client │
└──────────────────────────────────────┘
┌──────────────────────────────────────┐
│ API Routes (REST) │
│ /api/items (CRUD) │
│ /api/items/[id] (CRUD) │
│ /api/items/[id]/images (Upload) │
│ /api/outfits (CRUD) │
│ /api/outfits/generate (AI) │
│ /api/images/[id] (Stream) │
│ /api/ai/jobs (Status) │
│ /api/export (Backup) │
│ /api/import (Restore) │
│ /api/reminders (Laundry) │
│ /api/tags (CRUD) │
└──────────────┬───────────────────────┘
│
┌──────────────▼───────────────────────┐
│ Business Logic Layer │
│ - Validation (Zod schemas) │
│ - Database operations (Prisma) │
│ - File operations (fs/sharp) │
│ - Queue management (BullMQ) │
└──────────────┬───────────────────────┘
│
┌──────────────▼───────────────────────┐
│ Data Layer │
│ - Prisma Client (ORM) │
│ - Redis Client (ioredis) │
│ - File System (Node fs) │
└──────────────────────────────────────┘
User takes photo
│
▼
[Upload to /api/items/[id]/images]
│
├─► Save to disk (DATA_DIR/images)
├─► Create ImageAsset record (Prisma)
└─► Enqueue AI jobs:
├─► generate_catalog_image (if original_main)
├─► generate_catalog_image (if original_back)
├─► infer_item (category, colors, tags)
└─► extract_label (if label_brand/label_care)
│
▼
[BullMQ Worker picks up jobs]
│
├─► Call OpenRouter API
├─► Process response
├─► Save results to DB
└─► Mark job as succeeded/failed
│
▼
[UI polls for status]
│
└─► Update item display
User sets constraints (weather, vibe, occasion)
│
▼
[POST /api/outfits/generate]
│
├─► Fetch available items (state = available)
├─► Filter by constraints
├─► Build prompt for AI
│
▼
[Enqueue generate_outfit job]
│
▼
[BullMQ Worker]
│
├─► Call OpenRouter API
├─► Parse structured response
├─► Create Outfit + OutfitItem records
└─► Return outfit ID
│
▼
[Redirect user to outfit view]
Export:
User clicks "Export Wardrobe"
│
▼
[POST /api/export]
│
├─► Query all data (items, outfits, tags)
├─► Generate JSON export
├─► Generate CSV files
├─► Create ZIP archive
│ ├─► export.json
│ ├─► items.csv
│ ├─► outfits.csv
│ ├─► images/ (copy all)
│ └─► manifest.json
│
└─► Stream ZIP to user
Import:
User uploads ZIP backup
│
▼
[POST /api/import]
│
├─► Extract ZIP to temp directory
├─► Validate manifest
├─► Parse export.json
│
├─► If mode = "replace":
│ └─► Delete all existing data
│
├─► If mode = "merge":
│ └─► Skip duplicates (by ID)
│
├─► Import data:
│ ├─► Tags
│ ├─► Items
│ ├─► Images (copy files)
│ └─► Outfits
│
└─► Return import summary
┌─────────────┐
│ Queued │ Job created, waiting for worker
└──────┬──────┘
│
▼
┌─────────────┐
│ Running │ Worker picked up job, calling AI API
└──────┬──────┘
│
├──► Success ──┐
│ ▼
│ ┌─────────────┐
│ │ Succeeded │ Result saved to DB
│ └─────────────┘
│
├──► Failure ──┐
│ ▼
│ ┌─────────────┐
│ │ Failed │ Error logged, retry?
│ └──────┬──────┘
│ │
│ (if attempts < maxAttempts)
│ │
└────────────────┘
└──► Needs Review ──┐
▼
┌──────────────┐
│Needs Review │ AI uncertain, human review needed
└──────────────┘
1. generate_catalog_image
2. infer_item
3. extract_label
4. generate_outfit
// lib/ai/worker.ts
const worker = new Worker('ai-jobs', async (job) => {
switch (job.data.type) {
case 'generate_catalog_image':
return await generateCatalogImage(job.data);
case 'infer_item':
return await inferItem(job.data);
case 'extract_label':
return await extractLabel(job.data);
case 'generate_outfit':
return await generateOutfit(job.data);
}
}, {
connection: redis,
concurrency: 3, // Process 3 jobs concurrently
removeOnComplete: { age: 86400 }, // Keep completed jobs for 24h
removeOnFail: { age: 604800 }, // Keep failed jobs for 7 days
});
Currently no caching layer. Future considerations:
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
┌─────────────┐
│ Tag │
│─────────────│
│ id (PK) │
│ name (UQ) │
└──────┬──────┘
│
│ M:N
│
┌──────▼──────┐ ┌──────────────┐
│ ItemTag │ │ Item │
│─────────────│ │──────────────│
│ itemId (FK) │◄──────┤ id (PK) │
│ tagId (FK) │ │ title │
└─────────────┘ │ category │
│ state │
│ brand │
│ colorPalette │
│ attributes │
└──────┬───────┘
│
│ 1:N
│
┌──────▼───────┐
│ ImageAsset │
│──────────────│
│ id (PK) │
│ itemId (FK) │
│ kind (ENUM) │
│ filePath │
│ width, height│
└──────────────┘
│
│ 1:N
│
┌──────▼───────┐
│ AIJob │
│──────────────│
│ id (PK) │
│ itemId (FK) │
│ type (ENUM) │
│ status │
│ outputJson │
│ error │
└──────────────┘
┌─────────────┐ ┌──────────────┐
│ Outfit │ │ OutfitItem │
│─────────────│ │──────────────│
│ id (PK) │◄──────┤ outfitId (FK)│
│ title │ │ itemId (FK) │
│ rating │ │ role (ENUM) │
│ weather │ └──────┬───────┘
│ vibe │ │
│ explanation │ │
└─────────────┘ │
│
┌──────▼───────┐
│ Item │
│──────────────│
│ id (PK) │
└──────────────┘
1. ImageAsset separate from Item
kind values (original, catalog, thumbnail, label)2. Tags as separate entities
3. Outfit references Items, not copies
4. AIJob tracks all AI operations
5. JSON fields for flexible data
colorPalette: Array of hex colorsattributes: Object with category-specific fieldsoutputJson: Store full AI responseWould require: