Voice commands now create activities directly via API
- Replace navigation to pre-filled forms with direct API activity creation - Fetch children from family and use first child (can be enhanced for name matching) - Show success/error messages with proper feedback - Auto-close dialog after successful save - Add test endpoint /api/v1/voice/test-classify for easy testing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,9 @@ import MicIcon from '@mui/icons-material/Mic';
|
||||
import MicOffIcon from '@mui/icons-material/MicOff';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useVoiceInput } from '@/hooks/useVoiceInput';
|
||||
import { useAuth } from '@/lib/auth/AuthContext';
|
||||
import { trackingApi } from '@/lib/api/tracking';
|
||||
import { childrenApi } from '@/lib/api/children';
|
||||
|
||||
/**
|
||||
* Floating voice input button
|
||||
@@ -30,6 +33,7 @@ import { useVoiceInput } from '@/hooks/useVoiceInput';
|
||||
*/
|
||||
export function VoiceFloatingButton() {
|
||||
const router = useRouter();
|
||||
const { user } = useAuth();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [isProcessing, setIsProcessing] = useState(false);
|
||||
const [classificationResult, setClassificationResult] = useState<any>(null);
|
||||
@@ -44,6 +48,8 @@ export function VoiceFloatingButton() {
|
||||
severity: 'info',
|
||||
});
|
||||
|
||||
const familyId = user?.families?.[0]?.familyId;
|
||||
|
||||
const { isListening, isSupported, transcript, classification, error, usesFallback, startListening, stopListening, reset } =
|
||||
useVoiceInput();
|
||||
|
||||
@@ -133,7 +139,7 @@ export function VoiceFloatingButton() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleClassifiedIntent = (result: any) => {
|
||||
const handleClassifiedIntent = async (result: any) => {
|
||||
if (result.error) {
|
||||
setSnackbar({
|
||||
open: true,
|
||||
@@ -153,36 +159,68 @@ export function VoiceFloatingButton() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Show success message with activity type
|
||||
const activityLabel = result.type.charAt(0).toUpperCase() + result.type.slice(1);
|
||||
setSnackbar({
|
||||
open: true,
|
||||
message: `${activityLabel} activity detected!`,
|
||||
severity: 'success',
|
||||
});
|
||||
// Get the first child from the family
|
||||
if (!familyId) {
|
||||
setSnackbar({
|
||||
open: true,
|
||||
message: 'No family found. Please set up your profile first.',
|
||||
severity: 'error',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Auto-close dialog and navigate with pre-filled data
|
||||
setTimeout(() => {
|
||||
handleClose();
|
||||
try {
|
||||
setIsProcessing(true);
|
||||
|
||||
// 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));
|
||||
}
|
||||
// Fetch children
|
||||
const children = await childrenApi.getChildren(familyId);
|
||||
if (children.length === 0) {
|
||||
setSnackbar({
|
||||
open: true,
|
||||
message: 'No children found. Please add a child first.',
|
||||
severity: 'error',
|
||||
});
|
||||
}
|
||||
if (result.timestamp) {
|
||||
params.set('timestamp', result.timestamp);
|
||||
setIsProcessing(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const queryString = params.toString();
|
||||
const url = `/track/${result.type}${queryString ? `?${queryString}` : ''}`;
|
||||
// Use the first child (or you could enhance this to support child name matching)
|
||||
const childId = children[0].id;
|
||||
|
||||
router.push(url);
|
||||
}, 1500);
|
||||
// Create the activity
|
||||
const activityData = {
|
||||
type: result.type,
|
||||
timestamp: result.timestamp || new Date().toISOString(),
|
||||
data: result.details || {},
|
||||
notes: result.details?.notes || undefined,
|
||||
};
|
||||
|
||||
console.log('[Voice] Creating activity:', activityData);
|
||||
|
||||
await trackingApi.createActivity(childId, activityData);
|
||||
|
||||
// Show success message
|
||||
const activityLabel = result.type.charAt(0).toUpperCase() + result.type.slice(1);
|
||||
setSnackbar({
|
||||
open: true,
|
||||
message: `${activityLabel} activity saved successfully!`,
|
||||
severity: 'success',
|
||||
});
|
||||
|
||||
// Auto-close dialog
|
||||
setTimeout(() => {
|
||||
handleClose();
|
||||
}, 1500);
|
||||
} catch (error: any) {
|
||||
console.error('[Voice] Failed to create activity:', error);
|
||||
setSnackbar({
|
||||
open: true,
|
||||
message: error.response?.data?.message || 'Failed to save activity. Please try again.',
|
||||
severity: 'error',
|
||||
});
|
||||
} finally {
|
||||
setIsProcessing(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCloseSnackbar = () => {
|
||||
|
||||
Reference in New Issue
Block a user