Skip to main content
Glama
Chatbot.tsx10.6 kB
import { useState } from 'react'; import './Chatbot.css'; import { DynamicMarker } from '../App'; interface ChatbotProps { onRecenterMap: (lat: number, lng: number, location: string) => void; onSetZoom: (zoom: number) => void; onAddMarker: (marker: DynamicMarker) => void; onClearMarkers: () => void; onUpdateMarker: (name: string, updates: Partial<DynamicMarker>) => void; } // Simple geocoding lookup for common cities const CITY_COORDINATES: Record<string, { lat: number; lng: number }> = { 'new york': { lat: 40.7128, lng: -74.0060 }, 'los angeles': { lat: 34.0522, lng: -118.2437 }, 'san francisco': { lat: 37.7749, lng: -122.4194 }, 'chicago': { lat: 41.8781, lng: -87.6298 }, 'houston': { lat: 29.7604, lng: -95.3698 }, 'phoenix': { lat: 33.4484, lng: -112.0740 }, 'philadelphia': { lat: 39.9526, lng: -75.1652 }, 'san antonio': { lat: 29.4241, lng: -98.4936 }, 'san diego': { lat: 32.7157, lng: -117.1611 }, 'dallas': { lat: 32.7767, lng: -96.7970 }, 'austin': { lat: 30.2672, lng: -97.7431 }, 'london': { lat: 51.5074, lng: -0.1278 }, 'paris': { lat: 48.8566, lng: 2.3522 }, 'tokyo': { lat: 35.6762, lng: 139.6503 }, 'beijing': { lat: 39.9042, lng: 116.4074 }, 'moscow': { lat: 55.7558, lng: 37.6173 }, 'dubai': { lat: 25.2048, lng: 55.2708 }, 'singapore': { lat: 1.3521, lng: 103.8198 }, 'sydney': { lat: -33.8688, lng: 151.2093 }, 'mumbai': { lat: 19.0760, lng: 72.8777 }, 'berlin': { lat: 52.5200, lng: 13.4050 }, }; export function Chatbot({ onRecenterMap, onSetZoom, onAddMarker, onClearMarkers, onUpdateMarker }: ChatbotProps) { const [isOpen, setIsOpen] = useState(false); const [messages, setMessages] = useState<{ text: string; sender: 'user' | 'bot' }[]>([ { text: 'Hello! How can I help you with satellite imagery today?', sender: 'bot' } ]); const [input, setInput] = useState(''); const handleSend = async () => { if (!input.trim()) return; const userMessage = { text: input, sender: 'user' as const }; const newMessages = [...messages, userMessage]; setMessages(newMessages); setInput(''); try { // Call backend API const response = await fetch('http://localhost:3001/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ messages: newMessages }), }); if (!response.ok) { throw new Error('Failed to get response'); } const data = await response.json(); console.log('📨 Received from backend:', data); // Handle MCP data (satellite imagery, geocoding, etc.) if (data.mcpData && Array.isArray(data.mcpData)) { console.log('🛰️ MCP data received:', data.mcpData); // Format MCP results for display let mcpSummary = '\n\n'; for (const result of data.mcpData) { if (result.error) { const errorMsg = typeof result.error === 'string' ? result.error : result.error.error || result.error.details || JSON.stringify(result.error); mcpSummary += `❌ **Error**: ${errorMsg}\n\n`; } else if (result.data) { try { const parsed = JSON.parse(result.data); // Format based on tool type if (result.tool === 'get_pricing_estimate') { if (parsed.success && parsed.pricing) { const productType = parsed.pricing.productTypes?.[0]; const resolution = productType?.resolutions?.[0]; if (resolution && resolution.pricing) { const p = resolution.pricing; const exampleArea = p.taskingMinSqkm; const exampleCost = exampleArea * p.taskingPriceOneSqkm; mcpSummary += `High-resolution satellite imagery (${resolution.resolution}) costs $${p.taskingPriceOneSqkm} per square kilometer. You can order between ${p.taskingMinSqkm} km² and ${p.taskingMaxSqkm} km².\n\n`; mcpSummary += `For example, a typical ${exampleArea} km² area would cost $${exampleCost}.`; } } else { mcpSummary += 'Pricing information is not available for this location at the moment.'; } } else if (result.tool === 'create_monitor') { if (parsed.id) { mcpSummary += `✅ **Monitor Created Successfully**\n\n`; mcpSummary += `- **Monitor ID**: \`${parsed.id}\`\n`; mcpSummary += `- **Created**: ${new Date(parsed.createdAt).toLocaleString()}\n`; if (parsed.productType) { mcpSummary += `- **Product Type**: ${parsed.productType}\n`; } mcpSummary += `\nYou'll receive notifications when new imagery becomes available for this area.`; } else { mcpSummary += 'I encountered an issue setting up the monitor. Please try again or contact support.'; } } else if (result.tool === 'check_order_feasibility') { if (parsed.available) { mcpSummary += 'Good news! Satellite imagery is available for this location. '; if (parsed.resolution) { mcpSummary += `We can provide ${parsed.resolution} resolution imagery. `; } if (parsed.estimatedCost) { mcpSummary += `The estimated cost is $${parsed.estimatedCost}.`; } } else { mcpSummary += 'Unfortunately, satellite imagery is not currently available for this location.'; } } else { // Generic formatting for other tools mcpSummary += `✅ **${result.tool}**\n${JSON.stringify(parsed, null, 2)}\n\n`; } } catch { // Not JSON or parsing failed, display as-is mcpSummary += `✅ **${result.tool}**\n${result.data}\n\n`; } } } // Store MCP summary for later data.mcpSummary = mcpSummary; } // Handle tool calls if present (map controls) if (data.toolCalls && Array.isArray(data.toolCalls)) { console.log('🔧 Processing tool calls:', data.toolCalls); for (const toolCall of data.toolCalls) { console.log('🛠️ Tool call:', toolCall.name, toolCall.arguments); if (toolCall.name === 'recenter_map') { const args = toolCall.arguments; const lat = args.latitude; const lng = args.longitude; console.log('📍 Recenter request:', { location: args.location, lat, lng }); if (lat !== undefined && lng !== undefined) { console.log('🗺️ Calling onRecenterMap with:', { lat, lng, location: args.location }); onRecenterMap(lat, lng, args.location); } else { console.log('⚠️ Missing coordinates, cannot recenter'); } } else if (toolCall.name === 'set_zoom') { console.log('🔍 Setting zoom to:', toolCall.arguments.zoom); onSetZoom(toolCall.arguments.zoom); } else if (toolCall.name === 'add_marker') { const args = toolCall.arguments; console.log('📍 Adding marker:', args); const marker: DynamicMarker = { id: `${args.name}-${Date.now()}`, name: args.name, lat: args.latitude, lng: args.longitude, description: args.description, category: args.category, additionalInfo: args.additionalInfo, satelliteInfo: args.satelliteInfo }; onAddMarker(marker); } else if (toolCall.name === 'clear_markers') { console.log('🗑️ Clearing markers'); onClearMarkers(); } else if (toolCall.name === 'update_marker') { const args = toolCall.arguments; console.log('🔄 Updating marker:', args.name, args); const updates: Partial<DynamicMarker> = {}; if (args.description) { updates.description = args.description; } if (args.additionalInfo) { updates.additionalInfo = args.additionalInfo; } if (args.satelliteInfo) { updates.satelliteInfo = args.satelliteInfo; } onUpdateMarker(args.name, updates); } } } else { console.log('ℹ️ No tool calls in response'); } // Display AI message first const initialMessage = { text: data.message, sender: 'bot' as const }; setMessages(prev => [...prev, initialMessage]); // If we have MCP data, send it as a separate follow-up message if (data.mcpSummary) { const mcpMessage = { text: data.mcpSummary.trim(), sender: 'bot' as const }; setMessages(prev => [...prev, mcpMessage]); } } catch (error) { console.error('Chat error:', error); const errorMessage = { text: 'Sorry, I encountered an error. Please try again.', sender: 'bot' as const }; setMessages(prev => [...prev, errorMessage]); } }; const handleKeyPress = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { handleSend(); } }; return ( <div className="chatbot-container"> {isOpen && ( <div className="chatbot-window"> <div className="chatbot-header"> <h3>SkyFi Assistant</h3> <button onClick={() => setIsOpen(false)} className="close-btn">×</button> </div> <div className="chatbot-messages"> {messages.map((msg, idx) => ( <div key={idx} className={`message ${msg.sender}`}> {msg.text} </div> ))} </div> <div className="chatbot-input"> <input type="text" value={input} onChange={(e) => setInput(e.target.value)} onKeyPress={handleKeyPress} placeholder="Ask me anything..." /> <button onClick={handleSend}>Send</button> </div> </div> )} <button className="chatbot-toggle" onClick={() => setIsOpen(!isOpen)} aria-label="Toggle chat" > 💬 </button> </div> ); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mfuechec/SkyFiMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server