Fix voice command data structure and prevent duplicate activities
Backend changes: - Update LLM prompt to use correct field names matching frontend interfaces - Use 'diaperType' instead of 'type' for diaper activities - Use 'feedingType' instead of 'method' for feeding activities - Simplify sleep structure (duration, quality, location only) Frontend changes: - Add processedClassificationId tracking to prevent infinite loop - Create unique ID for each classification to avoid duplicate processing - Reset processed ID when dialog opens/closes or new recording starts This fixes the issue where voice commands created multiple duplicate activities and had mismatched data structures causing tracker warnings. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -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",
|
"timestamp": "ISO 8601 datetime if mentioned (e.g., '3pm', '30 minutes ago'), otherwise use current time",
|
||||||
"details": {
|
"details": {
|
||||||
// For feeding:
|
// For feeding:
|
||||||
|
"feedingType": "bottle|breast|solids",
|
||||||
"amount": number or null,
|
"amount": number or null,
|
||||||
"unit": "ml|oz" or null,
|
"unit": "ml|oz" or null,
|
||||||
"method": "bottle|breast|solids" or null,
|
"side": "left|right|both" or null (for breastfeeding only),
|
||||||
"side": "left|right|both" or null,
|
|
||||||
"duration": number (minutes) or null,
|
"duration": number (minutes) or null,
|
||||||
"notes": string or null
|
"notes": string or null
|
||||||
|
|
||||||
// For sleep:
|
// For sleep:
|
||||||
"start_time": "ISO 8601" or null,
|
"quality": "peaceful|restless|fussy",
|
||||||
"end_time": "ISO 8601" or null,
|
"location": "crib|bassinet|arms|bed|stroller|car seat",
|
||||||
"duration": number (minutes) or null,
|
"duration": number (minutes) or null,
|
||||||
"quality": "peaceful|restless|fussy" or null,
|
|
||||||
"location": string or null,
|
|
||||||
"notes": string or null
|
"notes": string or null
|
||||||
|
|
||||||
// For diaper:
|
// For diaper:
|
||||||
"type": "wet|dirty|both",
|
"diaperType": "wet|dirty|both",
|
||||||
"color": string or null,
|
"color": string or null,
|
||||||
"consistency": string or null,
|
"consistency": string or null,
|
||||||
"rash": boolean or null,
|
"rash": boolean or null,
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ export function VoiceFloatingButton() {
|
|||||||
const [processingStatus, setProcessingStatus] = useState<'listening' | 'understanding' | 'saving' | null>(null);
|
const [processingStatus, setProcessingStatus] = useState<'listening' | 'understanding' | 'saving' | null>(null);
|
||||||
const [identifiedActivity, setIdentifiedActivity] = useState<string>('');
|
const [identifiedActivity, setIdentifiedActivity] = useState<string>('');
|
||||||
const [classificationResult, setClassificationResult] = useState<any>(null);
|
const [classificationResult, setClassificationResult] = useState<any>(null);
|
||||||
|
const [processedClassificationId, setProcessedClassificationId] = useState<string | null>(null);
|
||||||
const [snackbar, setSnackbar] = useState<{
|
const [snackbar, setSnackbar] = useState<{
|
||||||
open: boolean;
|
open: boolean;
|
||||||
message: string;
|
message: string;
|
||||||
@@ -67,11 +68,19 @@ export function VoiceFloatingButton() {
|
|||||||
// Auto-use classification from backend when transcription completes
|
// Auto-use classification from backend when transcription completes
|
||||||
// MediaRecorder sends audio to backend, which transcribes + classifies in one call
|
// MediaRecorder sends audio to backend, which transcribes + classifies in one call
|
||||||
React.useEffect(() => {
|
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);
|
setClassificationResult(classification);
|
||||||
handleClassifiedIntent(classification);
|
handleClassifiedIntent(classification);
|
||||||
}
|
}
|
||||||
}, [classification, isListening, isProcessing, open]);
|
}, [classification, isListening, isProcessing, open, transcript, processedClassificationId]);
|
||||||
|
|
||||||
const handleOpen = () => {
|
const handleOpen = () => {
|
||||||
if (!isSupported) {
|
if (!isSupported) {
|
||||||
@@ -87,6 +96,7 @@ export function VoiceFloatingButton() {
|
|||||||
setClassificationResult(null);
|
setClassificationResult(null);
|
||||||
setProcessingStatus(null);
|
setProcessingStatus(null);
|
||||||
setIdentifiedActivity('');
|
setIdentifiedActivity('');
|
||||||
|
setProcessedClassificationId(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
@@ -98,11 +108,13 @@ export function VoiceFloatingButton() {
|
|||||||
setClassificationResult(null);
|
setClassificationResult(null);
|
||||||
setProcessingStatus(null);
|
setProcessingStatus(null);
|
||||||
setIdentifiedActivity('');
|
setIdentifiedActivity('');
|
||||||
|
setProcessedClassificationId(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleStartListening = () => {
|
const handleStartListening = () => {
|
||||||
reset();
|
reset();
|
||||||
setClassificationResult(null);
|
setClassificationResult(null);
|
||||||
|
setProcessedClassificationId(null);
|
||||||
startListening();
|
startListening();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user