local-deep-research 0.4.4__py3-none-any.whl → 0.5.0__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 +5 -3
  149. local_deep_research/web/database/models.py +51 -2
  150. local_deep_research/web/database/schema_upgrade.py +49 -29
  151. local_deep_research/web/models/database.py +51 -61
  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 +227 -41
  158. local_deep_research/web/routes/settings_routes.py +156 -55
  159. local_deep_research/web/services/research_service.py +310 -103
  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.0.dist-info}/METADATA +7 -1
  216. local_deep_research-0.5.0.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.0.dist-info}/WHEEL +0 -0
  219. {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.0.dist-info}/entry_points.txt +0 -0
  220. {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -41,7 +41,9 @@ def get_db_session() -> Session:
41
41
  return current_app.extensions["sqlalchemy"].session()
42
42
 
43
43
 
44
- def validate_setting(setting: Setting, value: Any) -> Tuple[bool, Optional[str]]:
44
+ def validate_setting(
45
+ setting: Setting, value: Any
46
+ ) -> Tuple[bool, Optional[str]]:
45
47
  """
46
48
  Validate a setting value based on its type and constraints.
47
49
 
@@ -101,7 +103,9 @@ def save_all_settings():
101
103
  form_data = request.get_json()
102
104
  if not form_data:
103
105
  return (
104
- jsonify({"status": "error", "message": "No settings data provided"}),
106
+ jsonify(
107
+ {"status": "error", "message": "No settings data provided"}
108
+ ),
105
109
  400,
106
110
  )
107
111
 
@@ -163,7 +167,8 @@ def save_all_settings():
163
167
 
164
168
  # Special handling for corrupted or empty values
165
169
  if value == "[object Object]" or (
166
- isinstance(value, str) and value.strip() in ["{}", "[]", "{", "["]
170
+ isinstance(value, str)
171
+ and value.strip() in ["{}", "[]", "{", "["]
167
172
  ):
168
173
  if key.startswith("report."):
169
174
  value = {}
@@ -199,18 +204,22 @@ def save_all_settings():
199
204
 
200
205
  if current_setting:
201
206
  # Validate the setting
202
- is_valid, error_message = validate_setting(current_setting, value)
207
+ is_valid, error_message = validate_setting(
208
+ current_setting, value
209
+ )
203
210
 
204
211
  if is_valid:
205
212
  # Save the setting
206
- success = set_setting(key, value, db_session=db_session)
213
+ success = set_setting(key, value)
207
214
  if success:
208
215
  updated_settings.append(key)
209
216
 
210
217
  # Track settings by type for exporting
211
218
  if current_setting.type not in settings_by_type:
212
219
  settings_by_type[current_setting.type] = []
213
- settings_by_type[current_setting.type].append(current_setting)
220
+ settings_by_type[current_setting.type].append(
221
+ current_setting
222
+ )
214
223
  else:
215
224
  # Add to validation errors
216
225
  validation_errors.append(
@@ -235,7 +244,9 @@ def save_all_settings():
235
244
  # Determine better UI element based on value type
236
245
  if isinstance(value, bool):
237
246
  new_setting["ui_element"] = "checkbox"
238
- elif isinstance(value, (int, float)) and not isinstance(value, bool):
247
+ elif isinstance(value, (int, float)) and not isinstance(
248
+ value, bool
249
+ ):
239
250
  new_setting["ui_element"] = "number"
240
251
  elif isinstance(value, (dict, list)):
241
252
  new_setting["ui_element"] = "textarea"
@@ -339,10 +350,15 @@ def save_all_settings():
339
350
  }
340
351
  )
341
352
 
342
- except Exception as e:
353
+ except Exception:
343
354
  logger.exception("Error saving settings")
344
355
  return (
345
- jsonify({"status": "error", "message": f"Error saving settings: {str(e)}"}),
356
+ jsonify(
357
+ {
358
+ "status": "error",
359
+ "message": "An internal error occurred while saving settings.",
360
+ }
361
+ ),
346
362
  500,
347
363
  )
348
364
 
@@ -367,7 +383,9 @@ def reset_to_defaults():
367
383
  # Fallback to predefined settings if file import fails
368
384
  logger.info("Falling back to predefined settings")
369
385
  # Import here to avoid circular imports
370
- from ..database.migrations import setup_predefined_settings as setup_settings
386
+ from ..database.migrations import (
387
+ setup_predefined_settings as setup_settings,
388
+ )
371
389
 
372
390
  setup_settings(db_session)
373
391
 
@@ -400,7 +418,9 @@ def api_get_all_settings():
400
418
  filtered_settings = {}
401
419
  # Need to get all setting details to check category
402
420
  db_settings = db_session.query(Setting).all()
403
- category_keys = [s.key for s in db_settings if s.category == category]
421
+ category_keys = [
422
+ s.key for s in db_settings if s.category == category
423
+ ]
404
424
 
405
425
  # Filter settings by keys
406
426
  for key, value in settings.items():
@@ -424,12 +444,14 @@ def api_get_setting(key):
424
444
  # get_settings_manager(db_session)
425
445
 
426
446
  # Get setting
427
- value = get_setting(key, db_session=db_session)
447
+ value = get_setting(key)
428
448
  if value is None:
429
449
  return jsonify({"error": f"Setting not found: {key}"}), 404
430
450
 
431
451
  # Get additional metadata from database.
432
- db_setting = db_session.query(Setting).filter(Setting.key == key).first()
452
+ db_setting = (
453
+ db_session.query(Setting).filter(Setting.key == key).first()
454
+ )
433
455
 
434
456
  if db_setting:
435
457
  # Return full setting details
@@ -477,7 +499,9 @@ def api_update_setting(key):
477
499
  # get_settings_manager(db_session)
478
500
 
479
501
  # Check if setting exists
480
- db_setting = db_session.query(Setting).filter(Setting.key == key).first()
502
+ db_setting = (
503
+ db_session.query(Setting).filter(Setting.key == key).first()
504
+ )
481
505
 
482
506
  if db_setting:
483
507
  # Check if setting is editable
@@ -487,9 +511,13 @@ def api_update_setting(key):
487
511
  # Update setting
488
512
  success = set_setting(key, value)
489
513
  if success:
490
- return jsonify({"message": f"Setting {key} updated successfully"})
514
+ return jsonify(
515
+ {"message": f"Setting {key} updated successfully"}
516
+ )
491
517
  else:
492
- return jsonify({"error": f"Failed to update setting {key}"}), 500
518
+ return jsonify(
519
+ {"error": f"Failed to update setting {key}"}
520
+ ), 500
493
521
  else:
494
522
  # Create new setting with default metadata
495
523
  setting_dict = {
@@ -535,7 +563,9 @@ def api_update_setting(key):
535
563
  201,
536
564
  )
537
565
  else:
538
- return jsonify({"error": f"Failed to create setting {key}"}), 500
566
+ return jsonify(
567
+ {"error": f"Failed to create setting {key}"}
568
+ ), 500
539
569
  except Exception as e:
540
570
  logger.exception(f"Error updating setting {key}")
541
571
  return jsonify({"error": str(e)}), 500
@@ -549,7 +579,9 @@ def api_delete_setting(key):
549
579
  settings_manager = get_settings_manager(db_session)
550
580
 
551
581
  # Check if setting exists
552
- db_setting = db_session.query(Setting).filter(Setting.key == key).first()
582
+ db_setting = (
583
+ db_session.query(Setting).filter(Setting.key == key).first()
584
+ )
553
585
  if not db_setting:
554
586
  return jsonify({"error": f"Setting not found: {key}"}), 404
555
587
 
@@ -568,8 +600,7 @@ def api_delete_setting(key):
568
600
  def api_import_settings():
569
601
  """Import settings from defaults file"""
570
602
  try:
571
- db_session = get_db_session()
572
- settings_manager = get_settings_manager(db_session)
603
+ settings_manager = get_settings_manager(get_db_session())
573
604
 
574
605
  success = settings_manager.load_from_defaults_file()
575
606
 
@@ -673,7 +704,9 @@ def api_get_available_models():
673
704
  else "http://localhost:11434"
674
705
  )
675
706
 
676
- ollama_response = requests.get(f"{base_url}/api/tags", timeout=5)
707
+ ollama_response = requests.get(
708
+ f"{base_url}/api/tags", timeout=5
709
+ )
677
710
 
678
711
  logger.debug(
679
712
  f"Ollama API response: Status {ollama_response.status_code}"
@@ -681,7 +714,9 @@ def api_get_available_models():
681
714
 
682
715
  # Try to parse the response even if status code is not 200 to help with debugging
683
716
  response_text = ollama_response.text
684
- logger.debug(f"Ollama API raw response: {response_text[:500]}...")
717
+ logger.debug(
718
+ f"Ollama API raw response: {response_text[:500]}..."
719
+ )
685
720
 
686
721
  if ollama_response.status_code == 200:
687
722
  try:
@@ -700,7 +735,9 @@ def api_get_available_models():
700
735
  name = model.get("name", "")
701
736
  if name:
702
737
  # Improved display name formatting
703
- display_name = re.sub(r"[:/]", " ", name).strip()
738
+ display_name = re.sub(
739
+ r"[:/]", " ", name
740
+ ).strip()
704
741
  display_name = " ".join(
705
742
  word.capitalize()
706
743
  for word in display_name.split()
@@ -725,7 +762,9 @@ def api_get_available_models():
725
762
  name = model.get("name", "")
726
763
  if name:
727
764
  # Improved display name formatting
728
- display_name = re.sub(r"[:/]", " ", name).strip()
765
+ display_name = re.sub(
766
+ r"[:/]", " ", name
767
+ ).strip()
729
768
  display_name = " ".join(
730
769
  word.capitalize()
731
770
  for word in display_name.split()
@@ -748,7 +787,9 @@ def api_get_available_models():
748
787
  logger.error(
749
788
  f"Failed to parse Ollama API response as JSON: {json_err}"
750
789
  )
751
- raise Exception(f"Ollama API returned invalid JSON: {json_err}")
790
+ raise Exception(
791
+ f"Ollama API returned invalid JSON: {json_err}"
792
+ )
752
793
  else:
753
794
  logger.warning(
754
795
  f"Ollama API returned non-200 status code: {ollama_response.status_code}"
@@ -760,7 +801,9 @@ def api_get_available_models():
760
801
  except requests.exceptions.RequestException as e:
761
802
  logger.warning(f"Could not connect to Ollama API: {str(e)}")
762
803
  # Fallback to default models if Ollama is not running
763
- logger.info("Using fallback Ollama models due to connection error")
804
+ logger.info(
805
+ "Using fallback Ollama models due to connection error"
806
+ )
764
807
  ollama_models = [
765
808
  {
766
809
  "value": "llama3",
@@ -793,8 +836,16 @@ def api_get_available_models():
793
836
  # Use fallback models
794
837
  logger.info("Using fallback Ollama models due to error")
795
838
  providers["ollama_models"] = [
796
- {"value": "llama3", "label": "Llama 3 (Ollama)", "provider": "OLLAMA"},
797
- {"value": "mistral", "label": "Mistral (Ollama)", "provider": "OLLAMA"},
839
+ {
840
+ "value": "llama3",
841
+ "label": "Llama 3 (Ollama)",
842
+ "provider": "OLLAMA",
843
+ },
844
+ {
845
+ "value": "mistral",
846
+ "label": "Mistral (Ollama)",
847
+ "provider": "OLLAMA",
848
+ },
798
849
  {
799
850
  "value": "gemma:latest",
800
851
  "label": "Gemma (Ollama)",
@@ -833,7 +884,8 @@ def api_get_available_models():
833
884
  # Create a clean display name
834
885
  display_name = model_id.replace("-", " ").strip()
835
886
  display_name = " ".join(
836
- word.capitalize() for word in display_name.split()
887
+ word.capitalize()
888
+ for word in display_name.split()
837
889
  )
838
890
 
839
891
  openai_endpoint_models.append(
@@ -883,7 +935,9 @@ def api_get_available_models():
883
935
  model_id = model.get("id", "")
884
936
  if model_id:
885
937
  # Create a clean display name
886
- display_name = model_id.replace("-", " ").strip()
938
+ display_name = model_id.replace(
939
+ "-", " "
940
+ ).strip()
887
941
  display_name = " ".join(
888
942
  word.capitalize()
889
943
  for word in display_name.split()
@@ -902,7 +956,9 @@ def api_get_available_models():
902
956
  except Exception as e:
903
957
  logger.error(f"Error getting OpenAI Endpoint models: {str(e)}")
904
958
  # Use fallback models (empty in this case)
905
- logger.info("Using fallback (empty) OpenAI Endpoint models due to error")
959
+ logger.info(
960
+ "Using fallback (empty) OpenAI Endpoint models due to error"
961
+ )
906
962
 
907
963
  # Always set the openai_endpoint_models in providers
908
964
  providers["openai_endpoint_models"] = openai_endpoint_models
@@ -913,7 +969,9 @@ def api_get_available_models():
913
969
  # Get OpenAI models using the OpenAI package
914
970
  openai_models = []
915
971
  try:
916
- logger.info("Attempting to connect to OpenAI API using OpenAI package")
972
+ logger.info(
973
+ "Attempting to connect to OpenAI API using OpenAI package"
974
+ )
917
975
 
918
976
  # Get the API key from settings
919
977
  api_key = get_db_setting("llm.openai.api_key", "")
@@ -938,7 +996,8 @@ def api_get_available_models():
938
996
  # Create a clean display name
939
997
  display_name = model_id.replace("-", " ").strip()
940
998
  display_name = " ".join(
941
- word.capitalize() for word in display_name.split()
999
+ word.capitalize()
1000
+ for word in display_name.split()
942
1001
  )
943
1002
 
944
1003
  openai_models.append(
@@ -960,7 +1019,9 @@ def api_get_available_models():
960
1019
  logger.info("No OpenAI models found due to API error")
961
1020
 
962
1021
  else:
963
- logger.info("OpenAI API key not configured, no models available")
1022
+ logger.info(
1023
+ "OpenAI API key not configured, no models available"
1024
+ )
964
1025
 
965
1026
  except ImportError:
966
1027
  logger.warning("OpenAI package not installed. No models available.")
@@ -1001,7 +1062,8 @@ def api_get_available_models():
1001
1062
  # Create a clean display name
1002
1063
  display_name = model_id.replace("-", " ").strip()
1003
1064
  display_name = " ".join(
1004
- word.capitalize() for word in display_name.split()
1065
+ word.capitalize()
1066
+ for word in display_name.split()
1005
1067
  )
1006
1068
 
1007
1069
  anthropic_models.append(
@@ -1035,7 +1097,9 @@ def api_get_available_models():
1035
1097
  logger.info(f"Final Anthropic models count: {len(anthropic_models)}")
1036
1098
 
1037
1099
  # Return all options
1038
- return jsonify({"provider_options": provider_options, "providers": providers})
1100
+ return jsonify(
1101
+ {"provider_options": provider_options, "providers": providers}
1102
+ )
1039
1103
 
1040
1104
  except Exception as e:
1041
1105
  logger.exception("Error getting available models")
@@ -1068,7 +1132,10 @@ def api_get_available_search_engines():
1068
1132
 
1069
1133
  description = (
1070
1134
  db_session.query(Setting)
1071
- .filter(Setting.key == f"search.engine.web.{engine_name}.description")
1135
+ .filter(
1136
+ Setting.key
1137
+ == f"search.engine.web.{engine_name}.description"
1138
+ )
1072
1139
  .first()
1073
1140
  )
1074
1141
  if description is None:
@@ -1078,7 +1145,9 @@ def api_get_available_search_engines():
1078
1145
 
1079
1146
  strengths = (
1080
1147
  db_session.query(Setting)
1081
- .filter(Setting.key == f"search.engine.web.{engine_name}.strengths")
1148
+ .filter(
1149
+ Setting.key == f"search.engine.web.{engine_name}.strengths"
1150
+ )
1082
1151
  .first()
1083
1152
  )
1084
1153
  if strengths is None:
@@ -1088,7 +1157,9 @@ def api_get_available_search_engines():
1088
1157
  strengths = strengths.value
1089
1158
 
1090
1159
  engines_dict[engine_name] = dict(
1091
- display_name=display_name, strengths=strengths, description=description
1160
+ display_name=display_name,
1161
+ strengths=strengths,
1162
+ description=description,
1092
1163
  )
1093
1164
 
1094
1165
  # Format as options for dropdown
@@ -1100,7 +1171,9 @@ def api_get_available_search_engines():
1100
1171
  for key in engines_dict.keys()
1101
1172
  ]
1102
1173
 
1103
- return jsonify({"engines": engines_dict, "engine_options": engine_options})
1174
+ return jsonify(
1175
+ {"engines": engines_dict, "engine_options": engine_options}
1176
+ )
1104
1177
 
1105
1178
  except Exception as e:
1106
1179
  logger.exception("Error getting available search engines")
@@ -1141,13 +1214,19 @@ def open_file_location():
1141
1214
  flash("No file path provided", "error")
1142
1215
  return redirect(url_for("settings.settings_page"))
1143
1216
 
1144
- # Get the directory containing the file
1145
- dir_path = os.path.dirname(os.path.abspath(file_path))
1146
-
1147
- # Open the directory in the file explorer
1148
1217
  try:
1218
+ # Validate the file path
1219
+ file_path = os.path.abspath(file_path)
1220
+ if not os.path.exists(file_path):
1221
+ flash("File path does not exist", "error")
1222
+ return redirect(url_for("settings.settings_page"))
1223
+
1224
+ # Get the directory containing the file
1225
+ dir_path = os.path.dirname(file_path)
1226
+
1227
+ # Open the directory in the file explorer
1149
1228
  if platform.system() == "Windows":
1150
- subprocess.Popen(f'explorer "{dir_path}"')
1229
+ subprocess.Popen(["explorer", dir_path])
1151
1230
  elif platform.system() == "Darwin": # macOS
1152
1231
  subprocess.Popen(["open", dir_path])
1153
1232
  else: # Linux
@@ -1215,12 +1294,16 @@ def fix_corrupted_settings():
1215
1294
  "app.knowledge_accumulation_context_limit",
1216
1295
  "app.output_dir",
1217
1296
  ]:
1218
- setting = db_session.query(Setting).filter(Setting.key == key).first()
1297
+ setting = (
1298
+ db_session.query(Setting).filter(Setting.key == key).first()
1299
+ )
1219
1300
  if setting:
1220
1301
  # Move to proper category if not already there
1221
1302
  proper_key = key.replace("app.", "report.")
1222
1303
  existing_proper = (
1223
- db_session.query(Setting).filter(Setting.key == proper_key).first()
1304
+ db_session.query(Setting)
1305
+ .filter(Setting.key == proper_key)
1306
+ .first()
1224
1307
  )
1225
1308
 
1226
1309
  if not existing_proper:
@@ -1261,12 +1344,16 @@ def fix_corrupted_settings():
1261
1344
  "app.search_language",
1262
1345
  "app.snippets_only",
1263
1346
  ]:
1264
- setting = db_session.query(Setting).filter(Setting.key == key).first()
1347
+ setting = (
1348
+ db_session.query(Setting).filter(Setting.key == key).first()
1349
+ )
1265
1350
  if setting:
1266
1351
  # Move to proper category if not already there
1267
1352
  proper_key = key.replace("app.", "search.")
1268
1353
  existing_proper = (
1269
- db_session.query(Setting).filter(Setting.key == proper_key).first()
1354
+ db_session.query(Setting)
1355
+ .filter(Setting.key == proper_key)
1356
+ .first()
1270
1357
  )
1271
1358
 
1272
1359
  if not existing_proper:
@@ -1306,12 +1393,16 @@ def fix_corrupted_settings():
1306
1393
  "app.lmstudio_url",
1307
1394
  "app.llamacpp_model_path",
1308
1395
  ]:
1309
- setting = db_session.query(Setting).filter(Setting.key == key).first()
1396
+ setting = (
1397
+ db_session.query(Setting).filter(Setting.key == key).first()
1398
+ )
1310
1399
  if setting:
1311
1400
  # Move to proper category if not already there
1312
1401
  proper_key = key.replace("app.", "llm.")
1313
1402
  existing_proper = (
1314
- db_session.query(Setting).filter(Setting.key == proper_key).first()
1403
+ db_session.query(Setting)
1404
+ .filter(Setting.key == proper_key)
1405
+ .first()
1315
1406
  )
1316
1407
 
1317
1408
  if not existing_proper:
@@ -1407,7 +1498,10 @@ def fix_corrupted_settings():
1407
1498
  elif setting.key == "report.detailed_citations":
1408
1499
  default_value = True
1409
1500
  elif setting.key.startswith("app."):
1410
- if setting.key == "app.theme" or setting.key == "app.default_theme":
1501
+ if (
1502
+ setting.key == "app.theme"
1503
+ or setting.key == "app.default_theme"
1504
+ ):
1411
1505
  default_value = "dark"
1412
1506
  elif setting.key == "app.enable_notifications":
1413
1507
  default_value = True
@@ -1476,16 +1570,23 @@ def check_ollama_status():
1476
1570
  """Check if Ollama is running and available"""
1477
1571
  try:
1478
1572
  # Get Ollama URL from settings
1479
- raw_base_url = get_db_setting("llm.ollama.url", "http://localhost:11434")
1573
+ raw_base_url = get_db_setting(
1574
+ "llm.ollama.url", "http://localhost:11434"
1575
+ )
1480
1576
  base_url = (
1481
- normalize_url(raw_base_url) if raw_base_url else "http://localhost:11434"
1577
+ normalize_url(raw_base_url)
1578
+ if raw_base_url
1579
+ else "http://localhost:11434"
1482
1580
  )
1483
1581
 
1484
1582
  response = requests.get(f"{base_url}/api/version", timeout=2.0)
1485
1583
 
1486
1584
  if response.status_code == 200:
1487
1585
  return jsonify(
1488
- {"running": True, "version": response.json().get("version", "unknown")}
1586
+ {
1587
+ "running": True,
1588
+ "version": response.json().get("version", "unknown"),
1589
+ }
1489
1590
  )
1490
1591
  else:
1491
1592
  return jsonify(