local-deep-research 0.4.4__py3-none-any.whl → 0.5.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.
Files changed (220) hide show
  1. local_deep_research/__init__.py +7 -0
  2. local_deep_research/__version__.py +1 -1
  3. local_deep_research/advanced_search_system/answer_decoding/__init__.py +5 -0
  4. local_deep_research/advanced_search_system/answer_decoding/browsecomp_answer_decoder.py +421 -0
  5. local_deep_research/advanced_search_system/candidate_exploration/README.md +219 -0
  6. local_deep_research/advanced_search_system/candidate_exploration/__init__.py +25 -0
  7. local_deep_research/advanced_search_system/candidate_exploration/adaptive_explorer.py +329 -0
  8. local_deep_research/advanced_search_system/candidate_exploration/base_explorer.py +341 -0
  9. local_deep_research/advanced_search_system/candidate_exploration/constraint_guided_explorer.py +436 -0
  10. local_deep_research/advanced_search_system/candidate_exploration/diversity_explorer.py +457 -0
  11. local_deep_research/advanced_search_system/candidate_exploration/parallel_explorer.py +250 -0
  12. local_deep_research/advanced_search_system/candidate_exploration/progressive_explorer.py +255 -0
  13. local_deep_research/advanced_search_system/candidates/__init__.py +5 -0
  14. local_deep_research/advanced_search_system/candidates/base_candidate.py +59 -0
  15. local_deep_research/advanced_search_system/constraint_checking/README.md +150 -0
  16. local_deep_research/advanced_search_system/constraint_checking/__init__.py +35 -0
  17. local_deep_research/advanced_search_system/constraint_checking/base_constraint_checker.py +122 -0
  18. local_deep_research/advanced_search_system/constraint_checking/constraint_checker.py +223 -0
  19. local_deep_research/advanced_search_system/constraint_checking/constraint_satisfaction_tracker.py +387 -0
  20. local_deep_research/advanced_search_system/constraint_checking/dual_confidence_checker.py +424 -0
  21. local_deep_research/advanced_search_system/constraint_checking/evidence_analyzer.py +174 -0
  22. local_deep_research/advanced_search_system/constraint_checking/intelligent_constraint_relaxer.py +503 -0
  23. local_deep_research/advanced_search_system/constraint_checking/rejection_engine.py +143 -0
  24. local_deep_research/advanced_search_system/constraint_checking/strict_checker.py +259 -0
  25. local_deep_research/advanced_search_system/constraint_checking/threshold_checker.py +213 -0
  26. local_deep_research/advanced_search_system/constraints/__init__.py +6 -0
  27. local_deep_research/advanced_search_system/constraints/base_constraint.py +58 -0
  28. local_deep_research/advanced_search_system/constraints/constraint_analyzer.py +143 -0
  29. local_deep_research/advanced_search_system/evidence/__init__.py +12 -0
  30. local_deep_research/advanced_search_system/evidence/base_evidence.py +57 -0
  31. local_deep_research/advanced_search_system/evidence/evaluator.py +159 -0
  32. local_deep_research/advanced_search_system/evidence/requirements.py +122 -0
  33. local_deep_research/advanced_search_system/filters/base_filter.py +3 -1
  34. local_deep_research/advanced_search_system/filters/cross_engine_filter.py +8 -2
  35. local_deep_research/advanced_search_system/filters/journal_reputation_filter.py +43 -29
  36. local_deep_research/advanced_search_system/findings/repository.py +54 -17
  37. local_deep_research/advanced_search_system/knowledge/standard_knowledge.py +3 -1
  38. local_deep_research/advanced_search_system/query_generation/adaptive_query_generator.py +405 -0
  39. local_deep_research/advanced_search_system/questions/__init__.py +16 -0
  40. local_deep_research/advanced_search_system/questions/atomic_fact_question.py +171 -0
  41. local_deep_research/advanced_search_system/questions/browsecomp_question.py +287 -0
  42. local_deep_research/advanced_search_system/questions/decomposition_question.py +13 -4
  43. local_deep_research/advanced_search_system/questions/entity_aware_question.py +184 -0
  44. local_deep_research/advanced_search_system/questions/standard_question.py +9 -3
  45. local_deep_research/advanced_search_system/search_optimization/cross_constraint_manager.py +624 -0
  46. local_deep_research/advanced_search_system/source_management/diversity_manager.py +613 -0
  47. local_deep_research/advanced_search_system/strategies/__init__.py +42 -0
  48. local_deep_research/advanced_search_system/strategies/adaptive_decomposition_strategy.py +564 -0
  49. local_deep_research/advanced_search_system/strategies/base_strategy.py +4 -4
  50. local_deep_research/advanced_search_system/strategies/browsecomp_entity_strategy.py +1031 -0
  51. local_deep_research/advanced_search_system/strategies/browsecomp_optimized_strategy.py +778 -0
  52. local_deep_research/advanced_search_system/strategies/concurrent_dual_confidence_strategy.py +446 -0
  53. local_deep_research/advanced_search_system/strategies/constrained_search_strategy.py +1348 -0
  54. local_deep_research/advanced_search_system/strategies/constraint_parallel_strategy.py +522 -0
  55. local_deep_research/advanced_search_system/strategies/direct_search_strategy.py +217 -0
  56. local_deep_research/advanced_search_system/strategies/dual_confidence_strategy.py +320 -0
  57. local_deep_research/advanced_search_system/strategies/dual_confidence_with_rejection.py +219 -0
  58. local_deep_research/advanced_search_system/strategies/early_stop_constrained_strategy.py +369 -0
  59. local_deep_research/advanced_search_system/strategies/entity_aware_source_strategy.py +140 -0
  60. local_deep_research/advanced_search_system/strategies/evidence_based_strategy.py +1248 -0
  61. local_deep_research/advanced_search_system/strategies/evidence_based_strategy_v2.py +1337 -0
  62. local_deep_research/advanced_search_system/strategies/focused_iteration_strategy.py +537 -0
  63. local_deep_research/advanced_search_system/strategies/improved_evidence_based_strategy.py +782 -0
  64. local_deep_research/advanced_search_system/strategies/iterative_reasoning_strategy.py +760 -0
  65. local_deep_research/advanced_search_system/strategies/iterdrag_strategy.py +55 -21
  66. local_deep_research/advanced_search_system/strategies/llm_driven_modular_strategy.py +865 -0
  67. local_deep_research/advanced_search_system/strategies/modular_strategy.py +1142 -0
  68. local_deep_research/advanced_search_system/strategies/parallel_constrained_strategy.py +506 -0
  69. local_deep_research/advanced_search_system/strategies/parallel_search_strategy.py +34 -16
  70. local_deep_research/advanced_search_system/strategies/rapid_search_strategy.py +29 -9
  71. local_deep_research/advanced_search_system/strategies/recursive_decomposition_strategy.py +492 -0
  72. local_deep_research/advanced_search_system/strategies/smart_decomposition_strategy.py +284 -0
  73. local_deep_research/advanced_search_system/strategies/smart_query_strategy.py +515 -0
  74. local_deep_research/advanced_search_system/strategies/source_based_strategy.py +48 -24
  75. local_deep_research/advanced_search_system/strategies/standard_strategy.py +34 -14
  76. local_deep_research/advanced_search_system/tools/base_tool.py +7 -2
  77. local_deep_research/api/benchmark_functions.py +6 -2
  78. local_deep_research/api/research_functions.py +10 -4
  79. local_deep_research/benchmarks/__init__.py +9 -7
  80. local_deep_research/benchmarks/benchmark_functions.py +6 -2
  81. local_deep_research/benchmarks/cli/benchmark_commands.py +27 -10
  82. local_deep_research/benchmarks/cli.py +38 -13
  83. local_deep_research/benchmarks/comparison/__init__.py +4 -2
  84. local_deep_research/benchmarks/comparison/evaluator.py +316 -239
  85. local_deep_research/benchmarks/datasets/__init__.py +1 -1
  86. local_deep_research/benchmarks/datasets/base.py +91 -72
  87. local_deep_research/benchmarks/datasets/browsecomp.py +54 -33
  88. local_deep_research/benchmarks/datasets/custom_dataset_template.py +19 -19
  89. local_deep_research/benchmarks/datasets/simpleqa.py +14 -14
  90. local_deep_research/benchmarks/datasets/utils.py +48 -29
  91. local_deep_research/benchmarks/datasets.py +4 -11
  92. local_deep_research/benchmarks/efficiency/__init__.py +8 -4
  93. local_deep_research/benchmarks/efficiency/resource_monitor.py +223 -171
  94. local_deep_research/benchmarks/efficiency/speed_profiler.py +62 -48
  95. local_deep_research/benchmarks/evaluators/browsecomp.py +3 -1
  96. local_deep_research/benchmarks/evaluators/composite.py +6 -2
  97. local_deep_research/benchmarks/evaluators/simpleqa.py +36 -13
  98. local_deep_research/benchmarks/graders.py +32 -10
  99. local_deep_research/benchmarks/metrics/README.md +1 -1
  100. local_deep_research/benchmarks/metrics/calculation.py +25 -10
  101. local_deep_research/benchmarks/metrics/reporting.py +7 -3
  102. local_deep_research/benchmarks/metrics/visualization.py +42 -23
  103. local_deep_research/benchmarks/metrics.py +1 -1
  104. local_deep_research/benchmarks/optimization/__init__.py +3 -1
  105. local_deep_research/benchmarks/optimization/api.py +7 -1
  106. local_deep_research/benchmarks/optimization/optuna_optimizer.py +75 -26
  107. local_deep_research/benchmarks/runners.py +48 -15
  108. local_deep_research/citation_handler.py +65 -92
  109. local_deep_research/citation_handlers/__init__.py +15 -0
  110. local_deep_research/citation_handlers/base_citation_handler.py +70 -0
  111. local_deep_research/citation_handlers/forced_answer_citation_handler.py +179 -0
  112. local_deep_research/citation_handlers/precision_extraction_handler.py +550 -0
  113. local_deep_research/citation_handlers/standard_citation_handler.py +80 -0
  114. local_deep_research/config/llm_config.py +271 -169
  115. local_deep_research/config/search_config.py +14 -5
  116. local_deep_research/defaults/__init__.py +0 -1
  117. local_deep_research/metrics/__init__.py +13 -0
  118. local_deep_research/metrics/database.py +58 -0
  119. local_deep_research/metrics/db_models.py +115 -0
  120. local_deep_research/metrics/migrate_add_provider_to_token_usage.py +148 -0
  121. local_deep_research/metrics/migrate_call_stack_tracking.py +105 -0
  122. local_deep_research/metrics/migrate_enhanced_tracking.py +75 -0
  123. local_deep_research/metrics/migrate_research_ratings.py +31 -0
  124. local_deep_research/metrics/models.py +61 -0
  125. local_deep_research/metrics/pricing/__init__.py +12 -0
  126. local_deep_research/metrics/pricing/cost_calculator.py +237 -0
  127. local_deep_research/metrics/pricing/pricing_cache.py +143 -0
  128. local_deep_research/metrics/pricing/pricing_fetcher.py +240 -0
  129. local_deep_research/metrics/query_utils.py +51 -0
  130. local_deep_research/metrics/search_tracker.py +380 -0
  131. local_deep_research/metrics/token_counter.py +1078 -0
  132. local_deep_research/migrate_db.py +3 -1
  133. local_deep_research/report_generator.py +22 -8
  134. local_deep_research/search_system.py +390 -9
  135. local_deep_research/test_migration.py +15 -5
  136. local_deep_research/utilities/db_utils.py +7 -4
  137. local_deep_research/utilities/es_utils.py +115 -104
  138. local_deep_research/utilities/llm_utils.py +15 -5
  139. local_deep_research/utilities/log_utils.py +151 -0
  140. local_deep_research/utilities/search_cache.py +387 -0
  141. local_deep_research/utilities/search_utilities.py +14 -6
  142. local_deep_research/utilities/threading_utils.py +92 -0
  143. local_deep_research/utilities/url_utils.py +6 -0
  144. local_deep_research/web/api.py +347 -0
  145. local_deep_research/web/app.py +13 -17
  146. local_deep_research/web/app_factory.py +71 -66
  147. local_deep_research/web/database/migrate_to_ldr_db.py +12 -4
  148. local_deep_research/web/database/migrations.py +20 -3
  149. local_deep_research/web/database/models.py +74 -25
  150. local_deep_research/web/database/schema_upgrade.py +49 -29
  151. local_deep_research/web/models/database.py +63 -83
  152. local_deep_research/web/routes/api_routes.py +56 -22
  153. local_deep_research/web/routes/benchmark_routes.py +4 -1
  154. local_deep_research/web/routes/globals.py +22 -0
  155. local_deep_research/web/routes/history_routes.py +71 -46
  156. local_deep_research/web/routes/metrics_routes.py +1155 -0
  157. local_deep_research/web/routes/research_routes.py +192 -54
  158. local_deep_research/web/routes/settings_routes.py +156 -55
  159. local_deep_research/web/services/research_service.py +412 -251
  160. local_deep_research/web/services/resource_service.py +36 -11
  161. local_deep_research/web/services/settings_manager.py +55 -17
  162. local_deep_research/web/services/settings_service.py +12 -4
  163. local_deep_research/web/services/socket_service.py +295 -188
  164. local_deep_research/web/static/css/custom_dropdown.css +180 -0
  165. local_deep_research/web/static/css/styles.css +39 -1
  166. local_deep_research/web/static/js/components/detail.js +633 -267
  167. local_deep_research/web/static/js/components/details.js +751 -0
  168. local_deep_research/web/static/js/components/fallback/formatting.js +11 -11
  169. local_deep_research/web/static/js/components/fallback/ui.js +23 -23
  170. local_deep_research/web/static/js/components/history.js +76 -76
  171. local_deep_research/web/static/js/components/logpanel.js +61 -13
  172. local_deep_research/web/static/js/components/progress.js +13 -2
  173. local_deep_research/web/static/js/components/research.js +99 -12
  174. local_deep_research/web/static/js/components/results.js +239 -106
  175. local_deep_research/web/static/js/main.js +40 -40
  176. local_deep_research/web/static/js/services/audio.js +1 -1
  177. local_deep_research/web/static/js/services/formatting.js +11 -11
  178. local_deep_research/web/static/js/services/keyboard.js +157 -0
  179. local_deep_research/web/static/js/services/pdf.js +80 -80
  180. local_deep_research/web/static/sounds/README.md +1 -1
  181. local_deep_research/web/templates/base.html +1 -0
  182. local_deep_research/web/templates/components/log_panel.html +7 -1
  183. local_deep_research/web/templates/components/mobile_nav.html +1 -1
  184. local_deep_research/web/templates/components/sidebar.html +3 -0
  185. local_deep_research/web/templates/pages/cost_analytics.html +1245 -0
  186. local_deep_research/web/templates/pages/details.html +325 -24
  187. local_deep_research/web/templates/pages/history.html +1 -1
  188. local_deep_research/web/templates/pages/metrics.html +1929 -0
  189. local_deep_research/web/templates/pages/progress.html +2 -2
  190. local_deep_research/web/templates/pages/research.html +53 -17
  191. local_deep_research/web/templates/pages/results.html +12 -1
  192. local_deep_research/web/templates/pages/star_reviews.html +803 -0
  193. local_deep_research/web/utils/formatters.py +9 -3
  194. local_deep_research/web_search_engines/default_search_engines.py +5 -3
  195. local_deep_research/web_search_engines/engines/full_search.py +8 -2
  196. local_deep_research/web_search_engines/engines/meta_search_engine.py +59 -20
  197. local_deep_research/web_search_engines/engines/search_engine_arxiv.py +19 -6
  198. local_deep_research/web_search_engines/engines/search_engine_brave.py +6 -2
  199. local_deep_research/web_search_engines/engines/search_engine_ddg.py +3 -1
  200. local_deep_research/web_search_engines/engines/search_engine_elasticsearch.py +81 -58
  201. local_deep_research/web_search_engines/engines/search_engine_github.py +46 -15
  202. local_deep_research/web_search_engines/engines/search_engine_google_pse.py +16 -6
  203. local_deep_research/web_search_engines/engines/search_engine_guardian.py +39 -15
  204. local_deep_research/web_search_engines/engines/search_engine_local.py +58 -25
  205. local_deep_research/web_search_engines/engines/search_engine_local_all.py +15 -5
  206. local_deep_research/web_search_engines/engines/search_engine_pubmed.py +63 -21
  207. local_deep_research/web_search_engines/engines/search_engine_searxng.py +37 -11
  208. local_deep_research/web_search_engines/engines/search_engine_semantic_scholar.py +27 -9
  209. local_deep_research/web_search_engines/engines/search_engine_serpapi.py +12 -4
  210. local_deep_research/web_search_engines/engines/search_engine_wayback.py +31 -10
  211. local_deep_research/web_search_engines/engines/search_engine_wikipedia.py +12 -3
  212. local_deep_research/web_search_engines/search_engine_base.py +83 -35
  213. local_deep_research/web_search_engines/search_engine_factory.py +25 -8
  214. local_deep_research/web_search_engines/search_engines_config.py +9 -3
  215. {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.2.dist-info}/METADATA +7 -1
  216. local_deep_research-0.5.2.dist-info/RECORD +265 -0
  217. local_deep_research-0.4.4.dist-info/RECORD +0 -177
  218. {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.2.dist-info}/WHEEL +0 -0
  219. {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.2.dist-info}/entry_points.txt +0 -0
  220. {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.2.dist-info}/licenses/LICENSE +0 -0
@@ -15,15 +15,15 @@ async function generatePdf(title, content, metadata = {}) {
15
15
  if (typeof jspdf === 'undefined') {
16
16
  throw new Error('PDF generation libraries not loaded (jsPDF missing)');
17
17
  }
18
-
18
+
19
19
  if (typeof html2canvas === 'undefined') {
20
20
  throw new Error('PDF generation libraries not loaded (html2canvas missing)');
21
21
  }
22
-
22
+
23
23
  // Ensure title and content are strings
24
24
  title = String(title || 'Research Report');
25
25
  content = String(content || '');
26
-
26
+
27
27
  // Create a temporary container to render the markdown
28
28
  const tempContainer = document.createElement('div');
29
29
  tempContainer.style.position = 'absolute';
@@ -31,7 +31,7 @@ async function generatePdf(title, content, metadata = {}) {
31
31
  tempContainer.style.top = '-9999px';
32
32
  tempContainer.style.width = '8.5in'; // US Letter width
33
33
  tempContainer.className = 'pdf-content';
34
-
34
+
35
35
  // Add PDF-specific styles
36
36
  tempContainer.innerHTML = `
37
37
  <style>
@@ -144,23 +144,23 @@ async function generatePdf(title, content, metadata = {}) {
144
144
  <p>Generated by Deep Research System</p>
145
145
  </div>
146
146
  `;
147
-
147
+
148
148
  // Add to the document body temporarily
149
149
  document.body.appendChild(tempContainer);
150
-
150
+
151
151
  try {
152
152
  console.log("Starting PDF generation...");
153
-
153
+
154
154
  // Use the jsPDF library from the window object
155
155
  const { jsPDF } = window.jspdf;
156
-
156
+
157
157
  // Create a new PDF document
158
158
  const pdf = new jsPDF('p', 'pt', 'letter');
159
159
  const pdfWidth = pdf.internal.pageSize.getWidth();
160
160
  const pdfHeight = pdf.internal.pageSize.getHeight();
161
161
  const margin = 40;
162
162
  const contentWidth = pdfWidth - 2 * margin;
163
-
163
+
164
164
  // Function to add page with header
165
165
  const addPageWithHeader = (pageNum) => {
166
166
  if (pageNum > 1) {
@@ -170,49 +170,49 @@ async function generatePdf(title, content, metadata = {}) {
170
170
  pdf.setTextColor(100, 100, 100);
171
171
  pdf.text(`Deep Research - ${title} - Page ${pageNum}`, margin, pdfHeight - 20);
172
172
  };
173
-
173
+
174
174
  // Process each element with a more optimized approach
175
175
  const contentDiv = tempContainer.querySelector('.pdf-body');
176
176
  if (!contentDiv) {
177
177
  throw new Error('PDF body container not found');
178
178
  }
179
-
179
+
180
180
  // Create a more efficient PDF generation approach that keeps text selectable
181
181
  const elements = Array.from(contentDiv.children);
182
182
  let currentY = margin;
183
183
  let pageNum = 1;
184
-
184
+
185
185
  // Add the first page with header
186
186
  addPageWithHeader(pageNum);
187
-
187
+
188
188
  // Process each element
189
189
  for (const element of elements) {
190
190
  // Skip style element
191
191
  if (element.tagName === 'STYLE') continue;
192
-
192
+
193
193
  try {
194
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') &&
195
+ if ((element.tagName === 'P' || element.tagName === 'DIV' || element.tagName === 'H1' ||
196
+ element.tagName === 'H2' || element.tagName === 'H3') &&
197
197
  !element.querySelector('img, canvas, svg, table') &&
198
198
  !element.innerHTML.includes('<')) {
199
-
199
+
200
200
  pdf.setFont('helvetica', element.tagName.startsWith('H') ? 'bold' : 'normal');
201
201
  pdf.setFontSize(element.tagName === 'H1' ? 18 : (element.tagName === 'H2' ? 16 : (element.tagName === 'H3' ? 14 : 11)));
202
202
  pdf.setTextColor(0, 0, 0);
203
-
203
+
204
204
  const text = element.textContent.trim();
205
205
  if (!text) continue; // Skip empty text
206
-
206
+
207
207
  const textLines = pdf.splitTextToSize(text, contentWidth);
208
-
208
+
209
209
  // Check if we need a new page
210
210
  if (currentY + (textLines.length * 14) > pdfHeight - margin) {
211
211
  pageNum++;
212
212
  addPageWithHeader(pageNum);
213
213
  currentY = margin;
214
214
  }
215
-
215
+
216
216
  pdf.text(textLines, margin, currentY + 12);
217
217
  currentY += (textLines.length * 14) + 10;
218
218
  }
@@ -223,22 +223,22 @@ async function generatePdf(title, content, metadata = {}) {
223
223
  const item = listItems[i];
224
224
  const itemText = item.textContent.trim();
225
225
  if (!itemText) continue;
226
-
226
+
227
227
  pdf.setFont('helvetica', 'normal');
228
228
  pdf.setFontSize(11);
229
229
  pdf.setTextColor(0, 0, 0);
230
-
230
+
231
231
  const bulletPoint = element.tagName === 'UL' ? '•' : `${i + 1}.`;
232
232
  const textWithBullet = `${bulletPoint} ${itemText}`;
233
233
  const textLines = pdf.splitTextToSize(textWithBullet, contentWidth - 10);
234
-
234
+
235
235
  // Check if we need a new page
236
236
  if (currentY + (textLines.length * 14) > pdfHeight - margin) {
237
237
  pageNum++;
238
238
  addPageWithHeader(pageNum);
239
239
  currentY = margin;
240
240
  }
241
-
241
+
242
242
  pdf.text(textLines, margin, currentY + 12);
243
243
  currentY += (textLines.length * 14) + 5;
244
244
  }
@@ -248,44 +248,44 @@ async function generatePdf(title, content, metadata = {}) {
248
248
  else if (element.tagName === 'TABLE') {
249
249
  const rows = Array.from(element.querySelectorAll('tr'));
250
250
  if (rows.length === 0) continue;
251
-
251
+
252
252
  // Get header cells
253
253
  const headerCells = Array.from(rows[0].querySelectorAll('th, td'));
254
254
  const colCount = headerCells.length || 1;
255
255
  const colWidth = contentWidth / colCount;
256
-
256
+
257
257
  // Start table at current position
258
258
  let tableY = currentY;
259
-
259
+
260
260
  // Check if we need a new page
261
261
  if (tableY + 20 > pdfHeight - margin) {
262
262
  pageNum++;
263
263
  addPageWithHeader(pageNum);
264
264
  tableY = margin;
265
265
  }
266
-
266
+
267
267
  // Draw header background
268
268
  pdf.setFillColor(240, 240, 240);
269
269
  pdf.rect(margin, tableY, contentWidth, 20, 'F');
270
-
270
+
271
271
  // Draw header text
272
272
  pdf.setFont("helvetica", "bold");
273
273
  pdf.setFontSize(10);
274
274
  pdf.setTextColor(0, 0, 0);
275
-
275
+
276
276
  headerCells.forEach((cell, index) => {
277
277
  const text = cell.textContent.trim();
278
278
  const x = margin + (index * colWidth) + 5;
279
279
  pdf.text(text, x, tableY + 13);
280
280
  });
281
-
281
+
282
282
  // Draw horizontal line after header
283
283
  pdf.setDrawColor(200, 200, 200);
284
284
  pdf.setLineWidth(0.5);
285
285
  pdf.line(margin, tableY + 20, margin + contentWidth, tableY + 20);
286
-
286
+
287
287
  tableY += 20;
288
-
288
+
289
289
  // Draw table rows
290
290
  pdf.setFont("helvetica", "normal");
291
291
  for (let i = 1; i < rows.length; i++) {
@@ -294,46 +294,46 @@ async function generatePdf(title, content, metadata = {}) {
294
294
  pageNum++;
295
295
  addPageWithHeader(pageNum);
296
296
  tableY = margin;
297
-
297
+
298
298
  // Redraw header on new page
299
299
  pdf.setFillColor(240, 240, 240);
300
300
  pdf.rect(margin, tableY, contentWidth, 20, 'F');
301
-
301
+
302
302
  pdf.setFont("helvetica", "bold");
303
303
  headerCells.forEach((cell, index) => {
304
304
  const text = cell.textContent.trim();
305
305
  const x = margin + (index * colWidth) + 5;
306
306
  pdf.text(text, x, tableY + 13);
307
307
  });
308
-
308
+
309
309
  pdf.line(margin, tableY + 20, margin + contentWidth, tableY + 20);
310
310
  tableY += 20;
311
311
  pdf.setFont("helvetica", "normal");
312
312
  }
313
-
313
+
314
314
  // Get cells for this row
315
315
  const cells = Array.from(rows[i].querySelectorAll('td, th'));
316
-
316
+
317
317
  // Draw cell content
318
318
  cells.forEach((cell, index) => {
319
319
  const text = cell.textContent.trim();
320
320
  if (!text) return;
321
-
321
+
322
322
  const x = margin + (index * colWidth) + 5;
323
323
  pdf.text(text, x, tableY + 13);
324
324
  });
325
-
325
+
326
326
  // Draw horizontal line after row
327
327
  pdf.line(margin, tableY + 20, margin + contentWidth, tableY + 20);
328
328
  tableY += 20;
329
329
  }
330
-
330
+
331
331
  // Draw vertical lines
332
332
  for (let i = 0; i <= colCount; i++) {
333
333
  const x = margin + (i * colWidth);
334
334
  pdf.line(x, currentY, x, tableY);
335
335
  }
336
-
336
+
337
337
  // Update current position to after the table
338
338
  currentY = tableY + 10;
339
339
  }
@@ -342,67 +342,67 @@ async function generatePdf(title, content, metadata = {}) {
342
342
  const preElement = element.tagName === 'PRE' ? element : element.querySelector('pre');
343
343
  const codeText = preElement.textContent.trim();
344
344
  if (!codeText) continue;
345
-
345
+
346
346
  pdf.setFont("courier", "normal"); // Use monospace font for code
347
347
  pdf.setFontSize(9);
348
348
  pdf.setTextColor(0, 0, 0);
349
-
349
+
350
350
  // Split code into lines, respecting line breaks
351
351
  const codeLines = codeText.split(/\r?\n/);
352
352
  const wrappedLines = [];
353
-
353
+
354
354
  codeLines.forEach(line => {
355
355
  // Wrap long lines
356
356
  const wrappedLine = pdf.splitTextToSize(line, contentWidth - 20);
357
357
  wrappedLines.push(...wrappedLine);
358
358
  });
359
-
359
+
360
360
  // Calculate code block height
361
361
  const lineHeight = 12;
362
362
  const codeHeight = wrappedLines.length * lineHeight + 20; // 10px padding top and bottom
363
-
363
+
364
364
  // Check if we need a new page
365
365
  if (currentY + codeHeight > pdfHeight - margin) {
366
366
  pageNum++;
367
367
  addPageWithHeader(pageNum);
368
368
  currentY = margin;
369
369
  }
370
-
370
+
371
371
  // Draw code block background
372
372
  pdf.setFillColor(245, 245, 245);
373
373
  pdf.rect(margin, currentY, contentWidth, codeHeight, 'F');
374
-
374
+
375
375
  // Draw code content
376
376
  pdf.setTextColor(0, 0, 0);
377
377
  wrappedLines.forEach((line, index) => {
378
378
  pdf.text(line, margin + 10, currentY + 15 + (index * lineHeight));
379
379
  });
380
-
380
+
381
381
  // Update position
382
382
  currentY += codeHeight + 10;
383
383
  }
384
384
  // Images - render as images
385
385
  else if (element.tagName === 'IMG' || element.querySelector('img')) {
386
386
  const imgElement = element.tagName === 'IMG' ? element : element.querySelector('img');
387
-
387
+
388
388
  if (!imgElement || !imgElement.src) continue;
389
-
389
+
390
390
  try {
391
391
  // Create a new image to get dimensions
392
392
  const img = new Image();
393
393
  img.src = imgElement.src;
394
-
394
+
395
395
  // Calculate dimensions
396
396
  const imgWidth = contentWidth;
397
397
  const imgHeight = img.height * (contentWidth / img.width);
398
-
398
+
399
399
  // Check if we need a new page
400
400
  if (currentY + imgHeight > pdfHeight - margin) {
401
401
  pageNum++;
402
402
  addPageWithHeader(pageNum);
403
403
  currentY = margin;
404
404
  }
405
-
405
+
406
406
  // Add image to PDF
407
407
  pdf.addImage(img.src, 'JPEG', margin, currentY, imgWidth, imgHeight);
408
408
  currentY += imgHeight + 10;
@@ -416,21 +416,21 @@ async function generatePdf(title, content, metadata = {}) {
416
416
  else {
417
417
  // Try to extract text content and render it directly first
418
418
  const textContent = element.textContent.trim();
419
-
419
+
420
420
  if (textContent) {
421
421
  pdf.setFont('helvetica', 'normal');
422
422
  pdf.setFontSize(11);
423
423
  pdf.setTextColor(0, 0, 0);
424
-
424
+
425
425
  const textLines = pdf.splitTextToSize(textContent, contentWidth);
426
-
426
+
427
427
  // Check if we need a new page
428
428
  if (currentY + (textLines.length * 14) > pdfHeight - margin) {
429
429
  pageNum++;
430
430
  addPageWithHeader(pageNum);
431
431
  currentY = margin;
432
432
  }
433
-
433
+
434
434
  pdf.text(textLines, margin, currentY + 12);
435
435
  currentY += (textLines.length * 14) + 10;
436
436
  } else {
@@ -442,17 +442,17 @@ async function generatePdf(title, content, metadata = {}) {
442
442
  logging: false,
443
443
  backgroundColor: '#FFFFFF'
444
444
  });
445
-
445
+
446
446
  const imgData = canvas.toDataURL('image/png');
447
447
  const imgWidth = contentWidth;
448
448
  const imgHeight = (canvas.height * contentWidth) / canvas.width;
449
-
449
+
450
450
  if (currentY + imgHeight > pdfHeight - margin) {
451
451
  pageNum++;
452
452
  addPageWithHeader(pageNum);
453
453
  currentY = margin;
454
454
  }
455
-
455
+
456
456
  pdf.addImage(imgData, 'PNG', margin, currentY, imgWidth, imgHeight);
457
457
  currentY += imgHeight + 10;
458
458
  } catch (canvasError) {
@@ -468,7 +468,7 @@ async function generatePdf(title, content, metadata = {}) {
468
468
  currentY += 20;
469
469
  }
470
470
  }
471
-
471
+
472
472
  // Generate the PDF blob
473
473
  const blob = pdf.output('blob');
474
474
  console.log("PDF generation completed successfully");
@@ -494,20 +494,20 @@ async function generatePdf(title, content, metadata = {}) {
494
494
  async function downloadPdf(titleOrData, content, metadata = {}) {
495
495
  try {
496
496
  console.log("Starting PDF download process...");
497
-
497
+
498
498
  let title, pdfContent, pdfMetadata;
499
-
499
+
500
500
  // Determine if we're being passed a research data object or direct parameters
501
501
  if (typeof titleOrData === 'object' && titleOrData !== null) {
502
502
  // We were passed a research data object
503
503
  const researchData = titleOrData;
504
504
  const researchId = content; // Second parameter is research ID in this case
505
-
505
+
506
506
  console.log("Processing research data object", { researchId });
507
-
507
+
508
508
  // Extract title from research data
509
509
  title = researchData.query || researchData.title || researchData.prompt || `Research ${researchId}`;
510
-
510
+
511
511
  // Extract content - try all possible locations based on how data might be structured
512
512
  if (researchData.markdown) {
513
513
  pdfContent = researchData.markdown;
@@ -545,7 +545,7 @@ async function downloadPdf(titleOrData, content, metadata = {}) {
545
545
  console.warn("Could not find content in research data, using JSON stringification");
546
546
  pdfContent = JSON.stringify(researchData, null, 2);
547
547
  }
548
-
548
+
549
549
  // Extract metadata
550
550
  pdfMetadata = {
551
551
  mode: researchData.mode,
@@ -559,12 +559,12 @@ async function downloadPdf(titleOrData, content, metadata = {}) {
559
559
  pdfContent = content || '';
560
560
  pdfMetadata = metadata || {};
561
561
  }
562
-
562
+
563
563
  // Ensure title is a string
564
564
  title = String(title || 'Research Report');
565
-
565
+
566
566
  console.log("PDF parameters prepared", { titleLength: title.length, contentLength: pdfContent?.length });
567
-
567
+
568
568
  // Show loading indicator
569
569
  const loadingIndicator = document.createElement('div');
570
570
  loadingIndicator.className = 'loading-indicator';
@@ -579,27 +579,27 @@ async function downloadPdf(titleOrData, content, metadata = {}) {
579
579
  loadingIndicator.style.padding = '20px';
580
580
  loadingIndicator.style.borderRadius = '5px';
581
581
  document.body.appendChild(loadingIndicator);
582
-
582
+
583
583
  // Generate the PDF
584
584
  const blob = await generatePdf(title, pdfContent, pdfMetadata);
585
-
585
+
586
586
  // Create a download link
587
587
  const url = URL.createObjectURL(blob);
588
588
  const downloadLink = document.createElement('a');
589
589
  downloadLink.href = url;
590
-
590
+
591
591
  // Generate a filename based on the title
592
592
  const safeTitle = title.replace(/[^a-z0-9]/gi, '_').toLowerCase().substring(0, 30);
593
593
  downloadLink.download = `${safeTitle}_research.pdf`;
594
-
594
+
595
595
  // Trigger the download
596
596
  document.body.appendChild(downloadLink);
597
597
  downloadLink.click();
598
-
598
+
599
599
  // Clean up
600
600
  document.body.removeChild(downloadLink);
601
601
  URL.revokeObjectURL(url);
602
-
602
+
603
603
  console.log("PDF download process completed");
604
604
  return true;
605
605
  } catch (error) {
@@ -619,4 +619,4 @@ async function downloadPdf(titleOrData, content, metadata = {}) {
619
619
  window.pdfService = {
620
620
  generatePdf,
621
621
  downloadPdf
622
- };
622
+ };
@@ -26,4 +26,4 @@ You can download copyright-free sound files from these sources:
26
26
 
27
27
  ## Usage
28
28
 
29
- The application will automatically use these sounds when research tasks complete or fail, but only when the browser tab is not in focus.
29
+ The application will automatically use these sounds when research tasks complete or fail, but only when the browser tab is not in focus.
@@ -41,6 +41,7 @@
41
41
  <script src="{{ url_for('research.serve_static', path='js/services/ui.js') }}"></script>
42
42
  <script src="{{ url_for('research.serve_static', path='js/services/api.js') }}"></script>
43
43
  <script src="{{ url_for('research.serve_static', path='js/services/socket.js') }}"></script>
44
+ <script src="{{ url_for('research.serve_static', path='js/services/keyboard.js') }}"></script>
44
45
 
45
46
  <!-- Shared Components -->
46
47
  <script src="{{ url_for('research.serve_static', path='js/components/logpanel.js') }}"></script>
@@ -11,9 +11,15 @@
11
11
  <button class="small-btn selected" onclick="window.filterLogsByType('all')">All</button>
12
12
  <button class="small-btn" onclick="window.filterLogsByType('milestone')">Milestones</button>
13
13
  <button class="small-btn" onclick="window.filterLogsByType('info')">Info</button>
14
+ <button class="small-btn" onclick="window.filterLogsByType('warning')">Warning</button>
14
15
  <button class="small-btn" onclick="window.filterLogsByType('error')">Errors</button>
15
16
  </div>
16
17
  </div>
18
+ <div class="log-download">
19
+ <button class="small-btn download-btn" id="log-download-button">
20
+ <i class="fas fa-download"></i> Download Logs
21
+ </button>
22
+ </div>
17
23
  </div>
18
24
  <div class="console-log" id="console-log-container">
19
25
  <!-- Logs will be added here dynamically -->
@@ -29,4 +35,4 @@
29
35
  <span class="log-badge"></span>
30
36
  <span class="log-message"></span>
31
37
  </div>
32
- </template>
38
+ </template>
@@ -19,4 +19,4 @@
19
19
  </a>
20
20
  </li>
21
21
  </ul>
22
- </nav>
22
+ </nav>
@@ -10,6 +10,9 @@
10
10
  <li {% if active_page == 'history' %}class="active"{% endif %} data-page="history">
11
11
  <a href="{{ url_for('research.history_page') }}"><i class="fas fa-history"></i> History</a>
12
12
  </li>
13
+ <li {% if active_page == 'metrics' %}class="active"{% endif %} data-page="metrics">
14
+ <a href="{{ url_for('metrics.metrics_dashboard') }}"><i class="fas fa-chart-bar"></i> Metrics</a>
15
+ </li>
13
16
  <li {% if active_page == 'settings' %}class="active"{% endif %} data-page="settings">
14
17
  <a href="{{ url_for('research.settings_page') }}"><i class="fas fa-cog"></i> Settings</a>
15
18
  </li>