local-deep-research 0.1.26__py3-none-any.whl → 0.2.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- local_deep_research/__init__.py +23 -22
- local_deep_research/__main__.py +16 -0
- local_deep_research/advanced_search_system/__init__.py +7 -0
- local_deep_research/advanced_search_system/filters/__init__.py +8 -0
- local_deep_research/advanced_search_system/filters/base_filter.py +38 -0
- local_deep_research/advanced_search_system/filters/cross_engine_filter.py +200 -0
- local_deep_research/advanced_search_system/findings/base_findings.py +81 -0
- local_deep_research/advanced_search_system/findings/repository.py +452 -0
- local_deep_research/advanced_search_system/knowledge/__init__.py +1 -0
- local_deep_research/advanced_search_system/knowledge/base_knowledge.py +151 -0
- local_deep_research/advanced_search_system/knowledge/standard_knowledge.py +159 -0
- local_deep_research/advanced_search_system/questions/__init__.py +1 -0
- local_deep_research/advanced_search_system/questions/base_question.py +64 -0
- local_deep_research/advanced_search_system/questions/decomposition_question.py +445 -0
- local_deep_research/advanced_search_system/questions/standard_question.py +119 -0
- local_deep_research/advanced_search_system/repositories/__init__.py +7 -0
- local_deep_research/advanced_search_system/strategies/__init__.py +1 -0
- local_deep_research/advanced_search_system/strategies/base_strategy.py +118 -0
- local_deep_research/advanced_search_system/strategies/iterdrag_strategy.py +450 -0
- local_deep_research/advanced_search_system/strategies/parallel_search_strategy.py +312 -0
- local_deep_research/advanced_search_system/strategies/rapid_search_strategy.py +270 -0
- local_deep_research/advanced_search_system/strategies/standard_strategy.py +300 -0
- local_deep_research/advanced_search_system/tools/__init__.py +1 -0
- local_deep_research/advanced_search_system/tools/base_tool.py +100 -0
- local_deep_research/advanced_search_system/tools/knowledge_tools/__init__.py +1 -0
- local_deep_research/advanced_search_system/tools/question_tools/__init__.py +1 -0
- local_deep_research/advanced_search_system/tools/search_tools/__init__.py +1 -0
- local_deep_research/api/__init__.py +5 -5
- local_deep_research/api/research_functions.py +154 -160
- local_deep_research/app.py +8 -0
- local_deep_research/citation_handler.py +25 -16
- local_deep_research/{config.py → config/config_files.py} +102 -110
- local_deep_research/config/llm_config.py +472 -0
- local_deep_research/config/search_config.py +77 -0
- local_deep_research/defaults/__init__.py +10 -5
- local_deep_research/defaults/main.toml +2 -2
- local_deep_research/defaults/search_engines.toml +60 -34
- local_deep_research/main.py +121 -19
- local_deep_research/migrate_db.py +147 -0
- local_deep_research/report_generator.py +87 -45
- local_deep_research/search_system.py +153 -283
- local_deep_research/setup_data_dir.py +35 -0
- local_deep_research/test_migration.py +178 -0
- local_deep_research/utilities/__init__.py +0 -0
- local_deep_research/utilities/db_utils.py +49 -0
- local_deep_research/{utilties → utilities}/enums.py +2 -2
- local_deep_research/{utilties → utilities}/llm_utils.py +63 -29
- local_deep_research/utilities/search_utilities.py +242 -0
- local_deep_research/{utilties → utilities}/setup_utils.py +4 -2
- local_deep_research/web/__init__.py +0 -1
- local_deep_research/web/app.py +86 -1709
- local_deep_research/web/app_factory.py +289 -0
- local_deep_research/web/database/README.md +70 -0
- local_deep_research/web/database/migrate_to_ldr_db.py +289 -0
- local_deep_research/web/database/migrations.py +447 -0
- local_deep_research/web/database/models.py +117 -0
- local_deep_research/web/database/schema_upgrade.py +107 -0
- local_deep_research/web/models/database.py +294 -0
- local_deep_research/web/models/settings.py +94 -0
- local_deep_research/web/routes/api_routes.py +559 -0
- local_deep_research/web/routes/history_routes.py +354 -0
- local_deep_research/web/routes/research_routes.py +715 -0
- local_deep_research/web/routes/settings_routes.py +1583 -0
- local_deep_research/web/services/research_service.py +947 -0
- local_deep_research/web/services/resource_service.py +149 -0
- local_deep_research/web/services/settings_manager.py +669 -0
- local_deep_research/web/services/settings_service.py +187 -0
- local_deep_research/web/services/socket_service.py +210 -0
- local_deep_research/web/static/css/custom_dropdown.css +277 -0
- local_deep_research/web/static/css/settings.css +1223 -0
- local_deep_research/web/static/css/styles.css +525 -48
- local_deep_research/web/static/js/components/custom_dropdown.js +428 -0
- local_deep_research/web/static/js/components/detail.js +348 -0
- local_deep_research/web/static/js/components/fallback/formatting.js +122 -0
- local_deep_research/web/static/js/components/fallback/ui.js +215 -0
- local_deep_research/web/static/js/components/history.js +487 -0
- local_deep_research/web/static/js/components/logpanel.js +949 -0
- local_deep_research/web/static/js/components/progress.js +1107 -0
- local_deep_research/web/static/js/components/research.js +1865 -0
- local_deep_research/web/static/js/components/results.js +766 -0
- local_deep_research/web/static/js/components/settings.js +3981 -0
- local_deep_research/web/static/js/components/settings_sync.js +106 -0
- local_deep_research/web/static/js/main.js +226 -0
- local_deep_research/web/static/js/services/api.js +253 -0
- local_deep_research/web/static/js/services/audio.js +31 -0
- local_deep_research/web/static/js/services/formatting.js +119 -0
- local_deep_research/web/static/js/services/pdf.js +622 -0
- local_deep_research/web/static/js/services/socket.js +882 -0
- local_deep_research/web/static/js/services/ui.js +546 -0
- local_deep_research/web/templates/base.html +72 -0
- local_deep_research/web/templates/components/custom_dropdown.html +47 -0
- local_deep_research/web/templates/components/log_panel.html +32 -0
- local_deep_research/web/templates/components/mobile_nav.html +22 -0
- local_deep_research/web/templates/components/settings_form.html +299 -0
- local_deep_research/web/templates/components/sidebar.html +21 -0
- local_deep_research/web/templates/pages/details.html +73 -0
- local_deep_research/web/templates/pages/history.html +51 -0
- local_deep_research/web/templates/pages/progress.html +57 -0
- local_deep_research/web/templates/pages/research.html +139 -0
- local_deep_research/web/templates/pages/results.html +59 -0
- local_deep_research/web/templates/settings_dashboard.html +78 -192
- local_deep_research/web/utils/__init__.py +0 -0
- local_deep_research/web/utils/formatters.py +76 -0
- local_deep_research/web_search_engines/engines/full_search.py +18 -16
- local_deep_research/web_search_engines/engines/meta_search_engine.py +182 -131
- local_deep_research/web_search_engines/engines/search_engine_arxiv.py +224 -139
- local_deep_research/web_search_engines/engines/search_engine_brave.py +88 -71
- local_deep_research/web_search_engines/engines/search_engine_ddg.py +48 -39
- local_deep_research/web_search_engines/engines/search_engine_github.py +415 -204
- local_deep_research/web_search_engines/engines/search_engine_google_pse.py +123 -90
- local_deep_research/web_search_engines/engines/search_engine_guardian.py +210 -157
- local_deep_research/web_search_engines/engines/search_engine_local.py +532 -369
- local_deep_research/web_search_engines/engines/search_engine_local_all.py +42 -36
- local_deep_research/web_search_engines/engines/search_engine_pubmed.py +358 -266
- local_deep_research/web_search_engines/engines/search_engine_searxng.py +212 -160
- local_deep_research/web_search_engines/engines/search_engine_semantic_scholar.py +213 -170
- local_deep_research/web_search_engines/engines/search_engine_serpapi.py +84 -68
- local_deep_research/web_search_engines/engines/search_engine_wayback.py +186 -154
- local_deep_research/web_search_engines/engines/search_engine_wikipedia.py +115 -77
- local_deep_research/web_search_engines/search_engine_base.py +174 -99
- local_deep_research/web_search_engines/search_engine_factory.py +192 -102
- local_deep_research/web_search_engines/search_engines_config.py +22 -15
- {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.2.dist-info}/METADATA +177 -97
- local_deep_research-0.2.2.dist-info/RECORD +135 -0
- {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.2.dist-info}/WHEEL +1 -2
- {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.2.dist-info}/entry_points.txt +3 -0
- local_deep_research/defaults/llm_config.py +0 -338
- local_deep_research/utilties/search_utilities.py +0 -114
- local_deep_research/web/static/js/app.js +0 -3763
- local_deep_research/web/templates/api_keys_config.html +0 -82
- local_deep_research/web/templates/collections_config.html +0 -90
- local_deep_research/web/templates/index.html +0 -348
- local_deep_research/web/templates/llm_config.html +0 -120
- local_deep_research/web/templates/main_config.html +0 -89
- local_deep_research/web/templates/search_engines_config.html +0 -154
- local_deep_research/web/templates/settings.html +0 -519
- local_deep_research-0.1.26.dist-info/RECORD +0 -61
- local_deep_research-0.1.26.dist-info/top_level.txt +0 -1
- /local_deep_research/{utilties → config}/__init__.py +0 -0
- {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,622 @@
|
|
1
|
+
/**
|
2
|
+
* PDF Service
|
3
|
+
* Handles generation of PDFs from research results
|
4
|
+
*/
|
5
|
+
|
6
|
+
/**
|
7
|
+
* Generate a PDF from the research content
|
8
|
+
* @param {string|any} title - The research title/query
|
9
|
+
* @param {string|any} content - The markdown content of the research
|
10
|
+
* @param {Object} metadata - Additional metadata for the PDF
|
11
|
+
* @returns {Promise<Blob>} A promise resolving to the generated PDF blob
|
12
|
+
*/
|
13
|
+
async function generatePdf(title, content, metadata = {}) {
|
14
|
+
// Check if necessary libraries are loaded
|
15
|
+
if (typeof jspdf === 'undefined') {
|
16
|
+
throw new Error('PDF generation libraries not loaded (jsPDF missing)');
|
17
|
+
}
|
18
|
+
|
19
|
+
if (typeof html2canvas === 'undefined') {
|
20
|
+
throw new Error('PDF generation libraries not loaded (html2canvas missing)');
|
21
|
+
}
|
22
|
+
|
23
|
+
// Ensure title and content are strings
|
24
|
+
title = String(title || 'Research Report');
|
25
|
+
content = String(content || '');
|
26
|
+
|
27
|
+
// Create a temporary container to render the markdown
|
28
|
+
const tempContainer = document.createElement('div');
|
29
|
+
tempContainer.style.position = 'absolute';
|
30
|
+
tempContainer.style.left = '-9999px';
|
31
|
+
tempContainer.style.top = '-9999px';
|
32
|
+
tempContainer.style.width = '8.5in'; // US Letter width
|
33
|
+
tempContainer.className = 'pdf-content';
|
34
|
+
|
35
|
+
// Add PDF-specific styles
|
36
|
+
tempContainer.innerHTML = `
|
37
|
+
<style>
|
38
|
+
.pdf-content {
|
39
|
+
font-family: Arial, sans-serif;
|
40
|
+
color: #333;
|
41
|
+
line-height: 1.5;
|
42
|
+
padding: 20px;
|
43
|
+
background-color: #ffffff;
|
44
|
+
}
|
45
|
+
.pdf-content h1 {
|
46
|
+
font-size: 24px;
|
47
|
+
color: #000;
|
48
|
+
margin-bottom: 12px;
|
49
|
+
}
|
50
|
+
.pdf-content h2 {
|
51
|
+
font-size: 20px;
|
52
|
+
color: #000;
|
53
|
+
margin-top: 20px;
|
54
|
+
margin-bottom: 10px;
|
55
|
+
}
|
56
|
+
.pdf-content h3 {
|
57
|
+
font-size: 16px;
|
58
|
+
color: #000;
|
59
|
+
margin-top: 16px;
|
60
|
+
margin-bottom: 8px;
|
61
|
+
}
|
62
|
+
.pdf-content p {
|
63
|
+
margin-bottom: 10px;
|
64
|
+
}
|
65
|
+
.pdf-content ul, .pdf-content ol {
|
66
|
+
margin-left: 20px;
|
67
|
+
margin-bottom: 10px;
|
68
|
+
}
|
69
|
+
.pdf-content li {
|
70
|
+
margin-bottom: 5px;
|
71
|
+
}
|
72
|
+
.pdf-content pre {
|
73
|
+
background-color: #f5f5f5;
|
74
|
+
padding: 10px;
|
75
|
+
border-radius: 4px;
|
76
|
+
overflow-x: auto;
|
77
|
+
font-family: monospace;
|
78
|
+
font-size: 12px;
|
79
|
+
margin-bottom: 10px;
|
80
|
+
}
|
81
|
+
.pdf-content code {
|
82
|
+
font-family: monospace;
|
83
|
+
background-color: #f5f5f5;
|
84
|
+
padding: 2px 4px;
|
85
|
+
border-radius: 2px;
|
86
|
+
font-size: 12px;
|
87
|
+
}
|
88
|
+
.pdf-content blockquote {
|
89
|
+
border-left: 4px solid #ddd;
|
90
|
+
padding-left: 15px;
|
91
|
+
margin-left: 0;
|
92
|
+
color: #666;
|
93
|
+
}
|
94
|
+
.pdf-content table {
|
95
|
+
border-collapse: collapse;
|
96
|
+
width: 100%;
|
97
|
+
margin-bottom: 15px;
|
98
|
+
}
|
99
|
+
.pdf-content table, .pdf-content th, .pdf-content td {
|
100
|
+
border: 1px solid #ddd;
|
101
|
+
}
|
102
|
+
.pdf-content th, .pdf-content td {
|
103
|
+
padding: 8px;
|
104
|
+
text-align: left;
|
105
|
+
}
|
106
|
+
.pdf-content th {
|
107
|
+
background-color: #f5f5f5;
|
108
|
+
}
|
109
|
+
.pdf-metadata {
|
110
|
+
color: #666;
|
111
|
+
font-size: 12px;
|
112
|
+
margin-bottom: 20px;
|
113
|
+
border-bottom: 1px solid #ddd;
|
114
|
+
padding-bottom: 10px;
|
115
|
+
}
|
116
|
+
.pdf-header {
|
117
|
+
text-align: center;
|
118
|
+
border-bottom: 2px solid #333;
|
119
|
+
padding-bottom: 10px;
|
120
|
+
margin-bottom: 20px;
|
121
|
+
}
|
122
|
+
.pdf-footer {
|
123
|
+
border-top: 1px solid #ddd;
|
124
|
+
padding-top: 10px;
|
125
|
+
margin-top: 20px;
|
126
|
+
font-size: 12px;
|
127
|
+
color: #666;
|
128
|
+
}
|
129
|
+
</style>
|
130
|
+
<div class="pdf-header">
|
131
|
+
<h1>${title}</h1>
|
132
|
+
</div>
|
133
|
+
<div class="pdf-metadata">
|
134
|
+
<p>Generated: ${new Date().toLocaleString()}</p>
|
135
|
+
${metadata.mode ? `<p>Mode: ${metadata.mode}</p>` : ''}
|
136
|
+
${metadata.iterations ? `<p>Search iterations: ${metadata.iterations}</p>` : ''}
|
137
|
+
${metadata.id ? `<p>Research ID: ${metadata.id}</p>` : ''}
|
138
|
+
${metadata.timestamp ? `<p>Research Date: ${new Date(metadata.timestamp).toLocaleString()}</p>` : ''}
|
139
|
+
</div>
|
140
|
+
<div class="pdf-body">
|
141
|
+
${window.marked ? window.marked.parse(content) : content}
|
142
|
+
</div>
|
143
|
+
<div class="pdf-footer">
|
144
|
+
<p>Generated by Deep Research System</p>
|
145
|
+
</div>
|
146
|
+
`;
|
147
|
+
|
148
|
+
// Add to the document body temporarily
|
149
|
+
document.body.appendChild(tempContainer);
|
150
|
+
|
151
|
+
try {
|
152
|
+
console.log("Starting PDF generation...");
|
153
|
+
|
154
|
+
// Use the jsPDF library from the window object
|
155
|
+
const { jsPDF } = window.jspdf;
|
156
|
+
|
157
|
+
// Create a new PDF document
|
158
|
+
const pdf = new jsPDF('p', 'pt', 'letter');
|
159
|
+
const pdfWidth = pdf.internal.pageSize.getWidth();
|
160
|
+
const pdfHeight = pdf.internal.pageSize.getHeight();
|
161
|
+
const margin = 40;
|
162
|
+
const contentWidth = pdfWidth - 2 * margin;
|
163
|
+
|
164
|
+
// Function to add page with header
|
165
|
+
const addPageWithHeader = (pageNum) => {
|
166
|
+
if (pageNum > 1) {
|
167
|
+
pdf.addPage();
|
168
|
+
}
|
169
|
+
pdf.setFontSize(8);
|
170
|
+
pdf.setTextColor(100, 100, 100);
|
171
|
+
pdf.text(`Deep Research - ${title} - Page ${pageNum}`, margin, pdfHeight - 20);
|
172
|
+
};
|
173
|
+
|
174
|
+
// Process each element with a more optimized approach
|
175
|
+
const contentDiv = tempContainer.querySelector('.pdf-body');
|
176
|
+
if (!contentDiv) {
|
177
|
+
throw new Error('PDF body container not found');
|
178
|
+
}
|
179
|
+
|
180
|
+
// Create a more efficient PDF generation approach that keeps text selectable
|
181
|
+
const elements = Array.from(contentDiv.children);
|
182
|
+
let currentY = margin;
|
183
|
+
let pageNum = 1;
|
184
|
+
|
185
|
+
// Add the first page with header
|
186
|
+
addPageWithHeader(pageNum);
|
187
|
+
|
188
|
+
// Process each element
|
189
|
+
for (const element of elements) {
|
190
|
+
// Skip style element
|
191
|
+
if (element.tagName === 'STYLE') continue;
|
192
|
+
|
193
|
+
try {
|
194
|
+
// Simple text content - handled directly by jsPDF for better text selection
|
195
|
+
if ((element.tagName === 'P' || element.tagName === 'DIV' || element.tagName === 'H1' ||
|
196
|
+
element.tagName === 'H2' || element.tagName === 'H3') &&
|
197
|
+
!element.querySelector('img, canvas, svg, table') &&
|
198
|
+
!element.innerHTML.includes('<')) {
|
199
|
+
|
200
|
+
pdf.setFont('helvetica', element.tagName.startsWith('H') ? 'bold' : 'normal');
|
201
|
+
pdf.setFontSize(element.tagName === 'H1' ? 18 : (element.tagName === 'H2' ? 16 : (element.tagName === 'H3' ? 14 : 11)));
|
202
|
+
pdf.setTextColor(0, 0, 0);
|
203
|
+
|
204
|
+
const text = element.textContent.trim();
|
205
|
+
if (!text) continue; // Skip empty text
|
206
|
+
|
207
|
+
const textLines = pdf.splitTextToSize(text, contentWidth);
|
208
|
+
|
209
|
+
// Check if we need a new page
|
210
|
+
if (currentY + (textLines.length * 14) > pdfHeight - margin) {
|
211
|
+
pageNum++;
|
212
|
+
addPageWithHeader(pageNum);
|
213
|
+
currentY = margin;
|
214
|
+
}
|
215
|
+
|
216
|
+
pdf.text(textLines, margin, currentY + 12);
|
217
|
+
currentY += (textLines.length * 14) + 10;
|
218
|
+
}
|
219
|
+
// List elements - handle bullets and numbering
|
220
|
+
else if (element.tagName === 'UL' || element.tagName === 'OL') {
|
221
|
+
const listItems = Array.from(element.querySelectorAll('li'));
|
222
|
+
for (let i = 0; i < listItems.length; i++) {
|
223
|
+
const item = listItems[i];
|
224
|
+
const itemText = item.textContent.trim();
|
225
|
+
if (!itemText) continue;
|
226
|
+
|
227
|
+
pdf.setFont('helvetica', 'normal');
|
228
|
+
pdf.setFontSize(11);
|
229
|
+
pdf.setTextColor(0, 0, 0);
|
230
|
+
|
231
|
+
const bulletPoint = element.tagName === 'UL' ? '•' : `${i + 1}.`;
|
232
|
+
const textWithBullet = `${bulletPoint} ${itemText}`;
|
233
|
+
const textLines = pdf.splitTextToSize(textWithBullet, contentWidth - 10);
|
234
|
+
|
235
|
+
// Check if we need a new page
|
236
|
+
if (currentY + (textLines.length * 14) > pdfHeight - margin) {
|
237
|
+
pageNum++;
|
238
|
+
addPageWithHeader(pageNum);
|
239
|
+
currentY = margin;
|
240
|
+
}
|
241
|
+
|
242
|
+
pdf.text(textLines, margin, currentY + 12);
|
243
|
+
currentY += (textLines.length * 14) + 5;
|
244
|
+
}
|
245
|
+
currentY += 5; // Add some space after the list
|
246
|
+
}
|
247
|
+
// Tables - render with proper cells
|
248
|
+
else if (element.tagName === 'TABLE') {
|
249
|
+
const rows = Array.from(element.querySelectorAll('tr'));
|
250
|
+
if (rows.length === 0) continue;
|
251
|
+
|
252
|
+
// Get header cells
|
253
|
+
const headerCells = Array.from(rows[0].querySelectorAll('th, td'));
|
254
|
+
const colCount = headerCells.length || 1;
|
255
|
+
const colWidth = contentWidth / colCount;
|
256
|
+
|
257
|
+
// Start table at current position
|
258
|
+
let tableY = currentY;
|
259
|
+
|
260
|
+
// Check if we need a new page
|
261
|
+
if (tableY + 20 > pdfHeight - margin) {
|
262
|
+
pageNum++;
|
263
|
+
addPageWithHeader(pageNum);
|
264
|
+
tableY = margin;
|
265
|
+
}
|
266
|
+
|
267
|
+
// Draw header background
|
268
|
+
pdf.setFillColor(240, 240, 240);
|
269
|
+
pdf.rect(margin, tableY, contentWidth, 20, 'F');
|
270
|
+
|
271
|
+
// Draw header text
|
272
|
+
pdf.setFont("helvetica", "bold");
|
273
|
+
pdf.setFontSize(10);
|
274
|
+
pdf.setTextColor(0, 0, 0);
|
275
|
+
|
276
|
+
headerCells.forEach((cell, index) => {
|
277
|
+
const text = cell.textContent.trim();
|
278
|
+
const x = margin + (index * colWidth) + 5;
|
279
|
+
pdf.text(text, x, tableY + 13);
|
280
|
+
});
|
281
|
+
|
282
|
+
// Draw horizontal line after header
|
283
|
+
pdf.setDrawColor(200, 200, 200);
|
284
|
+
pdf.setLineWidth(0.5);
|
285
|
+
pdf.line(margin, tableY + 20, margin + contentWidth, tableY + 20);
|
286
|
+
|
287
|
+
tableY += 20;
|
288
|
+
|
289
|
+
// Draw table rows
|
290
|
+
pdf.setFont("helvetica", "normal");
|
291
|
+
for (let i = 1; i < rows.length; i++) {
|
292
|
+
// Check if we need a new page
|
293
|
+
if (tableY + 20 > pdfHeight - margin) {
|
294
|
+
pageNum++;
|
295
|
+
addPageWithHeader(pageNum);
|
296
|
+
tableY = margin;
|
297
|
+
|
298
|
+
// Redraw header on new page
|
299
|
+
pdf.setFillColor(240, 240, 240);
|
300
|
+
pdf.rect(margin, tableY, contentWidth, 20, 'F');
|
301
|
+
|
302
|
+
pdf.setFont("helvetica", "bold");
|
303
|
+
headerCells.forEach((cell, index) => {
|
304
|
+
const text = cell.textContent.trim();
|
305
|
+
const x = margin + (index * colWidth) + 5;
|
306
|
+
pdf.text(text, x, tableY + 13);
|
307
|
+
});
|
308
|
+
|
309
|
+
pdf.line(margin, tableY + 20, margin + contentWidth, tableY + 20);
|
310
|
+
tableY += 20;
|
311
|
+
pdf.setFont("helvetica", "normal");
|
312
|
+
}
|
313
|
+
|
314
|
+
// Get cells for this row
|
315
|
+
const cells = Array.from(rows[i].querySelectorAll('td, th'));
|
316
|
+
|
317
|
+
// Draw cell content
|
318
|
+
cells.forEach((cell, index) => {
|
319
|
+
const text = cell.textContent.trim();
|
320
|
+
if (!text) return;
|
321
|
+
|
322
|
+
const x = margin + (index * colWidth) + 5;
|
323
|
+
pdf.text(text, x, tableY + 13);
|
324
|
+
});
|
325
|
+
|
326
|
+
// Draw horizontal line after row
|
327
|
+
pdf.line(margin, tableY + 20, margin + contentWidth, tableY + 20);
|
328
|
+
tableY += 20;
|
329
|
+
}
|
330
|
+
|
331
|
+
// Draw vertical lines
|
332
|
+
for (let i = 0; i <= colCount; i++) {
|
333
|
+
const x = margin + (i * colWidth);
|
334
|
+
pdf.line(x, currentY, x, tableY);
|
335
|
+
}
|
336
|
+
|
337
|
+
// Update current position to after the table
|
338
|
+
currentY = tableY + 10;
|
339
|
+
}
|
340
|
+
// Code blocks - render with monospace font and background
|
341
|
+
else if (element.tagName === 'PRE' || element.querySelector('pre')) {
|
342
|
+
const preElement = element.tagName === 'PRE' ? element : element.querySelector('pre');
|
343
|
+
const codeText = preElement.textContent.trim();
|
344
|
+
if (!codeText) continue;
|
345
|
+
|
346
|
+
pdf.setFont("courier", "normal"); // Use monospace font for code
|
347
|
+
pdf.setFontSize(9);
|
348
|
+
pdf.setTextColor(0, 0, 0);
|
349
|
+
|
350
|
+
// Split code into lines, respecting line breaks
|
351
|
+
const codeLines = codeText.split(/\r?\n/);
|
352
|
+
const wrappedLines = [];
|
353
|
+
|
354
|
+
codeLines.forEach(line => {
|
355
|
+
// Wrap long lines
|
356
|
+
const wrappedLine = pdf.splitTextToSize(line, contentWidth - 20);
|
357
|
+
wrappedLines.push(...wrappedLine);
|
358
|
+
});
|
359
|
+
|
360
|
+
// Calculate code block height
|
361
|
+
const lineHeight = 12;
|
362
|
+
const codeHeight = wrappedLines.length * lineHeight + 20; // 10px padding top and bottom
|
363
|
+
|
364
|
+
// Check if we need a new page
|
365
|
+
if (currentY + codeHeight > pdfHeight - margin) {
|
366
|
+
pageNum++;
|
367
|
+
addPageWithHeader(pageNum);
|
368
|
+
currentY = margin;
|
369
|
+
}
|
370
|
+
|
371
|
+
// Draw code block background
|
372
|
+
pdf.setFillColor(245, 245, 245);
|
373
|
+
pdf.rect(margin, currentY, contentWidth, codeHeight, 'F');
|
374
|
+
|
375
|
+
// Draw code content
|
376
|
+
pdf.setTextColor(0, 0, 0);
|
377
|
+
wrappedLines.forEach((line, index) => {
|
378
|
+
pdf.text(line, margin + 10, currentY + 15 + (index * lineHeight));
|
379
|
+
});
|
380
|
+
|
381
|
+
// Update position
|
382
|
+
currentY += codeHeight + 10;
|
383
|
+
}
|
384
|
+
// Images - render as images
|
385
|
+
else if (element.tagName === 'IMG' || element.querySelector('img')) {
|
386
|
+
const imgElement = element.tagName === 'IMG' ? element : element.querySelector('img');
|
387
|
+
|
388
|
+
if (!imgElement || !imgElement.src) continue;
|
389
|
+
|
390
|
+
try {
|
391
|
+
// Create a new image to get dimensions
|
392
|
+
const img = new Image();
|
393
|
+
img.src = imgElement.src;
|
394
|
+
|
395
|
+
// Calculate dimensions
|
396
|
+
const imgWidth = contentWidth;
|
397
|
+
const imgHeight = img.height * (contentWidth / img.width);
|
398
|
+
|
399
|
+
// Check if we need a new page
|
400
|
+
if (currentY + imgHeight > pdfHeight - margin) {
|
401
|
+
pageNum++;
|
402
|
+
addPageWithHeader(pageNum);
|
403
|
+
currentY = margin;
|
404
|
+
}
|
405
|
+
|
406
|
+
// Add image to PDF
|
407
|
+
pdf.addImage(img.src, 'JPEG', margin, currentY, imgWidth, imgHeight);
|
408
|
+
currentY += imgHeight + 10;
|
409
|
+
} catch (imgError) {
|
410
|
+
console.error('Error adding image:', imgError);
|
411
|
+
pdf.text("[Image could not be rendered]", margin, currentY + 12);
|
412
|
+
currentY += 20;
|
413
|
+
}
|
414
|
+
}
|
415
|
+
// Fallback for moderately complex elements - try to extract text first before using canvas
|
416
|
+
else {
|
417
|
+
// Try to extract text content and render it directly first
|
418
|
+
const textContent = element.textContent.trim();
|
419
|
+
|
420
|
+
if (textContent) {
|
421
|
+
pdf.setFont('helvetica', 'normal');
|
422
|
+
pdf.setFontSize(11);
|
423
|
+
pdf.setTextColor(0, 0, 0);
|
424
|
+
|
425
|
+
const textLines = pdf.splitTextToSize(textContent, contentWidth);
|
426
|
+
|
427
|
+
// Check if we need a new page
|
428
|
+
if (currentY + (textLines.length * 14) > pdfHeight - margin) {
|
429
|
+
pageNum++;
|
430
|
+
addPageWithHeader(pageNum);
|
431
|
+
currentY = margin;
|
432
|
+
}
|
433
|
+
|
434
|
+
pdf.text(textLines, margin, currentY + 12);
|
435
|
+
currentY += (textLines.length * 14) + 10;
|
436
|
+
} else {
|
437
|
+
// Only use html2canvas as a last resort for elements with no text content
|
438
|
+
try {
|
439
|
+
const canvas = await html2canvas(element, {
|
440
|
+
scale: 2,
|
441
|
+
useCORS: true,
|
442
|
+
logging: false,
|
443
|
+
backgroundColor: '#FFFFFF'
|
444
|
+
});
|
445
|
+
|
446
|
+
const imgData = canvas.toDataURL('image/png');
|
447
|
+
const imgWidth = contentWidth;
|
448
|
+
const imgHeight = (canvas.height * contentWidth) / canvas.width;
|
449
|
+
|
450
|
+
if (currentY + imgHeight > pdfHeight - margin) {
|
451
|
+
pageNum++;
|
452
|
+
addPageWithHeader(pageNum);
|
453
|
+
currentY = margin;
|
454
|
+
}
|
455
|
+
|
456
|
+
pdf.addImage(imgData, 'PNG', margin, currentY, imgWidth, imgHeight);
|
457
|
+
currentY += imgHeight + 10;
|
458
|
+
} catch (canvasError) {
|
459
|
+
console.error('Error rendering complex element:', canvasError);
|
460
|
+
pdf.text("[Content could not be rendered]", margin, currentY + 12);
|
461
|
+
currentY += 20;
|
462
|
+
}
|
463
|
+
}
|
464
|
+
}
|
465
|
+
} catch (elementError) {
|
466
|
+
console.error('Error processing element:', elementError);
|
467
|
+
pdf.text("[Error rendering content]", margin, currentY + 12);
|
468
|
+
currentY += 20;
|
469
|
+
}
|
470
|
+
}
|
471
|
+
|
472
|
+
// Generate the PDF blob
|
473
|
+
const blob = pdf.output('blob');
|
474
|
+
console.log("PDF generation completed successfully");
|
475
|
+
return blob;
|
476
|
+
} catch (error) {
|
477
|
+
console.error('Error in PDF generation:', error);
|
478
|
+
throw error;
|
479
|
+
} finally {
|
480
|
+
// Clean up
|
481
|
+
if (document.body.contains(tempContainer)) {
|
482
|
+
document.body.removeChild(tempContainer);
|
483
|
+
}
|
484
|
+
}
|
485
|
+
}
|
486
|
+
|
487
|
+
/**
|
488
|
+
* Generate and download a PDF from research content
|
489
|
+
* @param {Object|string} titleOrData - Either the research title or the entire research data object
|
490
|
+
* @param {string|null} content - The markdown content of the research, or research ID if first param is data object
|
491
|
+
* @param {Object} metadata - Additional metadata for the PDF
|
492
|
+
* @returns {Promise<void>} A promise that resolves when the PDF has been downloaded
|
493
|
+
*/
|
494
|
+
async function downloadPdf(titleOrData, content, metadata = {}) {
|
495
|
+
try {
|
496
|
+
console.log("Starting PDF download process...");
|
497
|
+
|
498
|
+
let title, pdfContent, pdfMetadata;
|
499
|
+
|
500
|
+
// Determine if we're being passed a research data object or direct parameters
|
501
|
+
if (typeof titleOrData === 'object' && titleOrData !== null) {
|
502
|
+
// We were passed a research data object
|
503
|
+
const researchData = titleOrData;
|
504
|
+
const researchId = content; // Second parameter is research ID in this case
|
505
|
+
|
506
|
+
console.log("Processing research data object", { researchId });
|
507
|
+
|
508
|
+
// Extract title from research data
|
509
|
+
title = researchData.query || researchData.title || researchData.prompt || `Research ${researchId}`;
|
510
|
+
|
511
|
+
// Extract content - try all possible locations based on how data might be structured
|
512
|
+
if (researchData.markdown) {
|
513
|
+
pdfContent = researchData.markdown;
|
514
|
+
} else if (researchData.content) {
|
515
|
+
pdfContent = researchData.content;
|
516
|
+
} else if (researchData.text) {
|
517
|
+
pdfContent = researchData.text;
|
518
|
+
} else if (researchData.summary) {
|
519
|
+
pdfContent = researchData.summary;
|
520
|
+
} else if (researchData.results && Array.isArray(researchData.results)) {
|
521
|
+
pdfContent = researchData.results.join('\n\n');
|
522
|
+
} else if (researchData.report) {
|
523
|
+
pdfContent = researchData.report;
|
524
|
+
} else if (researchData.research && researchData.research.content) {
|
525
|
+
pdfContent = researchData.research.content;
|
526
|
+
} else if (researchData.html) {
|
527
|
+
// If we have HTML, convert it to a reasonable markdown-like format
|
528
|
+
pdfContent = researchData.html
|
529
|
+
.replace(/<h1[^>]*>(.*?)<\/h1>/gi, '# $1\n\n')
|
530
|
+
.replace(/<h2[^>]*>(.*?)<\/h2>/gi, '## $1\n\n')
|
531
|
+
.replace(/<h3[^>]*>(.*?)<\/h3>/gi, '### $1\n\n')
|
532
|
+
.replace(/<p[^>]*>(.*?)<\/p>/gi, '$1\n\n')
|
533
|
+
.replace(/<li[^>]*>(.*?)<\/li>/gi, '- $1\n')
|
534
|
+
.replace(/<br\s*\/?>/gi, '\n')
|
535
|
+
.replace(/<(?:.|\n)*?>/gm, '') // Remove remaining HTML tags
|
536
|
+
.replace(/ /gi, ' ')
|
537
|
+
.replace(/</gi, '<')
|
538
|
+
.replace(/>/gi, '>')
|
539
|
+
.replace(/&/gi, '&')
|
540
|
+
.replace(/"/gi, '"')
|
541
|
+
.replace(/'/gi, "'")
|
542
|
+
.replace(/\n{3,}/g, '\n\n'); // Normalize excessive newlines
|
543
|
+
} else {
|
544
|
+
// Last resort: stringify the entire object
|
545
|
+
console.warn("Could not find content in research data, using JSON stringification");
|
546
|
+
pdfContent = JSON.stringify(researchData, null, 2);
|
547
|
+
}
|
548
|
+
|
549
|
+
// Extract metadata
|
550
|
+
pdfMetadata = {
|
551
|
+
mode: researchData.mode,
|
552
|
+
iterations: researchData.iterations,
|
553
|
+
timestamp: researchData.timestamp || researchData.created_at || new Date().toISOString(),
|
554
|
+
id: researchId
|
555
|
+
};
|
556
|
+
} else {
|
557
|
+
// We were passed direct parameters
|
558
|
+
title = titleOrData || 'Research Report';
|
559
|
+
pdfContent = content || '';
|
560
|
+
pdfMetadata = metadata || {};
|
561
|
+
}
|
562
|
+
|
563
|
+
// Ensure title is a string
|
564
|
+
title = String(title || 'Research Report');
|
565
|
+
|
566
|
+
console.log("PDF parameters prepared", { titleLength: title.length, contentLength: pdfContent?.length });
|
567
|
+
|
568
|
+
// Show loading indicator
|
569
|
+
const loadingIndicator = document.createElement('div');
|
570
|
+
loadingIndicator.className = 'loading-indicator';
|
571
|
+
loadingIndicator.innerHTML = '<div class="spinner"></div><div>Generating PDF...</div>';
|
572
|
+
loadingIndicator.style.position = 'fixed';
|
573
|
+
loadingIndicator.style.top = '50%';
|
574
|
+
loadingIndicator.style.left = '50%';
|
575
|
+
loadingIndicator.style.transform = 'translate(-50%, -50%)';
|
576
|
+
loadingIndicator.style.zIndex = '9999';
|
577
|
+
loadingIndicator.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
|
578
|
+
loadingIndicator.style.color = 'white';
|
579
|
+
loadingIndicator.style.padding = '20px';
|
580
|
+
loadingIndicator.style.borderRadius = '5px';
|
581
|
+
document.body.appendChild(loadingIndicator);
|
582
|
+
|
583
|
+
// Generate the PDF
|
584
|
+
const blob = await generatePdf(title, pdfContent, pdfMetadata);
|
585
|
+
|
586
|
+
// Create a download link
|
587
|
+
const url = URL.createObjectURL(blob);
|
588
|
+
const downloadLink = document.createElement('a');
|
589
|
+
downloadLink.href = url;
|
590
|
+
|
591
|
+
// Generate a filename based on the title
|
592
|
+
const safeTitle = title.replace(/[^a-z0-9]/gi, '_').toLowerCase().substring(0, 30);
|
593
|
+
downloadLink.download = `${safeTitle}_research.pdf`;
|
594
|
+
|
595
|
+
// Trigger the download
|
596
|
+
document.body.appendChild(downloadLink);
|
597
|
+
downloadLink.click();
|
598
|
+
|
599
|
+
// Clean up
|
600
|
+
document.body.removeChild(downloadLink);
|
601
|
+
URL.revokeObjectURL(url);
|
602
|
+
|
603
|
+
console.log("PDF download process completed");
|
604
|
+
return true;
|
605
|
+
} catch (error) {
|
606
|
+
console.error('Error generating PDF:', error);
|
607
|
+
alert('Error generating PDF: ' + (error.message || 'Unknown error'));
|
608
|
+
throw error;
|
609
|
+
} finally {
|
610
|
+
// Remove loading indicator
|
611
|
+
const loadingIndicator = document.querySelector('.loading-indicator');
|
612
|
+
if (loadingIndicator) {
|
613
|
+
document.body.removeChild(loadingIndicator);
|
614
|
+
}
|
615
|
+
}
|
616
|
+
}
|
617
|
+
|
618
|
+
// Export PDF functions
|
619
|
+
window.pdfService = {
|
620
|
+
generatePdf,
|
621
|
+
downloadPdf
|
622
|
+
};
|