crystalai's picture
Upload 42 files
cf165dd verified
import React, { useState, useCallback } from 'react';
import { groundedSearch } from '../services/geminiService';
import { Spinner } from '../components/Spinner';
import { GroundingChunk } from '../types';
import { MarkdownRenderer } from '../components/MarkdownRenderer';
const GroundedSearchModule: React.FC = () => {
const [prompt, setPrompt] = useState('');
const [tool, setTool] = useState<'googleSearch' | 'googleMaps'>('googleSearch');
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [response, setResponse] = useState<string | null>(null);
const [chunks, setChunks] = useState<GroundingChunk[]>([]);
const [location, setLocation] = useState<GeolocationCoordinates | null>(null);
const [locationError, setLocationError] = useState<string | null>(null);
const handleSearch = useCallback(async () => {
if (!prompt.trim()) {
setError('Please enter a search query.');
return;
}
setIsLoading(true);
setError(null);
setResponse(null);
setChunks([]);
let searchLocation: GeolocationCoordinates | undefined = undefined;
if(tool === 'googleMaps') {
if (!location) {
try {
const pos = await new Promise<GeolocationPosition>((resolve, reject) => {
navigator.geolocation.getCurrentPosition(resolve, reject);
});
setLocation(pos.coords);
searchLocation = pos.coords;
} catch (err) {
setLocationError('Could not get location. Please enable location services. Search will proceed without it.');
}
} else {
searchLocation = location;
}
}
try {
const result = await groundedSearch(prompt, tool, searchLocation);
setResponse(result.text);
if (result.candidates?.[0]?.groundingMetadata?.groundingChunks) {
setChunks(result.candidates[0].groundingMetadata.groundingChunks);
}
} catch (e: any) {
setError(e.message || 'An unknown error occurred.');
} finally {
setIsLoading(false);
}
}, [prompt, tool, location]);
return (
<div className="flex flex-col h-full w-full max-w-4xl mx-auto">
<h2 className="text-2xl font-bold text-cyan-300 mb-4 text-center">Grounded Search</h2>
<p className="text-gray-400 mb-6 text-center">Get up-to-date, verifiable information by grounding Gemini's responses with Google Search or Google Maps.</p>
<div className="flex flex-col sm:flex-row gap-2 mb-4">
<input
type="text"
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
placeholder="e.g., Who won the latest F1 race? or Good Italian restaurants nearby?"
className="flex-grow bg-gray-700 border border-gray-600 rounded-md p-3 focus:outline-none focus:ring-2 focus:ring-cyan-500 text-white"
disabled={isLoading}
/>
<button
onClick={handleSearch}
disabled={isLoading}
className="bg-cyan-600 hover:bg-cyan-500 text-white font-bold py-3 px-4 rounded-md disabled:bg-gray-500 transition-colors"
>
{isLoading ? 'Searching...' : 'Search'}
</button>
</div>
<div className="flex items-center justify-center gap-4 mb-6">
<label className="flex items-center gap-2 cursor-pointer">
<input type="radio" name="tool" value="googleSearch" checked={tool === 'googleSearch'} onChange={() => setTool('googleSearch')} className="form-radio text-cyan-500 bg-gray-800"/>
Google Search
</label>
<label className="flex items-center gap-2 cursor-pointer">
<input type="radio" name="tool" value="googleMaps" checked={tool === 'googleMaps'} onChange={() => setTool('googleMaps')} className="form-radio text-cyan-500 bg-gray-800"/>
Google Maps
</label>
</div>
{locationError && <p className="text-yellow-400 text-center mb-4">{locationError}</p>}
<div className="flex-grow overflow-y-auto p-4 bg-gray-800/50 rounded-lg border border-cyan-500/10 min-h-[40vh]">
{isLoading && <Spinner text="Retrieving grounded information..." />}
{error && <p className="text-red-400 text-center">{error}</p>}
{response && (
<div>
<MarkdownRenderer content={response} />
{chunks.length > 0 && (
<div className="mt-8 border-t border-cyan-500/20 pt-4">
<h4 className="font-bold text-cyan-400 mb-2">Sources:</h4>
<ul className="list-disc list-inside text-sm">
{chunks.map((chunk, index) => {
const uri = chunk.web?.uri || chunk.maps?.uri;
const title = chunk.web?.title || chunk.maps?.title;
if (!uri) return null;
return <li key={index}><a href={uri} target="_blank" rel="noopener noreferrer" className="text-purple-300 hover:underline">{title}</a></li>
})}
</ul>
</div>
)}
</div>
)}
</div>
</div>
);
};
export default GroundedSearchModule;