Skip to content

Commit 46cf185

Browse files
Add weather agent configuration file
Signed-off-by: Gilbert Algordo <69397216+gilbertalgordo@users.noreply.github.com>
1 parent 580e22c commit 46cf185

1 file changed

Lines changed: 309 additions & 0 deletions

File tree

‎weather_agent.yaml‎

Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
// src/App.tsx
2+
/**
3+
* @license
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import { useState, useEffect, useCallback } from 'react';
8+
import { motion, AnimatePresence } from 'motion/react';
9+
import { Search, MapPin, Wind, Droplets, Thermometer, Info, Sparkles, Navigation, Clock } from 'lucide-react';
10+
import { format } from 'date-fns';
11+
import { WeatherIcon } from './components/WeatherIcons';
12+
import { searchLocation, getWeatherData, LocationData, WeatherData } from './services/weatherService';
13+
import { getWeatherInsights, WeatherInsight } from './services/aiService';
14+
15+
export default function App() {
16+
const [query, setQuery] = useState('');
17+
const [results, setResults] = useState<LocationData[]>([]);
18+
const [selectedLocation, setSelectedLocation] = useState<LocationData | null>(null);
19+
const [weather, setWeather] = useState<WeatherData | null>(null);
20+
const [insights, setInsights] = useState<WeatherInsight | null>(null);
21+
const [loading, setLoading] = useState(false);
22+
const [searching, setSearching] = useState(false);
23+
const [currentTime, setCurrentTime] = useState(new Date());
24+
25+
useEffect(() => {
26+
const timer = setInterval(() => setCurrentTime(new Date()), 60000);
27+
return () => clearInterval(timer);
28+
}, []);
29+
30+
// Initialize with user location if possible
31+
useEffect(() => {
32+
if (navigator.geolocation) {
33+
navigator.geolocation.getCurrentPosition(async (position) => {
34+
const { latitude, longitude } = position.coords;
35+
try {
36+
setLoading(true);
37+
const data = await getWeatherData(latitude, longitude);
38+
setWeather(data);
39+
setSelectedLocation({ id: 0, name: "Current Location", latitude, longitude, country: "" });
40+
const aiInsights = await getWeatherInsights("your current location", data);
41+
setInsights(aiInsights);
42+
} catch (error) {
43+
console.error("Location init error:", error);
44+
} finally {
45+
setLoading(false);
46+
}
47+
});
48+
}
49+
}, []);
50+
51+
const handleSearch = useCallback(async (val: string) => {
52+
setQuery(val);
53+
if (val.length > 2) {
54+
setSearching(true);
55+
const res = await searchLocation(val);
56+
setResults(res);
57+
setSearching(false);
58+
} else {
59+
setResults([]);
60+
}
61+
}, []);
62+
63+
const selectLocation = async (loc: LocationData) => {
64+
setQuery('');
65+
setResults([]);
66+
setSelectedLocation(loc);
67+
setLoading(true);
68+
try {
69+
const data = await getWeatherData(loc.latitude, loc.longitude);
70+
setWeather(data);
71+
const aiInsights = await getWeatherInsights(loc.name, data);
72+
setInsights(aiInsights);
73+
} catch (err) {
74+
console.error(err);
75+
} finally {
76+
setLoading(false);
77+
}
78+
};
79+
80+
return (
81+
<div className="h-screen bg-[#0A0C10] text-white font-sans flex flex-col overflow-hidden selection:bg-blue-500/30">
82+
{/* Header */}
83+
<header className="flex items-center justify-between px-8 py-6 border-b border-gray-800 shrink-0">
84+
<div className="flex items-center gap-3">
85+
<div className="w-8 h-8 bg-gradient-to-tr from-blue-600 to-cyan-400 rounded-full blur-[1px]"></div>
86+
<span className="text-xl font-semibold tracking-tight text-white">SkyCast <span className="text-blue-500">AI</span></span>
87+
</div>
88+
89+
<div className="flex items-center gap-8">
90+
<div className="hidden md:flex flex-col items-end">
91+
<span className="text-[10px] uppercase tracking-widest text-gray-500">Local Environment</span>
92+
<span className="text-sm font-medium">{format(currentTime, 'h:mm a')} • {selectedLocation?.name || 'Awaiting Input'}</span>
93+
</div>
94+
95+
<div className="h-10 w-[1px] bg-gray-800 hidden md:block"></div>
96+
97+
<div className="relative w-48 md:w-64">
98+
<div className="relative flex items-center">
99+
<Search className="absolute left-3 w-4 h-4 text-gray-500" />
100+
<input
101+
type="text"
102+
value={query}
103+
onChange={(e) => handleSearch(e.target.value)}
104+
placeholder="Change Location..."
105+
className="w-full bg-[#14181F] border border-gray-700/50 rounded-lg py-2 pl-10 pr-3 text-sm focus:outline-none focus:ring-1 focus:ring-blue-500/50 transition-all placeholder:text-gray-600"
106+
/>
107+
{searching && (
108+
<div className="absolute right-3 animate-spin h-3 w-3 border-2 border-blue-500 border-t-transparent rounded-full" />
109+
)}
110+
</div>
111+
112+
<AnimatePresence>
113+
{results.length > 0 && (
114+
<motion.ul
115+
initial={{ opacity: 0, y: 5 }}
116+
animate={{ opacity: 1, y: 0 }}
117+
exit={{ opacity: 0, y: 5 }}
118+
className="absolute z-20 w-full mt-2 bg-[#0D1016] border border-gray-800 rounded-xl overflow-hidden shadow-2xl"
119+
>
120+
{results.map((loc) => (
121+
<li
122+
key={loc.id}
123+
onClick={() => selectLocation(loc)}
124+
className="px-4 py-3 hover:bg-[#14181F] cursor-pointer transition-colors border-b border-gray-800 last:border-0"
125+
>
126+
<div className="flex flex-col">
127+
<span className="text-sm font-medium">{loc.name}</span>
128+
<span className="text-[10px] text-gray-500 uppercase tracking-wider">{loc.country} {loc.admin1 && `• ${loc.admin1}`}</span>
129+
</div>
130+
</li>
131+
))}
132+
</motion.ul>
133+
)}
134+
</AnimatePresence>
135+
</div>
136+
</div>
137+
</header>
138+
139+
{/* Main Content Area */}
140+
<main className="flex-1 grid grid-cols-12 gap-0 overflow-hidden relative">
141+
{loading ? (
142+
<div className="col-span-12 flex flex-col items-center justify-center space-y-4">
143+
<div className="w-10 h-10 border-2 border-blue-500 border-t-transparent rounded-full animate-spin" />
144+
<p className="text-[10px] uppercase font-bold tracking-[0.3em] text-gray-500">Synthesizing Atmospheric Models</p>
145+
</div>
146+
) : weather && selectedLocation ? (
147+
<>
148+
{/* Left Panel: Analysis */}
149+
<div className="col-span-12 lg:col-span-8 p-6 md:p-10 flex flex-col justify-between overflow-y-auto lg:overflow-hidden bg-[#0A0C10]">
150+
<motion.div
151+
initial={{ opacity: 0, y: 20 }}
152+
animate={{ opacity: 1, y: 0 }}
153+
>
154+
<div className="inline-block px-3 py-1 bg-blue-500/10 border border-blue-500/20 rounded-full text-blue-400 text-[10px] font-bold uppercase tracking-widest mb-6">
155+
Agent Perspective • Real-time Synthesis
156+
</div>
157+
158+
<h1 className="text-[60px] md:text-[80px] leading-[0.9] font-light text-white tracking-tighter mb-8">
159+
Atmospheric <br/> <span className="font-medium text-blue-500 italic uppercase">Stability.</span>
160+
</h1>
161+
162+
{insights && (
163+
<motion.div
164+
initial={{ opacity: 0 }}
165+
animate={{ opacity: 1 }}
166+
transition={{ delay: 0.3 }}
167+
className="max-w-xl text-lg md:text-xl text-gray-400 leading-relaxed font-light"
168+
>
169+
"{insights.summary}"
170+
</motion.div>
171+
)}
172+
</motion.div>
173+
174+
{/* Key Stats Strip */}
175+
<motion.div
176+
initial={{ opacity: 0, y: 20 }}
177+
animate={{ opacity: 1, y: 0 }}
178+
transition={{ delay: 0.4 }}
179+
className="grid grid-cols-2 md:grid-cols-4 gap-4 mt-8 md:mt-0"
180+
>
181+
<div className="bg-[#14181F] p-5 rounded-2xl border border-gray-800/50 hover:border-blue-500/30 transition-colors group">
182+
<div className="flex items-center gap-2 mb-2">
183+
<Thermometer className="w-3 h-3 text-gray-500 group-hover:text-blue-400 transition-colors" />
184+
<p className="text-gray-500 text-[10px] uppercase tracking-widest">Temperature</p>
185+
</div>
186+
<p className="text-3xl font-semibold text-white">{weather.current.temp}°C</p>
187+
<p className="text-blue-400 text-[10px] mt-1 font-medium">{weather.current.description}</p>
188+
</div>
189+
190+
<div className="bg-[#14181F] p-5 rounded-2xl border border-gray-800/50 hover:border-blue-500/30 transition-colors group">
191+
<div className="flex items-center gap-2 mb-2">
192+
<Wind className="w-3 h-3 text-gray-500 group-hover:text-blue-400 transition-colors" />
193+
<p className="text-gray-500 text-[10px] uppercase tracking-widest">Wind Flow</p>
194+
</div>
195+
<p className="text-3xl font-semibold text-white">{weather.current.windSpeed} <span className="text-xs font-normal text-gray-500 uppercase">km/h</span></p>
196+
<p className="text-gray-400 text-[10px] mt-1">Status: Stable</p>
197+
</div>
198+
199+
<div className="bg-[#14181F] p-5 rounded-2xl border border-gray-800/50 hover:border-blue-500/30 transition-colors group">
200+
<div className="flex items-center gap-2 mb-2">
201+
<Droplets className="w-3 h-3 text-gray-500 group-hover:text-blue-400 transition-colors" />
202+
<p className="text-gray-500 text-[10px] uppercase tracking-widest">Saturation</p>
203+
</div>
204+
<p className="text-3xl font-semibold text-white">{weather.current.humidity}%</p>
205+
<p className="text-gray-400 text-[10px] mt-1">Relative Humidity</p>
206+
</div>
207+
208+
<div className="bg-[#14181F] p-5 rounded-2xl border border-gray-800/50 hover:border-blue-500/30 transition-colors group">
209+
<div className="flex items-center gap-2 mb-2">
210+
<Clock className="w-3 h-3 text-gray-500 group-hover:text-blue-400 transition-colors" />
211+
<p className="text-gray-500 text-[10px] uppercase tracking-widest">Forecast</p>
212+
</div>
213+
<p className="text-3xl font-semibold text-white">4%</p>
214+
<p className="text-gray-400 text-[10px] mt-1">Precipitation</p>
215+
</div>
216+
</motion.div>
217+
</div>
218+
219+
{/* Right Panel: Detailed Forecast & Recommendations */}
220+
<div className="col-span-12 lg:col-span-4 bg-[#0D1016] border-l border-gray-800 p-8 flex flex-col overflow-y-auto">
221+
<h2 className="text-[10px] font-bold uppercase tracking-[0.2em] text-gray-500 mb-8 flex items-center gap-2">
222+
<Navigation className="w-3 h-3" /> 72-Hour Outlook
223+
</h2>
224+
225+
<div className="space-y-3 mb-10">
226+
{weather.daily.map((day, idx) => (
227+
<motion.div
228+
initial={{ opacity: 0, x: 20 }}
229+
animate={{ opacity: 1, x: 0 }}
230+
transition={{ delay: 0.5 + (idx * 0.1) }}
231+
key={day.date}
232+
className={`flex items-center justify-between p-4 rounded-xl border transition-all group ${idx === 0 ? 'bg-[#14181F] border-blue-500/20' : 'bg-transparent border-transparent hover:bg-[#14181F]'}`}
233+
>
234+
<span className={`w-14 text-sm font-medium ${idx === 0 ? 'text-white' : 'text-gray-400'}`}>
235+
{idx === 0 ? 'Today' : format(new Date(day.date), 'EEE')}
236+
</span>
237+
<div className="flex-1 flex justify-center">
238+
<WeatherIcon name={day.icon} className={`w-5 h-5 ${idx === 0 ? 'text-blue-400 drop-shadow-[0_0_8px_rgba(59,130,246,0.3)]' : 'text-gray-500'}`} />
239+
</div>
240+
<span className={`text-sm font-bold ${idx === 0 ? 'text-white' : 'text-gray-400'}`}>
241+
{day.maxTemp}° / {day.minTemp}°
242+
</span>
243+
</motion.div>
244+
))}
245+
</div>
246+
247+
{insights && (
248+
<div className="mt-auto space-y-4">
249+
<div className="p-5 bg-[#14181F] rounded-2xl border border-gray-800/50">
250+
<p className="text-[10px] font-bold uppercase tracking-widest text-gray-500 mb-3">Agent Directives</p>
251+
<div className="space-y-3">
252+
<div>
253+
<p className="text-[9px] text-gray-600 uppercase mb-1">Recommended Activity</p>
254+
<p className="text-sm text-gray-300 font-light">{insights.activities.recommended[0]}</p>
255+
</div>
256+
<div>
257+
<p className="text-[9px] text-gray-600 uppercase mb-1">Recommended Attire</p>
258+
<p className="text-sm text-gray-300 font-light">{insights.clothing.slice(0, 2).join(', ')}</p>
259+
</div>
260+
</div>
261+
</div>
262+
263+
<div className="p-6 bg-gradient-to-br from-blue-600/10 to-purple-600/10 rounded-2xl border border-white/5 relative overflow-hidden group">
264+
<div className="absolute top-0 right-0 p-2 opacity-10">
265+
<Sparkles className="w-8 h-8 text-blue-400" />
266+
</div>
267+
<p className="text-[10px] font-bold uppercase tracking-wider text-blue-400 mb-2">Smart Recommendation</p>
268+
<p className="text-xs leading-relaxed text-gray-400 font-light italic">
269+
{insights.funFact}
270+
</p>
271+
</div>
272+
</div>
273+
)}
274+
</div>
275+
</>
276+
) : (
277+
<div className="col-span-12 flex flex-col items-center justify-center p-12 text-center">
278+
<motion.div
279+
initial={{ scale: 0.9, opacity: 0 }}
280+
animate={{ scale: 1, opacity: 1 }}
281+
className="max-w-md"
282+
>
283+
<div className="w-16 h-16 bg-blue-500/10 border border-blue-500/20 rounded-full flex items-center justify-center mx-auto mb-8">
284+
<Sparkles className="w-8 h-8 text-blue-500/40" />
285+
</div>
286+
<h2 className="text-3xl font-light tracking-tight text-white mb-4">Initialize SkyCast Proxy</h2>
287+
<p className="text-gray-500 text-sm leading-relaxed mb-8">
288+
Enter a geographical coordinate or allow sensor mesh access to synthesize real-time atmospheric data models for your location.
289+
</p>
290+
</motion.div>
291+
</div>
292+
)}
293+
</main>
294+
295+
{/* Footer Status Bar */}
296+
<footer className="px-8 py-4 bg-[#080A0E] border-t border-gray-900 shrink-0 flex justify-between items-center text-[9px] text-gray-600 uppercase tracking-[0.2em] font-medium">
297+
<div className="hidden md:flex gap-6">
298+
<span>Sensor Mesh: <span className="text-green-500/70">Active</span></span>
299+
<span>Latency: 42ms</span>
300+
<span>Atmospheric Model: GFS-1.0</span>
301+
</div>
302+
<div className="flex items-center gap-2">
303+
<div className="w-1 h-1 bg-green-500 rounded-full animate-pulse"></div>
304+
<span>AI Engine Fully Synchronized</span>
305+
</div>
306+
</footer>
307+
</div>
308+
);
309+
}

0 commit comments

Comments
 (0)