Spaces:
Sleeping
Sleeping
<html> | |
<head> | |
<title>Patent Explorer</title> | |
<meta charset="utf-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css" rel="stylesheet"> | |
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> | |
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script> | |
<style> | |
.visualization-container { | |
width: 100%; | |
height: 80vh; | |
margin-bottom: 20px; | |
background-color: #1a1a1a; | |
border-radius: 8px; | |
overflow: hidden; | |
} | |
.insights-panel { | |
background-color: #2d2d2d; | |
border-radius: 8px; | |
height: calc(100vh - 80px); | |
overflow-y: auto; | |
transition: all 0.3s ease; | |
padding: 2rem; | |
} | |
.cluster-card { | |
background-color: #3d3d3d; | |
border-radius: 6px; | |
margin-bottom: 10px; | |
transition: all 0.2s ease; | |
} | |
.cluster-card:hover { | |
transform: translateY(-2px); | |
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
} | |
.opportunity-card { | |
background-color: #2d4a3e; | |
border-radius: 6px; | |
margin-bottom: 10px; | |
transition: all 0.2s ease; | |
} | |
.opportunity-card:hover { | |
transform: translateY(-2px); | |
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
} | |
.loading-container { | |
background-color: #2d2d2d; | |
border-radius: 8px; | |
padding: 1.5rem; | |
margin-bottom: 1rem; | |
} | |
.progress-bar { | |
width: 100%; | |
height: 8px; | |
background-color: #4a5568; | |
border-radius: 4px; | |
margin-bottom: 1rem; | |
overflow: hidden; | |
} | |
.progress-fill { | |
height: 100%; | |
width: 0; | |
background-color: #4299e1; | |
transition: width 0.3s ease; | |
} | |
.status-list { | |
max-height: 150px; | |
overflow-y: auto; | |
} | |
.status-item { | |
display: flex; | |
align-items: center; | |
margin-bottom: 0.75rem; | |
padding: 0.5rem; | |
border-radius: 4px; | |
background-color: #374151; | |
opacity: 0.6; | |
transition: all 0.3s ease; | |
} | |
.status-item.active { | |
opacity: 1; | |
background-color: #3b4f7d; | |
} | |
.status-item.complete { | |
opacity: 0.8; | |
background-color: #2d4a3e; | |
} | |
.status-icon { | |
width: 24px; | |
height: 24px; | |
margin-right: 12px; | |
border-radius: 50%; | |
display: inline-flex; | |
align-items: center; | |
justify-content: center; | |
font-size: 12px; | |
font-weight: bold; | |
} | |
.status-text { | |
flex-grow: 1; | |
} | |
.status-time { | |
font-size: 0.8rem; | |
color: #9ca3af; | |
margin-left: 8px; | |
} | |
.status-pending { | |
background-color: #4a5568; | |
} | |
.status-processing { | |
background-color: #4299e1; | |
animation: pulse 1.5s infinite; | |
} | |
.status-complete { | |
background-color: #48bb78; | |
} | |
@keyframes pulse { | |
0% { opacity: 0.6; } | |
50% { opacity: 1; } | |
100% { opacity: 0.6; } | |
} | |
/* Custom scrollbar for insights panel */ | |
.insights-panel::-webkit-scrollbar { | |
width: 8px; | |
} | |
.insights-panel::-webkit-scrollbar-track { | |
background: #1a1a1a; | |
border-radius: 4px; | |
} | |
.insights-panel::-webkit-scrollbar-thumb { | |
background: #4a4a4a; | |
border-radius: 4px; | |
} | |
.insights-panel::-webkit-scrollbar-thumb:hover { | |
background: #555; | |
} | |
/* Add responsive sizing for different screen sizes */ | |
@media (max-width: 768px) { | |
.visualization-container { | |
height: 60vh; | |
} | |
} | |
@media (max-width: 480px) { | |
.visualization-container { | |
height: 50vh; | |
} | |
} | |
@media (min-width: 1920px) { | |
.visualization-container { | |
height: 85vh; | |
} | |
} | |
.innovation-analysis { | |
background-color: #1c2431; | |
border-radius: 8px; | |
padding: 1.5rem; | |
margin-bottom: 2rem; | |
border: 1px solid #4a5568; | |
} | |
.instructions-panel { | |
background-color: #2d3748; | |
border-radius: 8px; | |
padding: 1rem; | |
margin-bottom: 1rem; | |
border-left: 4px solid #4299e1; | |
} | |
.legend-item { | |
display: inline-flex; | |
align-items: center; | |
margin-right: 1.5rem; | |
margin-bottom: 0.5rem; | |
} | |
.legend-dot { | |
width: 12px; | |
height: 12px; | |
border-radius: 50%; | |
margin-right: 0.5rem; | |
} | |
</style> | |
</head> | |
<body class="bg-gray-900 text-gray-100 min-h-screen"> | |
<div class="container mx-auto px-4 py-8"> | |
<h1 class="text-4xl font-bold text-center text-blue-400 mb-8">Patent Technology Landscape</h1> | |
<!-- Search Form --> | |
<div class="max-w-2xl mx-auto mb-8"> | |
<form id="searchForm" class="bg-gray-800 shadow-lg rounded px-8 pt-6 pb-8 mb-4"> | |
<div class="mb-4"> | |
<input type="text" id="keywords" name="keywords" | |
class="w-full bg-gray-700 text-white rounded border border-gray-600 focus:border-blue-500 focus:ring-2 focus:ring-blue-900 py-2 px-4" | |
placeholder="Enter keywords to explore patent landscape..."> | |
</div> | |
<div class="flex items-center justify-center"> | |
<button type="submit" | |
class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-6 rounded focus:outline-none focus:shadow-outline transform hover:scale-105 transition-transform duration-200"> | |
Explore | |
</button> | |
</div> | |
</form> | |
</div> <!-- Instructions Panel --> | |
<div id="instructions-panel" class="instructions-panel hidden"> | |
<h3 class="text-lg font-semibold text-blue-300 mb-3">📍 Interactive Visualization Guide</h3> | |
<div class="text-gray-300 mb-3"> | |
<p class="mb-2"><strong>Click any point</strong> to open the corresponding Google Patents page in a new tab.</p> | |
<p class="mb-3"><strong>Hover over points</strong> to see detailed patent information including title, assignee, year, and abstract.</p> | |
</div> | |
<div class="flex flex-wrap items-center"> | |
<span class="text-sm font-medium text-gray-400 mr-3">Legend:</span> | |
<div class="legend-item"> | |
<div class="legend-dot" style="background-color: #636EFA;"></div> | |
<span class="text-sm">Technology Clusters</span> | |
</div> | |
</div> | |
</div> | |
<!-- Loading Status --> | |
<div id="loading" class="loading-container hidden"> | |
<div class="mb-4"> | |
<div class="flex justify-between items-center mb-2"> | |
<span class="text-sm font-medium" id="progress-text">Initializing...</span> | |
<span class="text-sm font-medium" id="progress-percentage">0%</span> | |
</div> | |
<div class="progress-bar"> | |
<div class="progress-fill" id="progress-fill"></div> | |
</div> | |
</div> | |
<div class="status-list"> | |
<div class="status-item" data-step="search"> | |
<div class="status-icon status-pending">1</div> | |
<div class="status-text">Searching for patents</div> | |
<div class="status-time"></div> | |
</div> | |
<div class="status-item" data-step="embedding"> | |
<div class="status-icon status-pending">2</div> | |
<div class="status-text">Generating patent embeddings</div> | |
<div class="status-time"></div> | |
</div> | |
<div class="status-item" data-step="clustering"> | |
<div class="status-icon status-pending">3</div> | |
<div class="status-text">Analyzing & clustering patents</div> | |
<div class="status-time"></div> | |
</div> | |
<div class="status-item" data-step="visualization"> | |
<div class="status-icon status-pending">4</div> | |
<div class="status-text">Creating visualization</div> | |
<div class="status-time"></div> | |
</div> | |
</div> | |
</div> | |
<!-- Visualization Container --> | |
<div id="visualization-section" class="relative"> | |
<div id="visualization" class="visualization-container"></div> | |
<div class="absolute top-4 right-4 flex gap-2"> | |
<a id="download-plot" href="/download_plot" class="hidden px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors duration-200 flex items-center"> | |
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> | |
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"></path> | |
</svg> | |
Download Plot | |
</a> | |
<a id="download-insights" href="/download_insights" class="hidden px-4 py-2 bg-green-600 text-white rounded hover:bg-green-700 transition-colors duration-200 flex items-center"> | |
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> | |
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M9 19l3 3m0 0l3-3m-3 3V10"></path> | |
</svg> | |
Download Insights | |
</a> | |
</div> | |
</div> | |
<!-- Insights Panel --> | |
<div id="insights" class="insights-panel p-4"></div> | |
</div> | |
<script> | |
let progressEventSource = null; | |
function startProgressMonitoring() { | |
if (progressEventSource) { | |
progressEventSource.close(); | |
} | |
progressEventSource = new EventSource('/progress'); | |
progressEventSource.onmessage = function(event) { | |
try { | |
const data = JSON.parse(event.data); | |
if (data.step === 'alive') { | |
return; // Ignore keep-alive messages | |
} | |
updateProgress(data.step, data.status, event); | |
} catch (e) { | |
console.error('Error processing progress update:', e); | |
} | |
}; | |
progressEventSource.onerror = function(e) { | |
console.error('SSE Error:', e); | |
if (progressEventSource.readyState === EventSource.CLOSED) { | |
progressEventSource.close(); | |
} | |
}; | |
} | |
function stopProgressMonitoring() { | |
if (progressEventSource) { | |
progressEventSource.close(); | |
progressEventSource = null; | |
} | |
} | |
function updateProgress(step, status) { | |
const steps = ['search', 'embedding', 'clustering', 'visualization']; | |
const stepIndex = steps.indexOf(step); | |
let progress; | |
if (step === 'complete') { | |
progress = 100; | |
} else if (step === 'alive') { | |
// Don't update progress for keep-alive messages | |
return; | |
} else { | |
const stepsTotal = steps.length; | |
// Calculate base progress for completed steps | |
const baseProgress = (stepIndex / stepsTotal) * 100; | |
// Calculate progress within current step | |
const stepProgress = (1 / stepsTotal) * 100; | |
if (status === 'processing') { | |
// Add partial progress within current step | |
progress = Math.min(100, baseProgress + (stepProgress * 0.5)); | |
} else { | |
progress = baseProgress; | |
} | |
} | |
// Update progress bar with smooth animation | |
$('#progress-fill').css({ | |
'width': `${progress}%`, | |
'transition': 'width 0.3s ease-in-out' | |
}); | |
$('#progress-percentage').text(`${Math.round(progress)}%`); | |
// Update status text with more detailed messages | |
const statusTexts = { | |
'search': 'Searching patent database...', | |
'embedding': 'Generating patent embeddings...', | |
'clustering': 'Analyzing patent relationships...', | |
'visualization': 'Creating visualization...', | |
'complete': 'Analysis complete!' | |
}; | |
// Use message from backend if provided, otherwise use default text | |
const text = event && event.data ? | |
JSON.parse(event.data).message || statusTexts[step] || 'Processing...' : | |
statusTexts[step] || 'Processing...'; | |
$('#progress-text').text(text); | |
// Update status items | |
steps.forEach((s, i) => { | |
const item = $(`.status-item[data-step="${s}"]`); | |
const time = item.find('.status-time'); | |
if (step === 'complete' || i < stepIndex || (i === stepIndex && status === 'done')) { | |
item.removeClass('active').addClass('complete'); | |
if (!time.text() || time.text() === 'In progress...') { | |
time.text(new Date().toLocaleTimeString()); | |
} | |
} else if (i === stepIndex) { | |
item.addClass('active').removeClass('complete'); | |
time.text('In progress...'); | |
} else { | |
item.removeClass('active complete'); | |
time.text(''); | |
} | |
}); | |
} | |
function showDownloadButtons() { | |
$('#download-plot').removeClass('hidden'); | |
$('#download-insights').removeClass('hidden'); | |
} | |
function hideDownloadButtons() { | |
$('#download-plot').addClass('hidden'); | |
$('#download-insights').addClass('hidden'); | |
} | |
function showInstructions() { | |
$('#instructions-panel').removeClass('hidden'); | |
} | |
function hideInstructions() { | |
$('#instructions-panel').addClass('hidden'); | |
} | |
$(document).ready(function() { | |
hideDownloadButtons(); // Initially hide download buttons | |
hideInstructions(); // Initially hide instructions | |
$('#searchForm').on('submit', function(e) { | |
e.preventDefault(); | |
hideDownloadButtons(); // Hide buttons when starting new search | |
const keywords = $('#keywords').val(); | |
if (!keywords) { | |
alert('Please enter search keywords'); | |
return; | |
} | |
console.log('Starting search with keywords:', keywords); | |
// Reset and show loading status | |
$('#loading').removeClass('hidden'); | |
$('#visualization').empty(); | |
$('#insights').empty(); | |
$('.progress-fill').css('width', '0%'); | |
$('#progress-percentage').text('0%'); | |
$('#progress-text').text('Initializing...'); | |
$('.status-item').removeClass('active complete'); | |
$('.status-time').text(''); | |
// Start progress monitoring | |
startProgressMonitoring(); | |
$.ajax({ | |
url: '/search', | |
method: 'POST', | |
data: { keywords: keywords }, | |
success: function(response) { | |
console.log('Received response:', response); | |
if (response.error) { | |
console.error('Search error:', response.error); | |
stopProgressMonitoring(); | |
$('#loading').addClass('hidden'); | |
alert(response.error); | |
return; | |
} | |
// Reset visualization and insights | |
$('#visualization').empty(); | |
$('#insights').empty(); | |
$('#innovation-analysis').empty(); | |
// Display visualization | |
if (response.visualization) { | |
console.log('Creating visualization...', response.visualization.slice(0, 100) + '...'); | |
try { | |
const vizData = JSON.parse(response.visualization); | |
Plotly.newPlot('visualization', vizData.data, vizData.layout); | |
const vizElement = document.getElementById('visualization'); | |
vizElement.on('plotly_click', function(data) { | |
if (data.points && data.points[0] && data.points[0].customdata) { | |
const link = data.points[0].customdata; | |
window.open(link, '_blank'); | |
} | |
}); | |
console.log('Visualization created successfully'); | |
// Show instructions once visualization is loaded | |
showInstructions(); | |
} catch (e) { | |
console.error('Error creating visualization:', e); | |
$('#visualization').html('<p class="text-red-500">Error creating visualization</p>'); | |
} | |
} else { | |
console.warn('No visualization data received'); | |
$('#visualization').html('<p class="text-yellow-500">No visualization data available</p>'); | |
} | |
// Display insights | |
if (response.insights) { | |
console.log('Displaying insights...'); | |
const clusters = response.insights.filter(i => i.type === 'cluster'); | |
console.log('Found clusters:', clusters.length); | |
// Start with Innovation Analysis | |
let insightsHtml = ''; | |
if (response.innovationAnalysis) { | |
insightsHtml += ` | |
<div class="bg-gray-800 p-6 mb-6 rounded-lg"> | |
<h3 class="text-2xl font-bold mb-4 text-blue-300">Innovation Opportunities Analysis</h3> | |
<div class="text-gray-300 whitespace-pre-line"> | |
${response.innovationAnalysis} | |
</div> | |
</div> | |
`; | |
} | |
// Technology Clusters section | |
insightsHtml += '<div class="p-6">'; | |
insightsHtml += '<h3 class="text-2xl font-bold mb-4 text-blue-400">Technology Clusters</h3>'; | |
if (clusters.length > 0) { | |
insightsHtml += '<div class="space-y-4">'; | |
clusters.forEach(cluster => { | |
insightsHtml += ` | |
<div class="cluster-card p-6 text-base"> | |
<div class="text-blue-300 text-lg font-bold mb-3">${cluster.label} (${cluster.size} patents)</div> | |
<div class="text-gray-300 whitespace-pre-line leading-relaxed">${cluster.description}</div> | |
</div> | |
`; | |
}); | |
insightsHtml += '</div>'; | |
} else { | |
insightsHtml += '<p class="text-gray-400">No technology clusters identified.</p>'; | |
} | |
insightsHtml += '</div>'; | |
$('#insights').html(insightsHtml); | |
} else { | |
console.warn('No insights data received'); | |
} | |
// Innovation Analysis Section | |
if (response.innovationAnalysis) { | |
console.log('Displaying innovation analysis...'); | |
$('#innovation-analysis').html(` | |
<div class="text-gray-300 whitespace-pre-line bg-gray-800 p-4 rounded-lg mt-6"> | |
${response.innovationAnalysis} | |
</div> | |
`); | |
} else { | |
console.warn('No innovation analysis data received'); | |
$('#innovation-analysis').html('<p class="text-gray-400 mt-6">No innovation analysis available.</p>'); | |
} | |
// Stop progress monitoring and hide loading status | |
console.log('Search completed successfully'); | |
stopProgressMonitoring(); | |
$('#loading').addClass('hidden'); | |
// Show download buttons | |
showDownloadButtons(); | |
}, | |
error: function(jqXHR, textStatus, errorThrown) { | |
console.error('Ajax error:', textStatus, errorThrown); | |
console.error('Response:', jqXHR.responseText); | |
stopProgressMonitoring(); | |
$('#loading').addClass('hidden'); | |
alert('An error occurred while analyzing patents.'); | |
} | |
}); | |
}); | |
}); | |
</script> | |
</body> | |
</html> |