Spaces:
Sleeping
Sleeping
Commit
·
7d1b966
1
Parent(s):
ef62dae
back to original
Browse files- .hugginface-space +0 -4
- Dockerfile +9 -13
- app.py +14 -206
- entrypoint.sh +0 -11
- requirements.txt +1 -3
- runtime.yaml +0 -6
- templates/index.html +20 -202
- 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
|
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 |
-
#
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
ENV PORT=7860
|
28 |
|
29 |
-
#
|
30 |
-
|
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
|
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 |
-
|
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 |
-
#
|
37 |
-
os.
|
38 |
-
os.
|
39 |
-
|
40 |
-
|
41 |
-
|
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 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
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 |
-
|
1385 |
-
|
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 -
|
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...'
|
426 |
-
|
427 |
-
|
428 |
-
|
429 |
-
|
430 |
-
const
|
431 |
-
|
432 |
-
|
433 |
-
|
434 |
-
|
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 |
-
|
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:
|
514 |
insightsHtml += '<div class="col-span-1">';
|
515 |
-
insightsHtml += '<h3 class="text-2xl font-bold mb-4 text-green-400">
|
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
|
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(
|
566 |
-
console.error('
|
|
|
567 |
stopProgressMonitoring();
|
568 |
$('#loading').addClass('hidden');
|
569 |
-
alert('
|
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)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|