Fix login data structure and improve voice input UX
- Fix login endpoint to return families as array of objects instead of strings - Update auth interface to match /auth/me endpoint structure - Add silence detection to voice input (auto-stop after 1.5s) - Add comprehensive status messages to voice modal (Listening, Understanding, Saving) - Unify voice input flow to use MediaRecorder + backend for all platforms - Add null checks to prevent tracking page crashes from invalid data - Wait for auth completion before loading family data in HomePage 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -16,7 +16,7 @@ export async function POST(request: NextRequest) {
|
||||
let transcribedText: string;
|
||||
|
||||
if (contentType.includes('application/json')) {
|
||||
// Text input (already transcribed)
|
||||
// Text input (already transcribed) - forward to backend for LLM classification
|
||||
const body = await request.json();
|
||||
transcribedText = body.text;
|
||||
|
||||
@@ -29,6 +29,41 @@ export async function POST(request: NextRequest) {
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Forward text to backend for LLM-based classification
|
||||
const backendUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3020';
|
||||
const backendResponse = await fetch(`${backendUrl}/api/v1/voice/transcribe`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
// Forward auth token if present
|
||||
...(request.headers.get('authorization') && {
|
||||
authorization: request.headers.get('authorization')!,
|
||||
}),
|
||||
},
|
||||
body: JSON.stringify({
|
||||
text: transcribedText,
|
||||
language: body.language || 'en',
|
||||
childName: body.childName,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!backendResponse.ok) {
|
||||
const errorData = await backendResponse.json();
|
||||
return NextResponse.json(errorData, { status: backendResponse.status });
|
||||
}
|
||||
|
||||
const result = await backendResponse.json();
|
||||
|
||||
// Backend returns { success, transcript, classification }
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: true,
|
||||
transcript: result.transcript,
|
||||
classification: result.classification,
|
||||
},
|
||||
{ status: 200 }
|
||||
);
|
||||
} else if (contentType.includes('multipart/form-data')) {
|
||||
// Audio file upload - forward to backend for Whisper transcription
|
||||
const formData = await request.formData();
|
||||
|
||||
@@ -25,7 +25,7 @@ import { childrenApi, Child } from '@/lib/api/children';
|
||||
import { format } from 'date-fns';
|
||||
|
||||
export default function HomePage() {
|
||||
const { user } = useAuth();
|
||||
const { user, isLoading: authLoading } = useAuth();
|
||||
const router = useRouter();
|
||||
const [children, setChildren] = useState<Child[]>([]);
|
||||
const [selectedChild, setSelectedChild] = useState<Child | null>(null);
|
||||
@@ -33,17 +33,29 @@ export default function HomePage() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const familyId = user?.families?.[0]?.familyId;
|
||||
|
||||
// Load children and daily summary
|
||||
useEffect(() => {
|
||||
const loadData = async () => {
|
||||
// Wait for auth to complete before trying to load data
|
||||
if (authLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!familyId) {
|
||||
console.log('[HomePage] No familyId found');
|
||||
console.log('[HomePage] User object:', JSON.stringify(user, null, 2));
|
||||
console.log('[HomePage] User.families:', user?.families);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[HomePage] Loading data for familyId:', familyId);
|
||||
|
||||
try {
|
||||
// Load children
|
||||
const childrenData = await childrenApi.getChildren(familyId);
|
||||
console.log('[HomePage] Children loaded:', childrenData.length);
|
||||
setChildren(childrenData);
|
||||
|
||||
if (childrenData.length > 0) {
|
||||
@@ -56,14 +68,14 @@ export default function HomePage() {
|
||||
setDailySummary(summary);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load data:', error);
|
||||
console.error('[HomePage] Failed to load data:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
loadData();
|
||||
}, [familyId]);
|
||||
}, [familyId, authLoading, user]);
|
||||
|
||||
const quickActions = [
|
||||
{ icon: <Restaurant />, label: 'Feeding', color: '#FFB6C1', path: '/track/feeding' },
|
||||
|
||||
@@ -619,6 +619,11 @@ export default function DiaperTrackPage() {
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
{recentDiapers.map((activity, index) => {
|
||||
const data = activity.data as DiaperData;
|
||||
// Skip activities with invalid data structure
|
||||
if (!data || !data.diaperType) {
|
||||
console.warn('[Diaper] Activity missing diaperType:', activity);
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<motion.div
|
||||
key={activity.id}
|
||||
|
||||
@@ -601,6 +601,11 @@ function FeedingTrackPage() {
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
{recentFeedings.map((activity, index) => {
|
||||
const data = activity.data as FeedingData;
|
||||
// Skip activities with invalid data structure
|
||||
if (!data || !data.feedingType) {
|
||||
console.warn('[Feeding] Activity missing feedingType:', activity);
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<motion.div
|
||||
key={activity.id}
|
||||
|
||||
@@ -557,6 +557,11 @@ export default function SleepTrackPage() {
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
{recentSleeps.map((activity, index) => {
|
||||
const data = activity.data as SleepData;
|
||||
// Skip activities with invalid data structure
|
||||
if (!data || !data.quality || !data.location) {
|
||||
console.warn('[Sleep] Activity missing required fields:', activity);
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<motion.div
|
||||
key={activity.id}
|
||||
|
||||
Reference in New Issue
Block a user