'use client';
import { useState, useRef, useEffect } from 'react';
import { Send, Mic, MicOff, Volume2, Loader2, Sprout, Sun, Cloud, Droplets, Wind, Zap, Leaf, MessageCircle, X, Sparkles } from 'lucide-react';
interface Message {
id: string;
text: string;
sender: 'user' | 'ai';
audioUrl?: string;
timestamp: Date;
}
export default function SmartFarmingChat() {
const [messages, setMessages] = useState<Message[]>([
{
id: '1',
text: 'Namaste! ๐ I\'m your Smart Farming Assistant. I can help you with:\n\n๐พ Crop recommendations & planting schedules\n๐ง๏ธ Seasonal farming advice (Kharif, Rabi, Zaid)\n๐ฑ Soil management & fertilizers\n๐ Pest control & disease prevention\n๐ง Irrigation techniques\n\nHow can I assist you today?',
sender: 'ai',
timestamp: new Date(),
}
]);
const [input, setInput] = useState('');
const [loading, setLoading] = useState(false);
const [recording, setRecording] = useState(false);
const [mediaRecorder, setMediaRecorder] = useState<MediaRecorder | null>(null);
const audioRef = useRef<HTMLAudioElement>(null);
const messagesEndRef = useRef<HTMLDivElement>(null);
const audioChunksRef = useRef<Blob[]>([]);
// Backend URL from environment variable
const BACKEND_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000';
// Auto-scroll to bottom
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
// Send text message
const sendMessage = async () => {
if (!input.trim() || loading) return;
const userMessage: Message = {
id: Date.now().toString(),
text: input,
sender: 'user',
timestamp: new Date(),
};
setMessages(prev => [...prev, userMessage]);
const currentInput = input;
setInput('');
setLoading(true);
try {
// โ
FIX 1: Use FormData instead of JSON
const formData = new FormData();
formData.append('text', currentInput);
// โ
FIX 2: Correct endpoint - /api/chat
const response = await fetch(`${BACKEND_URL}/api/chat`, {
method: 'POST',
body: formData, // Don't set Content-Type header - browser handles it
});
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
// โ
FIX 3: Backend returns { text, voice } not { success, text, audio_url }
const data = await response.json();
const aiMessage: Message = {
id: (Date.now() + 1).toString(),
text: data.text,
sender: 'ai',
audioUrl: data.voice ? `${BACKEND_URL}${data.voice}` : undefined, // โ
FIX 4: Prepend backend URL
timestamp: new Date(),
};
setMessages(prev => [...prev, aiMessage]);
} catch (error) {
console.error('Error:', error);
const errorMessage: Message = {
id: (Date.now() + 1).toString(),
text: 'โ ๏ธ Sorry, I encountered an error. Please make sure the backend API is running at ' + BACKEND_URL,
sender: 'ai',
timestamp: new Date(),
};
setMessages(prev => [...prev, errorMessage]);
} finally {
setLoading(false);
}
};
// Start voice recording
const startRecording = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const recorder = new MediaRecorder(stream);
audioChunksRef.current = [];
recorder.ondataavailable = (event) => {
audioChunksRef.current.push(event.data);
};
recorder.onstop = async () => {
const audioBlob = new Blob(audioChunksRef.current, { type: 'audio/webm' });
await sendVoiceMessage(audioBlob);
stream.getTracks().forEach(track => track.stop());
};
recorder.start();
setMediaRecorder(recorder);
setRecording(true);
} catch (error) {
console.error('Error accessing microphone:', error);
alert('Could not access microphone. Please check permissions.');
}
};
// Stop recording
const stopRecording = () => {
if (mediaRecorder && recording) {
mediaRecorder.stop();
setRecording(false);
setMediaRecorder(null);
}
};
// Send voice message
const sendVoiceMessage = async (audioBlob: Blob) => {
const userMessage: Message = {
id: Date.now().toString(),
text: '๐ค Processing voice...',
sender: 'user',
timestamp: new Date(),
};
setMessages(prev => [...prev, userMessage]);
setLoading(true);
try {
// โ
Send audio to get transcription
const formData = new FormData();
formData.append('audio', audioBlob, 'recording.webm');
const response = await fetch(`${BACKEND_URL}/api/chat`, {
method: 'POST',
body: formData,
});
if (!response.ok) {
throw new Error('Failed to process audio');
}
const data = await response.json();
// โ
Backend returns { text } for audio (transcription only)
const transcription = data.text;
// Update user message with transcription
setMessages(prev => prev.map(msg =>
msg.id === userMessage.id
? { ...msg, text: `๐ค "${transcription}"` }
: msg
));
// Now send transcription as text to get AI response
const textFormData = new FormData();
textFormData.append('text', transcription);
const aiResponse = await fetch(`${BACKEND_URL}/api/chat`, {
method: 'POST',
body: textFormData,
});
if (aiResponse.ok) {
const aiData = await aiResponse.json();
const aiMessage: Message = {
id: (Date.now() + 1).toString(),
text: aiData.text,
sender: 'ai',
audioUrl: aiData.voice ? `${BACKEND_URL}${aiData.voice}` : undefined,
timestamp: new Date(),
};
setMessages(prev => [...prev, aiMessage]);
}
} catch (error) {
console.error('Error:', error);
const errorMessage: Message = {
id: (Date.now() + 1).toString(),
text: 'โ ๏ธ Failed to process voice message.',
sender: 'ai',
timestamp: new Date(),
};
setMessages(prev => [...prev, errorMessage]);
} finally {
setLoading(false);
}
};
// Play audio response
const playAudio = (url: string) => {
if (audioRef.current) {
audioRef.current.src = url;
audioRef.current.play().catch(err => console.error('Audio play error:', err));
}
};
// Handle Enter key
const handleKeyPress = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
};
// Suggested questions
const suggestedQuestions = [
"๐พ What crops for monsoon season?",
"๐ฑ Best time to plant wheat?",
"๐ง How to save water in farming?",
"๐ Natural pest control methods?",
];
return (
<div className="relative">
{/* Agricultural Background Pattern */}
<div className="absolute inset-0 opacity-5 pointer-events-none">
<div className="absolute top-10 left-10 text-6xl">๐พ</div>
<div className="absolute top-32 right-20 text-5xl">๐ฑ</div>
<div className="absolute bottom-20 left-1/4 text-6xl">๐</div>
<div className="absolute top-1/3 right-10 text-5xl">๐ป</div>
<div className="absolute bottom-10 right-1/3 text-6xl">๐ฝ</div>
</div>
<div className="relative flex flex-col h-[calc(100vh-3rem)] glass rounded-3xl border-2 border-green-500/20 overflow-hidden shadow-2xl">
{/* Header - Agricultural Theme */}
<div className="relative p-6 bg-gradient-to-r from-green-600/20 via-emerald-600/20 to-green-600/20 border-b-2 border-green-500/30">
{/* Animated background elements */}
<div className="absolute inset-0 overflow-hidden opacity-10">
<div className="absolute -top-10 -left-10 w-40 h-40 bg-green-400 rounded-full blur-3xl animate-pulse" />
<div className="absolute -bottom-10 -right-10 w-40 h-40 bg-emerald-400 rounded-full blur-3xl animate-pulse" style={{ animationDelay: '1s' }} />
</div>
<div className="relative z-10 flex items-center justify-between">
<div className="flex items-center gap-4">
{/* Animated Logo */}
<div className="relative">
<div className="absolute inset-0 bg-gradient-to-br from-green-400 to-emerald-600 rounded-2xl blur-xl opacity-50 animate-pulse" />
<div className="relative w-16 h-16 bg-gradient-to-br from-green-500 to-emerald-600 rounded-2xl flex items-center justify-center shadow-lg">
<Sprout className="w-8 h-8 text-white animate-bounce" style={{ animationDuration: '3s' }} />
</div>
</div>
{/* Title */}
<div>
<h3 className="text-2xl font-black text-white flex items-center gap-2" style={{ fontFamily: 'Outfit' }}>
<span className="bg-gradient-to-r from-green-200 to-emerald-200 bg-clip-text text-transparent">
Smart Farming Assistant
</span>
<span className="text-2xl">๐พ</span>
</h3>
<div className="flex items-center gap-2 mt-1">
<div className="w-2 h-2 bg-green-400 rounded-full animate-ping" />
<div className="w-2 h-2 bg-green-400 rounded-full absolute" />
<p className="text-sm text-green-300 font-medium ml-2">Online • AI-Powered</p>
</div>
</div>
</div>
{/* Weather Icons (Decorative) */}
<div className="hidden md:flex items-center gap-3 text-green-300">
<Sun className="w-6 h-6 animate-pulse" style={{ animationDuration: '3s' }} />
<Cloud className="w-6 h-6 opacity-70" />
<Droplets className="w-6 h-6 animate-bounce" style={{ animationDuration: '2s' }} />
<Wind className="w-5 h-5 opacity-70" />
</div>
</div>
</div>
{/* Stats Bar */}
<div className="px-6 py-3 bg-gradient-to-r from-green-900/30 to-emerald-900/30 border-b border-green-500/20">
<div className="flex items-center justify-between text-sm">
<div className="flex items-center gap-6">
<div className="flex items-center gap-2">
<MessageCircle className="w-4 h-4 text-green-400" />
<span className="text-gray-300">Messages: <span className="font-bold text-white">{messages.length}</span></span>
</div>
<div className="flex items-center gap-2">
<Zap className="w-4 h-4 text-yellow-400" />
<span className="text-gray-300">AI Model: <span className="font-bold text-white">GPT-5-Nano</span></span>
</div>
</div>
<div className="flex items-center gap-2">
<Leaf className="w-4 h-4 text-green-400 animate-pulse" />
<span className="text-green-300 font-medium">Helping Indian Farmers</span>
</div>
</div>
</div>
{/* Messages Area */}
<div className="flex-1 overflow-y-auto p-6 space-y-4 bg-gradient-to-b from-transparent to-green-900/5">
{/* Welcome Suggestions */}
{messages.length === 1 && (
<div className="mb-6 space-y-4">
<div className="text-center">
<div className="inline-flex items-center gap-2 px-4 py-2 bg-gradient-to-r from-green-500/20 to-emerald-500/20 rounded-full border border-green-400/30 mb-4">
<Sparkles className="w-4 h-4 text-green-400" />
<span className="text-sm font-semibold text-green-300">Quick Start Questions</span>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
{suggestedQuestions.map((question, idx) => (
<button
key={idx}
onClick={() => setInput(question)}
className="group p-4 text-left glass rounded-2xl border-2 border-green-500/20 hover:border-green-400/50 transition-all text-sm text-gray-300 hover:text-white hover:scale-105 transform duration-300"
>
<div className="flex items-center gap-2">
<div className="w-8 h-8 bg-gradient-to-br from-green-500/30 to-emerald-500/30 rounded-lg flex items-center justify-center group-hover:scale-110 transition-transform">
<MessageCircle className="w-4 h-4 text-green-400" />
</div>
<span className="font-medium">{question}</span>
</div>
</button>
))}
</div>
</div>
)}
{/* Messages */}
{messages.map((message, index) => (
<div
key={message.id}
className={`flex ${message.sender === 'user' ? 'justify-end' : 'justify-start'} animate-fadeIn`}
style={{ animationDelay: `${index * 50}ms` }}
>
<div
className={`
max-w-[85%] md:max-w-[75%] rounded-3xl p-5 shadow-lg
${message.sender === 'user'
? 'bg-gradient-to-br from-green-600 to-emerald-600 text-white ml-auto'
: 'glass border-2 border-green-500/30 text-white'
}
`}
>
{/* AI Header */}
{message.sender === 'ai' && (
<div className="flex items-center gap-2 mb-3 pb-2 border-b border-green-400/30">
<div className="w-8 h-8 bg-gradient-to-br from-green-500 to-emerald-600 rounded-full flex items-center justify-center">
<Sprout className="w-4 h-4 text-white" />
</div>
<span className="text-xs font-bold text-green-400 uppercase tracking-wide">AI Farm Expert</span>
</div>
)}
{/* Message Text */}
<p className="whitespace-pre-wrap leading-relaxed text-[15px]">{message.text}</p>
{/* Audio Player */}
{message.audioUrl && (
<button
onClick={() => playAudio(message.audioUrl!)}
className="mt-4 flex items-center gap-3 px-4 py-2.5 bg-green-500/20 hover:bg-green-500/30 rounded-xl transition-all group border border-green-400/30"
>
<Volume2 className="w-5 h-5 text-green-400 group-hover:scale-125 transition-transform" />
<span className="text-sm font-semibold text-green-300">Play Audio Response</span>
<div className="flex gap-1 ml-auto">
<div className="w-1 h-3 bg-green-400 rounded-full animate-pulse" />
<div className="w-1 h-4 bg-green-400 rounded-full animate-pulse" style={{ animationDelay: '0.1s' }} />
<div className="w-1 h-5 bg-green-400 rounded-full animate-pulse" style={{ animationDelay: '0.2s' }} />
<div className="w-1 h-4 bg-green-400 rounded-full animate-pulse" style={{ animationDelay: '0.3s' }} />
</div>
</button>
)}
{/* Timestamp */}
<p className={`text-xs mt-3 ${message.sender === 'user' ? 'text-green-100' : 'text-gray-400'}`}>
{message.timestamp.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' })}
</p>
</div>
</div>
))}
{/* Loading Animation */}
{loading && (
<div className="flex justify-start animate-fadeIn">
<div className="glass border-2 border-green-500/30 rounded-3xl p-5 flex items-center gap-3">
<Loader2 className="w-6 h-6 text-green-400 animate-spin" />
<div className="space-y-2">
<div className="flex gap-1">
<div className="w-2 h-2 bg-green-400 rounded-full animate-bounce" />
<div className="w-2 h-2 bg-green-400 rounded-full animate-bounce" style={{ animationDelay: '0.1s' }} />
<div className="w-2 h-2 bg-green-400 rounded-full animate-bounce" style={{ animationDelay: '0.2s' }} />
</div>
<span className="text-gray-300 text-sm font-medium">AI is thinking...</span>
</div>
</div>
</div>
)}
<div ref={messagesEndRef} />
</div>
{/* Input Area - Agricultural Theme */}
<div className="p-4 bg-gradient-to-r from-green-900/40 via-emerald-900/40 to-green-900/40 border-t-2 border-green-500/30">
{/* Recording Indicator */}
{recording && (
<div className="mb-3 px-4 py-2 bg-red-500/20 border border-red-400/50 rounded-xl flex items-center justify-between animate-pulse">
<div className="flex items-center gap-2">
<div className="w-3 h-3 bg-red-500 rounded-full animate-ping" />
<div className="w-3 h-3 bg-red-500 rounded-full absolute" />
<span className="text-red-300 font-semibold text-sm ml-3">Recording voice message...</span>
</div>
<button
onClick={stopRecording}
className="px-3 py-1 bg-red-500 hover:bg-red-600 rounded-lg text-white text-sm font-bold transition-colors"
>
Stop
</button>
</div>
)}
<div className="flex gap-3">
{/* Voice Button */}
<button
onClick={recording ? stopRecording : startRecording}
disabled={loading}
className={`p-4 rounded-2xl transition-all duration-300 ${
recording
? 'bg-red-500 hover:bg-red-600 animate-pulse'
: 'glass border-2 border-green-500/30 hover:border-green-400/50 hover:scale-110'
} disabled:opacity-50 disabled:cursor-not-allowed`}
title={recording ? 'Stop Recording' : 'Voice Message'}
>
{recording ? (
<MicOff className="w-6 h-6 text-white" />
) : (
<Mic className="w-6 h-6 text-green-400" />
)}
</button>
{/* Text Input */}
<div className="flex-1 relative">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={handleKeyPress}
placeholder="Ask about crops, seasons, pests, irrigation..."
className="w-full px-6 py-4 glass rounded-2xl border-2 border-green-500/30 text-white placeholder-gray-400 outline-none focus:border-green-400/60 transition-all text-[15px] pr-24"
disabled={loading || recording}
/>
{/* Character count */}
<div className="absolute right-4 top-1/2 -translate-y-1/2 text-xs text-gray-500">
{input.length}/500
</div>
</div>
{/* Send Button */}
<button
onClick={sendMessage}
disabled={loading || !input.trim() || recording}
className="px-8 py-4 bg-gradient-to-br from-green-500 to-emerald-600 hover:from-green-600 hover:to-emerald-700 rounded-2xl text-white font-bold hover:scale-110 transition-all duration-300 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100 flex items-center gap-2 shadow-lg"
>
<Send className="w-6 h-6" />
<span className="hidden md:inline">Send</span>
</button>
</div>
{/* Footer Info */}
<div className="mt-3 flex items-center justify-between text-xs text-gray-500">
<div className="flex items-center gap-4">
<span>๐พ Press Enter to send</span>
<span>•</span>
<span>๐ค Click mic for voice</span>
</div>
<span className="text-green-400 font-medium">Powered by OpenAI</span>
</div>
</div>
</div>
{/* Hidden Audio Element */}
<audio ref={audioRef} className="hidden" />
</div>
);
}