Add voice command auto-fill and server-side logging
- Add URL parameter reading to diaper tracking page for voice-extracted data - Add comprehensive server-side logging in voice controller and service - Log request type (Web Speech API vs MediaRecorder), input text/audio, GPT calls, and classification results - Enable automatic form pre-filling when voice commands navigate to tracking pages 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -39,7 +39,7 @@ import {
|
||||
ChildCare,
|
||||
Add,
|
||||
} from '@mui/icons-material';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import { AppShell } from '@/components/layouts/AppShell/AppShell';
|
||||
import { ProtectedRoute } from '@/components/common/ProtectedRoute';
|
||||
import { useAuth } from '@/lib/auth/AuthContext';
|
||||
@@ -57,6 +57,7 @@ interface DiaperData {
|
||||
|
||||
export default function DiaperTrackPage() {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const { user } = useAuth();
|
||||
const [children, setChildren] = useState<Child[]>([]);
|
||||
const [selectedChild, setSelectedChild] = useState<string>('');
|
||||
@@ -108,6 +109,58 @@ export default function DiaperTrackPage() {
|
||||
}
|
||||
}, [selectedChild]);
|
||||
|
||||
// Pre-fill form from URL parameters (voice command)
|
||||
useEffect(() => {
|
||||
const type = searchParams.get('type');
|
||||
const timestampParam = searchParams.get('timestamp');
|
||||
const notesParam = searchParams.get('notes');
|
||||
const rashParam = searchParams.get('rash');
|
||||
const colorParam = searchParams.get('color');
|
||||
const consistencyParam = searchParams.get('consistency');
|
||||
|
||||
if (type) {
|
||||
// Map backend type values to frontend diaperType
|
||||
const typeMap: Record<string, 'wet' | 'dirty' | 'both' | 'dry'> = {
|
||||
'wet': 'wet',
|
||||
'dirty': 'dirty',
|
||||
'both': 'both',
|
||||
'dry': 'dry',
|
||||
};
|
||||
if (type in typeMap) {
|
||||
setDiaperType(typeMap[type]);
|
||||
}
|
||||
}
|
||||
|
||||
if (timestampParam) {
|
||||
try {
|
||||
const date = new Date(timestampParam);
|
||||
setTimestamp(format(date, "yyyy-MM-dd'T'HH:mm"));
|
||||
} catch (e) {
|
||||
console.warn('[Diaper] Invalid timestamp from URL:', timestampParam);
|
||||
}
|
||||
}
|
||||
|
||||
if (notesParam) {
|
||||
setNotes(notesParam);
|
||||
}
|
||||
|
||||
if (rashParam) {
|
||||
setHasRash(rashParam === 'true');
|
||||
}
|
||||
|
||||
// Map color and consistency to conditions
|
||||
const newConditions: string[] = [];
|
||||
if (colorParam && availableConditions.includes(colorParam)) {
|
||||
newConditions.push(colorParam);
|
||||
}
|
||||
if (consistencyParam && availableConditions.includes(consistencyParam)) {
|
||||
newConditions.push(consistencyParam);
|
||||
}
|
||||
if (newConditions.length > 0) {
|
||||
setConditions(newConditions);
|
||||
}
|
||||
}, [searchParams]);
|
||||
|
||||
const loadChildren = async () => {
|
||||
if (!familyId) return;
|
||||
|
||||
|
||||
@@ -143,23 +143,45 @@ export function VoiceFloatingButton() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Show success message
|
||||
// Handle unknown or low confidence
|
||||
if (result.type === 'unknown' || (result.confidence && result.confidence < 0.3)) {
|
||||
setSnackbar({
|
||||
open: true,
|
||||
message: 'Could not understand the command. Please try again or use manual entry.',
|
||||
severity: 'warning',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Show success message with activity type
|
||||
const activityLabel = result.type.charAt(0).toUpperCase() + result.type.slice(1);
|
||||
setSnackbar({
|
||||
open: true,
|
||||
message: `Understood: ${result.intent} command`,
|
||||
message: `${activityLabel} activity detected!`,
|
||||
severity: 'success',
|
||||
});
|
||||
|
||||
// Auto-close dialog and navigate
|
||||
// Auto-close dialog and navigate with pre-filled data
|
||||
setTimeout(() => {
|
||||
handleClose();
|
||||
if (result.intent === 'feeding') {
|
||||
router.push('/track/feeding');
|
||||
} else if (result.intent === 'sleep') {
|
||||
router.push('/track/sleep');
|
||||
} else if (result.intent === 'diaper') {
|
||||
router.push('/track/diaper');
|
||||
|
||||
// Encode the details as URL parameters for pre-filling the form
|
||||
const params = new URLSearchParams();
|
||||
if (result.details) {
|
||||
Object.entries(result.details).forEach(([key, value]) => {
|
||||
if (value !== null && value !== undefined) {
|
||||
params.set(key, String(value));
|
||||
}
|
||||
});
|
||||
}
|
||||
if (result.timestamp) {
|
||||
params.set('timestamp', result.timestamp);
|
||||
}
|
||||
|
||||
const queryString = params.toString();
|
||||
const url = `/track/${result.type}${queryString ? `?${queryString}` : ''}`;
|
||||
|
||||
router.push(url);
|
||||
}, 1500);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user