| {% extends "base.html" %} |
|
|
| {% block content %} |
| <div class="min-h-screen bg-slate-50"> |
| |
| <div class="bg-gradient-to-r from-indigo-600 to-purple-600 text-white py-12"> |
| <div class="max-w-7xl mx-auto px-8"> |
| <div class="flex items-center justify-between"> |
| <div> |
| <h1 class="text-4xl font-black uppercase italic mb-2"> |
| <i class="fas fa-robot mr-3"></i>Agentic AI Dashboard |
| </h1> |
| <p class="text-indigo-100 font-medium">Autonomous Intelligence for Research Discovery</p> |
| </div> |
| <div class="text-right"> |
| <div class="text-5xl font-black">{{ total_profiles }}</div> |
| <div class="text-sm text-indigo-200 uppercase tracking-wider">Indexed Profiles</div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="max-w-7xl mx-auto px-8 py-8"> |
| |
| <div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8"> |
| |
| <div class="bg-white rounded-2xl p-6 shadow-sm border border-slate-200"> |
| <div class="flex items-center justify-between mb-4"> |
| <h3 class="text-xs font-black text-slate-400 uppercase tracking-widest">Autonomous Discovery</h3> |
| <span class="w-3 h-3 rounded-full bg-green-500 animate-pulse"></span> |
| </div> |
| |
| <form id="discoveryForm" class="space-y-4"> |
| <div> |
| <label class="block text-xs font-bold text-slate-600 mb-2">Research Domain</label> |
| <input type="text" id="discoveryQuery" |
| placeholder="e.g., quantum computing" |
| class="w-full px-4 py-3 border-2 border-slate-200 rounded-xl focus:border-indigo-500 outline-none text-sm"> |
| </div> |
| |
| <div> |
| <label class="block text-xs font-bold text-slate-600 mb-2">Max Profiles: <span id="maxProfilesValue">20</span></label> |
| <input type="range" id="maxProfiles" min="5" max="50" value="20" |
| class="w-full h-2 bg-slate-200 rounded-lg appearance-none cursor-pointer" |
| oninput="document.getElementById('maxProfilesValue').textContent = this.value"> |
| </div> |
| |
| <button type="submit" |
| class="w-full bg-indigo-600 text-white py-3 rounded-xl font-bold hover:bg-indigo-700 transition-all active:scale-95"> |
| <i class="fas fa-rocket mr-2"></i>Launch Discovery |
| </button> |
| </form> |
| |
| <div id="discoveryStatus" class="mt-4 p-3 bg-slate-50 rounded-lg text-xs hidden"> |
| <div class="flex items-center"> |
| <i class="fas fa-spinner fa-spin text-indigo-600 mr-2"></i> |
| <span class="font-medium text-slate-700">Discovering...</span> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="bg-white rounded-2xl p-6 shadow-sm border border-slate-200"> |
| <div class="flex items-center justify-between mb-4"> |
| <h3 class="text-xs font-black text-slate-400 uppercase tracking-widest">Semantic Search</h3> |
| <i class="fas fa-search text-indigo-400"></i> |
| </div> |
| |
| <form id="searchForm" class="space-y-4"> |
| <div> |
| <label class="block text-xs font-bold text-slate-600 mb-2">Search Query</label> |
| <input type="text" id="searchQuery" |
| placeholder="e.g., neural networks experts" |
| class="w-full px-4 py-3 border-2 border-slate-200 rounded-xl focus:border-indigo-500 outline-none text-sm"> |
| </div> |
| |
| <button type="submit" |
| class="w-full bg-purple-600 text-white py-3 rounded-xl font-bold hover:bg-purple-700 transition-all active:scale-95"> |
| <i class="fas fa-search mr-2"></i>Search Profiles |
| </button> |
| </form> |
| |
| <div id="searchResults" class="mt-4 space-y-2 max-h-32 overflow-y-auto hidden"> |
| |
| </div> |
| </div> |
|
|
| |
| <div class="bg-white rounded-2xl p-6 shadow-sm border border-slate-200"> |
| <div class="flex items-center justify-between mb-4"> |
| <h3 class="text-xs font-black text-slate-400 uppercase tracking-widest">System Status</h3> |
| <i class="fas fa-chart-line text-green-400"></i> |
| </div> |
| |
| <div class="space-y-4"> |
| <div class="flex items-center justify-between py-3 border-b border-slate-100"> |
| <span class="text-xs font-bold text-slate-600">Vector Store</span> |
| <span class="text-xs font-black text-indigo-600">{{ rag_stats.vector_store_type or 'InMemory' }}</span> |
| </div> |
| |
| <div class="flex items-center justify-between py-3 border-b border-slate-100"> |
| <span class="text-xs font-bold text-slate-600">Embedding Model</span> |
| <span class="text-[10px] font-medium text-slate-500">MiniLM-L6</span> |
| </div> |
| |
| <div class="flex items-center justify-between py-3 border-b border-slate-100"> |
| <span class="text-xs font-bold text-slate-600">LLM Model</span> |
| <span class="text-[10px] font-medium text-slate-500">Llama-3-8B</span> |
| </div> |
| |
| <div class="flex items-center justify-between py-3"> |
| <span class="text-xs font-bold text-slate-600">Status</span> |
| <span class="text-xs font-black text-green-600"> |
| <i class="fas fa-check-circle mr-1"></i>Active |
| </span> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="bg-white rounded-2xl p-8 shadow-sm border border-slate-200"> |
| <div class="flex items-center justify-between mb-6"> |
| <h2 class="text-xl font-black text-slate-900 uppercase italic"> |
| <i class="fas fa-database text-indigo-600 mr-3"></i>Indexed Profiles |
| </h2> |
| <div class="flex items-center gap-4"> |
| <input type="text" id="filterProfiles" |
| placeholder="Filter by name..." |
| class="px-4 py-2 border-2 border-slate-200 rounded-xl text-sm outline-none focus:border-indigo-500"> |
| <button onclick="refreshProfiles()" |
| class="px-4 py-2 bg-slate-100 rounded-xl text-xs font-bold hover:bg-slate-200 transition-colors"> |
| <i class="fas fa-sync-alt mr-1"></i>Refresh |
| </button> |
| </div> |
| </div> |
|
|
| {% if profiles %} |
| <div class="overflow-x-auto"> |
| <table class="w-full" id="profilesTable"> |
| <thead> |
| <tr class="border-b-2 border-slate-200"> |
| <th class="text-left py-4 px-4 text-xs font-black text-slate-400 uppercase tracking-wider">Researcher</th> |
| <th class="text-left py-4 px-4 text-xs font-black text-slate-400 uppercase tracking-wider">Affiliation</th> |
| <th class="text-center py-4 px-4 text-xs font-black text-slate-400 uppercase tracking-wider">H-Index</th> |
| <th class="text-center py-4 px-4 text-xs font-black text-slate-400 uppercase tracking-wider">Citations</th> |
| <th class="text-center py-4 px-4 text-xs font-black text-slate-400 uppercase tracking-wider">Papers</th> |
| <th class="text-left py-4 px-4 text-xs font-black text-slate-400 uppercase tracking-wider">Interests</th> |
| <th class="text-center py-4 px-4 text-xs font-black text-slate-400 uppercase tracking-wider">Actions</th> |
| </tr> |
| </thead> |
| <tbody> |
| {% for profile in profiles %} |
| <tr class="border-b border-slate-100 hover:bg-slate-50 transition-colors"> |
| <td class="py-4 px-4"> |
| <div class="font-bold text-slate-900">{{ profile.name }}</div> |
| <div class="text-[10px] text-slate-400 uppercase">{{ profile.source }}</div> |
| </td> |
| <td class="py-4 px-4 text-sm text-slate-600">{{ profile.affiliation }}</td> |
| <td class="py-4 px-4 text-center"> |
| <span class="inline-block bg-indigo-100 text-indigo-700 px-3 py-1 rounded-full text-xs font-bold"> |
| {{ profile.h_index }} |
| </span> |
| </td> |
| <td class="py-4 px-4 text-center text-sm font-bold text-slate-700"> |
| {{ "{:,}".format(profile.total_citations) }} |
| </td> |
| <td class="py-4 px-4 text-center text-sm font-bold text-slate-700"> |
| {{ profile.total_papers }} |
| </td> |
| <td class="py-4 px-4"> |
| <div class="flex flex-wrap gap-1"> |
| {% for interest in profile.interests[:3] %} |
| <span class="inline-block bg-slate-100 text-slate-600 px-2 py-1 rounded text-[10px] font-medium"> |
| {{ interest }} |
| </span> |
| {% endfor %} |
| </div> |
| </td> |
| <td class="py-4 px-4 text-center"> |
| <a href="{{ profile.profile_url }}" target="_blank" |
| class="inline-block bg-indigo-600 text-white px-3 py-2 rounded-lg text-xs font-bold hover:bg-indigo-700 transition-colors"> |
| <i class="fas fa-external-link-alt mr-1"></i>View |
| </a> |
| </td> |
| </tr> |
| {% endfor %} |
| </tbody> |
| </table> |
| </div> |
| {% else %} |
| <div class="text-center py-20"> |
| <div class="w-20 h-20 bg-slate-100 rounded-full flex items-center justify-center mx-auto mb-4"> |
| <i class="fas fa-inbox text-3xl text-slate-300"></i> |
| </div> |
| <h3 class="text-xl font-bold text-slate-800 mb-2">No Profiles Indexed</h3> |
| <p class="text-slate-500 text-sm mb-6">Launch autonomous discovery to start collecting researcher profiles</p> |
| <button onclick="document.getElementById('discoveryQuery').focus()" |
| class="bg-indigo-600 text-white px-6 py-3 rounded-xl font-bold hover:bg-indigo-700 transition-all"> |
| Start Discovery |
| </button> |
| </div> |
| {% endif %} |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| |
| document.getElementById('discoveryForm').addEventListener('submit', async (e) => { |
| e.preventDefault(); |
| |
| const query = document.getElementById('discoveryQuery').value; |
| const maxProfiles = document.getElementById('maxProfiles').value; |
| const statusDiv = document.getElementById('discoveryStatus'); |
| |
| if (!query) { |
| alert('Please enter a research domain'); |
| return; |
| } |
| |
| statusDiv.classList.remove('hidden'); |
| |
| try { |
| const response = await fetch('/api/agentic/discover', { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json' |
| }, |
| body: JSON.stringify({ |
| query: query, |
| max_profiles: parseInt(maxProfiles) |
| }) |
| }); |
| |
| const result = await response.json(); |
| |
| if (result.success) { |
| statusDiv.innerHTML = ` |
| <div class="flex items-center justify-between"> |
| <div class="flex items-center"> |
| <i class="fas fa-check-circle text-green-600 mr-2"></i> |
| <span class="font-medium text-slate-700">Discovery complete!</span> |
| </div> |
| <span class="font-black text-indigo-600">${result.profiles_collected} profiles</span> |
| </div> |
| `; |
| |
| |
| setTimeout(() => { |
| window.location.reload(); |
| }, 2000); |
| } else { |
| throw new Error(result.message || 'Discovery failed'); |
| } |
| |
| } catch (error) { |
| statusDiv.innerHTML = ` |
| <div class="flex items-center"> |
| <i class="fas fa-exclamation-circle text-red-600 mr-2"></i> |
| <span class="font-medium text-red-700">Error: ${error.message}</span> |
| </div> |
| `; |
| } |
| }); |
| |
| |
| document.getElementById('searchForm').addEventListener('submit', async (e) => { |
| e.preventDefault(); |
| |
| const query = document.getElementById('searchQuery').value; |
| const resultsDiv = document.getElementById('searchResults'); |
| |
| if (!query) { |
| alert('Please enter a search query'); |
| return; |
| } |
| |
| resultsDiv.innerHTML = '<div class="text-xs text-slate-500">Searching...</div>'; |
| resultsDiv.classList.remove('hidden'); |
| |
| try { |
| const response = await fetch(`/api/agentic/search?q=${encodeURIComponent(query)}&k=5`); |
| const result = await response.json(); |
| |
| if (result.error) { |
| throw new Error(result.error); |
| } |
| |
| if (result.results && result.results.length > 0) { |
| resultsDiv.innerHTML = result.results.map((profile, i) => ` |
| <div class="p-3 bg-slate-50 rounded-lg"> |
| <div class="text-xs font-bold text-slate-900">${i + 1}. ${profile.name}</div> |
| <div class="text-[10px] text-slate-500">${profile.affiliation}</div> |
| </div> |
| `).join(''); |
| } else { |
| resultsDiv.innerHTML = '<div class="text-xs text-slate-500">No results found</div>'; |
| } |
| |
| } catch (error) { |
| resultsDiv.innerHTML = `<div class="text-xs text-red-600">Error: ${error.message}</div>`; |
| } |
| }); |
| |
| |
| document.getElementById('filterProfiles')?.addEventListener('input', (e) => { |
| const filter = e.target.value.toLowerCase(); |
| const rows = document.querySelectorAll('#profilesTable tbody tr'); |
| |
| rows.forEach(row => { |
| const name = row.cells[0].textContent.toLowerCase(); |
| if (name.includes(filter)) { |
| row.style.display = ''; |
| } else { |
| row.style.display = 'none'; |
| } |
| }); |
| }); |
| |
| |
| function refreshProfiles() { |
| window.location.reload(); |
| } |
| </script> |
| {% endblock %} |