1
1
'use client' ;
2
2
import React , { useState , useEffect , useRef } from 'react' ;
3
3
import { motion } from 'framer-motion' ;
4
- import { FaPaperPlane , FaCamera , FaUpload , FaMapMarkerAlt , FaSatellite , FaRobot } from 'react-icons/fa' ;
4
+ import { FaPaperPlane , FaMapMarkerAlt , FaSatellite , FaRobot , FaWater } from 'react-icons/fa' ;
5
5
import ReactMarkdown from 'react-markdown' ;
6
6
import { Input } from '@/components/ui/input' ;
7
7
import { Button } from '@/components/ui/button' ;
8
- import Image from 'next/image' ;
9
8
import { Inter } from 'next/font/google' ;
10
9
import { format , subDays } from 'date-fns' ;
11
10
import Header from '../components/Header' ;
12
11
13
12
const inter = Inter ( { subsets : [ 'latin' ] } ) ;
14
13
15
- const AgriChatbot = ( ) => {
14
+ const ChatBotPage = ( ) => {
16
15
const [ messages , setMessages ] = useState ( [ ] ) ;
17
16
const [ input , setInput ] = useState ( '' ) ;
18
17
const [ isLoading , setIsLoading ] = useState ( false ) ;
19
- const [ mode , setMode ] = useState ( 'text' ) ;
20
- const [ image , setImage ] = useState ( null ) ;
21
18
const [ latitude , setLatitude ] = useState ( '' ) ;
22
19
const [ longitude , setLongitude ] = useState ( '' ) ;
23
20
const chatEndRef = useRef ( null ) ;
24
- const fileInputRef = useRef ( null ) ;
25
21
const [ nasaData , setNasaData ] = useState ( null ) ;
26
22
const [ showNasaData , setShowNasaData ] = useState ( false ) ;
23
+ const [ waterStressLevel , setWaterStressLevel ] = useState ( null ) ;
24
+ const [ location , setLocation ] = useState ( '' ) ;
25
+
26
+ // Predefined locations
27
+ const predefinedLocations = [
28
+ { name : "Gojra (Punjab)" , lat : "31.1497" , lon : "72.6871" } ,
29
+ { name : "Sehwan (Sindh)" , lat : "26.4249" , lon : "67.8613" } ,
30
+ { name : "Charsadda (KPK)" , lat : "34.1484" , lon : "71.7415" } ,
31
+ { name : "Khuzdar (Balochistan)" , lat : "27.8120" , lon : "66.6101" } ,
32
+ { name : "Skardu (Gilgit-Baltistan)" , lat : "35.2895" , lon : "75.6350" }
33
+ ] ;
27
34
28
35
useEffect ( ( ) => {
29
36
chatEndRef . current ?. scrollIntoView ( { behavior : 'smooth' } ) ;
30
37
} , [ messages ] ) ;
31
38
39
+ useEffect ( ( ) => {
40
+ handleGetCurrentLocation ( ) ;
41
+ } , [ ] ) ;
42
+
43
+ useEffect ( ( ) => {
44
+ if ( nasaData ) {
45
+ calculateWaterStress ( ) ;
46
+ }
47
+ } , [ nasaData ] ) ;
48
+
32
49
const fetchNasaData = async ( ) => {
33
50
if ( ! latitude || ! longitude ) return null ;
34
51
const endDate = format ( new Date ( ) , 'yyyyMMdd' ) ;
35
- // 12 days bhi chal jae ga but 10 for safe side if user prompt is big
36
52
const startDate = format ( subDays ( new Date ( ) , 10 ) , 'yyyyMMdd' ) ;
37
53
38
54
try {
39
55
const res = await fetch (
40
56
`https://power.larc.nasa.gov/api/temporal/hourly/point?start=${ startDate } &end=${ endDate } &latitude=${ latitude } &longitude=${ longitude } &community=re¶meters=T2M,PRECTOTCORR,WS2M,RH2M,ALLSKY_SFC_SW_DWN&format=json&user=demo&header=true`
41
57
) ;
42
58
const result = await res . json ( ) ;
59
+ setNasaData ( result ) ;
43
60
return result ;
44
61
} catch ( err ) {
45
62
console . error ( "Error fetching NASA data:" , err ) ;
46
63
return null ;
47
64
}
48
65
} ;
49
66
67
+ const calculateWaterStress = ( ) => {
68
+ if ( nasaData && nasaData . properties && nasaData . properties . parameter ) {
69
+ const { T2M , PRECTOTCORR } = nasaData . properties . parameter ;
70
+ const avgTemperature = Object . values ( T2M ) . reduce ( ( sum , val ) => sum + val , 0 ) / Object . values ( T2M ) . length ;
71
+ const avgPrecipitation = Object . values ( PRECTOTCORR ) . reduce ( ( sum , val ) => sum + val , 0 ) / Object . values ( PRECTOTCORR ) . length ;
72
+
73
+ const stressLevel = ( ( avgTemperature - 273.15 ) / 10 ) - ( avgPrecipitation * 2 ) ;
74
+ setWaterStressLevel ( Math . max ( 0 , Math . min ( 10 , stressLevel ) ) ) ;
75
+ }
76
+ } ;
77
+
78
+ const getWaterStressColor = ( level ) => {
79
+ if ( level <= 3 ) return 'bg-green-500' ;
80
+ if ( level <= 6 ) return 'bg-yellow-500' ;
81
+ return 'bg-red-500' ;
82
+ } ;
83
+
50
84
const handleSubmit = async ( e ) => {
51
85
e . preventDefault ( ) ;
52
- if ( ( ! input . trim ( ) && mode === 'text' ) || ( ! image && ( mode === 'camera' || mode === 'upload' ) ) ) return ;
86
+ if ( ! input . trim ( ) ) return ;
53
87
54
88
setIsLoading ( true ) ;
55
89
56
- const fetchedNasaData = await fetchNasaData ( ) ;
57
-
58
- let userMessage = {
90
+ const userMessage = {
59
91
role : 'user' ,
60
- content : mode === 'text' ? input : { image : image }
92
+ content : input
61
93
} ;
62
94
63
95
setMessages ( prev => [ ...prev , userMessage ] ) ;
64
96
setInput ( '' ) ;
65
97
98
+ const fetchedNasaData = await fetchNasaData ( ) ;
99
+
100
+ if ( waterStressLevel === null && fetchedNasaData ) {
101
+ calculateWaterStress ( ) ;
102
+ }
103
+
66
104
try {
105
+ const waterStressInfo = waterStressLevel !== null ? `
106
+ Current Water Stress Level: ${ waterStressLevel . toFixed ( 2 ) } (${ waterStressLevel <= 3 ? 'Low' : waterStressLevel <= 6 ? 'Moderate' : 'High' } )
107
+ ` : '' ;
108
+
67
109
const response = await fetch ( '/api/chat' , {
68
110
method : 'POST' ,
69
111
headers : { 'Content-Type' : 'application/json' } ,
70
112
body : JSON . stringify ( {
71
- messages : [ ...messages , {
72
- role : 'user' ,
73
- content : `
74
- User query: ${ userMessage . content }
75
-
76
- NASA POWER Data for the location:
77
- ${ JSON . stringify ( fetchedNasaData ) }
78
-
79
- Please analyze the NASA POWER data provided above and use it to inform your response to the user's query. Consider how the weather conditions might affect agricultural practices or crop growth in the area.
80
- `
81
- } ] ,
82
- mode,
113
+ messages : [
114
+ {
115
+ role : 'system' ,
116
+ content : `You are an AI assistant specializing in agriculture. The user is located at: ${ location } (Coordinates: ${ latitude } °N, ${ longitude } °E). ${ waterStressInfo }
117
+ NASA POWER Data for the location: ${ JSON . stringify ( fetchedNasaData ) }
118
+ Please consider this information when answering the user's query.`
119
+ } ,
120
+ ...messages ,
121
+ userMessage
122
+ ] ,
83
123
} ) ,
84
124
} ) ;
85
125
@@ -92,41 +132,6 @@ Please analyze the NASA POWER data provided above and use it to inform your resp
92
132
setMessages ( prev => [ ...prev , { role : 'assistant' , content : "I'm sorry, I encountered an error. Please try again later." } ] ) ;
93
133
} finally {
94
134
setIsLoading ( false ) ;
95
- setImage ( null ) ;
96
- setMode ( 'text' ) ;
97
- }
98
- } ;
99
-
100
- const handleCameraCapture = async ( ) => {
101
- try {
102
- const stream = await navigator . mediaDevices . getUserMedia ( { video : true } ) ;
103
- const videoElement = document . createElement ( 'video' ) ;
104
- videoElement . srcObject = stream ;
105
- await videoElement . play ( ) ;
106
-
107
- const canvas = document . createElement ( 'canvas' ) ;
108
- canvas . width = videoElement . videoWidth ;
109
- canvas . height = videoElement . videoHeight ;
110
- canvas . getContext ( '2d' ) . drawImage ( videoElement , 0 , 0 ) ;
111
-
112
- setImage ( canvas . toDataURL ( 'image/jpeg' ) ) ;
113
- setMode ( 'camera' ) ;
114
-
115
- stream . getTracks ( ) . forEach ( track => track . stop ( ) ) ;
116
- } catch ( error ) {
117
- console . error ( 'Error accessing camera:' , error ) ;
118
- }
119
- } ;
120
-
121
- const handleImageUpload = ( e ) => {
122
- const file = e . target . files [ 0 ] ;
123
- if ( file ) {
124
- const reader = new FileReader ( ) ;
125
- reader . onloadend = ( ) => {
126
- setImage ( reader . result ) ;
127
- setMode ( 'upload' ) ;
128
- } ;
129
- reader . readAsDataURL ( file ) ;
130
135
}
131
136
} ;
132
137
@@ -136,6 +141,8 @@ Please analyze the NASA POWER data provided above and use it to inform your resp
136
141
( position ) => {
137
142
setLatitude ( position . coords . latitude . toFixed ( 6 ) ) ;
138
143
setLongitude ( position . coords . longitude . toFixed ( 6 ) ) ;
144
+ setLocation ( 'Current Location' ) ;
145
+ fetchNasaData ( ) ;
139
146
} ,
140
147
( error ) => console . error ( "Error getting location:" , error )
141
148
) ;
@@ -144,6 +151,13 @@ Please analyze the NASA POWER data provided above and use it to inform your resp
144
151
}
145
152
} ;
146
153
154
+ const handleLocationSelect = ( loc ) => {
155
+ setLatitude ( loc . lat ) ;
156
+ setLongitude ( loc . lon ) ;
157
+ setLocation ( loc . name ) ;
158
+ fetchNasaData ( ) ;
159
+ } ;
160
+
147
161
const renderMessageContent = ( content ) => {
148
162
if ( typeof content === 'string' ) {
149
163
return (
@@ -162,8 +176,6 @@ Please analyze the NASA POWER data provided above and use it to inform your resp
162
176
{ content }
163
177
</ ReactMarkdown >
164
178
) ;
165
- } else if ( content && typeof content === 'object' && content . image ) {
166
- return < img src = { content . image } alt = "User uploaded" className = "max-w-full h-auto rounded-lg" /> ;
167
179
}
168
180
return < p > Unsupported message content</ p > ;
169
181
} ;
@@ -205,6 +217,43 @@ Please analyze the NASA POWER data provided above and use it to inform your resp
205
217
) ;
206
218
} ;
207
219
220
+ const renderWaterStressIndicator = ( ) => {
221
+ if ( waterStressLevel === null ) return null ;
222
+
223
+ let stressDescription , precautions ;
224
+ if ( waterStressLevel <= 3 ) {
225
+ stressDescription = "Low water stress indicates favorable conditions for most crops." ;
226
+ precautions = "Monitor soil moisture and maintain regular irrigation schedules." ;
227
+ } else if ( waterStressLevel <= 6 ) {
228
+ stressDescription = "Moderate water stress may impact sensitive crops and reduce yields." ;
229
+ precautions = "Increase irrigation frequency, consider mulching, and prioritize water-efficient practices." ;
230
+ } else {
231
+ stressDescription = "High water stress can severely affect crop health and yield." ;
232
+ precautions = "Implement drought-resistant strategies, consider crop selection changes, and optimize water usage." ;
233
+ }
234
+
235
+ return (
236
+ < div className = "mb-6 p-4 bg-white rounded-2xl shadow-xl" >
237
+ < h3 className = "text-lg font-semibold text-gray-800 mb-2 flex items-center" >
238
+ < FaWater className = "mr-2 text-blue-500" /> Water Stress Level
239
+ </ h3 >
240
+ < div className = "w-full bg-gray-200 rounded-full h-2.5 mb-2" >
241
+ < div
242
+ className = { `h-2.5 rounded-full ${ getWaterStressColor ( waterStressLevel ) } ` }
243
+ style = { { width : `${ waterStressLevel * 10 } %` } }
244
+ > </ div >
245
+ </ div >
246
+ < p className = "text-gray-600 font-semibold mb-2" >
247
+ { waterStressLevel <= 3 ? 'Low' : waterStressLevel <= 6 ? 'Moderate' : 'High' } water stress
248
+ </ p >
249
+ < div className = "text-sm text-gray-600" >
250
+ < p className = "mb-2" > < strong > Significance:</ strong > { stressDescription } </ p >
251
+ < p > < strong > Precautions:</ strong > { precautions } </ p >
252
+ </ div >
253
+ </ div >
254
+ ) ;
255
+ } ;
256
+
208
257
return (
209
258
< div className = { `${ inter . className } bg-gradient-to-br from-green-50 to-blue-50 min-h-screen flex flex-col` } >
210
259
< Header />
@@ -216,6 +265,8 @@ Please analyze the NASA POWER data provided above and use it to inform your resp
216
265
< p className = "text-gray-600" > Your AI-powered agricultural companion. Ask questions, get insights, and make data-driven decisions for your farm.</ p >
217
266
</ div >
218
267
268
+ { renderWaterStressIndicator ( ) }
269
+
219
270
< div className = "flex-grow overflow-y-auto mb-6 space-y-6 scrollbar-thin scrollbar-thumb-green-400 scrollbar-track-gray-200 pr-2 sm:pr-4 bg-white rounded-2xl shadow-xl p-6" >
220
271
{ messages . map ( ( message , index ) => (
221
272
< motion . div
@@ -235,6 +286,13 @@ Please analyze the NASA POWER data provided above and use it to inform your resp
235
286
236
287
< div className = "bg-white rounded-2xl p-6 space-y-4 sm:space-y-6 shadow-xl" >
237
288
< div className = "flex flex-col sm:flex-row items-center space-y-4 sm:space-y-0 sm:space-x-4" >
289
+ < Input
290
+ type = "text"
291
+ value = { location }
292
+ onChange = { ( e ) => setLocation ( e . target . value ) }
293
+ placeholder = "Enter location"
294
+ className = "w-full sm:flex-grow bg-gray-100 text-gray-900 placeholder-gray-500 border-gray-300"
295
+ />
238
296
< Input
239
297
type = "text"
240
298
value = { latitude }
@@ -251,41 +309,22 @@ Please analyze the NASA POWER data provided above and use it to inform your resp
251
309
/>
252
310
< Button onClick = { handleGetCurrentLocation } variant = "secondary" className = "w-full sm:w-auto bg-green-600 hover:bg-green-700 text-white" >
253
311
< FaMapMarkerAlt className = "mr-2" />
254
- Get Location
312
+ Get Current Location
255
313
</ Button >
256
314
</ div >
257
315
258
- < div className = "flex flex-wrap justify-center gap-2 mt-4" >
259
- { [
260
- { name : "Gojra (Punjab)" , lat : "31.582045" , lon : "74.329376" } ,
261
- { name : "Sehwan (Sindh)" , lat : "24.860966" , lon : "67.001137" } ,
262
- { name : "Charsadda (KPK)" , lat : "34.008053" , lon : "71.578640" } ,
263
- { name : "Khuzdar (Balochistan)" , lat : "28.394857" , lon : "66.261768" } ,
264
- { name : "Skardu (Gilgit-Baltistan)" , lat : "35.350659" , lon : "74.857989" }
265
- ] . map ( ( location ) => (
266
- < Button
267
- key = { location . name }
268
- onClick = { ( ) => { setLatitude ( location . lat ) ; setLongitude ( location . lon ) ; } }
316
+ < div className = "flex flex-wrap justify-center gap-2" >
317
+ { predefinedLocations . map ( ( loc ) => (
318
+ < Button
319
+ key = { loc . name }
320
+ onClick = { ( ) => handleLocationSelect ( loc ) }
269
321
className = "bg-blue-100 hover:bg-blue-200 text-blue-800 text-xs sm:text-sm py-1 px-2 rounded-full"
270
322
>
271
- { location . name }
323
+ { loc . name }
272
324
</ Button >
273
325
) ) }
274
326
</ div >
275
327
276
- { image && (
277
- < div className = "mt-4 sm:mt-6" >
278
- < Image
279
- src = { image }
280
- alt = "Captured or uploaded image"
281
- width = { 500 }
282
- height = { 300 }
283
- layout = "responsive"
284
- className = "rounded-xl shadow-lg"
285
- />
286
- </ div >
287
- ) }
288
-
289
328
< form onSubmit = { handleSubmit } className = "flex flex-col sm:flex-row items-center space-y-4 sm:space-y-0 sm:space-x-4" >
290
329
< Input
291
330
type = "text"
@@ -315,4 +354,4 @@ Please analyze the NASA POWER data provided above and use it to inform your resp
315
354
) ;
316
355
} ;
317
356
318
- export default AgriChatbot ;
357
+ export default ChatBotPage ;
0 commit comments