feat: Implement smart AI features - contextual follow-up questions
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

Smart Features Completed:
1. Growth Spurt Detection - Already fully implemented 
   - Backend: Pattern analysis service with 20%+ feeding spike detection
   - Frontend: GrowthSpurtAlert component with collapsible details
   - Age-based probability calculation (2,3,6,12,16,24,36 weeks)
   - Integrated into analytics dashboard

2. AI Personalization System - Already fully implemented 
   - Backend: PersonalizationService with preference tracking
   - Response style adaptation (Concise/Detailed/Balanced)
   - Tone customization (Friendly/Professional/Casual/Empathetic)
   - Topic weight learning and feedback integration
   - Formatting preferences (bullets, step-by-step, examples)

3. Suggested Follow-Up Questions - NEW IMPLEMENTATION 🧠
   - Created SuggestedQuestions component with animated Chip buttons
   - Context-aware question generation based on topic detection
   - 7 topic categories: sleep, feeding, development, health, crying, schedule, growth
   - Smart question selection using keyword matching
   - One-tap to ask follow-up (auto-sends message)
   - Framer Motion animations with glass morphism design
   - Integrated into AIChatInterface after each AI response

Files Changed:
Frontend:
- components/features/ai-chat/SuggestedQuestions.tsx (new)
- lib/ai/suggestedQuestions.ts (new)
- components/features/ai-chat/AIChatInterface.tsx (modified)

Documentation:
- docs/REMAINING_FEATURES.md (updated)
  * 76/139 features complete (55%)
  * All high-priority + smart features complete!
  * Updated statistics and checklists

Technical Implementation:
- Topic detection with regex pattern matching
- Generic follow-up questions as fallback
- Response-specific question prioritization
- Duplicate removal and smart limiting
- Integration with existing chat message flow

🎉 Result: ParentFlow AI is now smart, personalized, and interactive!

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-04 11:51:57 +00:00
parent e4b97df0c0
commit a0e0bbb002
5 changed files with 360 additions and 49 deletions

View File

@@ -55,12 +55,15 @@ import remarkGfm from 'remark-gfm';
import { useTranslation } from '@/hooks/useTranslation';
import { useStreamingChat } from '@/hooks/useStreamingChat';
import { MessageFeedback } from './MessageFeedback';
import { SuggestedQuestions } from './SuggestedQuestions';
import { generateFollowUpQuestions } from '@/lib/ai/suggestedQuestions';
interface Message {
id: string;
role: 'user' | 'assistant';
content: string;
timestamp: Date;
suggestedQuestions?: string[];
}
interface Conversation {
@@ -374,12 +377,20 @@ export const AIChatInterface: React.FC = () => {
},
// On complete
() => {
// Generate suggested follow-up questions
const suggestedQuestions = generateFollowUpQuestions(
messageText,
accumulatedMessage,
3
);
// Add the complete message to messages
const assistantMessage: Message = {
id: (Date.now() + 1).toString(),
role: 'assistant',
content: accumulatedMessage,
timestamp: new Date(),
suggestedQuestions,
};
setMessages((prev) => [...prev, assistantMessage]);
setStreamingMessage('');
@@ -418,11 +429,20 @@ export const AIChatInterface: React.FC = () => {
});
const responseData = response.data.data;
// Generate suggested follow-up questions
const suggestedQuestions = generateFollowUpQuestions(
messageText,
responseData.message,
3
);
const assistantMessage: Message = {
id: (Date.now() + 1).toString(),
role: 'assistant',
content: responseData.message,
timestamp: new Date(responseData.timestamp),
suggestedQuestions,
};
setMessages((prev) => [...prev, assistantMessage]);
@@ -801,6 +821,17 @@ export const AIChatInterface: React.FC = () => {
</Avatar>
)}
</Box>
{/* Suggested Follow-up Questions */}
{message.role === 'assistant' && message.suggestedQuestions && message.suggestedQuestions.length > 0 && (
<Box sx={{ maxWidth: '70%', ml: 7 }}>
<SuggestedQuestions
questions={message.suggestedQuestions}
onQuestionClick={handleSuggestedQuestion}
loading={isLoading}
/>
</Box>
)}
</motion.div>
))}
</AnimatePresence>

View File

@@ -0,0 +1,87 @@
'use client';
import { Box, Chip, Typography } from '@mui/material';
import { AutoAwesome } from '@mui/icons-material';
import { motion, AnimatePresence } from 'framer-motion';
interface SuggestedQuestionsProps {
questions: string[];
onQuestionClick: (question: string) => void;
loading?: boolean;
}
export function SuggestedQuestions({
questions,
onQuestionClick,
loading = false,
}: SuggestedQuestionsProps) {
if (loading || questions.length === 0) {
return null;
}
return (
<Box
sx={{
mt: 2,
p: 2,
borderRadius: 2,
bgcolor: 'rgba(255, 255, 255, 0.7)',
backdropFilter: 'blur(10px)',
border: '1px solid',
borderColor: 'divider',
}}
>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1.5 }}>
<AutoAwesome sx={{ fontSize: 18, color: 'primary.main' }} />
<Typography variant="caption" fontWeight={600} color="text.secondary">
Suggested follow-up questions
</Typography>
</Box>
<Box
sx={{
display: 'flex',
flexWrap: 'wrap',
gap: 1,
}}
>
<AnimatePresence>
{questions.map((question, index) => (
<motion.div
key={question}
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.8 }}
transition={{ delay: index * 0.1 }}
>
<Chip
label={question}
onClick={() => onQuestionClick(question)}
sx={{
py: 2.5,
px: 1,
borderRadius: 2,
fontSize: '0.875rem',
bgcolor: 'background.paper',
border: 1,
borderColor: 'primary.light',
cursor: 'pointer',
transition: 'all 0.2s',
'&:hover': {
bgcolor: 'primary.light',
borderColor: 'primary.main',
transform: 'translateY(-2px)',
boxShadow: 2,
},
'&:active': {
transform: 'translateY(0)',
},
}}
/>
</motion.div>
))}
</AnimatePresence>
</Box>
</Box>
);
}

View File

@@ -0,0 +1,171 @@
/**
* Generate contextual follow-up questions based on AI response
*/
export function generateFollowUpQuestions(
userMessage: string,
aiResponse: string,
limit: number = 3
): string[] {
const questions: string[] = [];
const lowerResponse = aiResponse.toLowerCase();
const lowerMessage = userMessage.toLowerCase();
// Topic-based follow-up questions
const topicQuestions: Record<string, string[]> = {
sleep: [
"What if my baby still wakes up frequently?",
"How can I create a better bedtime routine?",
"When should I be concerned about sleep patterns?",
"Tips for sleep training?",
"How long should naps be?",
],
feeding: [
"How do I know if my baby is eating enough?",
"What are signs of overfeeding?",
"When should I introduce solids?",
"How to handle feeding refusal?",
"Tips for establishing a feeding schedule?",
],
development: [
"What milestones should I watch for next?",
"How can I support my baby development?",
"When should I talk to my pediatrician?",
"Activities to encourage development?",
"Is my baby developing normally?",
],
health: [
"When should I call the doctor?",
"What are warning signs to watch for?",
"How to prevent common illnesses?",
"When to give medicine?",
"Normal vs concerning symptoms?",
],
crying: [
"How to soothe a crying baby?",
"What if nothing seems to work?",
"Is this normal crying or colic?",
"When does crying typically decrease?",
"Tips for staying calm?",
],
schedule: [
"How to create a daily routine?",
"When to adjust the schedule?",
"Flexibility vs consistency?",
"Managing multiple children schedules?",
"Sample schedule for this age?",
],
growth: [
"How to track growth?",
"What is a healthy growth rate?",
"When to worry about weight?",
"Growth spurts vs concerns?",
"Nutrition for healthy growth?",
],
};
// Detect topics in the conversation
const detectedTopics = new Set<string>();
Object.keys(topicQuestions).forEach((topic) => {
if (lowerResponse.includes(topic) || lowerMessage.includes(topic)) {
detectedTopics.add(topic);
}
});
// Add sleep-related keywords
if (
lowerResponse.match(/\b(sleep|nap|bedtime|wake|waking|night)\b/) ||
lowerMessage.match(/\b(sleep|nap|bedtime|wake|waking|night)\b/)
) {
detectedTopics.add("sleep");
}
// Add feeding-related keywords
if (
lowerResponse.match(/\b(feed|feeding|eat|eating|bottle|breast|formula|milk|nurse|nursing)\b/) ||
lowerMessage.match(/\b(feed|feeding|eat|eating|bottle|breast|formula|milk|nurse|nursing)\b/)
) {
detectedTopics.add("feeding");
}
// Add development keywords
if (
lowerResponse.match(/\b(milestone|develop|crawl|walk|talk|sit|roll)\b/) ||
lowerMessage.match(/\b(milestone|develop|crawl|walk|talk|sit|roll)\b/)
) {
detectedTopics.add("development");
}
// Add health keywords
if (
lowerResponse.match(/\b(sick|fever|doctor|medicine|vaccine|health|symptom)\b/) ||
lowerMessage.match(/\b(sick|fever|doctor|medicine|vaccine|health|symptom)\b/)
) {
detectedTopics.add("health");
}
// Add crying keywords
if (
lowerResponse.match(/\b(cry|crying|fuss|fussy|colic)\b/) ||
lowerMessage.match(/\b(cry|crying|fuss|fussy|colic)\b/)
) {
detectedTopics.add("crying");
}
// Add schedule keywords
if (
lowerResponse.match(/\b(schedule|routine|timing|consistent|pattern)\b/) ||
lowerMessage.match(/\b(schedule|routine|timing|consistent|pattern)\b/)
) {
detectedTopics.add("schedule");
}
// Add growth keywords
if (
lowerResponse.match(/\b(growth|spurt|weight|height|size|growing)\b/) ||
lowerMessage.match(/\b(growth|spurt|weight|height|size|growing)\b/)
) {
detectedTopics.add("growth");
}
// Collect questions from detected topics
detectedTopics.forEach((topic) => {
const topicQs = topicQuestions[topic] || [];
// Add random questions from this topic
const shuffled = topicQs.sort(() => Math.random() - 0.5);
questions.push(...shuffled.slice(0, 2));
});
// Generic follow-ups if no specific topics detected or to fill gaps
const genericQuestions = [
"Can you explain that in more detail?",
"What else should I know about this?",
"Are there any common mistakes to avoid?",
"How long does this usually last?",
"What can I do to help?",
];
if (questions.length < limit) {
const needed = limit - questions.length;
const shuffledGeneric = genericQuestions.sort(() => Math.random() - 0.5);
questions.push(...shuffledGeneric.slice(0, needed));
}
// Response-specific follow-ups based on keywords
if (lowerResponse.includes("consult") || lowerResponse.includes("pediatrician")) {
questions.unshift("What symptoms should prompt an immediate call?");
}
if (lowerResponse.includes("normal") || lowerResponse.includes("typical")) {
questions.unshift("What are signs this might NOT be normal?");
}
if (lowerResponse.includes("weeks") || lowerResponse.includes("months")) {
questions.push("What comes after this stage?");
}
// Remove duplicates and limit
const uniqueQuestions = Array.from(new Set(questions));
return uniqueQuestions.slice(0, limit);
}

File diff suppressed because one or more lines are too long