'use client' import { useState, useRef, useEffect } from 'react' import { useTranslations } from 'next-intl' import Box from '@mui/material/Box' import Typography from '@mui/material/Typography' import Card from '@mui/material/Card' import IconButton from '@mui/material/IconButton' import TextField from '@mui/material/TextField' import Collapse from '@mui/material/Collapse' import classnames from 'classnames' import type { FarmContext, FarmAIMessage, AIResponseSection } from './farmAiAssistantTypes' // ─── Constants ───────────────────────────────────────────────────────────── const DEFAULT_FARM_CONTEXT: FarmContext = { soilType: 'Loamy', waterEC: '1.2 dS/m', selectedCrop: 'Tomato', growthStage: 'Flowering', lastIrrigationStatus: '2 days ago' } const SUGGESTION_CHIPS = [ { id: 'yellow-leaves', labelKey: 'suggestions.yellowLeaves' }, { id: 'irrigation-plan', labelKey: 'suggestions.irrigationPlan' }, { id: 'fertilizer-flowering', labelKey: 'suggestions.fertilizerFlowering' }, { id: 'plant-disease', labelKey: 'suggestions.plantDisease' } ] // Demo structured AI response for display const DEMO_AI_RESPONSE_SECTIONS: AIResponseSection[] = [ { type: 'recommendation', title: 'Irrigation recommendation', icon: 'droplet', frequency: '3 times per week', amount: '15–20 L per plant', timing: 'Early morning (05:00–07:00)', expandableExplanation: 'Your loamy soil holds moisture well, but tomatoes during flowering need consistent moisture. Water EC of 1.2 dS/m is suitable. Last irrigation was 2 days ago—avoid overwatering to prevent blossom-end rot.' }, { type: 'list', title: 'Key points', icon: 'leaf', items: [ 'Avoid midday watering to reduce evaporation', 'Drip irrigation preferred for root zone targeting', 'Monitor soil moisture before each session' ] }, { type: 'warning', title: 'Weather advisory', icon: 'warning', content: 'High temps forecasted next week. Consider increasing frequency to 4x/week temporarily.' } ] // ─── Main Component ──────────────────────────────────────────────────────── export default function FarmAiAssistantChat() { const t = useTranslations('farmAiAssistant') const [messages, setMessages] = useState([]) const [inputValue, setInputValue] = useState('') const [isContextExpanded, setIsContextExpanded] = useState(true) const [isTyping, setIsTyping] = useState(false) const [selectedChip, setSelectedChip] = useState(null) const [expandedExplanations, setExpandedExplanations] = useState>(new Set()) const scrollRef = useRef(null) const farmContext = DEFAULT_FARM_CONTEXT // Scroll to bottom on new messages useEffect(() => { scrollRef.current?.scrollTo({ top: scrollRef.current.scrollHeight, behavior: 'smooth' }) }, [messages, isTyping]) const handleSuggestionClick = (chipId: string, labelKey: string) => { const label = t(labelKey) setSelectedChip(prev => (prev === chipId ? null : chipId)) handleSend(label) } const handleSend = async (text?: string) => { const content = (text || inputValue).trim() if (!content) return setInputValue('') setSelectedChip(null) const userMessage: FarmAIMessage = { id: `u-${Date.now()}`, role: 'user', content, timestamp: new Date() } setMessages(prev => [...prev, userMessage]) setIsTyping(true) // Simulate AI response (replace with actual API call) await new Promise(resolve => setTimeout(resolve, 1500)) const aiMessage: FarmAIMessage = { id: `a-${Date.now()}`, role: 'assistant', content: '', timestamp: new Date(), sections: DEMO_AI_RESPONSE_SECTIONS } setMessages(prev => [...prev, aiMessage]) setIsTyping(false) } const toggleExplanation = (id: string) => { setExpandedExplanations(prev => { const next = new Set(prev) if (next.has(id)) next.delete(id) else next.add(id) return next }) } return ( {/* 1) Smart Header */} {t('header.title')} {t('header.subtitle')} {/* 2) Expandable Farm Context Bar */} setIsContextExpanded(!isContextExpanded)} className='w-full flex items-center justify-between px-4 py-3 text-start' sx={{ '&:hover': { bgcolor: 'rgba(34, 197, 94, 0.04)' }, transition: 'background 0.2s' }} > {t('context.title')} {/* 3) Chat Area */} {messages.length === 0 && !isTyping && ( {t('emptyState.title')} {t('emptyState.subtitle')} )} {messages.map(msg => ( ))} {/* Typing indicator */} {isTyping && ( )} {/* 4) Suggestion Chips */} {SUGGESTION_CHIPS.map(chip => ( handleSuggestionClick(chip.id, chip.labelKey)} className={classnames( 'px-4 py-2 rounded-2xl text-sm font-medium whitespace-nowrap transition-all duration-200 shrink-0', 'hover:scale-[1.02] active:scale-[0.98]' )} sx={{ background: selectedChip === chip.id ? 'linear-gradient(135deg, #22c55e 0%, #16a34a 100%)' : 'linear-gradient(145deg, #ffffff 0%, rgba(34, 197, 94, 0.08) 100%)', color: selectedChip === chip.id ? '#ffffff' : 'text.primary', border: selectedChip === chip.id ? 'none' : '1px solid rgba(34, 197, 94, 0.2)', boxShadow: selectedChip === chip.id ? '0 4px 12px rgba(34, 197, 94, 0.35)' : '0 2px 8px rgba(0,0,0,0.04)' }} > {t(chip.labelKey)} ))} {/* 5) Input Area - Sticky Bottom */} setInputValue(e.target.value)} onKeyDown={e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault() handleSend() } }} variant='standard' InputProps={{ disableUnderline: true, sx: { px: 1.5, py: 1, fontSize: '0.95rem' } }} sx={{ flex: 1, minWidth: 0 }} /> handleSend()} disabled={!inputValue.trim()} className='shrink-0' sx={{ background: inputValue.trim() ? 'linear-gradient(135deg, #22c55e 0%, #16a34a 100%)' : 'action.disabledBackground', color: inputValue.trim() ? '#ffffff' : 'action.disabled', '&:hover': inputValue.trim() ? { background: 'linear-gradient(135deg, #16a34a 0%, #15803d 100%)', boxShadow: '0 4px 12px rgba(34, 197, 94, 0.4)' } : {}, '&.Mui-disabled': { background: 'action.disabledBackground', color: 'action.disabled' } }} > ) } // ─── Sub-components ──────────────────────────────────────────────────────── function ContextBadge({ icon, label, value, colSpan = 1 }: { icon: string label: string value: string colSpan?: number }) { return ( {label} {value} ) } function MessageBubble({ message, expandedExplanations, onToggleExplanation, t }: { message: FarmAIMessage expandedExplanations: Set onToggleExplanation: (id: string) => void t: (key: string) => string }) { const isUser = message.role === 'user' if (isUser) { return ( {message.content} ) } // AI message - structured cards return ( {message.sections?.map((section, idx) => ( ))} ) } function AISectionCard({ section, expandedExplanations, onToggleExplanation, messageId, idx, t }: { section: AIResponseSection expandedExplanations: Set onToggleExplanation: (id: string) => void messageId: string idx: number t: (key: string) => string }) { const expId = `${messageId}-exp-${idx}` const iconMap = { droplet: 'tabler-droplet', leaf: 'tabler-leaf', warning: 'tabler-alert-triangle', fertilizer: 'tabler-atom-2', calendar: 'tabler-calendar' } const iconClass = section.icon ? iconMap[section.icon] : 'tabler-leaf' if (section.type === 'recommendation') { return ( {section.title} {section.frequency && ( {t('recommendation.frequency')} {section.frequency} )} {section.amount && ( {t('recommendation.amount')} {section.amount} )} {section.timing && ( {t('recommendation.timing')} {section.timing} )} {section.expandableExplanation && ( onToggleExplanation(expId)} className='flex items-center gap-1 text-sm font-medium' sx={{ color: '#22c55e', '&:hover': { color: '#16a34a' } }} > {t('recommendation.whyThis')} {section.expandableExplanation} )} ) } if (section.type === 'warning') { return ( {section.title && ( {section.title} )} {section.content} ) } if (section.type === 'list') { return ( {section.title && ( {section.title} )} {section.items && ( {section.items.map((item, i) => ( {item} ))} )} ) } return null } function TypingIndicator() { return ( {[0, 1, 2].map(i => ( ))} ) }