diff --git a/maternal-app/maternal-app-backend/src/modules/voice/voice.service.ts b/maternal-app/maternal-app-backend/src/modules/voice/voice.service.ts index 284fc89..6b0cdb0 100644 --- a/maternal-app/maternal-app-backend/src/modules/voice/voice.service.ts +++ b/maternal-app/maternal-app-backend/src/modules/voice/voice.service.ts @@ -172,23 +172,21 @@ Extract activity details from the user's text and respond ONLY with valid JSON ( "timestamp": "ISO 8601 datetime if mentioned (e.g., '3pm', '30 minutes ago'), otherwise use current time", "details": { // For feeding: + "feedingType": "bottle|breast|solids", "amount": number or null, "unit": "ml|oz" or null, - "method": "bottle|breast|solids" or null, - "side": "left|right|both" or null, + "side": "left|right|both" or null (for breastfeeding only), "duration": number (minutes) or null, "notes": string or null // For sleep: - "start_time": "ISO 8601" or null, - "end_time": "ISO 8601" or null, + "quality": "peaceful|restless|fussy", + "location": "crib|bassinet|arms|bed|stroller|car seat", "duration": number (minutes) or null, - "quality": "peaceful|restless|fussy" or null, - "location": string or null, "notes": string or null // For diaper: - "type": "wet|dirty|both", + "diaperType": "wet|dirty|both", "color": string or null, "consistency": string or null, "rash": boolean or null, diff --git a/maternal-web/components/voice/VoiceFloatingButton.tsx b/maternal-web/components/voice/VoiceFloatingButton.tsx index 15e7d56..309f4a2 100644 --- a/maternal-web/components/voice/VoiceFloatingButton.tsx +++ b/maternal-web/components/voice/VoiceFloatingButton.tsx @@ -39,6 +39,7 @@ export function VoiceFloatingButton() { const [processingStatus, setProcessingStatus] = useState<'listening' | 'understanding' | 'saving' | null>(null); const [identifiedActivity, setIdentifiedActivity] = useState(''); const [classificationResult, setClassificationResult] = useState(null); + const [processedClassificationId, setProcessedClassificationId] = useState(null); const [snackbar, setSnackbar] = useState<{ open: boolean; message: string; @@ -67,11 +68,19 @@ export function VoiceFloatingButton() { // Auto-use classification from backend when transcription completes // MediaRecorder sends audio to backend, which transcribes + classifies in one call React.useEffect(() => { - if (classification && !isListening && !isProcessing && open) { + // Create a unique ID for this classification based on transcript + type + timestamp + const classificationId = classification + ? `${transcript}-${classification.type}-${classification.timestamp}` + : null; + + // Only process if we haven't already processed this exact classification + if (classification && !isListening && !isProcessing && open && classificationId !== processedClassificationId) { + console.log('[Voice] New classification detected, processing...', classificationId); + setProcessedClassificationId(classificationId); setClassificationResult(classification); handleClassifiedIntent(classification); } - }, [classification, isListening, isProcessing, open]); + }, [classification, isListening, isProcessing, open, transcript, processedClassificationId]); const handleOpen = () => { if (!isSupported) { @@ -87,6 +96,7 @@ export function VoiceFloatingButton() { setClassificationResult(null); setProcessingStatus(null); setIdentifiedActivity(''); + setProcessedClassificationId(null); }; const handleClose = () => { @@ -98,11 +108,13 @@ export function VoiceFloatingButton() { setClassificationResult(null); setProcessingStatus(null); setIdentifiedActivity(''); + setProcessedClassificationId(null); }; const handleStartListening = () => { reset(); setClassificationResult(null); + setProcessedClassificationId(null); startListening(); };