nexusbert commited on
Commit
6d4ee55
·
1 Parent(s): 1b83317
WARDROBE_FEATURE_FLOW.md ADDED
@@ -0,0 +1,358 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # StyleGPT Wardrobe Feature - Complete Flow
2
+
3
+ ## Overview
4
+ The wardrobe feature allows users to upload clothing items, automatically classify them using AI, organize them by categories, and get personalized outfit suggestions based on their actual wardrobe.
5
+
6
+ ---
7
+
8
+ ## 1. Upload Flow
9
+
10
+ ### 1.1 User Initiates Upload
11
+ - **User Action**: Navigates to Upload page
12
+ - **UI**: Shows file upload area with style selection dropdown
13
+ - **User Selects**:
14
+ - Images (1-20 files)
15
+ - Style: Casual, Formal, Streetwear, or Sportswear
16
+
17
+ ### 1.2 Image Processing Pipeline
18
+
19
+ #### Step 1: Background Removal
20
+ ```
21
+ User uploads image → Background Remover API
22
+ → Returns: Image with transparent background (PNG)
23
+ → Purpose: Better accuracy for FashionClip classification
24
+ ```
25
+
26
+ #### Step 2: FashionClip Identification
27
+ ```
28
+ Background-removed image → FashionClip /identify endpoint
29
+ → Returns: {
30
+ precise_identification: [
31
+ { label: "bape", score: 0.82, type: "brand" },
32
+ { label: "t-shirt", score: 0.61, type: "item" },
33
+ { label: "brown", score: 0.53, type: "color" }
34
+ ],
35
+ detected_components: {
36
+ top_brands: [{ brand: "bape", score: 0.82 }],
37
+ top_colors: [{ color: "brown", score: 0.53 }],
38
+ top_items: [{ item: "t-shirt", score: 0.61 }]
39
+ }
40
+ }
41
+ ```
42
+
43
+ #### Step 3: Category Normalization
44
+ ```
45
+ FashionClip item label → Category Normalizer
46
+ Examples:
47
+ - "t-shirt", "shirt", "polo shirt" → "shirts"
48
+ - "pants", "jeans", "trousers" → "pants"
49
+ - "sneakers", "boots", "heels" → "shoes"
50
+ ```
51
+
52
+ #### Step 4: Item Name Generation
53
+ ```
54
+ Brand + Color + Category = Item Name
55
+ Example: "bape" + "brown" + "t-shirt" = "bape brown t-shirt"
56
+ ```
57
+
58
+ #### Step 5: Storage
59
+ ```
60
+ Original image (with background) → Cloudinary
61
+ → Stored with public_id: "wardrobe/bape_brown_t_shirt_[timestamp]"
62
+
63
+ Database Record:
64
+ {
65
+ imageUrl: "https://cloudinary.../wardrobe/bape_brown_t_shirt_...",
66
+ category: "shirts", // normalized
67
+ style: "streetwear", // user-selected
68
+ name: "bape brown t-shirt",
69
+ brand: "bape",
70
+ color: "brown",
71
+ userId: [user_id]
72
+ }
73
+ ```
74
+
75
+ ---
76
+
77
+ ## 2. Wardrobe Organization Flow
78
+
79
+ ### 2.1 Category-Based Display
80
+ - **User Action**: Opens Wardrobe page
81
+ - **System**: Groups items by normalized category
82
+ - **UI Shows**:
83
+ - Category buttons (e.g., "Shirts (5)", "Pants (3)", "Shoes (2)")
84
+ - All items grid (default view)
85
+
86
+ ### 2.2 Category Selection
87
+ - **User Action**: Clicks a category button (e.g., "Shirts")
88
+ - **Animation**: Items slide out from left with staggered delay
89
+ - **UI Shows**: Only items from selected category
90
+ - **Display**: Each item shows:
91
+ - Image (original with background)
92
+ - Item name (e.g., "bape brown t-shirt")
93
+ - Style badge (Casual/Formal/Streetwear/Sportswear)
94
+ - Delete button (on hover)
95
+
96
+ ### 2.3 Filtering & Search
97
+ - **User Can**:
98
+ - Search by category name
99
+ - Filter by style (Casual/Formal/Streetwear/Sportswear)
100
+ - Clear filters to see all items
101
+
102
+ ---
103
+
104
+ ## 3. AI Suggestion Flow
105
+
106
+ ### 3.1 User Requests Suggestion
107
+ - **User Action**: Types message in Chat (e.g., "Suggest an elegant outfit for dinner")
108
+ - **System**: Authenticates user and fetches their wardrobe
109
+
110
+ ### 3.2 Wardrobe Data Preparation
111
+ ```
112
+ User's Wardrobe Items → Formatted for AI:
113
+ Item 1: "bape brown t-shirt" - shirts (streetwear style)
114
+ Item 2: "zara black pants" - pants (casual style)
115
+ Item 3: "nike white sneakers" - shoes (sportswear style)
116
+ ...
117
+
118
+ Available categories: shirts, pants, shoes, jackets, ...
119
+ ```
120
+
121
+ ### 3.3 AI Prompt Construction
122
+ ```
123
+ Prompt to AI:
124
+ "User Wardrobe (X items total):
125
+ Item 1: "bape brown t-shirt" - shirts (streetwear style)
126
+ Item 2: "zara black pants" - pants (casual style)
127
+ ...
128
+
129
+ Available categories: shirts, pants, shoes, ...
130
+
131
+ User request: Suggest an elegant outfit for dinner
132
+
133
+ IMPORTANT INSTRUCTIONS:
134
+ 1. Use ONLY items listed above
135
+ 2. Reference items by EXACT names (e.g., "the black zara pants", "brown shoes")
136
+ 3. Use brand, color, and category when available
137
+ 4. Only suggest from available categories
138
+ 5. Make response natural and fluid, mentioning specific item names"
139
+ ```
140
+
141
+ ### 3.4 AI Response Processing
142
+ ```
143
+ AI Response: "For an elegant dinner, I suggest pairing the black zara pants
144
+ with a crisp white shirt and brown leather shoes. This creates a sophisticated
145
+ look that balances formal and casual elements..."
146
+
147
+ System Extracts:
148
+ - Mentions "pants" → Matches to "zara black pants" (pants category)
149
+ - Mentions "shirt" → Matches to available shirt items
150
+ - Mentions "shoes" → Matches to available shoe items
151
+ ```
152
+
153
+ ### 3.5 Item Matching Algorithm
154
+ ```
155
+ 1. Parse AI response for category keywords
156
+ 2. Match keywords to user's wardrobe categories
157
+ 3. Select best matching item from each category
158
+ 4. Return selected items with images
159
+ ```
160
+
161
+ ### 3.6 Response Display
162
+ ```
163
+ Chat UI Shows:
164
+ 1. AI text response (formatted)
165
+ 2. Selected items grid with images:
166
+ - "zara black pants" (image)
167
+ - "white shirt" (image)
168
+ - "brown shoes" (image)
169
+ 3. "Suggest Another Outfit" button
170
+ ```
171
+
172
+ ### 3.7 Multiple Suggestions
173
+ - **User Action**: Clicks "Suggest Another Outfit" or types new request
174
+ - **System**: Removes previous suggestion, generates new one
175
+ - **Behavior**: Only one suggestion visible at a time
176
+
177
+ ---
178
+
179
+ ## 4. Data Flow Diagram
180
+
181
+ ```
182
+ ┌─────────────┐
183
+ │ User Upload │
184
+ │ Images │
185
+ └──────┬──────┘
186
+
187
+
188
+ ┌──────────────────┐
189
+ │ Background │
190
+ │ Removal API │
191
+ └──────┬───────────┘
192
+
193
+
194
+ ┌──────────────────┐
195
+ │ FashionClip │
196
+ │ /identify │
197
+ │ (brand, color, │
198
+ │ item detection)│
199
+ └──────┬───────────┘
200
+
201
+
202
+ ┌──────────────────┐
203
+ │ Category │
204
+ │ Normalization │
205
+ │ (t-shirt → shirts)│
206
+ └──────┬───────────┘
207
+
208
+
209
+ ┌──────────────────┐
210
+ │ Name Generation │
211
+ │ (brand+color+cat)│
212
+ └──────┬───────────┘
213
+
214
+
215
+ ┌──────────────────┐
216
+ │ Cloudinary │
217
+ │ (store original) │
218
+ └──────┬───────────┘
219
+
220
+
221
+ ┌──────────────────┐
222
+ │ Database │
223
+ │ (save metadata) │
224
+ └──────┬───────────┘
225
+
226
+
227
+ ┌──────────────────┐
228
+ │ Wardrobe UI │
229
+ │ (categorized) │
230
+ └──────┬───────────┘
231
+
232
+
233
+ ┌──────────────────┐
234
+ │ AI Suggestion │
235
+ │ (uses item names)│
236
+ └──────────────────┘
237
+ ```
238
+
239
+ ---
240
+
241
+ ## 5. Key Features
242
+
243
+ ### 5.1 User-Selected Style
244
+ - **Purpose**: Better organization and filtering
245
+ - **Options**: Casual, Formal, Streetwear, Sportswear
246
+ - **Applied**: To all items in upload batch
247
+
248
+ ### 5.2 Item Naming
249
+ - **Format**: `[brand] [color] [category]`
250
+ - **Examples**:
251
+ - "bape brown t-shirt"
252
+ - "zara black pants"
253
+ - "nike white sneakers"
254
+ - "louis vuitton white shirt"
255
+
256
+ ### 5.3 Category Organization
257
+ - **Normalized Categories**: shirts, pants, shoes, jackets, dresses, etc.
258
+ - **Visual**: Category buttons with item counts
259
+ - **Interaction**: Click to filter, animation on selection
260
+
261
+ ### 5.4 AI Integration
262
+ - **Context**: Full wardrobe with item names
263
+ - **Response Style**: Natural, references actual items
264
+ - **Item Matching**: Keyword-based extraction from AI response
265
+ - **Visual Display**: Shows selected items with images
266
+
267
+ ### 5.5 Item Management
268
+ - **View**: By category or all items
269
+ - **Delete**: Hover to reveal delete button
270
+ - **Search**: By category name
271
+ - **Filter**: By style
272
+
273
+ ---
274
+
275
+ ## 6. User Journey Example
276
+
277
+ ### Scenario: User wants elegant dinner outfit
278
+
279
+ 1. **Upload Phase**:
280
+ - User uploads 5 items (shirt, pants, shoes, jacket, watch)
281
+ - Selects "Formal" style
282
+ - System processes: removes backgrounds, identifies items, stores with names
283
+
284
+ 2. **Organization Phase**:
285
+ - Wardrobe shows: "Shirts (1)", "Pants (1)", "Shoes (1)", etc.
286
+ - User clicks "Pants" → sees "zara black pants" with animation
287
+
288
+ 3. **Suggestion Phase**:
289
+ - User types: "Suggest an elegant outfit for dinner"
290
+ - AI receives: Full wardrobe with item names
291
+ - AI responds: "I suggest the black zara pants with a crisp white shirt..."
292
+ - System extracts: Matches "pants" and "shirt" to user's items
293
+ - UI displays: AI response + images of selected items
294
+
295
+ 4. **Refinement Phase**:
296
+ - User clicks "Suggest Another Outfit"
297
+ - System generates new suggestion with different items
298
+ - Previous suggestion removed, new one displayed
299
+
300
+ ---
301
+
302
+ ## 7. Technical Components
303
+
304
+ ### Backend
305
+ - `src/routes/upload.ts`: Handles image upload and processing
306
+ - `src/routes/suggest.ts`: Generates AI suggestions
307
+ - `src/routes/wardrobe.ts`: Manages wardrobe CRUD operations
308
+ - `src/utils/backgroundRemoval.ts`: Background removal service
309
+ - `src/utils/hfClient.ts`: FashionClip API client
310
+ - `src/utils/categoryNormalizer.ts`: Category normalization logic
311
+
312
+ ### Frontend
313
+ - `stylegptUI/src/pages/Upload.jsx`: Upload interface with style selection
314
+ - `stylegptUI/src/pages/Wardrobe.jsx`: Category-based wardrobe display
315
+ - `stylegptUI/src/pages/Chat.jsx`: AI suggestion interface
316
+ - `stylegptUI/src/utils/api.js`: API communication
317
+
318
+ ### Database
319
+ - `WardrobeItem` entity: Stores item metadata
320
+ - Fields: imageUrl, category, style, name, brand, color, userId
321
+
322
+ ---
323
+
324
+ ## 8. Data Flow Summary
325
+
326
+ ```
327
+ Upload → Background Removal → FashionClip → Normalization → Storage
328
+
329
+ Wardrobe UI ← Category Grouping ← Database Query ← User Request
330
+
331
+ Category Selection → Animation → Item Display
332
+
333
+ Chat Request → AI Prompt (with item names) → AI Response → Item Extraction → Display
334
+ ```
335
+
336
+ ---
337
+
338
+ ## 9. Key Design Decisions
339
+
340
+ 1. **Store Original Images**: Users see items with original backgrounds
341
+ 2. **Background Removal for Classification**: Better AI accuracy
342
+ 3. **User-Selected Style**: More accurate than auto-detection
343
+ 4. **Category Normalization**: Consistent organization
344
+ 5. **Full Item Names**: AI can reference items naturally
345
+ 6. **One Suggestion at a Time**: Cleaner UI, less confusion
346
+ 7. **Category Animation**: Better UX, visual feedback
347
+
348
+ ---
349
+
350
+ ## 10. Future Enhancements
351
+
352
+ - Outfit saving/favorites
353
+ - Style mixing suggestions
354
+ - Seasonal wardrobe organization
355
+ - Item tagging (favorite, worn recently)
356
+ - Outfit history tracking
357
+ - Social sharing of outfits
358
+
src/index.ts CHANGED
@@ -49,7 +49,6 @@ app.get("/", (req, res) => {
49
  "Express.js",
50
  "PostgreSQL",
51
  "TypeORM",
52
- "Cloudinary",
53
  "Hugging Face Fashion-CLIP",
54
  "JWT Authentication"
55
  ]
 
49
  "Express.js",
50
  "PostgreSQL",
51
  "TypeORM",
 
52
  "Hugging Face Fashion-CLIP",
53
  "JWT Authentication"
54
  ]
src/routes/profile.ts CHANGED
@@ -1,6 +1,5 @@
1
  import express from "express";
2
  import multer from "multer";
3
- import cloudinary from "../utils/cloudinary";
4
  import { AppDataSource } from "../utils/dataSource";
5
  import { User } from "../entity/User";
6
  import { authenticateToken, AuthRequest } from "../middleware/auth";
@@ -136,48 +135,24 @@ router.put("/", authenticateToken, upload.single("profilePicture"), async (req:
136
  }
137
 
138
  // Handle profile picture removal (check if removePicture flag is set)
139
- if (req.body.removePicture === "true" && user.profilePicture) {
140
- const publicId = user.profilePicture.split("/").slice(-2).join("/").split(".")[0];
141
- try {
142
- await cloudinary.uploader.destroy(`profile-pictures/${publicId}`);
143
- } catch (deleteError) {
144
- console.error("Error deleting old profile picture:", deleteError);
145
- }
146
  user.profilePicture = undefined;
147
  }
148
 
149
  // Handle profile picture upload
150
  if (file) {
151
  try {
152
- // Convert buffer to base64
153
  const base64Image = `data:${file.mimetype};base64,${file.buffer.toString("base64")}`;
154
 
155
- // Upload to Cloudinary
156
- const uploadResult = await cloudinary.uploader.upload(base64Image, {
157
- folder: "profile-pictures",
158
- resource_type: "image",
159
- transformation: [
160
- { width: 400, height: 400, crop: "fill", gravity: "face" },
161
- { quality: "auto" },
162
- ],
163
- });
164
-
165
- // Delete old profile picture if exists
166
- if (user.profilePicture) {
167
- const publicId = user.profilePicture.split("/").slice(-2).join("/").split(".")[0];
168
- try {
169
- await cloudinary.uploader.destroy(`profile-pictures/${publicId}`);
170
- } catch (deleteError) {
171
- console.error("Error deleting old profile picture:", deleteError);
172
- }
173
- }
174
-
175
- user.profilePicture = uploadResult.secure_url;
176
  } catch (uploadError: any) {
177
  console.error("Profile picture upload error:", uploadError);
178
  return res.status(500).json({
179
  success: false,
180
- error: "Failed to upload profile picture",
181
  });
182
  }
183
  }
 
1
  import express from "express";
2
  import multer from "multer";
 
3
  import { AppDataSource } from "../utils/dataSource";
4
  import { User } from "../entity/User";
5
  import { authenticateToken, AuthRequest } from "../middleware/auth";
 
135
  }
136
 
137
  // Handle profile picture removal (check if removePicture flag is set)
138
+ if (req.body.removePicture === "true") {
 
 
 
 
 
 
139
  user.profilePicture = undefined;
140
  }
141
 
142
  // Handle profile picture upload
143
  if (file) {
144
  try {
145
+ // Convert buffer to base64 data URL
146
  const base64Image = `data:${file.mimetype};base64,${file.buffer.toString("base64")}`;
147
 
148
+ // Store base64 directly in database
149
+ user.profilePicture = base64Image;
150
+ console.log("✅ Profile picture converted to base64 and stored");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  } catch (uploadError: any) {
152
  console.error("Profile picture upload error:", uploadError);
153
  return res.status(500).json({
154
  success: false,
155
+ error: "Failed to process profile picture",
156
  });
157
  }
158
  }
src/routes/suggest.ts CHANGED
@@ -118,7 +118,7 @@ function extractSelectedItems(aiResponse: string, wardrobe: WardrobeItem[]): War
118
  */
119
  router.post("/", authenticateToken, async (req: AuthRequest, res) => {
120
  try {
121
- const { message, session_id } = req.body;
122
  const userId = req.userId!;
123
 
124
  if (!message) {
@@ -158,13 +158,20 @@ router.post("/", authenticateToken, async (req: AuthRequest, res) => {
158
 
159
  console.log(`[Suggest] Sending structured wardrobe data with ${wardrobe.length} items`);
160
 
 
 
 
 
 
 
 
 
 
 
 
161
  const response = await axios.post(
162
  process.env.CHAT_ENDPOINT!,
163
- {
164
- message: message,
165
- session_id: session_id || `user_${userId}`,
166
- wardrobe: wardrobeArray,
167
- },
168
  { headers: { "Content-Type": "application/json" } }
169
  );
170
 
 
118
  */
119
  router.post("/", authenticateToken, async (req: AuthRequest, res) => {
120
  try {
121
+ const { message, session_id, images } = req.body;
122
  const userId = req.userId!;
123
 
124
  if (!message) {
 
158
 
159
  console.log(`[Suggest] Sending structured wardrobe data with ${wardrobe.length} items`);
160
 
161
+ const requestBody: any = {
162
+ message: message,
163
+ session_id: session_id || `user_${userId}`,
164
+ wardrobe: wardrobeArray,
165
+ };
166
+
167
+ if (images && Array.isArray(images) && images.length > 0) {
168
+ requestBody.images = images;
169
+ console.log(`[Suggest] Sending ${images.length} image(s) with request`);
170
+ }
171
+
172
  const response = await axios.post(
173
  process.env.CHAT_ENDPOINT!,
174
+ requestBody,
 
 
 
 
175
  { headers: { "Content-Type": "application/json" } }
176
  );
177
 
src/routes/upload.ts CHANGED
@@ -1,6 +1,5 @@
1
  import express from "express";
2
  import multer from "multer";
3
- import cloudinary from "../utils/cloudinary";
4
  import { classifyFashionImage, identifyFashionItem } from "../utils/hfClient";
5
  import { removeBackground } from "../utils/backgroundRemoval";
6
  import { AppDataSource } from "../utils/dataSource";
@@ -144,21 +143,18 @@ router.post("/", authenticateToken, upload.array("image", 20), async (req: AuthR
144
  nameParts.push(itemLabel);
145
  const itemName = nameParts.join(" ");
146
 
147
- const sanitizedName = itemName.replace(/[^a-zA-Z0-9\s]/g, "").replace(/\s+/g, "_").toLowerCase();
148
- const publicId = `wardrobe/${sanitizedName}_${Date.now()}`;
149
-
150
  const base64Image = `data:${file.mimetype};base64,${file.buffer.toString("base64")}`;
151
- const uploadResult = await cloudinary.uploader.upload(base64Image, {
152
- public_id: publicId,
153
- resource_type: "image",
154
- });
155
- console.log(`✅ Original image uploaded: ${itemName}`);
156
 
157
  const style = selectedStyle;
158
 
159
  const newItem = itemRepo.create({
160
- imageUrl: uploadResult.secure_url,
161
- processedImageUrl: undefined,
162
  category: normalizedCategory,
163
  style: style,
164
  name: itemName,
 
1
  import express from "express";
2
  import multer from "multer";
 
3
  import { classifyFashionImage, identifyFashionItem } from "../utils/hfClient";
4
  import { removeBackground } from "../utils/backgroundRemoval";
5
  import { AppDataSource } from "../utils/dataSource";
 
143
  nameParts.push(itemLabel);
144
  const itemName = nameParts.join(" ");
145
 
 
 
 
146
  const base64Image = `data:${file.mimetype};base64,${file.buffer.toString("base64")}`;
147
+ const processedBase64Image = processedBuffer
148
+ ? `data:image/png;base64,${processedBuffer.toString("base64")}`
149
+ : undefined;
150
+
151
+ console.log(`✅ Image converted to base64: ${itemName}`);
152
 
153
  const style = selectedStyle;
154
 
155
  const newItem = itemRepo.create({
156
+ imageUrl: base64Image,
157
+ processedImageUrl: processedBase64Image,
158
  category: normalizedCategory,
159
  style: style,
160
  name: itemName,