Add voice intent classification for hands-free tracking
Implemented comprehensive voice command understanding system: **Intent Classification:** - Feeding intent (bottle, breastfeeding, solid food) - Sleep intent (naps, nighttime sleep) - Diaper intent (wet, dirty, both, dry) - Unknown intent handling **Entity Extraction:** - Amounts with units (ml, oz, tbsp): "120 ml", "4 ounces" - Durations in minutes: "15 minutes", "for 20 mins" - Time expressions: "at 3:30 pm", "30 minutes ago", "just now" - Breast feeding side: "left", "right", "both" - Diaper types: "wet", "dirty", "both" - Sleep types: "nap", "night" **Structured Data Output:** - FeedingData: type, amount, unit, duration, side, timestamps - SleepData: type, duration, start/end times - DiaperData: type, timestamp - Ready for direct activity creation **Pattern Matching:** - 15+ feeding patterns (bottle, breast, solid) - 8+ sleep patterns (nap, sleep, woke up) - 8+ diaper patterns (wet, dirty, bowel movement) - Robust keyword detection with variations **Confidence Scoring:** - High: >= 0.8 (strong match) - Medium: 0.5-0.79 (probable match) - Low: < 0.5 (uncertain) - Minimum threshold: 0.3 for validation **API Endpoint:** - POST /api/voice/transcribe - Classify text or audio - GET /api/voice/transcribe - Get supported commands - JSON response with intent, confidence, entities, structured data - Audio transcription placeholder (Whisper integration ready) **Implementation Files:** - lib/voice/intentClassifier.ts - Core classification (600+ lines) - app/api/voice/transcribe/route.ts - API endpoint - scripts/test-voice-intent.mjs - Test suite (25 tests) - lib/voice/README.md - Complete documentation **Test Coverage:** 25 tests, 100% pass rate ✅ Bottle feeding (3 tests) ✅ Breastfeeding (3 tests) ✅ Solid food (2 tests) ✅ Sleep tracking (6 tests) ✅ Diaper changes (7 tests) ✅ Edge cases (4 tests) **Example Commands:** - "Fed baby 120 ml" → bottle, 120ml - "Nursed on left breast for 15 minutes" → breast_left, 15min - "Changed wet and dirty diaper" → both - "Napped for 45 minutes" → nap, 45min System converts natural language to structured tracking data with high accuracy for common parenting voice commands. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
139
maternal-web/app/api/voice/transcribe/route.ts
Normal file
139
maternal-web/app/api/voice/transcribe/route.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { classifyIntent, validateClassification, getConfidenceLevel } from '@/lib/voice/intentClassifier';
|
||||
|
||||
/**
|
||||
* Voice transcription and intent classification endpoint
|
||||
*
|
||||
* Accepts audio file or transcribed text and returns:
|
||||
* - Intent classification (feeding/sleep/diaper)
|
||||
* - Extracted entities (amounts, times, durations)
|
||||
* - Structured data ready for activity creation
|
||||
*/
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const contentType = request.headers.get('content-type') || '';
|
||||
|
||||
let transcribedText: string;
|
||||
|
||||
if (contentType.includes('application/json')) {
|
||||
// Text input (already transcribed)
|
||||
const body = await request.json();
|
||||
transcribedText = body.text;
|
||||
|
||||
if (!transcribedText || typeof transcribedText !== 'string') {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'VOICE_INVALID_INPUT',
|
||||
message: 'Text must be a non-empty string',
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
} else if (contentType.includes('multipart/form-data')) {
|
||||
// Audio file upload (needs transcription)
|
||||
// TODO: Implement Whisper API integration for audio transcription
|
||||
// For now, return not implemented
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'VOICE_AUDIO_NOT_IMPLEMENTED',
|
||||
message: 'Audio transcription not yet implemented. Use text input for now.',
|
||||
hint: 'Send JSON with { "text": "your voice command" }',
|
||||
},
|
||||
{ status: 501 }
|
||||
);
|
||||
} else {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'VOICE_INVALID_CONTENT_TYPE',
|
||||
message: 'Content-Type must be application/json or multipart/form-data',
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Classify intent
|
||||
const classification = classifyIntent(transcribedText);
|
||||
|
||||
// Validate classification
|
||||
if (!validateClassification(classification)) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'VOICE_CLASSIFICATION_FAILED',
|
||||
message: 'Could not understand the command. Please try again.',
|
||||
suggestion: 'Try saying something like "Fed baby 100ml" or "Changed wet diaper"',
|
||||
classification: {
|
||||
intent: classification.intent,
|
||||
confidence: classification.confidence,
|
||||
confidenceLevel: getConfidenceLevel(classification.confidence),
|
||||
},
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Return classification result
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: true,
|
||||
transcription: transcribedText,
|
||||
classification: {
|
||||
intent: classification.intent,
|
||||
confidence: classification.confidence,
|
||||
confidenceLevel: getConfidenceLevel(classification.confidence),
|
||||
entities: classification.entities,
|
||||
structuredData: classification.structuredData,
|
||||
},
|
||||
},
|
||||
{ status: 200 }
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('[Voice] Transcription error:', error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'VOICE_TRANSCRIPTION_FAILED',
|
||||
message: 'Failed to process voice command. Please try again.',
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get supported voice commands and examples
|
||||
*/
|
||||
export async function GET() {
|
||||
return NextResponse.json(
|
||||
{
|
||||
supportedIntents: ['feeding', 'sleep', 'diaper'],
|
||||
examples: {
|
||||
feeding: [
|
||||
'Fed baby 120 ml',
|
||||
'Gave him 4 ounces',
|
||||
'Nursed on left breast for 15 minutes',
|
||||
'Breastfed on both sides',
|
||||
'Baby ate solid food',
|
||||
],
|
||||
sleep: [
|
||||
'Baby fell asleep',
|
||||
'Napped for 45 minutes',
|
||||
'Put baby down for bedtime',
|
||||
'Baby woke up',
|
||||
],
|
||||
diaper: [
|
||||
'Changed wet diaper',
|
||||
'Dirty diaper change',
|
||||
'Changed a wet and dirty diaper',
|
||||
'Baby had a bowel movement',
|
||||
],
|
||||
},
|
||||
entities: {
|
||||
amounts: ['120 ml', '4 oz', '2 tablespoons'],
|
||||
durations: ['15 minutes', '45 mins', '2 hours'],
|
||||
times: ['at 3:30 pm', '30 minutes ago', 'just now'],
|
||||
breastSides: ['left breast', 'right side', 'both sides'],
|
||||
diaperTypes: ['wet', 'dirty', 'wet and dirty', 'bowel movement'],
|
||||
},
|
||||
},
|
||||
{ status: 200 }
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user