What would you like to research?
+Ask anything about companies, products, technologies, or markets
+diff --git a/advanced-agent/app.py b/advanced-agent/app.py new file mode 100644 index 0000000..b693999 --- /dev/null +++ b/advanced-agent/app.py @@ -0,0 +1,367 @@ +#!/usr/bin/env python3 +""" +Web UI for Multi Agent Researcher +""" + +from flask import Flask, render_template, request, jsonify, Response +from flask_cors import CORS +import json +import time +import sys +import os +from datetime import datetime + +# Add src to path +sys.path.append(os.path.join(os.path.dirname(__file__), 'src')) + +from src.workflow import Workflow + +app = Flask(__name__) +CORS(app) + +# Global workflow instance +workflow = None +current_research = None + +def init_workflow(): + """Initialize workflow on startup""" + global workflow + try: + workflow = Workflow() + print("ā Workflow initialized successfully") + except Exception as e: + print(f"ā Error initializing workflow: {e}") + workflow = None + +@app.route('/') +def index(): + """Main page""" + return render_template('index.html') + +@app.route('/api/research-stream') +def research_stream(): + """Handle research requests with real-time streaming using SSE""" + global workflow, current_research + + if not workflow: + return "data: " + json.dumps({'step': 'error', 'message': 'Workflow not initialized', 'status': 'error'}) + "\n\n" + + # Get parameters from query string + query = request.args.get('query', '').strip() + research_mode = request.args.get('mode', 'auto') + selected_pdfs_str = request.args.get('selected_pdfs', '[]') + + try: + selected_pdfs = json.loads(selected_pdfs_str) if selected_pdfs_str else [] + except: + selected_pdfs = [] + + if not query: + return "data: " + json.dumps({'step': 'error', 'message': 'Query is required', 'status': 'error'}) + "\n\n" + + def generate_research_stream(): + try: + # Initialize research + current_research = { + 'query': query, + 'mode': research_mode, + 'status': 'running', + 'start_time': datetime.now().isoformat(), + 'progress': [] + } + + # Step 1: Intent Detection + yield f"data: {json.dumps({'step': 'intent', 'message': 'Analyzing your research query...', 'status': 'running'})}\n\n" + time.sleep(0.8) + + # Step 2: Web Search + yield f"data: {json.dumps({'step': 'search', 'message': 'Searching web sources...', 'status': 'running'})}\n\n" + time.sleep(0.5) + + # Simulate finding sources based on query + if 'ai' in query.lower() or 'artificial intelligence' in query.lower(): + sources = [ + {'name': 'OpenAI.com', 'url': 'https://openai.com'}, + {'name': 'Anthropic.com', 'url': 'https://anthropic.com'}, + {'name': 'Google AI', 'url': 'https://ai.google'} + ] + elif 'university' in query.lower() or 'college' in query.lower(): + sources = [ + {'name': 'MIT.edu', 'url': 'https://mit.edu'}, + {'name': 'Stanford.edu', 'url': 'https://stanford.edu'}, + {'name': 'Harvard.edu', 'url': 'https://harvard.edu'} + ] + elif 'python' in query.lower() or 'framework' in query.lower(): + sources = [ + {'name': 'Django Project', 'url': 'https://djangoproject.com'}, + {'name': 'Flask.palletsprojects.com', 'url': 'https://flask.palletsprojects.com'}, + {'name': 'FastAPI.tiangolo.com', 'url': 'https://fastapi.tiangolo.com'} + ] + else: + sources = [ + {'name': 'Wikipedia.org', 'url': 'https://wikipedia.org'}, + {'name': 'GitHub.com', 'url': 'https://github.com'}, + {'name': 'StackOverflow.com', 'url': 'https://stackoverflow.com'} + ] + + for i, source in enumerate(sources[:3]): + message = f"Reading {source['name']}..." + yield f"data: {json.dumps({'step': 'source', 'message': message, 'url': source['url'], 'status': 'running'})}\n\n" + time.sleep(1.2) + + # Step 3: PDF Processing + yield f"data: {json.dumps({'step': 'pdf', 'message': 'Processing relevant documents...', 'status': 'running'})}\n\n" + time.sleep(1.0) + + # Step 4: AI Analysis + yield f"data: {json.dumps({'step': 'analysis', 'message': 'AI analyzing gathered information...', 'status': 'running'})}\n\n" + time.sleep(1.5) + + # Run actual research + if research_mode == 'select_pdfs' and selected_pdfs: + result = workflow.run_with_selected_pdfs(query, selected_pdfs) + else: + result = workflow.run(query) + + # Format results + formatted_result = format_research_result(result) + + # Send completion + yield f"data: {json.dumps({'step': 'complete', 'message': 'Research completed!', 'result': formatted_result, 'status': 'completed'})}\n\n" + + except Exception as e: + yield f"data: {json.dumps({'step': 'error', 'message': str(e), 'status': 'error'})}\n\n" + + return Response(generate_research_stream(), + mimetype='text/event-stream', + headers={ + 'Cache-Control': 'no-cache', + 'Connection': 'keep-alive', + 'Access-Control-Allow-Origin': '*' + }) + +@app.route('/api/research', methods=['POST']) +def research(): + """Handle research requests (fallback for non-streaming)""" + global workflow, current_research + + if not workflow: + return jsonify({ + 'error': 'Workflow not initialized. Please check your configuration.', + 'success': False + }), 500 + + try: + data = request.get_json() + query = data.get('query', '').strip() + research_mode = data.get('mode', 'auto') + selected_pdfs = data.get('selected_pdfs', []) + + if not query: + return jsonify({ + 'error': 'Query is required', + 'success': False + }), 400 + + # Store current research info + current_research = { + 'query': query, + 'mode': research_mode, + 'status': 'running', + 'start_time': datetime.now().isoformat(), + 'progress': [] + } + + # Run research based on mode + if research_mode == 'select_pdfs' and selected_pdfs: + result = workflow.run_with_selected_pdfs(query, selected_pdfs) + else: + result = workflow.run(query) + + # Format results + formatted_result = format_research_result(result) + + current_research['status'] = 'completed' + current_research['end_time'] = datetime.now().isoformat() + + return jsonify({ + 'success': True, + 'result': formatted_result, + 'research_info': current_research + }) + + except Exception as e: + current_research['status'] = 'error' if current_research else None + return jsonify({ + 'error': str(e), + 'success': False + }), 500 + +@app.route('/api/pdfs') +def get_pdfs(): + """Get available PDFs""" + global workflow + + if not workflow: + return jsonify({ + 'error': 'Workflow not initialized', + 'success': False + }), 500 + + try: + pdf_notetaker = workflow.pdf_notetaker + available_pdfs = pdf_notetaker.get_available_pdfs_for_selection() + + return jsonify({ + 'success': True, + 'pdfs': available_pdfs + }) + + except Exception as e: + return jsonify({ + 'error': str(e), + 'success': False + }), 500 + +@app.route('/api/relevant-pdfs', methods=['POST']) +def get_relevant_pdfs(): + """Get PDFs ranked by relevance to query""" + global workflow + + if not workflow: + return jsonify({ + 'error': 'Workflow not initialized', + 'success': False + }), 500 + + try: + data = request.get_json() + query = data.get('query', '').strip() + + if not query: + return jsonify({ + 'error': 'Query is required', + 'success': False + }), 400 + + pdf_notetaker = workflow.pdf_notetaker + ranked_pdfs = pdf_notetaker.filter_and_rank_pdfs(query, max_pdfs=10) + + return jsonify({ + 'success': True, + 'pdfs': ranked_pdfs + }) + + except Exception as e: + return jsonify({ + 'error': str(e), + 'success': False + }), 500 + +@app.route('/api/status') +def get_status(): + """Get current research status""" + global current_research + + return jsonify({ + 'success': True, + 'research': current_research, + 'workflow_ready': workflow is not None + }) + +def format_research_result(result): + """Format research result for web display""" + try: + research_type = getattr(result, "research_type", "general") + + formatted = { + 'research_type': research_type, + 'query': getattr(result, 'query', ''), + 'entities': [], + 'analysis': getattr(result, 'analysis', ''), + 'market_insights': None + } + + # Format entities based on research type + if hasattr(result, 'companies') and result.companies: + # Developer tools format + for company in result.companies[:5]: + entity = { + 'name': company.name, + 'website': company.website, + 'description': company.description, + 'type': 'company', + 'details': { + 'pricing': company.pricing_model, + 'open_source': company.is_open_source, + 'tech_stack': company.tech_stack[:5] if company.tech_stack else [], + 'api_available': company.api_available, + 'languages': company.language_support[:5] if company.language_support else [], + 'integrations': company.integration_capabilities[:4] if company.integration_capabilities else [] + } + } + formatted['entities'].append(entity) + + elif hasattr(result, 'market_entities') and result.market_entities: + # Market research format + for entity in result.market_entities[:5]: + formatted_entity = { + 'name': entity.name, + 'category': entity.category, + 'website': entity.website, + 'description': entity.description, + 'type': entity.category, + 'details': {} + } + + # Add type-specific details + if research_type == "product_research": + formatted_entity['details'] = { + 'price': getattr(entity, 'price', 'Unknown'), + 'rating': getattr(entity, 'rating', 'Unknown'), + 'brand': getattr(entity, 'brand', 'Unknown'), + 'features': getattr(entity, 'features', [])[:3], + 'target_audience': getattr(entity, 'target_audience', 'Unknown') + } + else: + # General details + formatted_entity['details'] = { + 'market_position': getattr(entity, 'market_position', 'Unknown'), + 'revenue': getattr(entity, 'revenue', 'Unknown'), + 'employees': getattr(entity, 'employees', 'Unknown') + } + + formatted['entities'].append(formatted_entity) + + # Add market insights if available + if hasattr(result, 'market_insights') and result.market_insights: + insights = result.market_insights + formatted['market_insights'] = { + 'market_size': insights.market_size, + 'growth_rate': insights.growth_rate, + 'key_drivers': insights.key_drivers, + 'market_trends': insights.market_trends + } + + return formatted + + except Exception as e: + print(f"Error formatting result: {e}") + return { + 'research_type': 'error', + 'entities': [], + 'analysis': f'Error formatting results: {str(e)}', + 'market_insights': None + } + +if __name__ == '__main__': + print("š Starting Multi Agent Researcher Web UI...") + + # Initialize workflow + init_workflow() + + if workflow: + print("ā Ready to serve requests!") + app.run(debug=True, host='0.0.0.0', port=5002) + else: + print("ā Failed to initialize. Please check your configuration.") \ No newline at end of file diff --git a/advanced-agent/demo_test.py b/advanced-agent/demo_test.py new file mode 100644 index 0000000..f22edf9 --- /dev/null +++ b/advanced-agent/demo_test.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +""" +Demo test to show the basic functionality of the Advanced Research Agent. +This test doesn't require API keys or AWS credentials. +""" + +import sys +import os + +# Add the src directory to the path +sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src')) + +def demo_basic_functionality(): + """Demonstrate basic functionality without external dependencies.""" + print("š Advanced Research Agent - Demo Test") + print("=" * 50) + + try: + # Test 1: Import core modules + print("š¦ Testing imports...") + from models import ResearchState, PDFDocument, MarketEntity, CompanyInfo + from prompts import DeveloperToolsPrompts + print("ā All core modules imported successfully") + + # Test 2: Create basic models + print("\nšļø Testing model creation...") + + # Create a research state + state = ResearchState(query="Best AI development tools") + print(f"ā Created ResearchState with query: '{state.query}'") + + # Create a PDF document + pdf_doc = PDFDocument( + s3_key="demo.pdf", + filename="demo.pdf", + content="This is a demo PDF content for testing purposes." + ) + print(f"ā Created PDFDocument: {pdf_doc.filename}") + + # Create a market entity + entity = MarketEntity( + name="Demo Company", + category="technology", + description="A demo technology company for testing", + website="https://demo.com" + ) + print(f"ā Created MarketEntity: {entity.name}") + + # Create a company info + company = CompanyInfo( + name="Demo Tech", + description="A demo technology company", + website="https://demotech.com" + ) + print(f"ā Created CompanyInfo: {company.name}") + + # Test 3: Test prompt generation + print("\nš¬ Testing prompt generation...") + prompts = DeveloperToolsPrompts() + + # Generate a tool extraction prompt + tool_prompt = prompts.tool_extraction_user("AI development tools", "Demo content about AI tools") + print(f"ā Generated tool extraction prompt ({len(tool_prompt)} characters)") + + # Generate a tool analysis prompt + analysis_prompt = prompts.tool_analysis_user("Demo Tool", "Demo tool content for analysis") + print(f"ā Generated tool analysis prompt ({len(analysis_prompt)} characters)") + + print("\n" + "=" * 50) + print("š All demo tests passed!") + print("š This demonstrates the basic functionality without external dependencies.") + print("š To test with real data, you'll need to configure API keys in .env") + + return True + + except ImportError as e: + print(f"ā Import error: {e}") + return False + except Exception as e: + print(f"ā Unexpected error: {e}") + return False + +def show_usage_instructions(): + """Show how to use the system with real API keys.""" + print("\nš Usage Instructions:") + print("=" * 30) + print("1. Configure API keys in .env file:") + print(" - ANTHROPIC_API_KEY=your_anthropic_key") + print(" - FIRECRAWL_API_KEY=your_firecrawl_key") + print(" - AWS credentials for PDF processing") + print() + print("2. Run the main application:") + print(" python main.py") + print() + print("3. Example queries to test:") + print(" - 'Best Python web frameworks'") + print(" - 'Compare React vs Vue'") + print(" - 'Top universities for computer science'") + print(" - 'Stock analysis for tech companies'") + +def main(): + """Run the demo test.""" + success = demo_basic_functionality() + + if success: + show_usage_instructions() + return 0 + else: + print("ā Demo test failed!") + return 1 + +if __name__ == "__main__": + sys.exit(main()) diff --git a/advanced-agent/requirements.txt.backup b/advanced-agent/requirements.txt.backup new file mode 100644 index 0000000..e9821d3 --- /dev/null +++ b/advanced-agent/requirements.txt.backup @@ -0,0 +1,30 @@ +# Core LLM and AI dependencies +langchain==0.1.0 +langchain-anthropic==0.1.0 +langchain-core==0.1.0 +anthropic==0.18.1 + +# Web scraping and content extraction +firecrawl==0.1.0 +scrapy==2.11.0 +requests==2.31.0 +beautifulsoup4==4.12.2 +newspaper3k==0.2.8 + +# PDF processing +pdfplumber==0.10.3 +PyPDF2==3.0.1 + +# AWS integration +boto3==1.34.0 +botocore==1.34.0 + +# Data processing and validation +pydantic==2.5.0 +pydantic-settings==2.1.0 + +# Environment and configuration +python-dotenv==1.0.0 + +# Utilities +typing-extensions==4.8.0 \ No newline at end of file diff --git a/advanced-agent/run_web_ui.py b/advanced-agent/run_web_ui.py new file mode 100644 index 0000000..50b8ed0 --- /dev/null +++ b/advanced-agent/run_web_ui.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +""" +Simple script to run the web UI +""" + +import os +import sys +from dotenv import load_dotenv + +# Load environment variables +load_dotenv() + +from app import app, init_workflow + +def main(): + print("š Starting Multi Agent Researcher Web UI...") + print("=" * 50) + + # Load environment variables again to be sure + load_dotenv() + + # Check environment + required_vars = ['AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AWS_REGION', 'FIRECRAWL_API_KEY'] + missing_vars = [var for var in required_vars if not os.getenv(var)] + + if missing_vars: + print(f"ā Missing environment variables: {', '.join(missing_vars)}") + print("Please check your .env file") + print("Current working directory:", os.getcwd()) + print("Looking for .env file at:", os.path.join(os.getcwd(), '.env')) + print("File exists:", os.path.exists('.env')) + sys.exit(1) + + # Initialize workflow + print("š§ Initializing research workflow...") + init_workflow() + + print("ā Web UI ready!") + print("š Open your browser and go to: http://localhost:5005") + print("=" * 50) + + # Run the app + app.run(debug=False, host='0.0.0.0', port=5005) + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/advanced-agent/src/bedrock_service.py b/advanced-agent/src/bedrock_service.py new file mode 100644 index 0000000..bebe739 --- /dev/null +++ b/advanced-agent/src/bedrock_service.py @@ -0,0 +1,57 @@ +import os +import boto3 +from langchain_aws import ChatBedrock +from dotenv import load_dotenv + +load_dotenv() + + +class BedrockService: + """AWS Bedrock service wrapper for Claude models""" + + def __init__(self, model_id: str = None): + """ + Initialize Bedrock service + + Args: + model_id: Bedrock model ID for Claude (optional, uses env var if not provided) + - us.anthropic.claude-3-5-sonnet-20240620-v1:0 (Claude 3.5 Sonnet) + - us.anthropic.claude-3-haiku-20240307-v1:0 (Claude 3 Haiku) + - us.anthropic.claude-3-sonnet-20240229-v1:0 (Claude 3 Sonnet) + """ + self.model_id = model_id or os.getenv("BEDROCK_MODEL_ID", "us.anthropic.claude-3-5-sonnet-20240620-v1:0") + self.region = os.getenv("AWS_REGION", "us-east-1") + + # Initialize boto3 client for Bedrock + self.bedrock_client = boto3.client( + service_name="bedrock-runtime", + region_name=self.region, + aws_access_key_id=os.getenv("AWS_ACCESS_KEY_ID"), + aws_secret_access_key=os.getenv("AWS_SECRET_ACCESS_KEY") + ) + + # Initialize LangChain ChatBedrock + self.llm = ChatBedrock( + client=self.bedrock_client, + model_id=self.model_id, + model_kwargs={ + "temperature": 0.1, + "max_tokens": 4000, + } + ) + + def get_llm(self): + """Get the LangChain ChatBedrock instance""" + return self.llm + + def with_structured_output(self, schema): + """Get LLM with structured output for Pydantic models""" + return self.llm.with_structured_output(schema) + + def invoke(self, messages): + """Direct invoke method for compatibility""" + return self.llm.invoke(messages) + + +# Singleton instance for easy import +bedrock_service = BedrockService() \ No newline at end of file diff --git a/advanced-agent/static/css/style.css b/advanced-agent/static/css/style.css new file mode 100644 index 0000000..232f138 --- /dev/null +++ b/advanced-agent/static/css/style.css @@ -0,0 +1,987 @@ +/* Modern Multi Agent Researcher UI - Perplexity Style */ + +/* Reset and Base */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +:root { + --primary-color: #2563eb; + --primary-hover: #1d4ed8; + --secondary-color: #64748b; + --accent-color: #06b6d4; + --success-color: #10b981; + --warning-color: #f59e0b; + --error-color: #ef4444; + + --bg-primary: #ffffff; + --bg-secondary: #f8fafc; + --bg-tertiary: #f1f5f9; + --bg-card: #ffffff; + + --text-primary: #0f172a; + --text-secondary: #475569; + --text-muted: #94a3b8; + + --border-color: #e2e8f0; + --border-hover: #cbd5e1; + + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); + --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); + + --radius-sm: 0.375rem; + --radius-md: 0.5rem; + --radius-lg: 0.75rem; + --radius-xl: 1rem; +} + +/* Dark Mode Variables */ +[data-theme="dark"] { + --bg-primary: #1e293b; + --bg-secondary: #0f172a; + --bg-tertiary: #334155; + --bg-card: #1e293b; + + --text-primary: #f1f5f9; + --text-secondary: #cbd5e1; + --text-muted: #64748b; + + --border-color: #334155; + --border-hover: #475569; + + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.3); + --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.4), 0 2px 4px -2px rgb(0 0 0 / 0.4); + --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.4), 0 4px 6px -4px rgb(0 0 0 / 0.4); +} + +body { + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + background: var(--bg-secondary); + color: var(--text-primary); + line-height: 1.6; + font-size: 14px; + overflow-x: hidden; +} + +/* Navigation */ +.navbar { + background: var(--bg-primary); + border-bottom: 1px solid var(--border-color); + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 1000; + height: 60px; +} + +.nav-container { + max-width: 1400px; + margin: 0 auto; + padding: 0 24px; + height: 100%; + display: flex; + align-items: center; + justify-content: space-between; +} + +.nav-brand { + display: flex; + align-items: center; + gap: 12px; + font-weight: 600; + font-size: 18px; +} + +.brand-icon { + width: 32px; + height: 32px; + background: linear-gradient(135deg, var(--primary-color), var(--accent-color)); + border-radius: var(--radius-md); + display: flex; + align-items: center; + justify-content: center; + color: white; +} + +.nav-actions { + display: flex; + align-items: center; + gap: 16px; +} + +.nav-btn { + width: 36px; + height: 36px; + border: none; + background: var(--bg-tertiary); + border-radius: var(--radius-md); + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.2s ease; + color: var(--text-secondary); +} + +.nav-btn:hover { + background: var(--border-color); + color: var(--text-primary); +} + +.status-indicator { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 12px; + background: var(--bg-tertiary); + border-radius: var(--radius-lg); + font-size: 13px; + font-weight: 500; +} + +.status-dot { + width: 8px; + height: 8px; + background: var(--success-color); + border-radius: 50%; + animation: pulse 2s infinite; +} + +@keyframes pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.5; } +} + +/* App Container */ +.app-container { + display: flex; + margin-top: 60px; + min-height: calc(100vh - 60px); +} + +/* Sidebar */ +.sidebar { + width: 320px; + background: var(--bg-primary); + border-right: 1px solid var(--border-color); + position: fixed; + left: 0; + top: 60px; + height: calc(100vh - 60px); + overflow-y: auto; +} + +.sidebar-content { + padding: 24px; + display: flex; + flex-direction: column; + gap: 32px; +} + +.research-modes h3, +.research-history h3 { + font-size: 16px; + font-weight: 600; + margin-bottom: 16px; + color: var(--text-primary); +} + +.mode-options { + display: flex; + flex-direction: column; + gap: 8px; +} + +.mode-option { + cursor: pointer; +} + +.mode-option input[type="radio"] { + display: none; +} + +.mode-card { + display: flex; + align-items: center; + gap: 12px; + padding: 16px; + border: 2px solid var(--border-color); + border-radius: var(--radius-lg); + transition: all 0.2s ease; + background: var(--bg-primary); +} + +.mode-option input[type="radio"]:checked + .mode-card { + border-color: var(--primary-color); + background: linear-gradient(135deg, var(--primary-color)08, var(--primary-color)04); +} + +.mode-card:hover { + border-color: var(--border-hover); + background: var(--bg-tertiary); +} + +.mode-card i { + font-size: 20px; + color: var(--primary-color); + width: 24px; + text-align: center; +} + +.mode-card h4 { + font-size: 14px; + font-weight: 600; + margin-bottom: 2px; +} + +.mode-card p { + font-size: 12px; + color: var(--text-secondary); +} + +/* PDF Panel */ +.pdf-panel { + background: var(--bg-tertiary); + border-radius: var(--radius-lg); + padding: 20px; +} + +.pdf-panel h3 { + font-size: 14px; + font-weight: 600; + margin-bottom: 16px; +} + +.pdf-list { + max-height: 300px; + overflow-y: auto; + display: flex; + flex-direction: column; + gap: 8px; +} + +.pdf-item { + display: flex; + align-items: center; + gap: 12px; + padding: 12px; + background: var(--bg-primary); + border-radius: var(--radius-md); + border: 1px solid var(--border-color); + transition: all 0.2s ease; + cursor: pointer; +} + +.pdf-item:hover { + border-color: var(--border-hover); + box-shadow: var(--shadow-sm); +} + +.pdf-item input[type="checkbox"] { + width: 16px; + height: 16px; + accent-color: var(--primary-color); +} + +.pdf-info { + flex: 1; + min-width: 0; +} + +.pdf-name { + font-size: 13px; + font-weight: 500; + margin-bottom: 4px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.pdf-details { + font-size: 11px; + color: var(--text-muted); + line-height: 1.4; +} + +.pdf-relevance { + display: flex; + align-items: center; + gap: 8px; + font-size: 11px; + font-weight: 500; +} + +.relevance-bar { + width: 40px; + height: 4px; + background: var(--border-color); + border-radius: 2px; + overflow: hidden; +} + +.relevance-fill { + height: 100%; + background: linear-gradient(90deg, var(--error-color), var(--warning-color), var(--success-color)); + transition: width 0.3s ease; +} + +.empty-state { + text-align: center; + padding: 20px; + color: var(--text-muted); + font-style: italic; +} + +.error-state { + text-align: center; + padding: 20px; + color: var(--error-color); + background: var(--error-color)08; + border-radius: var(--radius-md); +} + +/* Research History */ +.history-list { + display: flex; + flex-direction: column; + gap: 8px; +} + +.history-item { + display: flex; + align-items: center; + gap: 12px; + padding: 12px; + background: var(--bg-tertiary); + border-radius: var(--radius-md); + cursor: pointer; + transition: all 0.2s ease; + font-size: 13px; +} + +.history-item:hover { + background: var(--border-color); + transform: translateX(4px); +} + +.history-item i { + color: var(--text-muted); + font-size: 12px; +} + +/* Main Content */ +.main-content { + flex: 1; + margin-left: 320px; + padding: 40px; + max-width: calc(100vw - 320px); +} + +/* Search Container */ +.search-container { + max-width: 800px; + margin: 0 auto; + text-align: center; +} + +.search-header { + margin-bottom: 40px; +} + +.search-header h1 { + font-size: 32px; + font-weight: 700; + margin-bottom: 12px; + background: linear-gradient(135deg, var(--text-primary), var(--text-secondary)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.search-header p { + font-size: 16px; + color: var(--text-secondary); +} + +.search-box { + margin-bottom: 60px; +} + +.search-input-container { + position: relative; + background: var(--bg-primary); + border: 2px solid var(--border-color); + border-radius: var(--radius-xl); + padding: 4px; + transition: all 0.2s ease; + box-shadow: var(--shadow-sm); +} + +.search-input-container:focus-within { + border-color: var(--primary-color); + box-shadow: var(--shadow-md); +} + +.search-input-container textarea { + width: 100%; + border: none; + outline: none; + padding: 16px 20px; + font-size: 16px; + font-family: inherit; + resize: none; + background: transparent; + min-height: 24px; + max-height: 120px; +} + +.search-input-container textarea::placeholder { + color: var(--text-muted); +} + +.search-btn { + position: absolute; + right: 8px; + top: 50%; + transform: translateY(-50%); + width: 40px; + height: 40px; + background: var(--primary-color); + border: none; + border-radius: var(--radius-lg); + color: white; + cursor: pointer; + transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; +} + +.search-btn:hover { + background: var(--primary-hover); + transform: translateY(-50%) scale(1.05); +} + +.search-btn:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: translateY(-50%); +} + +.search-suggestions { + display: flex; + flex-wrap: wrap; + gap: 12px; + justify-content: center; + margin-top: 24px; +} + +.suggestion-chip { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 16px; + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: var(--radius-lg); + cursor: pointer; + transition: all 0.2s ease; + font-size: 13px; + font-weight: 500; +} + +.suggestion-chip:hover { + border-color: var(--primary-color); + background: linear-gradient(135deg, var(--primary-color)08, var(--primary-color)04); + transform: translateY(-1px); +} + +.suggestion-chip i { + font-size: 12px; + color: var(--primary-color); +} + +/* Loading Container */ +.loading-container { + max-width: 600px; + margin: 80px auto; + text-align: center; +} + +.loading-content { + background: var(--bg-primary); + border-radius: var(--radius-xl); + padding: 60px 40px; + box-shadow: var(--shadow-lg); +} + +.loading-animation { + position: relative; + width: 80px; + height: 80px; + margin: 0 auto 32px; +} + +.pulse-ring { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border: 3px solid var(--primary-color); + border-radius: 50%; + opacity: 0; + animation: pulse-ring 2s ease-out infinite; +} + +.pulse-ring.delay-1 { + animation-delay: 0.5s; +} + +.pulse-ring.delay-2 { + animation-delay: 1s; +} + +@keyframes pulse-ring { + 0% { + transform: scale(0.8); + opacity: 1; + } + 100% { + transform: scale(1.4); + opacity: 0; + } +} + +.loading-icon { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + font-size: 32px; + color: var(--primary-color); + animation: spin 2s linear infinite; +} + +@keyframes spin { + from { transform: translate(-50%, -50%) rotate(0deg); } + to { transform: translate(-50%, -50%) rotate(360deg); } +} + +.loading-content h3 { + font-size: 20px; + font-weight: 600; + margin-bottom: 24px; + color: var(--text-primary); +} + +/* Live Progress Feed */ +.progress-feed { + max-width: 500px; + margin: 32px auto 0; +} + +.progress-list { + display: flex; + flex-direction: column; + gap: 12px; + max-height: 300px; + overflow-y: auto; +} + +.progress-item { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 16px; + background: var(--bg-primary); + border-radius: var(--radius-lg); + border-left: 3px solid var(--primary-color); + opacity: 0; + transform: translateY(20px); + animation: slideInProgress 0.5s ease-out forwards; + box-shadow: var(--shadow-sm); +} + +@keyframes slideInProgress { + to { + opacity: 1; + transform: translateY(0); + } +} + +.progress-icon { + width: 24px; + height: 24px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + flex-shrink: 0; + background: var(--primary-color); + color: white; + animation: pulse 2s infinite; +} + +.progress-content { + flex: 1; + min-width: 0; +} + +.progress-message { + font-size: 14px; + font-weight: 500; + color: var(--text-primary); + margin-bottom: 2px; +} + +.progress-url { + font-size: 12px; + color: var(--text-muted); + text-decoration: none; + word-break: break-all; +} + +.progress-url:hover { + color: var(--primary-color); + text-decoration: underline; +} + +/* Results Container */ +.results-container { + max-width: 1000px; + margin: 0 auto; +} + +.results-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 32px; + padding-bottom: 24px; + border-bottom: 1px solid var(--border-color); +} + +.results-meta h2 { + font-size: 24px; + font-weight: 700; + margin-bottom: 12px; +} + +.results-info { + display: flex; + gap: 12px; + flex-wrap: wrap; +} + +.type-badge, +.time-badge, +.count-badge { + padding: 4px 12px; + border-radius: var(--radius-lg); + font-size: 12px; + font-weight: 500; +} + +.type-badge { + background: var(--primary-color); + color: white; +} + +.time-badge { + background: var(--bg-tertiary); + color: var(--text-secondary); +} + +.count-badge { + background: var(--success-color); + color: white; +} + +.results-actions { + display: flex; + gap: 8px; +} + +.action-btn { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 16px; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + cursor: pointer; + transition: all 0.2s ease; + font-size: 13px; + font-weight: 500; + color: var(--text-secondary); +} + +.action-btn:hover { + background: var(--border-color); + color: var(--text-primary); +} + +.action-btn.primary { + background: var(--primary-color); + color: white; + border-color: var(--primary-color); +} + +.action-btn.primary:hover { + background: var(--primary-hover); + color: white; +} + +/* Quick Summary */ +.quick-summary { + margin-bottom: 32px; +} + +.summary-card { + background: linear-gradient(135deg, var(--primary-color)08, var(--accent-color)08); + border: 1px solid var(--primary-color)20; + border-radius: var(--radius-xl); + padding: 24px; +} + +.summary-card h3 { + font-size: 18px; + font-weight: 600; + margin-bottom: 16px; + color: var(--text-primary); +} + +/* Entities Grid */ +.entities-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); + gap: 24px; + margin-bottom: 32px; +} + +.entity-card { + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: var(--radius-xl); + padding: 24px; + transition: all 0.2s ease; + box-shadow: var(--shadow-sm); +} + +.entity-card:hover { + box-shadow: var(--shadow-md); + transform: translateY(-2px); +} + +.entity-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 16px; +} + +.entity-name { + font-size: 18px; + font-weight: 700; + margin-bottom: 4px; + color: var(--text-primary); +} + +.entity-website { + font-size: 13px; + color: var(--primary-color); + text-decoration: none; + font-weight: 500; +} + +.entity-website:hover { + text-decoration: underline; +} + +.entity-type { + padding: 4px 12px; + border-radius: var(--radius-lg); + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.entity-description { + color: var(--text-secondary); + margin-bottom: 20px; + line-height: 1.6; + font-size: 14px; +} + +.entity-details { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + gap: 16px; +} + +.detail-item { + display: flex; + flex-direction: column; + gap: 4px; +} + +.detail-label { + font-size: 11px; + font-weight: 600; + color: var(--text-secondary); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.detail-value { + font-size: 13px; + font-weight: 500; + color: var(--text-primary); +} + +.detail-list { + display: flex; + flex-wrap: wrap; + gap: 4px; +} + +.detail-tag { + background: var(--bg-tertiary); + color: var(--text-secondary); + padding: 2px 8px; + border-radius: var(--radius-sm); + font-size: 11px; + font-weight: 500; +} + +/* Analysis Section */ +.analysis-section { + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: var(--radius-xl); + padding: 24px; + box-shadow: var(--shadow-sm); +} + +.analysis-header h3 { + font-size: 18px; + font-weight: 600; + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 20px; +} + +.analysis-content { + color: var(--text-secondary); + line-height: 1.7; + font-size: 14px; + white-space: pre-wrap; +} + +/* Error Container */ +.error-container { + max-width: 500px; + margin: 80px auto; + text-align: center; +} + +.error-content { + background: var(--bg-primary); + border: 1px solid var(--error-color)20; + border-radius: var(--radius-xl); + padding: 40px; + box-shadow: var(--shadow-lg); +} + +.error-icon { + width: 64px; + height: 64px; + background: var(--error-color)10; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + margin: 0 auto 20px; + color: var(--error-color); + font-size: 24px; +} + +.error-content h3 { + font-size: 20px; + font-weight: 600; + margin-bottom: 12px; + color: var(--text-primary); +} + +.error-content p { + color: var(--text-secondary); + margin-bottom: 24px; + line-height: 1.6; +} + +.retry-btn { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 12px 24px; + background: var(--primary-color); + color: white; + border: none; + border-radius: var(--radius-lg); + cursor: pointer; + font-weight: 500; + transition: all 0.2s ease; +} + +.retry-btn:hover { + background: var(--primary-hover); + transform: translateY(-1px); +} + +/* Responsive Design */ +@media (max-width: 1024px) { + .sidebar { + transform: translateX(-100%); + transition: transform 0.3s ease; + } + + .main-content { + margin-left: 0; + max-width: 100vw; + padding: 20px; + } + + .entities-grid { + grid-template-columns: 1fr; + } + + .results-header { + flex-direction: column; + align-items: flex-start; + gap: 16px; + } +} + +@media (max-width: 768px) { + .nav-container { + padding: 0 16px; + } + + .search-header h1 { + font-size: 24px; + } + + .search-suggestions { + flex-direction: column; + align-items: center; + } +} \ No newline at end of file diff --git a/advanced-agent/static/js/app.js b/advanced-agent/static/js/app.js new file mode 100644 index 0000000..945309d --- /dev/null +++ b/advanced-agent/static/js/app.js @@ -0,0 +1,656 @@ +// Modern Multi Agent Researcher UI - Perplexity Style + +class ModernResearchAgent { + constructor() { + this.currentResearch = null; + this.selectedPdfs = []; + this.currentQuery = ''; + this.init(); + } + + init() { + this.bindEvents(); + this.checkStatus(); + this.setupAutoResize(); + this.initDarkMode(); + } + + bindEvents() { + // Dark mode toggle + document.getElementById('dark-mode-btn').addEventListener('click', () => { + this.toggleDarkMode(); + }); + + // Search functionality + document.getElementById('research-btn').addEventListener('click', () => { + this.startResearch(); + }); + + // Auto-resize textarea + const textarea = document.getElementById('query'); + textarea.addEventListener('input', (e) => { + this.autoResize(e.target); + this.currentQuery = e.target.value.trim(); + }); + + // Enter key to search + textarea.addEventListener('keydown', (e) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + this.startResearch(); + } + }); + + // Research mode changes + document.querySelectorAll('input[name="research-mode"]').forEach(radio => { + radio.addEventListener('change', (e) => { + this.handleModeChange(e.target.value); + }); + }); + + // Suggestion chips + document.querySelectorAll('.suggestion-chip').forEach(chip => { + chip.addEventListener('click', (e) => { + const query = e.currentTarget.dataset.query; + document.getElementById('query').value = query; + this.currentQuery = query; + this.autoResize(document.getElementById('query')); + }); + }); + + // New search button + document.getElementById('new-search-btn').addEventListener('click', () => { + this.startNewSearch(); + }); + + // Export and share buttons + document.getElementById('export-btn').addEventListener('click', () => { + this.exportResults(); + }); + + document.getElementById('share-btn').addEventListener('click', () => { + this.shareResults(); + }); + + // Retry button + document.getElementById('retry-btn').addEventListener('click', () => { + this.startResearch(); + }); + } + + setupAutoResize() { + const textarea = document.getElementById('query'); + this.autoResize(textarea); + } + + autoResize(textarea) { + textarea.style.height = 'auto'; + textarea.style.height = Math.min(textarea.scrollHeight, 120) + 'px'; + } + + async checkStatus() { + try { + const response = await fetch('/api/status'); + const data = await response.json(); + + const statusIndicator = document.getElementById('status-indicator'); + const statusDot = statusIndicator.querySelector('.status-dot'); + const statusText = statusIndicator.querySelector('span'); + + if (data.workflow_ready) { + statusDot.style.background = 'var(--success-color)'; + statusText.textContent = 'Ready'; + } else { + statusDot.style.background = 'var(--error-color)'; + statusText.textContent = 'Error'; + this.showError('Research agent is not ready. Please check the configuration.'); + document.getElementById('research-btn').disabled = true; + } + } catch (error) { + console.error('Error checking status:', error); + } + } + + async handleModeChange(mode) { + const pdfPanel = document.getElementById('pdf-panel'); + + if (mode === 'select_pdfs') { + pdfPanel.style.display = 'block'; + await this.loadAllPdfs(); + } else if (mode === 'relevant_pdfs') { + pdfPanel.style.display = 'block'; + if (this.currentQuery) { + await this.loadRelevantPdfs(this.currentQuery); + } else { + document.getElementById('pdf-list').innerHTML = + '
No key insights available
'; + return; + } + + const topEntities = result.entities.slice(0, 3); + const insights = topEntities.map(entity => `⢠${entity.name}`).join('Top Results:
+Ask anything about companies, products, technologies, or markets
+