PhyllisPeh's picture
fixed corrupted file
de629ac
<!DOCTYPE html>
<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>