Voice commands now create activities directly via API
Some checks failed
CI/CD Pipeline / Lint and Test (push) Has been cancelled
CI/CD Pipeline / E2E Tests (push) Has been cancelled
CI/CD Pipeline / Build Application (push) Has been cancelled

- 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:
2025-10-02 08:08:31 +00:00
parent db0ff8067a
commit 4b8828fdad
4 changed files with 383 additions and 25 deletions

View File

@@ -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 = () => {