Skip to content

Commit a41ff47

Browse files
committed
feat(profile): remove outfit images, enforce avoided colors hard in shopping verdict, stronger profile prompt
1 parent d3ed324 commit a41ff47

3 files changed

Lines changed: 22 additions & 45 deletions

File tree

backend/app/api/routes.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -294,14 +294,28 @@ async def shopping_analyze(
294294
data["item_description"] = groq_fallback.FALLBACK_NOTE + " " + data["item_description"]
295295

296296
verdict = (data.get("buy_verdict") or "no").strip().lower()
297+
verdict_reason = data.get("verdict_reason", "")
297298
compatible = data.get("compatible_items") or []
298299
incompatible = data.get("incompatible_items") or []
299300

301+
# Hard-enforce avoided colors from profile — override Gemini's verdict
302+
if profile_context and profile_context.strip():
303+
import re
304+
avoid_match = re.search(r'MUST AVOID.*?:\s*([^\n.]+)', profile_context, re.IGNORECASE)
305+
if avoid_match:
306+
avoided = [c.strip().lower() for c in avoid_match.group(1).split(',')]
307+
item_desc_lower = (data.get("item_description") or "").lower()
308+
for color in avoided:
309+
if color and color in item_desc_lower:
310+
verdict = "no"
311+
verdict_reason = f"You have marked {color} as a colour to avoid. This item should be skipped."
312+
break
313+
300314
segments = []
301315
if data.get("item_description"):
302316
segments.append({"id": "item", "text": data["item_description"]})
303-
if data.get("verdict_reason"):
304-
segments.append({"id": "verdict", "text": data["verdict_reason"]})
317+
if verdict_reason:
318+
segments.append({"id": "verdict", "text": verdict_reason})
305319
if compatible:
306320
segments.append({"id": "compatible", "text": "Goes with: " + ", ".join(compatible) + "."})
307321
if incompatible:

frontend/src/contexts/ProfileContext.jsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,8 @@ function buildProfileContext(p) {
4444
if (p.weight) parts.push(`Weight: ${p.weight}`)
4545
if (p.bodyType) parts.push(`Body type: ${p.bodyType}`)
4646
if (p.colorPrefs?.length) parts.push(`Preferred colours: ${p.colorPrefs.join(', ')}`)
47-
if (p.avoidColors?.length) parts.push(`Avoids colours: ${p.avoidColors.join(', ')}`)
47+
if (p.avoidColors?.length) parts.push(`MUST AVOID these colours (never recommend or approve items in these colours): ${p.avoidColors.join(', ')}`)
4848
if (p.patterns?.length) parts.push(`Preferred patterns: ${p.patterns.join(', ')}`)
49-
if (p.stylePrefs) parts.push(`Style: ${p.stylePrefs}`)
50-
if (p.outfits?.length) parts.push(`${p.outfits.length} pre-saved outfit${p.outfits.length !== 1 ? 's' : ''} uploaded`)
51-
return parts.length > 0 ? 'User profile — ' + parts.join('. ') + '.' : ''
49+
if (p.stylePrefs) parts.push(`Preferred style: ${p.stylePrefs}`)
50+
return parts.length > 0 ? 'User preferences — ' + parts.join('. ') + '.' : ''
5251
}

frontend/src/screens/ProfileScreen.jsx

Lines changed: 3 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -23,30 +23,18 @@ export function ProfileScreen() {
2323
const [avoidColors, setAvoidColors] = useState(profile.avoidColors || [])
2424
const [patterns, setPatterns] = useState(profile.patterns || [])
2525
const [stylePrefs, setStylePrefs] = useState(profile.stylePrefs || '')
26-
const [outfits, setOutfits] = useState(profile.outfits || []) // array of dataUrls
2726
const [saved, setSaved] = useState(false)
2827

2928
const toggleArr = (arr, setArr, val) => {
3029
setArr(arr.includes(val) ? arr.filter((v) => v !== val) : [...arr, val])
3130
}
3231

33-
const handleOutfitUpload = useCallback((e) => {
34-
const files = Array.from(e.target.files || [])
35-
if (!files.length) return
36-
const remaining = 5 - outfits.length
37-
files.slice(0, remaining).forEach((file) => {
38-
const reader = new FileReader()
39-
reader.onload = (ev) => setOutfits((prev) => [...prev, ev.target.result].slice(0, 5))
40-
reader.readAsDataURL(file)
41-
})
42-
}, [outfits])
43-
4432
const handleSave = useCallback(() => {
45-
saveProfile({ height, weight, bodyType, colorPrefs, avoidColors, patterns, stylePrefs, outfits })
33+
saveProfile({ height, weight, bodyType, colorPrefs, avoidColors, patterns, stylePrefs })
4634
setSaved(true)
47-
speak('Profile saved. The app will use this to personalise your suggestions.')
35+
speak('Profile saved. Shopping mode and outfit suggestions will now respect your colour and style preferences.')
4836
setTimeout(() => setSaved(false), 2000)
49-
}, [height, weight, bodyType, colorPrefs, avoidColors, patterns, stylePrefs, outfits, saveProfile, speak])
37+
}, [height, weight, bodyType, colorPrefs, avoidColors, patterns, stylePrefs, saveProfile, speak])
5038

5139
return (
5240
<Screen title="My Profile" subtitle="All fields are optional">
@@ -69,30 +57,6 @@ export function ProfileScreen() {
6957
<ChipGroup label="Style" options={STYLE_OPTIONS} selected={[stylePrefs]} onToggle={(v) => setStylePrefs(stylePrefs === v ? '' : v)} single />
7058
</Section>
7159

72-
<Section label={`My Outfits (${outfits.length}/5)`}>
73-
<p style={{ fontSize: 13, color: COLORS.TEXT_MUTED, lineHeight: 1.6, marginBottom: 12, marginTop: 0 }}>
74-
Upload up to 5 outfits you already wear. The AI will learn your style from them.
75-
</p>
76-
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginBottom: outfits.length < 5 ? 12 : 0 }}>
77-
{outfits.map((src, i) => (
78-
<div key={i} style={{ position: 'relative', width: 72, height: 72 }}>
79-
<img src={src} alt={`Outfit ${i + 1}`} style={{ width: '100%', height: '100%', objectFit: 'cover', borderRadius: COLORS.RADIUS, border: `2px solid ${COLORS.BORDER}` }} />
80-
<button
81-
onClick={() => setOutfits(outfits.filter((_, j) => j !== i))}
82-
aria-label={`Remove outfit ${i + 1}`}
83-
style={{ position: 'absolute', top: -8, right: -8, width: 22, height: 22, borderRadius: '50%', background: COLORS.DANGER, border: 'none', color: '#fff', fontSize: 12, fontWeight: 900, cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center' }}
84-
>×</button>
85-
</div>
86-
))}
87-
</div>
88-
{outfits.length < 5 && (
89-
<label style={{ display: 'inline-flex', alignItems: 'center', gap: 8, border: `2px solid ${COLORS.BORDER}`, borderRadius: COLORS.RADIUS, padding: '10px 16px', cursor: 'pointer', fontSize: 13, fontWeight: 700, color: COLORS.TEXT_MUTED }}>
90-
+ Add Outfit Photo
91-
<input type="file" accept="image/*" multiple onChange={handleOutfitUpload} style={{ display: 'none' }} />
92-
</label>
93-
)}
94-
</Section>
95-
9660
<div style={{ marginTop: 8 }}>
9761
<BigButton
9862
label={saved ? 'Saved!' : 'Save Profile'}

0 commit comments

Comments
 (0)