Spaces:
Sleeping
Sleeping
| from fastapi import FastAPI, HTTPException, Request | |
| from fastapi.staticfiles import StaticFiles | |
| from fastapi.templating import Jinja2Templates | |
| from fastapi.responses import HTMLResponse, JSONResponse | |
| from pydantic import BaseModel | |
| import asyncio | |
| import json | |
| from datetime import datetime | |
| from typing import List, Dict, Any, Optional | |
| import os | |
| from dotenv import load_dotenv | |
| load_dotenv() | |
| from src.agent.research_agent import Web3ResearchAgent | |
| from src.api.airaa_integration import AIRAAIntegration | |
| from src.utils.logger import get_logger | |
| from src.utils.config import config | |
| logger = get_logger(__name__) | |
| app = FastAPI( | |
| title="Web3 Research Co-Pilot", | |
| description="AI-powered cryptocurrency research assistant", | |
| version="1.0.0" | |
| ) | |
| # Pydantic models for request/response | |
| class QueryRequest(BaseModel): | |
| query: str | |
| chat_history: Optional[List[Dict[str, str]]] = [] | |
| class QueryResponse(BaseModel): | |
| success: bool | |
| response: str | |
| sources: Optional[List[str]] = [] | |
| metadata: Optional[Dict[str, Any]] = {} | |
| error: Optional[str] = None | |
| class Web3CoPilotService: | |
| def __init__(self): | |
| try: | |
| logger.info("π Initializing Web3CoPilotService...") | |
| logger.info(f"π GEMINI_API_KEY configured: {'Yes' if config.GEMINI_API_KEY else 'No'}") | |
| if config.GEMINI_API_KEY: | |
| logger.info("π€ Initializing AI agent...") | |
| self.agent = Web3ResearchAgent() | |
| logger.info("β AI agent initialized successfully") | |
| else: | |
| logger.warning("β οΈ GEMINI_API_KEY not found - AI features disabled") | |
| self.agent = None | |
| logger.info("π Initializing AIRAA integration...") | |
| self.airaa = AIRAAIntegration() | |
| logger.info(f"π AIRAA integration: {'Enabled' if self.airaa.enabled else 'Disabled'}") | |
| self.enabled = bool(config.GEMINI_API_KEY) | |
| logger.info(f"π― Web3CoPilotService initialized successfully (AI enabled: {self.enabled})") | |
| except Exception as e: | |
| logger.error(f"β Service initialization failed: {e}") | |
| self.agent = None | |
| self.airaa = None | |
| self.enabled = False | |
| async def process_query(self, query: str) -> QueryResponse: | |
| logger.info(f"π Processing query: {query[:50]}{'...' if len(query) > 50 else ''}") | |
| if not query.strip(): | |
| logger.warning("β οΈ Empty query received") | |
| return QueryResponse(success=False, response="Please enter a query.", error="Empty query") | |
| try: | |
| if not self.enabled: | |
| logger.info("π§ AI disabled - providing limited response") | |
| response = """β οΈ **AI Agent Disabled**: GEMINI_API_KEY not configured. | |
| **Limited Data Available:** | |
| - CoinGecko API (basic crypto data) | |
| - DeFiLlama API (DeFi protocols) | |
| - Etherscan API (gas prices) | |
| Please configure GEMINI_API_KEY for full AI analysis.""" | |
| return QueryResponse(success=True, response=response, sources=["Configuration"]) | |
| logger.info("π€ Sending query to AI agent...") | |
| result = await self.agent.research_query(query) | |
| logger.info(f"β AI agent responded: {result.get('success', False)}") | |
| if result.get("success"): | |
| response = result.get("result", "No response generated") | |
| sources = result.get("sources", []) | |
| metadata = result.get("metadata", {}) | |
| # Send to AIRAA if enabled | |
| if self.airaa and self.airaa.enabled: | |
| try: | |
| logger.info("π Sending data to AIRAA...") | |
| await self.airaa.send_research_data(query, response) | |
| logger.info("β Data sent to AIRAA successfully") | |
| except Exception as e: | |
| logger.warning(f"β οΈ AIRAA integration failed: {e}") | |
| logger.info("β Query processed successfully") | |
| return QueryResponse(success=True, response=response, sources=sources, metadata=metadata) | |
| else: | |
| error_msg = result.get("error", "Research failed. Please try again.") | |
| logger.error(f"β AI agent failed: {error_msg}") | |
| return QueryResponse(success=False, response=error_msg, error=error_msg) | |
| except Exception as e: | |
| logger.error(f"β Query processing error: {e}") | |
| error_msg = f"Error processing query: {str(e)}" | |
| return QueryResponse(success=False, response=error_msg, error=error_msg) | |
| # Initialize service | |
| logger.info("π Starting Web3 Research Co-Pilot...") | |
| service = Web3CoPilotService() | |
| # API Routes | |
| async def get_homepage(request: Request): | |
| logger.info("π Serving homepage") | |
| html_content = """ | |
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Web3 Research Co-Pilot</title> | |
| <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>π</text></svg>"> | |
| <style> | |
| * { margin: 0; padding: 0; box-sizing: border-box; } | |
| body { | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif; | |
| background: linear-gradient(135deg, #0f1419 0%, #1a1f2e 100%); | |
| color: #e6e6e6; | |
| min-height: 100vh; | |
| overflow-x: hidden; | |
| } | |
| .container { max-width: 1200px; margin: 0 auto; padding: 20px; } | |
| .header { | |
| text-align: center; | |
| margin-bottom: 30px; | |
| background: linear-gradient(135deg, #00d4aa, #4a9eff); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| background-clip: text; | |
| } | |
| .header h1 { | |
| font-size: 3em; | |
| margin-bottom: 10px; | |
| font-weight: 700; | |
| text-shadow: 2px 2px 4px rgba(0,0,0,0.3); | |
| } | |
| .header p { | |
| color: #b0b0b0; | |
| font-size: 1.2em; | |
| font-weight: 300; | |
| } | |
| .status { | |
| padding: 15px; | |
| border-radius: 12px; | |
| margin-bottom: 25px; | |
| text-align: center; | |
| font-weight: 500; | |
| box-shadow: 0 4px 15px rgba(0,0,0,0.2); | |
| transition: all 0.3s ease; | |
| } | |
| .status.enabled { | |
| background: linear-gradient(135deg, #1a4d3a, #2a5d4a); | |
| border: 2px solid #00d4aa; | |
| color: #00d4aa; | |
| } | |
| .status.disabled { | |
| background: linear-gradient(135deg, #4d1a1a, #5d2a2a); | |
| border: 2px solid #ff6b6b; | |
| color: #ff6b6b; | |
| } | |
| .status.checking { | |
| background: linear-gradient(135deg, #3a3a1a, #4a4a2a); | |
| border: 2px solid #ffdd59; | |
| color: #ffdd59; | |
| animation: pulse 1.5s infinite; | |
| } | |
| @keyframes pulse { | |
| 0% { opacity: 1; } | |
| 50% { opacity: 0.7; } | |
| 100% { opacity: 1; } | |
| } | |
| .chat-container { | |
| background: rgba(26, 26, 26, 0.8); | |
| border-radius: 16px; | |
| padding: 25px; | |
| margin-bottom: 25px; | |
| backdrop-filter: blur(10px); | |
| border: 1px solid rgba(255,255,255,0.1); | |
| box-shadow: 0 8px 32px rgba(0,0,0,0.3); | |
| } | |
| .chat-messages { | |
| height: 450px; | |
| overflow-y: auto; | |
| background: rgba(10, 10, 10, 0.6); | |
| border-radius: 12px; | |
| padding: 20px; | |
| margin-bottom: 20px; | |
| border: 1px solid rgba(255,255,255,0.05); | |
| } | |
| .chat-messages::-webkit-scrollbar { width: 6px; } | |
| .chat-messages::-webkit-scrollbar-track { background: #2a2a2a; border-radius: 3px; } | |
| .chat-messages::-webkit-scrollbar-thumb { background: #555; border-radius: 3px; } | |
| .chat-messages::-webkit-scrollbar-thumb:hover { background: #777; } | |
| .message { | |
| margin-bottom: 20px; | |
| padding: 16px; | |
| border-radius: 12px; | |
| transition: all 0.3s ease; | |
| position: relative; | |
| } | |
| .message:hover { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(0,0,0,0.2); } | |
| .message.user { | |
| background: linear-gradient(135deg, #2a2a3a, #3a3a4a); | |
| border-left: 4px solid #00d4aa; | |
| margin-left: 50px; | |
| } | |
| .message.assistant { | |
| background: linear-gradient(135deg, #1a2a1a, #2a3a2a); | |
| border-left: 4px solid #4a9eff; | |
| margin-right: 50px; | |
| } | |
| .message .sender { | |
| font-weight: 600; | |
| margin-bottom: 8px; | |
| font-size: 0.9em; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| .message.user .sender { color: #00d4aa; } | |
| .message.assistant .sender { color: #4a9eff; } | |
| .message .content { line-height: 1.6; } | |
| .input-container { | |
| display: flex; | |
| gap: 12px; | |
| align-items: stretch; | |
| } | |
| .input-container input { | |
| flex: 1; | |
| padding: 16px; | |
| border: 2px solid #333; | |
| background: rgba(42, 42, 42, 0.8); | |
| color: #e6e6e6; | |
| border-radius: 12px; | |
| font-size: 16px; | |
| backdrop-filter: blur(10px); | |
| transition: all 0.3s ease; | |
| } | |
| .input-container input:focus { | |
| outline: none; | |
| border-color: #00d4aa; | |
| box-shadow: 0 0 0 3px rgba(0, 212, 170, 0.2); | |
| } | |
| .input-container input::placeholder { color: #888; } | |
| .input-container button { | |
| padding: 16px 24px; | |
| background: linear-gradient(135deg, #00d4aa, #00b894); | |
| color: #000; | |
| border: none; | |
| border-radius: 12px; | |
| cursor: pointer; | |
| font-weight: 600; | |
| font-size: 16px; | |
| transition: all 0.3s ease; | |
| white-space: nowrap; | |
| } | |
| .input-container button:hover:not(:disabled) { | |
| background: linear-gradient(135deg, #00b894, #00a085); | |
| transform: translateY(-2px); | |
| box-shadow: 0 4px 12px rgba(0, 212, 170, 0.3); | |
| } | |
| .input-container button:active { transform: translateY(0); } | |
| .input-container button:disabled { | |
| background: #666; | |
| cursor: not-allowed; | |
| transform: none; | |
| box-shadow: none; | |
| } | |
| .examples { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); | |
| gap: 15px; | |
| margin-top: 25px; | |
| } | |
| .example-btn { | |
| padding: 16px; | |
| background: linear-gradient(135deg, #2a2a3a, #3a3a4a); | |
| border: 2px solid #444; | |
| border-radius: 12px; | |
| cursor: pointer; | |
| text-align: center; | |
| transition: all 0.3s ease; | |
| font-weight: 500; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .example-btn:before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: -100%; | |
| width: 100%; | |
| height: 100%; | |
| background: linear-gradient(90deg, transparent, rgba(0, 212, 170, 0.1), transparent); | |
| transition: left 0.5s; | |
| } | |
| .example-btn:hover:before { left: 100%; } | |
| .example-btn:hover { | |
| background: linear-gradient(135deg, #3a3a4a, #4a4a5a); | |
| border-color: #00d4aa; | |
| transform: translateY(-3px); | |
| box-shadow: 0 6px 20px rgba(0, 212, 170, 0.2); | |
| } | |
| .loading { | |
| color: #ffdd59; | |
| font-style: italic; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| .loading:after { | |
| content: ''; | |
| width: 12px; | |
| height: 12px; | |
| border: 2px solid #ffdd59; | |
| border-top: 2px solid transparent; | |
| border-radius: 50%; | |
| animation: spin 1s linear infinite; | |
| } | |
| @keyframes spin { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| .sources { | |
| margin-top: 12px; | |
| font-size: 0.85em; | |
| color: #999; | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 6px; | |
| } | |
| .sources .label { margin-right: 8px; font-weight: 600; } | |
| .sources span { | |
| background: rgba(51, 51, 51, 0.8); | |
| padding: 4px 8px; | |
| border-radius: 6px; | |
| font-size: 0.8em; | |
| border: 1px solid #555; | |
| } | |
| .welcome-message { | |
| background: linear-gradient(135deg, #1a2a4a, #2a3a5a); | |
| border-left: 4px solid #4a9eff; | |
| border-radius: 12px; | |
| padding: 16px; | |
| margin-bottom: 20px; | |
| text-align: center; | |
| } | |
| .footer { | |
| text-align: center; | |
| margin-top: 30px; | |
| color: #666; | |
| font-size: 0.9em; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <div class="header"> | |
| <h1>π Web3 Research Co-Pilot</h1> | |
| <p>AI-powered cryptocurrency research assistant</p> | |
| </div> | |
| <div id="status" class="status checking"> | |
| <span>π Checking system status...</span> | |
| </div> | |
| <div class="chat-container"> | |
| <div id="chatMessages" class="chat-messages"> | |
| <div class="welcome-message"> | |
| <div class="sender">π€ AI Research Assistant</div> | |
| <div>π Welcome! I'm your Web3 Research Co-Pilot. Ask me anything about cryptocurrency markets, DeFi protocols, blockchain analysis, or trading insights.</div> | |
| </div> | |
| </div> | |
| <div class="input-container"> | |
| <input type="text" id="queryInput" placeholder="Ask about Bitcoin, Ethereum, DeFi yields, market analysis..." maxlength="500"> | |
| <button id="sendBtn" onclick="sendQuery()">π Research</button> | |
| </div> | |
| </div> | |
| <div class="examples"> | |
| <div class="example-btn" onclick="setQuery('What is the current Bitcoin price and market sentiment?')"> | |
| π Bitcoin Analysis | |
| </div> | |
| <div class="example-btn" onclick="setQuery('Show me the top DeFi protocols by TVL')"> | |
| π¦ DeFi Overview | |
| </div> | |
| <div class="example-btn" onclick="setQuery('What are the trending cryptocurrencies today?')"> | |
| π₯ Trending Coins | |
| </div> | |
| <div class="example-btn" onclick="setQuery('Analyze Ethereum gas prices and network activity')"> | |
| β½ Gas Tracker | |
| </div> | |
| <div class="example-btn" onclick="setQuery('Find the best yield farming opportunities')"> | |
| πΎ Yield Farming | |
| </div> | |
| <div class="example-btn" onclick="setQuery('Compare Solana vs Ethereum ecosystems')"> | |
| βοΈ Ecosystem Compare | |
| </div> | |
| </div> | |
| <div class="footer"> | |
| <p>Powered by AI β’ Real-time Web3 data β’ Built with β€οΈ</p> | |
| </div> | |
| </div> | |
| <script> | |
| let chatHistory = []; | |
| async function checkStatus() { | |
| try { | |
| console.log('π Checking system status...'); | |
| const response = await fetch('/status'); | |
| const status = await response.json(); | |
| console.log('π Status received:', status); | |
| const statusDiv = document.getElementById('status'); | |
| if (status.enabled && status.gemini_configured) { | |
| statusDiv.className = 'status enabled'; | |
| statusDiv.innerHTML = ` | |
| <span>β AI Research Agent: Online</span><br> | |
| <small>Tools available: ${status.tools_available.join(', ')}</small> | |
| `; | |
| console.log('β System fully operational'); | |
| } else { | |
| statusDiv.className = 'status disabled'; | |
| statusDiv.innerHTML = ` | |
| <span>β οΈ Limited Mode: GEMINI_API_KEY not configured</span><br> | |
| <small>Basic data available: ${status.tools_available.join(', ')}</small> | |
| `; | |
| console.log('β οΈ System in limited mode'); | |
| } | |
| } catch (error) { | |
| console.error('β Status check failed:', error); | |
| const statusDiv = document.getElementById('status'); | |
| statusDiv.className = 'status disabled'; | |
| statusDiv.innerHTML = '<span>β Connection Error</span>'; | |
| } | |
| } | |
| async function sendQuery() { | |
| const input = document.getElementById('queryInput'); | |
| const sendBtn = document.getElementById('sendBtn'); | |
| const query = input.value.trim(); | |
| if (!query) { | |
| input.focus(); | |
| return; | |
| } | |
| console.log('π€ Sending query:', query); | |
| // Add user message | |
| addMessage('user', query); | |
| input.value = ''; | |
| // Show loading | |
| sendBtn.disabled = true; | |
| sendBtn.innerHTML = '<span class="loading">Processing</span>'; | |
| try { | |
| const response = await fetch('/query', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ query, chat_history: chatHistory }) | |
| }); | |
| const result = await response.json(); | |
| console.log('π₯ Response received:', result); | |
| if (result.success) { | |
| addMessage('assistant', result.response, result.sources); | |
| console.log('β Query processed successfully'); | |
| } else { | |
| addMessage('assistant', result.response || 'An error occurred'); | |
| console.log('β οΈ Query failed:', result.error); | |
| } | |
| } catch (error) { | |
| console.error('β Network error:', error); | |
| addMessage('assistant', 'β Network error. Please check your connection and try again.'); | |
| } finally { | |
| sendBtn.disabled = false; | |
| sendBtn.innerHTML = 'π Research'; | |
| input.focus(); | |
| } | |
| } | |
| function addMessage(sender, content, sources = []) { | |
| console.log(`π¬ Adding ${sender} message`); | |
| const messagesDiv = document.getElementById('chatMessages'); | |
| const messageDiv = document.createElement('div'); | |
| messageDiv.className = `message ${sender}`; | |
| let sourcesHtml = ''; | |
| if (sources && sources.length > 0) { | |
| sourcesHtml = `<div class="sources"><span class="label">Sources:</span> ${sources.map(s => `<span>${s}</span>`).join('')}</div>`; | |
| } | |
| const senderIcon = sender === 'user' ? 'π€' : 'π€'; | |
| const senderName = sender === 'user' ? 'You' : 'AI Research Assistant'; | |
| messageDiv.innerHTML = ` | |
| <div class="sender">${senderIcon} ${senderName}</div> | |
| <div class="content">${content.replace(/\n/g, '<br>')}</div> | |
| ${sourcesHtml} | |
| `; | |
| messagesDiv.appendChild(messageDiv); | |
| messagesDiv.scrollTop = messagesDiv.scrollHeight; | |
| // Update chat history | |
| chatHistory.push({ role: sender, content }); | |
| if (chatHistory.length > 20) chatHistory = chatHistory.slice(-20); | |
| } | |
| function setQuery(query) { | |
| console.log('π Setting query:', query); | |
| const input = document.getElementById('queryInput'); | |
| input.value = query; | |
| input.focus(); | |
| // Optional: auto-send after a short delay | |
| setTimeout(() => { | |
| if (input.value === query) { // Only if user didn't change it | |
| sendQuery(); | |
| } | |
| }, 100); | |
| } | |
| // Handle Enter key | |
| document.getElementById('queryInput').addEventListener('keypress', function(e) { | |
| if (e.key === 'Enter') { | |
| sendQuery(); | |
| } | |
| }); | |
| // Initialize | |
| document.addEventListener('DOMContentLoaded', function() { | |
| console.log('π Web3 Research Co-Pilot initialized'); | |
| checkStatus(); | |
| document.getElementById('queryInput').focus(); | |
| }); | |
| </script> | |
| </body> | |
| </html> | |
| """ | |
| return HTMLResponse(content=html_content) | |
| async def get_status(): | |
| logger.info("π Status endpoint called") | |
| status = { | |
| "enabled": service.enabled, | |
| "gemini_configured": bool(config.GEMINI_API_KEY), | |
| "tools_available": ["CoinGecko", "DeFiLlama", "Etherscan"], | |
| "airaa_enabled": service.airaa.enabled if service.airaa else False, | |
| "timestamp": datetime.now().isoformat() | |
| } | |
| logger.info(f"π Status response: {status}") | |
| return status | |
| async def process_query(request: QueryRequest): | |
| logger.info(f"π₯ Query endpoint called: {request.query[:50]}{'...' if len(request.query) > 50 else ''}") | |
| result = await service.process_query(request.query) | |
| logger.info(f"π€ Query response: success={result.success}") | |
| return result | |
| async def health_check(): | |
| logger.info("β€οΈ Health check endpoint called") | |
| return { | |
| "status": "healthy", | |
| "timestamp": datetime.now().isoformat(), | |
| "service_enabled": service.enabled, | |
| "version": "1.0.0" | |
| } | |
| if __name__ == "__main__": | |
| import uvicorn | |
| logger.info("π Starting FastAPI server...") | |
| uvicorn.run(app, host="0.0.0.0", port=7860, log_level="info") | |