PhyllisPeh commited on
Commit
7d1b966
·
1 Parent(s): ef62dae

back to original

Browse files
Files changed (8) hide show
  1. .hugginface-space +0 -4
  2. Dockerfile +9 -13
  3. app.py +14 -206
  4. entrypoint.sh +0 -11
  5. requirements.txt +1 -3
  6. runtime.yaml +0 -6
  7. templates/index.html +20 -202
  8. wsgi.py +0 -11
.hugginface-space DELETED
@@ -1,4 +0,0 @@
1
- # Hugging Face Space configuration
2
- runtime: docker
3
- sdk: docker
4
- entrypoint: ["gunicorn", "--bind", "0.0.0.0:7860", "--workers", "1", "--threads", "8", "--timeout", "0", "--log-level", "info", "app:app"]
 
 
 
 
 
Dockerfile CHANGED
@@ -8,27 +8,23 @@ RUN apt-get update && apt-get install -y \
8
  && rm -rf /var/lib/apt/lists/*
9
 
10
  # Create necessary directories
11
- RUN mkdir -p /code/templates /code/static
12
 
13
  # Copy requirements first to leverage Docker cache
14
  COPY ./requirements.txt /code/requirements.txt
15
  RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
16
 
17
  # Copy application code
18
- COPY . /code/
 
19
 
20
  # Make port 7860 available to the world outside this container
21
  EXPOSE 7860
22
 
23
- # Set environment variables for production
24
- ENV FLASK_ENV=production
25
- ENV FLASK_APP=app.py
26
- ENV PYTHONUNBUFFERED=1
27
- ENV PORT=7860
28
 
29
- # Copy and set up entrypoint
30
- COPY entrypoint.sh /entrypoint.sh
31
- RUN chmod +x /entrypoint.sh
32
-
33
- # Set the entrypoint
34
- ENTRYPOINT ["/entrypoint.sh"]
 
8
  && rm -rf /var/lib/apt/lists/*
9
 
10
  # Create necessary directories
11
+ RUN mkdir -p /code/templates
12
 
13
  # Copy requirements first to leverage Docker cache
14
  COPY ./requirements.txt /code/requirements.txt
15
  RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
16
 
17
  # Copy application code
18
+ COPY ./app.py /code/app.py
19
+ COPY ./templates /code/templates
20
 
21
  # Make port 7860 available to the world outside this container
22
  EXPOSE 7860
23
 
24
+ # Required environment variables:
25
+ # - PATENTSVIEW_API_KEY: Your PatentsView API key
26
+ # - OPENAI_API_KEY: Your OpenAI API key
27
+ # Example: docker run -e PATENTSVIEW_API_KEY=your_key -e OPENAI_API_KEY=your_openai_key ...
 
28
 
29
+ # Run gunicorn
30
+ CMD ["gunicorn", "--bind", "0.0.0.0:7860", "app:app", "--timeout", "300"]
 
 
 
 
app.py CHANGED
@@ -1,4 +1,4 @@
1
- from flask import Flask, render_template, request, jsonify, Response, send_file
2
  from dotenv import load_dotenv
3
  import requests
4
  from datetime import datetime
@@ -20,89 +20,29 @@ import threading
20
  import hdbscan
21
  from sklearn.neighbors import NearestNeighbors
22
  import traceback
23
- import io
24
- from reportlab.lib import colors
25
- from reportlab.lib.pagesizes import letter
26
- from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
27
- from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
28
 
29
- # Load environment variables
30
  load_dotenv()
31
 
32
- # Initialize Flask application with proper template and static folders
33
- template_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), 'templates'))
34
- static_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), 'static'))
35
 
36
- # Ensure the template and static directories exist
37
- os.makedirs(template_dir, exist_ok=True)
38
- os.makedirs(static_dir, exist_ok=True)
39
-
40
- def create_app():
41
- """Create and configure the Flask application"""
42
- app = Flask(__name__,
43
- template_folder=template_dir,
44
- static_folder=static_dir)
45
-
46
- # Configure for production
47
- app.config['ENV'] = 'production'
48
- app.config['DEBUG'] = False
49
- app.config['PROPAGATE_EXCEPTIONS'] = True
50
-
51
- return app
52
-
53
- app = create_app()
54
-
55
- # Configure for production
56
- app.config['ENV'] = 'production'
57
- app.config['DEBUG'] = False
58
 
59
  # Global progress queue for SSE updates
60
  progress_queue = queue.Queue()
61
 
62
- # Get API keys from environment variables with defaults for demo
63
- SERPAPI_API_KEY = os.getenv('SERPAPI_API_KEY', 'default')
64
- OPENAI_API_KEY = os.getenv('OPENAI_API_KEY', 'default')
65
- MAX_PATENTS = 3000
66
- MIN_PATENTS_FOR_GAPS = 3000
67
- CACHE_FILE = os.path.join(os.path.dirname(__file__), 'patent_embeddings_cache.pkl')
68
 
69
  # Initialize OpenAI API key
70
  openai.api_key = OPENAI_API_KEY
71
 
72
- @app.route('/')
73
- def index():
74
- """Main page route"""
75
- try:
76
- return render_template('index.html')
77
- except Exception as e:
78
- app.logger.error(f"Error rendering template: {str(e)}")
79
- return jsonify({"error": "Template error", "details": str(e)}), 500
80
-
81
- @app.route('/health')
82
- def health_check():
83
- """Health check endpoint for HuggingFace Space"""
84
- return jsonify({
85
- "status": "healthy",
86
- "timestamp": datetime.now().isoformat()
87
- }), 200
88
-
89
- @app.route('/test')
90
- def test():
91
- """Simple test endpoint"""
92
- return jsonify({
93
- "status": "ok",
94
- "message": "Patent Explorer API is running"
95
- })
96
-
97
- @app.errorhandler(404)
98
- def not_found_error(error):
99
- return jsonify({"error": "Not found"}), 404
100
-
101
- @app.errorhandler(500)
102
- def internal_error(error):
103
- return jsonify({"error": "Internal server error"}), 500
104
-
105
- # Helper functions
106
  def load_cache():
107
  """Load cached embeddings from file"""
108
  try:
@@ -1157,22 +1097,18 @@ def analyze_innovation_opportunities(cluster_insights):
1157
  Clusters: {format_area_list(cluster_nums)}
1158
  Transitional Areas: {format_area_list(transitional_nums)}
1159
  Underexplored Areas: {format_area_list(underexplored_nums)}
1160
-
1161
  Area Descriptions:
1162
  {descriptions_text}
1163
-
1164
  Analyze the most promising innovation opportunities. For each opportunity:
1165
  1. Identify two technologically complementary areas (e.g. "Cluster 1 + Transitional Area 2")
1166
  2. Focus on specific technical capabilities that could be combined
1167
  3. Aim for practical, near-term innovations
1168
-
1169
  Provide 3 opportunities, formatted as:
1170
  Opportunity N:
1171
  [Area 1] + [Area 2]
1172
  - Gap: Specific technical capability missing between these areas
1173
  - Solution: Concrete technical approach using existing methods
1174
  - Impact: Clear technical or market advantage gained
1175
-
1176
  Prioritize:
1177
  - Technical feasibility over speculative concepts
1178
  - Cross-domain applications with clear synergies
@@ -1381,133 +1317,5 @@ def search():
1381
  progress_queue.put('DONE')
1382
  return jsonify({'error': str(e)})
1383
 
1384
- @app.route('/download_plot', methods=['POST'])
1385
- def download_plot():
1386
- """Download the 3D plot as HTML file"""
1387
- try:
1388
- # Get plot data from request
1389
- plot_data = request.json.get('plot_data')
1390
- if not plot_data:
1391
- return jsonify({'error': 'No plot data available'})
1392
-
1393
- # Create HTML file with plot
1394
- html_content = f"""
1395
- <html>
1396
- <head>
1397
- <title>Patent Technology Landscape</title>
1398
- <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
1399
- </head>
1400
- <body>
1401
- <div id="plot"></div>
1402
- <script>
1403
- var plotData = {plot_data};
1404
- Plotly.newPlot('plot', plotData.data, plotData.layout);
1405
- </script>
1406
- </body>
1407
- </html>
1408
- """
1409
-
1410
- # Create in-memory file
1411
- buffer = io.BytesIO()
1412
- buffer.write(html_content.encode())
1413
- buffer.seek(0)
1414
-
1415
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
1416
- return send_file(
1417
- buffer,
1418
- mimetype='text/html',
1419
- as_attachment=True,
1420
- download_name=f'patent_landscape_{timestamp}.html'
1421
- )
1422
- except Exception as e:
1423
- return jsonify({'error': str(e)})
1424
-
1425
- @app.route('/download_insights', methods=['POST'])
1426
- def download_insights():
1427
- """Download insights as PDF document"""
1428
- try:
1429
- # Get insights data from request
1430
- insights_data = request.json.get('insights_data')
1431
- innovation_analysis = request.json.get('innovation_analysis')
1432
- if not insights_data:
1433
- return jsonify({'error': 'No insights data available'})
1434
-
1435
- # Create PDF buffer
1436
- buffer = io.BytesIO()
1437
-
1438
- # Create PDF document
1439
- doc = SimpleDocTemplate(buffer, pagesize=letter)
1440
- styles = getSampleStyleSheet()
1441
-
1442
- # Create custom styles
1443
- styles.add(ParagraphStyle(
1444
- name='Heading1',
1445
- parent=styles['Heading1'],
1446
- fontSize=16,
1447
- spaceAfter=20
1448
- ))
1449
- styles.add(ParagraphStyle(
1450
- name='Heading2',
1451
- parent=styles['Heading2'],
1452
- fontSize=14,
1453
- spaceAfter=12
1454
- ))
1455
- styles.add(ParagraphStyle(
1456
- name='Normal',
1457
- parent=styles['Normal'],
1458
- fontSize=10,
1459
- spaceAfter=12
1460
- ))
1461
-
1462
- # Build document content
1463
- content = []
1464
-
1465
- # Add title
1466
- content.append(Paragraph("Patent Technology Landscape Analysis", styles['Heading1']))
1467
- content.append(Spacer(1, 20))
1468
-
1469
- # Add technology clusters
1470
- content.append(Paragraph("Technology Clusters", styles['Heading2']))
1471
- for insight in insights_data:
1472
- if insight['type'] == 'cluster':
1473
- text = f"<b>Cluster {insight['id']}</b> ({insight['size']} patents)<br/>{insight['description']}"
1474
- content.append(Paragraph(text, styles['Normal']))
1475
- content.append(Spacer(1, 10))
1476
-
1477
- # Add transitional areas
1478
- content.append(Spacer(1, 10))
1479
- content.append(Paragraph("Transitional Areas", styles['Heading2']))
1480
- for insight in insights_data:
1481
- if insight['type'] == 'transitional':
1482
- text = f"<b>{insight['label']}</b><br/>{insight['description']}"
1483
- content.append(Paragraph(text, styles['Normal']))
1484
- content.append(Spacer(1, 10))
1485
-
1486
- # Add underexplored areas
1487
- content.append(Spacer(1, 10))
1488
- content.append(Paragraph("Underexplored Areas", styles['Heading2']))
1489
- for insight in insights_data:
1490
- if insight['type'] == 'innovation_subcluster' and insight['id'] >= 1:
1491
- text = f"<b>{insight['label']}</b><br/>{insight['description']}"
1492
- content.append(Paragraph(text, styles['Normal']))
1493
- content.append(Spacer(1, 10))
1494
-
1495
- # Add innovation analysis if available
1496
- if innovation_analysis:
1497
- content.append(Spacer(1, 10))
1498
- content.append(Paragraph("Innovation Opportunities Analysis", styles['Heading2']))
1499
- content.append(Paragraph(innovation_analysis, styles['Normal']))
1500
-
1501
- # Build PDF
1502
- doc.build(content)
1503
- buffer.seek(0)
1504
-
1505
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
1506
- return send_file(
1507
- buffer,
1508
- mimetype='application/pdf',
1509
- as_attachment=True,
1510
- download_name=f'patent_insights_{timestamp}.pdf'
1511
- )
1512
- except Exception as e:
1513
- return jsonify({'error': str(e)})
 
1
+ from flask import Flask, render_template, request, jsonify, Response
2
  from dotenv import load_dotenv
3
  import requests
4
  from datetime import datetime
 
20
  import hdbscan
21
  from sklearn.neighbors import NearestNeighbors
22
  import traceback
 
 
 
 
 
23
 
 
24
  load_dotenv()
25
 
26
+ app = Flask(__name__)
 
 
27
 
28
+ # Get API keys from environment variables
29
+ SERPAPI_API_KEY = os.getenv('SERPAPI_API_KEY')
30
+ OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')
31
+ MAX_PATENTS = 3000 # Increased from 2000 to 5000 for better coverage
32
+ MIN_PATENTS_FOR_GAPS = 3000 # Minimum patents needed for reliable gap detection
33
+ CACHE_FILE = 'patent_embeddings_cache.pkl'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
 
35
  # Global progress queue for SSE updates
36
  progress_queue = queue.Queue()
37
 
38
+ if not SERPAPI_API_KEY:
39
+ raise ValueError("SERPAPI_API_KEY environment variable is not set")
40
+ if not OPENAI_API_KEY:
41
+ raise ValueError("OPENAI_API_KEY environment variable is not set")
 
 
42
 
43
  # Initialize OpenAI API key
44
  openai.api_key = OPENAI_API_KEY
45
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  def load_cache():
47
  """Load cached embeddings from file"""
48
  try:
 
1097
  Clusters: {format_area_list(cluster_nums)}
1098
  Transitional Areas: {format_area_list(transitional_nums)}
1099
  Underexplored Areas: {format_area_list(underexplored_nums)}
 
1100
  Area Descriptions:
1101
  {descriptions_text}
 
1102
  Analyze the most promising innovation opportunities. For each opportunity:
1103
  1. Identify two technologically complementary areas (e.g. "Cluster 1 + Transitional Area 2")
1104
  2. Focus on specific technical capabilities that could be combined
1105
  3. Aim for practical, near-term innovations
 
1106
  Provide 3 opportunities, formatted as:
1107
  Opportunity N:
1108
  [Area 1] + [Area 2]
1109
  - Gap: Specific technical capability missing between these areas
1110
  - Solution: Concrete technical approach using existing methods
1111
  - Impact: Clear technical or market advantage gained
 
1112
  Prioritize:
1113
  - Technical feasibility over speculative concepts
1114
  - Cross-domain applications with clear synergies
 
1317
  progress_queue.put('DONE')
1318
  return jsonify({'error': str(e)})
1319
 
1320
+ if __name__ == '__main__':
1321
+ app.run(host='0.0.0.0', port=7860)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
entrypoint.sh DELETED
@@ -1,11 +0,0 @@
1
- #!/bin/bash
2
- # entrypoint.sh
3
-
4
- # Make sure the script fails on any error
5
- set -e
6
-
7
- # Create necessary directories
8
- mkdir -p templates static
9
-
10
- # Start the application
11
- exec gunicorn --bind 0.0.0.0:7860 --workers 1 --threads 8 --timeout 0 --log-level info app:app
 
 
 
 
 
 
 
 
 
 
 
 
requirements.txt CHANGED
@@ -12,6 +12,4 @@ llvmlite==0.39.1
12
  numba==0.56.4
13
  setuptools>=65.5.1
14
  wheel>=0.38.0
15
- hdbscan
16
- reportlab==4.0.7
17
-
 
12
  numba==0.56.4
13
  setuptools>=65.5.1
14
  wheel>=0.38.0
15
+ hdbscan
 
 
runtime.yaml DELETED
@@ -1,6 +0,0 @@
1
- build_command: docker build -t patent-explorer .
2
- runtime: docker
3
- sdk: docker
4
- app_port: 7860
5
- healthcheck_url: /health
6
- predict: gunicorn app:app --bind 0.0.0.0:7860 --workers 1 --threads 8 --timeout 0
 
 
 
 
 
 
 
templates/index.html CHANGED
@@ -19,10 +19,9 @@
19
  .insights-panel {
20
  background-color: #2d2d2d;
21
  border-radius: 8px;
22
- height: calc(100vh - 140px);
23
  overflow-y: auto;
24
  transition: all 0.3s ease;
25
- padding: 2rem;
26
  }
27
  .cluster-card {
28
  background-color: #3d3d3d;
@@ -151,39 +150,6 @@
151
  height: 85vh;
152
  }
153
  }
154
-
155
- .innovation-analysis {
156
- background-color: #1c2431;
157
- border-radius: 8px;
158
- padding: 1.5rem;
159
- margin-bottom: 2rem;
160
- border: 1px solid #4a5568;
161
- }
162
-
163
- .download-btn {
164
- background-color: #2d3748;
165
- color: #fff;
166
- padding: 0.5rem 1rem;
167
- border-radius: 0.375rem;
168
- transition: all 0.2s;
169
- cursor: pointer;
170
- }
171
- .download-btn:hover {
172
- background-color: #4a5568;
173
- transform: translateY(-1px);
174
- }
175
- .download-btn:disabled {
176
- background-color: #4a5568;
177
- cursor: not-allowed;
178
- opacity: 0.5;
179
- }
180
- /* Add this to your existing styles */
181
- .download-container {
182
- display: flex;
183
- gap: 1rem;
184
- margin: 1rem 0;
185
- justify-content: flex-end;
186
- }
187
  </style>
188
  </head>
189
  <body class="bg-gray-900 text-gray-100 min-h-screen">
@@ -242,35 +208,9 @@
242
 
243
  <!-- Visualization Container -->
244
  <div id="visualization" class="visualization-container"></div>
245
-
246
- <!-- Download Buttons -->
247
- <div class="download-container">
248
- <button id="downloadPlot" class="download-btn" disabled>
249
- <svg class="inline-block w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
250
- <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>
251
- </svg>
252
- Download 3D Plot
253
- </button>
254
- <button id="downloadInsights" class="download-btn" disabled>
255
- <svg class="inline-block w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
256
- <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>
257
- </svg>
258
- Download Insights
259
- </button>
260
- </div>
261
 
262
  <!-- Insights Panel -->
263
  <div id="insights" class="insights-panel p-4"></div>
264
-
265
- <!-- Download Buttons (initially hidden) -->
266
- <div id="downloadButtons" class="download-container hidden">
267
- <button id="downloadCSV" class="download-btn">
268
- <i class="fas fa-file-csv mr-2"></i> Download CSV
269
- </button>
270
- <button id="downloadJSON" class="download-btn">
271
- <i class="fas fa-file-alt mr-2"></i> Download JSON
272
- </button>
273
- </div>
274
  </div>
275
 
276
  <script>
@@ -415,33 +355,20 @@
415
  return;
416
  }
417
 
418
- // Reset visualization and insights
419
- $('#visualization').empty();
420
- $('#insights').empty();
421
- $('#innovation-analysis').empty();
422
-
423
  // Display visualization
424
  if (response.visualization) {
425
- console.log('Creating visualization...', response.visualization.slice(0, 100) + '...');
426
- try {
427
- const vizData = JSON.parse(response.visualization);
428
- Plotly.newPlot('visualization', vizData.data, vizData.layout);
429
-
430
- const vizElement = document.getElementById('visualization');
431
- vizElement.on('plotly_click', function(data) {
432
- if (data.points && data.points[0] && data.points[0].customdata) {
433
- const link = data.points[0].customdata;
434
- window.open(link, '_blank');
435
- }
436
- });
437
- console.log('Visualization created successfully');
438
- } catch (e) {
439
- console.error('Error creating visualization:', e);
440
- $('#visualization').html('<p class="text-red-500">Error creating visualization</p>');
441
- }
442
  } else {
443
  console.warn('No visualization data received');
444
- $('#visualization').html('<p class="text-yellow-500">No visualization data available</p>');
445
  }
446
 
447
  // Display insights
@@ -454,21 +381,7 @@
454
  console.log('Found innovation subclusters:', innovationSubclusters.length);
455
  console.log('Found transitional areas:', transitionalAreas.length);
456
 
457
- // Start with Innovation Analysis
458
- let insightsHtml = '';
459
- if (response.innovationAnalysis) {
460
- insightsHtml += `
461
- <div class="bg-gray-800 p-6 mb-6 rounded-lg">
462
- <h3 class="text-2xl font-bold mb-4 text-blue-300">Innovation Opportunities Analysis</h3>
463
- <div class="text-gray-300 whitespace-pre-line">
464
- ${response.innovationAnalysis}
465
- </div>
466
- </div>
467
- `;
468
- }
469
-
470
- // Add the grid for clusters, transitional areas, and underexplored areas
471
- insightsHtml += '<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 p-6">';
472
 
473
  // Left column: Technology Clusters
474
  insightsHtml += '<div class="col-span-1">';
@@ -510,9 +423,9 @@
510
  }
511
  insightsHtml += '</div>';
512
 
513
- // Right column: Underexplored Areas
514
  insightsHtml += '<div class="col-span-1">';
515
- insightsHtml += '<h3 class="text-2xl font-bold mb-4 text-green-400">Underexplored Areas</h3>';
516
 
517
  if (innovationSubclusters.length > 0) {
518
  insightsHtml += '<div class="space-y-4">';
@@ -526,7 +439,7 @@
526
  });
527
  insightsHtml += '</div>';
528
  } else {
529
- insightsHtml += '<p class="text-gray-400">No significant underexplored areas identified in this technology space.</p>';
530
  }
531
  insightsHtml += '</div>';
532
 
@@ -536,115 +449,20 @@
536
  console.warn('No insights data received');
537
  }
538
 
539
- // Innovation Analysis Section
540
- if (response.innovationAnalysis) {
541
- console.log('Displaying innovation analysis...');
542
- $('#innovation-analysis').html(`
543
- <div class="text-gray-300 whitespace-pre-line bg-gray-800 p-4 rounded-lg mt-6">
544
- ${response.innovationAnalysis}
545
- </div>
546
- `);
547
- } else {
548
- console.warn('No innovation analysis data received');
549
- $('#innovation-analysis').html('<p class="text-gray-400 mt-6">No innovation analysis available.</p>');
550
- }
551
-
552
  // Stop progress monitoring and hide loading status
 
553
  stopProgressMonitoring();
554
  $('#loading').addClass('hidden');
555
-
556
- // Enable download buttons
557
- $('#downloadPlot').prop('disabled', false);
558
- $('#downloadInsights').prop('disabled', false);
559
-
560
- // Store data for downloads
561
- window.plotData = response.visualization;
562
- window.insightsData = response.insights;
563
- window.innovationAnalysis = response.innovationAnalysis;
564
  },
565
- error: function(xhr, status, error) {
566
- console.error('Search error:', error);
 
567
  stopProgressMonitoring();
568
  $('#loading').addClass('hidden');
569
- alert('Error performing search: ' + error);
570
  }
571
  });
572
  });
573
-
574
- // Handle plot download
575
- $('#downloadPlot').on('click', function() {
576
- if (!window.plotData) {
577
- alert('No visualization data available');
578
- return;
579
- }
580
-
581
- fetch('/download_plot', {
582
- method: 'POST',
583
- headers: {
584
- 'Content-Type': 'application/json',
585
- },
586
- body: JSON.stringify({
587
- plot_data: window.plotData
588
- })
589
- })
590
- .then(response => {
591
- if (!response.ok) throw new Error('Network response was not ok');
592
- return response.blob();
593
- })
594
- .then(blob => {
595
- const url = window.URL.createObjectURL(blob);
596
- const a = document.createElement('a');
597
- a.href = url;
598
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
599
- a.download = `patent_landscape_${timestamp}.html`;
600
- document.body.appendChild(a);
601
- a.click();
602
- window.URL.revokeObjectURL(url);
603
- a.remove();
604
- })
605
- .catch(error => {
606
- console.error('Download error:', error);
607
- alert('Error downloading visualization: ' + error.message);
608
- });
609
- });
610
-
611
- // Handle insights download
612
- $('#downloadInsights').on('click', function() {
613
- if (!window.insightsData) {
614
- alert('No insights data available');
615
- return;
616
- }
617
-
618
- fetch('/download_insights', {
619
- method: 'POST',
620
- headers: {
621
- 'Content-Type': 'application/json',
622
- },
623
- body: JSON.stringify({
624
- insights_data: window.insightsData,
625
- innovation_analysis: window.innovationAnalysis
626
- })
627
- })
628
- .then(response => {
629
- if (!response.ok) throw new Error('Network response was not ok');
630
- return response.blob();
631
- })
632
- .then(blob => {
633
- const url = window.URL.createObjectURL(blob);
634
- const a = document.createElement('a');
635
- a.href = url;
636
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
637
- a.download = `patent_insights_${timestamp}.pdf`;
638
- document.body.appendChild(a);
639
- a.click();
640
- window.URL.revokeObjectURL(url);
641
- a.remove();
642
- })
643
- .catch(error => {
644
- console.error('Download error:', error);
645
- alert('Error downloading insights: ' + error.message);
646
- });
647
- });
648
  });
649
  </script>
650
  </body>
 
19
  .insights-panel {
20
  background-color: #2d2d2d;
21
  border-radius: 8px;
22
+ height: calc(100vh - 40px);
23
  overflow-y: auto;
24
  transition: all 0.3s ease;
 
25
  }
26
  .cluster-card {
27
  background-color: #3d3d3d;
 
150
  height: 85vh;
151
  }
152
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
  </style>
154
  </head>
155
  <body class="bg-gray-900 text-gray-100 min-h-screen">
 
208
 
209
  <!-- Visualization Container -->
210
  <div id="visualization" class="visualization-container"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
211
 
212
  <!-- Insights Panel -->
213
  <div id="insights" class="insights-panel p-4"></div>
 
 
 
 
 
 
 
 
 
 
214
  </div>
215
 
216
  <script>
 
355
  return;
356
  }
357
 
 
 
 
 
 
358
  // Display visualization
359
  if (response.visualization) {
360
+ console.log('Creating visualization...');
361
+ const vizData = JSON.parse(response.visualization);
362
+ Plotly.newPlot('visualization', vizData.data, vizData.layout);
363
+
364
+ document.getElementById('visualization').on('plotly_click', function(data) {
365
+ const link = data.points[0].customdata;
366
+ if (link) {
367
+ window.open(link, '_blank');
368
+ }
369
+ });
 
 
 
 
 
 
 
370
  } else {
371
  console.warn('No visualization data received');
 
372
  }
373
 
374
  // Display insights
 
381
  console.log('Found innovation subclusters:', innovationSubclusters.length);
382
  console.log('Found transitional areas:', transitionalAreas.length);
383
 
384
+ let insightsHtml = '<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 p-6">';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
385
 
386
  // Left column: Technology Clusters
387
  insightsHtml += '<div class="col-span-1">';
 
423
  }
424
  insightsHtml += '</div>';
425
 
426
+ // Right column: Innovation Opportunities
427
  insightsHtml += '<div class="col-span-1">';
428
+ insightsHtml += '<h3 class="text-2xl font-bold mb-4 text-green-400">Innovation Opportunities</h3>';
429
 
430
  if (innovationSubclusters.length > 0) {
431
  insightsHtml += '<div class="space-y-4">';
 
439
  });
440
  insightsHtml += '</div>';
441
  } else {
442
+ insightsHtml += '<p class="text-gray-400">No significant innovation opportunities identified in this technology space.</p>';
443
  }
444
  insightsHtml += '</div>';
445
 
 
449
  console.warn('No insights data received');
450
  }
451
 
 
 
 
 
 
 
 
 
 
 
 
 
 
452
  // Stop progress monitoring and hide loading status
453
+ console.log('Search completed successfully');
454
  stopProgressMonitoring();
455
  $('#loading').addClass('hidden');
 
 
 
 
 
 
 
 
 
456
  },
457
+ error: function(jqXHR, textStatus, errorThrown) {
458
+ console.error('Ajax error:', textStatus, errorThrown);
459
+ console.error('Response:', jqXHR.responseText);
460
  stopProgressMonitoring();
461
  $('#loading').addClass('hidden');
462
+ alert('An error occurred while analyzing patents.');
463
  }
464
  });
465
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
466
  });
467
  </script>
468
  </body>
wsgi.py DELETED
@@ -1,11 +0,0 @@
1
- import os
2
- from app import app
3
-
4
- def create_app():
5
- """Create and configure the Flask application"""
6
- return app
7
-
8
- if __name__ == "__main__":
9
- # Run the application
10
- port = int(os.environ.get("PORT", 7860))
11
- app.run(host="0.0.0.0", port=port, debug=False)