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
@@ -59,9 +59,7 @@ def api_start_research():
59
59
  mode,
60
60
  "in_progress",
61
61
  created_at,
62
- json.dumps(
63
- [{"time": created_at, "message": "Research started", "progress": 0}]
64
- ),
62
+ json.dumps([{"time": created_at, "progress": 0}]),
65
63
  json.dumps(research_settings),
66
64
  ),
67
65
  )
@@ -89,9 +87,11 @@ def api_start_research():
89
87
  "research_id": research_id,
90
88
  }
91
89
  )
92
- except Exception as e:
93
- logger.error(f"Error starting research: {str(e)}")
94
- return jsonify({"status": "error", "message": str(e)}), 500
90
+ except Exception:
91
+ logger.exception("Error starting research")
92
+ return jsonify(
93
+ {"status": "error", "message": "Failed to start research"}, 500
94
+ )
95
95
 
96
96
 
97
97
  @api_bp.route("/status/<int:research_id>", methods=["GET"])
@@ -120,7 +120,9 @@ def api_research_status(research_id):
120
120
  try:
121
121
  metadata = json.loads(metadata_str)
122
122
  except json.JSONDecodeError:
123
- logger.warning(f"Invalid JSON in metadata for research {research_id}")
123
+ logger.warning(
124
+ f"Invalid JSON in metadata for research {research_id}"
125
+ )
124
126
 
125
127
  return jsonify(
126
128
  {
@@ -144,10 +146,18 @@ def api_terminate_research(research_id):
144
146
  try:
145
147
  result = cancel_research(research_id)
146
148
  return jsonify(
147
- {"status": "success", "message": "Research terminated", "result": result}
149
+ {
150
+ "status": "success",
151
+ "message": "Research terminated",
152
+ "result": result,
153
+ }
154
+ )
155
+ except Exception:
156
+ logger.exception("Error terminating research")
157
+ return (
158
+ jsonify({"status": "error", "message": "Failed to stop research."}),
159
+ 500,
148
160
  )
149
- except Exception as e:
150
- return jsonify({"status": "error", "message": str(e)}), 500
151
161
 
152
162
 
153
163
  @api_bp.route("/resources/<int:research_id>", methods=["GET"])
@@ -158,8 +168,11 @@ def api_get_resources(research_id):
158
168
  try:
159
169
  resources = get_resources_for_research(research_id)
160
170
  return jsonify({"status": "success", "resources": resources})
161
- except Exception as e:
162
- return jsonify({"status": "error", "message": str(e)}), 500
171
+ except Exception:
172
+ logger.exception("Error getting resources for research")
173
+ return jsonify(
174
+ {"status": "error", "message": "Failed to get resources"}, 500
175
+ )
163
176
 
164
177
 
165
178
  @api_bp.route("/resources/<int:research_id>", methods=["POST"])
@@ -182,19 +195,25 @@ def api_add_resource(research_id):
182
195
  # Validate required fields
183
196
  if not title or not url:
184
197
  return (
185
- jsonify({"status": "error", "message": "Title and URL are required"}),
198
+ jsonify(
199
+ {"status": "error", "message": "Title and URL are required"}
200
+ ),
186
201
  400,
187
202
  )
188
203
 
189
204
  # Check if the research exists
190
205
  conn = get_db_connection()
191
206
  cursor = conn.cursor()
192
- cursor.execute("SELECT id FROM research_history WHERE id = ?", (research_id,))
207
+ cursor.execute(
208
+ "SELECT id FROM research_history WHERE id = ?", (research_id,)
209
+ )
193
210
  result = cursor.fetchone()
194
211
  conn.close()
195
212
 
196
213
  if not result:
197
- return jsonify({"status": "error", "message": "Research not found"}), 404
214
+ return jsonify(
215
+ {"status": "error", "message": "Research not found"}
216
+ ), 404
198
217
 
199
218
  # Add the resource
200
219
  resource_id = add_resource(
@@ -231,10 +250,15 @@ def api_delete_resource(research_id, resource_id):
231
250
 
232
251
  if success:
233
252
  return jsonify(
234
- {"status": "success", "message": "Resource deleted successfully"}
253
+ {
254
+ "status": "success",
255
+ "message": "Resource deleted successfully",
256
+ }
235
257
  )
236
258
  else:
237
- return jsonify({"status": "error", "message": "Resource not found"}), 404
259
+ return jsonify(
260
+ {"status": "error", "message": "Resource not found"}
261
+ ), 404
238
262
  except Exception as e:
239
263
  logger.error(f"Error deleting resource: {str(e)}")
240
264
  return jsonify({"status": "error", "message": str(e)}), 500
@@ -252,7 +276,10 @@ def check_ollama_status():
252
276
 
253
277
  if provider.lower() != "ollama":
254
278
  return jsonify(
255
- {"running": True, "message": f"Using provider: {provider}, not Ollama"}
279
+ {
280
+ "running": True,
281
+ "message": f"Using provider: {provider}, not Ollama",
282
+ }
256
283
  )
257
284
 
258
285
  # Get Ollama API URL from LLM config
@@ -272,7 +299,9 @@ def check_ollama_status():
272
299
  response = requests.get(f"{ollama_base_url}/api/tags", timeout=5)
273
300
 
274
301
  # Add response details for debugging
275
- logger.debug(f"Ollama status check response code: {response.status_code}")
302
+ logger.debug(
303
+ f"Ollama status check response code: {response.status_code}"
304
+ )
276
305
 
277
306
  if response.status_code == 200:
278
307
  # Try to validate the response content
@@ -420,7 +449,9 @@ def check_ollama_model():
420
449
 
421
450
  # Debug log the first bit of the response
422
451
  response_preview = (
423
- str(data)[:500] + "..." if len(str(data)) > 500 else str(data)
452
+ str(data)[:500] + "..."
453
+ if len(str(data)) > 500
454
+ else str(data)
424
455
  )
425
456
  logger.debug(f"Ollama API response data: {response_preview}")
426
457
 
@@ -448,7 +479,8 @@ def check_ollama_model():
448
479
 
449
480
  # Case-insensitive model name comparison
450
481
  model_exists = any(
451
- m.get("name", "").lower() == model_name.lower() for m in models
482
+ m.get("name", "").lower() == model_name.lower()
483
+ for m in models
452
484
  )
453
485
 
454
486
  if model_exists:
@@ -561,7 +593,9 @@ def api_get_config():
561
593
  "search_tool", "auto"
562
594
  ),
563
595
  "features": {
564
- "notifications": current_app.config.get("ENABLE_NOTIFICATIONS", False)
596
+ "notifications": current_app.config.get(
597
+ "ENABLE_NOTIFICATIONS", False
598
+ )
565
599
  },
566
600
  }
567
601
 
@@ -190,7 +190,10 @@ def get_benchmark_config():
190
190
  ],
191
191
  "evaluation_models": {
192
192
  "openai_endpoint": [
193
- {"id": "anthropic/claude-3.7-sonnet", "name": "Claude 3.7 Sonnet"}
193
+ {
194
+ "id": "anthropic/claude-3.7-sonnet",
195
+ "name": "Claude 3.7 Sonnet",
196
+ }
194
197
  ],
195
198
  "openai": [
196
199
  {"id": "gpt-4o", "name": "GPT-4o"},
@@ -0,0 +1,22 @@
1
+ """
2
+ Stores global state.
3
+ """
4
+
5
+ # Active research processes and socket subscriptions
6
+ active_research = {}
7
+ socket_subscriptions = {}
8
+ # Add termination flags dictionary
9
+ termination_flags = {}
10
+
11
+
12
+ def get_globals():
13
+ """
14
+ Returns:
15
+ Global state for other modules to access.
16
+
17
+ """
18
+ return {
19
+ "active_research": active_research,
20
+ "socket_subscriptions": socket_subscriptions,
21
+ "termination_flags": termination_flags,
22
+ }
@@ -1,10 +1,17 @@
1
1
  import json
2
2
  import logging
3
3
  import traceback
4
+ from pathlib import Path
4
5
 
5
6
  from flask import Blueprint, jsonify, make_response
6
7
 
7
- from ..models.database import get_db_connection, get_logs_for_research
8
+ from ..models.database import (
9
+ get_db_connection,
10
+ get_logs_for_research,
11
+ get_total_logs_for_research,
12
+ )
13
+ from ..routes.globals import get_globals
14
+ from ..services.research_service import get_research_strategy
8
15
 
9
16
  # Initialize logger
10
17
  logger = logging.getLogger(__name__)
@@ -13,6 +20,20 @@ logger = logging.getLogger(__name__)
13
20
  history_bp = Blueprint("history", __name__)
14
21
 
15
22
 
23
+ def resolve_report_path(report_path: str) -> Path:
24
+ """
25
+ Resolve report path to absolute path using pathlib.
26
+ Handles both absolute and relative paths.
27
+ """
28
+ path = Path(report_path)
29
+ if path.is_absolute():
30
+ return path
31
+
32
+ # If relative path, make it relative to project root
33
+ project_root = Path(__file__).parent.parent.parent.parent
34
+ return project_root / path
35
+
36
+
16
37
  @history_bp.route("/history", methods=["GET"])
17
38
  def get_history():
18
39
  """Get the research history"""
@@ -24,7 +45,9 @@ def get_history():
24
45
  cursor = conn.cursor()
25
46
 
26
47
  # Get all history records ordered by latest first
27
- cursor.execute("SELECT * FROM research_history ORDER BY created_at DESC")
48
+ cursor.execute(
49
+ "SELECT * FROM research_history ORDER BY created_at DESC"
50
+ )
28
51
  results = cursor.fetchall()
29
52
  conn.close()
30
53
 
@@ -135,15 +158,16 @@ def get_research_status(research_id):
135
158
  column[0]: row[idx] for idx, column in enumerate(cursor.description)
136
159
  }
137
160
  cursor = conn.cursor()
138
- cursor.execute("SELECT * FROM research_history WHERE id = ?", (research_id,))
161
+ cursor.execute(
162
+ "SELECT * FROM research_history WHERE id = ?", (research_id,)
163
+ )
139
164
  result = cursor.fetchone()
140
165
  conn.close()
141
166
 
142
167
  if not result:
143
- return jsonify({"status": "error", "message": "Research not found"}), 404
144
-
145
- # Import globals from research routes
146
- from .research_routes import get_globals
168
+ return jsonify(
169
+ {"status": "error", "message": "Research not found"}
170
+ ), 404
147
171
 
148
172
  globals_dict = get_globals()
149
173
  active_research = globals_dict["active_research"]
@@ -176,18 +200,22 @@ def get_research_details(research_id):
176
200
  column[0]: row[idx] for idx, column in enumerate(cursor.description)
177
201
  }
178
202
  cursor = conn.cursor()
179
- cursor.execute("SELECT * FROM research_history WHERE id = ?", (research_id,))
203
+ cursor.execute(
204
+ "SELECT * FROM research_history WHERE id = ?", (research_id,)
205
+ )
180
206
  result = cursor.fetchone()
181
207
  conn.close()
182
208
 
183
209
  if not result:
184
- return jsonify({"status": "error", "message": "Research not found"}), 404
210
+ return jsonify(
211
+ {"status": "error", "message": "Research not found"}
212
+ ), 404
185
213
 
186
214
  # Get logs from the dedicated log database
187
215
  logs = get_logs_for_research(research_id)
188
216
 
189
- # Import globals from research routes
190
- from .research_routes import get_globals
217
+ # Get strategy information
218
+ strategy_name = get_research_strategy(research_id)
191
219
 
192
220
  globals_dict = get_globals()
193
221
  active_research = globals_dict["active_research"]
@@ -215,6 +243,7 @@ def get_research_details(research_id):
215
243
  "query": result.get("query"),
216
244
  "mode": result.get("mode"),
217
245
  "status": result.get("status"),
246
+ "strategy": strategy_name,
218
247
  "progress": active_research.get(research_id, {}).get(
219
248
  "progress", 100 if result.get("status") == "completed" else 0
220
249
  ),
@@ -232,7 +261,9 @@ def get_report(research_id):
232
261
  column[0]: row[idx] for idx, column in enumerate(cursor.description)
233
262
  }
234
263
  cursor = conn.cursor()
235
- cursor.execute("SELECT * FROM research_history WHERE id = ?", (research_id,))
264
+ cursor.execute(
265
+ "SELECT * FROM research_history WHERE id = ?", (research_id,)
266
+ )
236
267
  result = cursor.fetchone()
237
268
  conn.close()
238
269
 
@@ -240,7 +271,10 @@ def get_report(research_id):
240
271
  return jsonify({"status": "error", "message": "Report not found"}), 404
241
272
 
242
273
  try:
243
- with open(result["report_path"], "r", encoding="utf-8") as f:
274
+ # Resolve report path using helper function
275
+ report_path = resolve_report_path(result["report_path"])
276
+
277
+ with open(report_path, "r", encoding="utf-8") as f:
244
278
  content = f.read()
245
279
 
246
280
  # Create an enhanced metadata dictionary with database fields
@@ -280,7 +314,9 @@ def get_markdown(research_id):
280
314
  column[0]: row[idx] for idx, column in enumerate(cursor.description)
281
315
  }
282
316
  cursor = conn.cursor()
283
- cursor.execute("SELECT * FROM research_history WHERE id = ?", (research_id,))
317
+ cursor.execute(
318
+ "SELECT * FROM research_history WHERE id = ?", (research_id,)
319
+ )
284
320
  result = cursor.fetchone()
285
321
  conn.close()
286
322
 
@@ -288,7 +324,10 @@ def get_markdown(research_id):
288
324
  return jsonify({"status": "error", "message": "Report not found"}), 404
289
325
 
290
326
  try:
291
- with open(result["report_path"], "r", encoding="utf-8") as f:
327
+ # Resolve report path using helper function
328
+ report_path = resolve_report_path(result["report_path"])
329
+
330
+ with open(report_path, "r", encoding="utf-8") as f:
292
331
  content = f.read()
293
332
  return jsonify({"status": "success", "content": content})
294
333
  except Exception as e:
@@ -304,12 +343,16 @@ def get_research_logs(research_id):
304
343
  column[0]: row[idx] for idx, column in enumerate(cursor.description)
305
344
  }
306
345
  cursor = conn.cursor()
307
- cursor.execute("SELECT id FROM research_history WHERE id = ?", (research_id,))
346
+ cursor.execute(
347
+ "SELECT id FROM research_history WHERE id = ?", (research_id,)
348
+ )
308
349
  result = cursor.fetchone()
309
350
  conn.close()
310
351
 
311
352
  if not result:
312
- return jsonify({"status": "error", "message": "Research not found"}), 404
353
+ return jsonify(
354
+ {"status": "error", "message": "Research not found"}
355
+ ), 404
313
356
 
314
357
  # Retrieve logs from the database
315
358
  logs = get_logs_for_research(research_id)
@@ -317,38 +360,20 @@ def get_research_logs(research_id):
317
360
  # Format logs correctly if needed
318
361
  formatted_logs = []
319
362
  for log in logs:
363
+ log_entry = log.copy()
320
364
  # Ensure each log has time, message, and type fields
321
- log_entry = {
322
- "time": log.get("time", ""),
323
- "message": log.get("message", "No message"),
324
- "type": log.get("type", "info"),
325
- }
365
+ log_entry["time"] = log.get("time", "")
366
+ log_entry["message"] = log.get("message", "No message")
367
+ log_entry["type"] = log.get("type", "info")
326
368
  formatted_logs.append(log_entry)
327
369
 
328
- # Import globals from research routes
329
- from .research_routes import get_globals
330
-
331
- globals_dict = get_globals()
332
- active_research = globals_dict["active_research"]
333
-
334
- # Add any current logs from memory if this is an active research
335
- if research_id in active_research and active_research[research_id].get("log"):
336
- # Use the logs from memory temporarily until they're saved to the database
337
- memory_logs = active_research[research_id]["log"]
338
-
339
- # Format memory logs too
340
- for log in memory_logs:
341
- log_entry = {
342
- "time": log.get("time", ""),
343
- "message": log.get("message", "No message"),
344
- "type": log.get("type", "info"),
345
- }
370
+ return jsonify({"status": "success", "logs": formatted_logs})
346
371
 
347
- # Check if this log is already in our formatted logs by timestamp
348
- if not any(flog["time"] == log_entry["time"] for flog in formatted_logs):
349
- formatted_logs.append(log_entry)
350
372
 
351
- # Sort logs by timestamp
352
- formatted_logs.sort(key=lambda x: x["time"])
373
+ @history_bp.route("/log_count/<int:research_id>")
374
+ def get_log_count(research_id):
375
+ """Get the total number of logs for a specific research ID"""
376
+ # Get the total number of logs for this research ID
377
+ total_logs = get_total_logs_for_research(research_id)
353
378
 
354
- return jsonify({"status": "success", "logs": formatted_logs})
379
+ return jsonify({"status": "success", "total_logs": total_logs})