local-deep-research 0.1.26__py3-none-any.whl → 0.2.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. local_deep_research/__init__.py +23 -22
  2. local_deep_research/__main__.py +16 -0
  3. local_deep_research/advanced_search_system/__init__.py +7 -0
  4. local_deep_research/advanced_search_system/filters/__init__.py +8 -0
  5. local_deep_research/advanced_search_system/filters/base_filter.py +38 -0
  6. local_deep_research/advanced_search_system/filters/cross_engine_filter.py +200 -0
  7. local_deep_research/advanced_search_system/findings/base_findings.py +81 -0
  8. local_deep_research/advanced_search_system/findings/repository.py +452 -0
  9. local_deep_research/advanced_search_system/knowledge/__init__.py +1 -0
  10. local_deep_research/advanced_search_system/knowledge/base_knowledge.py +151 -0
  11. local_deep_research/advanced_search_system/knowledge/standard_knowledge.py +159 -0
  12. local_deep_research/advanced_search_system/questions/__init__.py +1 -0
  13. local_deep_research/advanced_search_system/questions/base_question.py +64 -0
  14. local_deep_research/advanced_search_system/questions/decomposition_question.py +445 -0
  15. local_deep_research/advanced_search_system/questions/standard_question.py +119 -0
  16. local_deep_research/advanced_search_system/repositories/__init__.py +7 -0
  17. local_deep_research/advanced_search_system/strategies/__init__.py +1 -0
  18. local_deep_research/advanced_search_system/strategies/base_strategy.py +118 -0
  19. local_deep_research/advanced_search_system/strategies/iterdrag_strategy.py +450 -0
  20. local_deep_research/advanced_search_system/strategies/parallel_search_strategy.py +312 -0
  21. local_deep_research/advanced_search_system/strategies/rapid_search_strategy.py +270 -0
  22. local_deep_research/advanced_search_system/strategies/standard_strategy.py +300 -0
  23. local_deep_research/advanced_search_system/tools/__init__.py +1 -0
  24. local_deep_research/advanced_search_system/tools/base_tool.py +100 -0
  25. local_deep_research/advanced_search_system/tools/knowledge_tools/__init__.py +1 -0
  26. local_deep_research/advanced_search_system/tools/question_tools/__init__.py +1 -0
  27. local_deep_research/advanced_search_system/tools/search_tools/__init__.py +1 -0
  28. local_deep_research/api/__init__.py +5 -5
  29. local_deep_research/api/research_functions.py +154 -160
  30. local_deep_research/app.py +8 -0
  31. local_deep_research/citation_handler.py +25 -16
  32. local_deep_research/{config.py → config/config_files.py} +102 -110
  33. local_deep_research/config/llm_config.py +472 -0
  34. local_deep_research/config/search_config.py +77 -0
  35. local_deep_research/defaults/__init__.py +10 -5
  36. local_deep_research/defaults/main.toml +2 -2
  37. local_deep_research/defaults/search_engines.toml +60 -34
  38. local_deep_research/main.py +121 -19
  39. local_deep_research/migrate_db.py +147 -0
  40. local_deep_research/report_generator.py +87 -45
  41. local_deep_research/search_system.py +153 -283
  42. local_deep_research/setup_data_dir.py +35 -0
  43. local_deep_research/test_migration.py +178 -0
  44. local_deep_research/utilities/__init__.py +0 -0
  45. local_deep_research/utilities/db_utils.py +49 -0
  46. local_deep_research/{utilties → utilities}/enums.py +2 -2
  47. local_deep_research/{utilties → utilities}/llm_utils.py +63 -29
  48. local_deep_research/utilities/search_utilities.py +242 -0
  49. local_deep_research/{utilties → utilities}/setup_utils.py +4 -2
  50. local_deep_research/web/__init__.py +0 -1
  51. local_deep_research/web/app.py +86 -1709
  52. local_deep_research/web/app_factory.py +289 -0
  53. local_deep_research/web/database/README.md +70 -0
  54. local_deep_research/web/database/migrate_to_ldr_db.py +289 -0
  55. local_deep_research/web/database/migrations.py +447 -0
  56. local_deep_research/web/database/models.py +117 -0
  57. local_deep_research/web/database/schema_upgrade.py +107 -0
  58. local_deep_research/web/models/database.py +294 -0
  59. local_deep_research/web/models/settings.py +94 -0
  60. local_deep_research/web/routes/api_routes.py +559 -0
  61. local_deep_research/web/routes/history_routes.py +354 -0
  62. local_deep_research/web/routes/research_routes.py +715 -0
  63. local_deep_research/web/routes/settings_routes.py +1583 -0
  64. local_deep_research/web/services/research_service.py +947 -0
  65. local_deep_research/web/services/resource_service.py +149 -0
  66. local_deep_research/web/services/settings_manager.py +669 -0
  67. local_deep_research/web/services/settings_service.py +187 -0
  68. local_deep_research/web/services/socket_service.py +210 -0
  69. local_deep_research/web/static/css/custom_dropdown.css +277 -0
  70. local_deep_research/web/static/css/settings.css +1223 -0
  71. local_deep_research/web/static/css/styles.css +525 -48
  72. local_deep_research/web/static/js/components/custom_dropdown.js +428 -0
  73. local_deep_research/web/static/js/components/detail.js +348 -0
  74. local_deep_research/web/static/js/components/fallback/formatting.js +122 -0
  75. local_deep_research/web/static/js/components/fallback/ui.js +215 -0
  76. local_deep_research/web/static/js/components/history.js +487 -0
  77. local_deep_research/web/static/js/components/logpanel.js +949 -0
  78. local_deep_research/web/static/js/components/progress.js +1107 -0
  79. local_deep_research/web/static/js/components/research.js +1865 -0
  80. local_deep_research/web/static/js/components/results.js +766 -0
  81. local_deep_research/web/static/js/components/settings.js +3981 -0
  82. local_deep_research/web/static/js/components/settings_sync.js +106 -0
  83. local_deep_research/web/static/js/main.js +226 -0
  84. local_deep_research/web/static/js/services/api.js +253 -0
  85. local_deep_research/web/static/js/services/audio.js +31 -0
  86. local_deep_research/web/static/js/services/formatting.js +119 -0
  87. local_deep_research/web/static/js/services/pdf.js +622 -0
  88. local_deep_research/web/static/js/services/socket.js +882 -0
  89. local_deep_research/web/static/js/services/ui.js +546 -0
  90. local_deep_research/web/templates/base.html +72 -0
  91. local_deep_research/web/templates/components/custom_dropdown.html +47 -0
  92. local_deep_research/web/templates/components/log_panel.html +32 -0
  93. local_deep_research/web/templates/components/mobile_nav.html +22 -0
  94. local_deep_research/web/templates/components/settings_form.html +299 -0
  95. local_deep_research/web/templates/components/sidebar.html +21 -0
  96. local_deep_research/web/templates/pages/details.html +73 -0
  97. local_deep_research/web/templates/pages/history.html +51 -0
  98. local_deep_research/web/templates/pages/progress.html +57 -0
  99. local_deep_research/web/templates/pages/research.html +139 -0
  100. local_deep_research/web/templates/pages/results.html +59 -0
  101. local_deep_research/web/templates/settings_dashboard.html +78 -192
  102. local_deep_research/web/utils/__init__.py +0 -0
  103. local_deep_research/web/utils/formatters.py +76 -0
  104. local_deep_research/web_search_engines/engines/full_search.py +18 -16
  105. local_deep_research/web_search_engines/engines/meta_search_engine.py +182 -131
  106. local_deep_research/web_search_engines/engines/search_engine_arxiv.py +224 -139
  107. local_deep_research/web_search_engines/engines/search_engine_brave.py +88 -71
  108. local_deep_research/web_search_engines/engines/search_engine_ddg.py +48 -39
  109. local_deep_research/web_search_engines/engines/search_engine_github.py +415 -204
  110. local_deep_research/web_search_engines/engines/search_engine_google_pse.py +123 -90
  111. local_deep_research/web_search_engines/engines/search_engine_guardian.py +210 -157
  112. local_deep_research/web_search_engines/engines/search_engine_local.py +532 -369
  113. local_deep_research/web_search_engines/engines/search_engine_local_all.py +42 -36
  114. local_deep_research/web_search_engines/engines/search_engine_pubmed.py +358 -266
  115. local_deep_research/web_search_engines/engines/search_engine_searxng.py +212 -160
  116. local_deep_research/web_search_engines/engines/search_engine_semantic_scholar.py +213 -170
  117. local_deep_research/web_search_engines/engines/search_engine_serpapi.py +84 -68
  118. local_deep_research/web_search_engines/engines/search_engine_wayback.py +186 -154
  119. local_deep_research/web_search_engines/engines/search_engine_wikipedia.py +115 -77
  120. local_deep_research/web_search_engines/search_engine_base.py +174 -99
  121. local_deep_research/web_search_engines/search_engine_factory.py +192 -102
  122. local_deep_research/web_search_engines/search_engines_config.py +22 -15
  123. {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.2.dist-info}/METADATA +177 -97
  124. local_deep_research-0.2.2.dist-info/RECORD +135 -0
  125. {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.2.dist-info}/WHEEL +1 -2
  126. {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.2.dist-info}/entry_points.txt +3 -0
  127. local_deep_research/defaults/llm_config.py +0 -338
  128. local_deep_research/utilties/search_utilities.py +0 -114
  129. local_deep_research/web/static/js/app.js +0 -3763
  130. local_deep_research/web/templates/api_keys_config.html +0 -82
  131. local_deep_research/web/templates/collections_config.html +0 -90
  132. local_deep_research/web/templates/index.html +0 -348
  133. local_deep_research/web/templates/llm_config.html +0 -120
  134. local_deep_research/web/templates/main_config.html +0 -89
  135. local_deep_research/web/templates/search_engines_config.html +0 -154
  136. local_deep_research/web/templates/settings.html +0 -519
  137. local_deep_research-0.1.26.dist-info/RECORD +0 -61
  138. local_deep_research-0.1.26.dist-info/top_level.txt +0 -1
  139. /local_deep_research/{utilties → config}/__init__.py +0 -0
  140. {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,354 @@
1
+ import json
2
+ import logging
3
+ import traceback
4
+
5
+ from flask import Blueprint, jsonify, make_response
6
+
7
+ from ..models.database import get_db_connection, get_logs_for_research
8
+
9
+ # Initialize logger
10
+ logger = logging.getLogger(__name__)
11
+
12
+ # Create a Blueprint for the history routes
13
+ history_bp = Blueprint("history", __name__)
14
+
15
+
16
+ @history_bp.route("/history", methods=["GET"])
17
+ def get_history():
18
+ """Get the research history"""
19
+ try:
20
+ conn = get_db_connection()
21
+ conn.row_factory = lambda cursor, row: {
22
+ column[0]: row[idx] for idx, column in enumerate(cursor.description)
23
+ }
24
+ cursor = conn.cursor()
25
+
26
+ # Get all history records ordered by latest first
27
+ cursor.execute("SELECT * FROM research_history ORDER BY created_at DESC")
28
+ results = cursor.fetchall()
29
+ conn.close()
30
+
31
+ # Convert to list of dicts
32
+ history = []
33
+ for result in results:
34
+ item = dict(result)
35
+
36
+ # Ensure all keys exist with default values
37
+ if "id" not in item:
38
+ item["id"] = None
39
+ if "query" not in item:
40
+ item["query"] = "Untitled Research"
41
+ if "mode" not in item:
42
+ item["mode"] = "quick"
43
+ if "status" not in item:
44
+ item["status"] = "unknown"
45
+ if "created_at" not in item:
46
+ item["created_at"] = None
47
+ if "completed_at" not in item:
48
+ item["completed_at"] = None
49
+ if "duration_seconds" not in item:
50
+ item["duration_seconds"] = None
51
+ if "report_path" not in item:
52
+ item["report_path"] = None
53
+ if "metadata" not in item:
54
+ item["metadata"] = "{}"
55
+ if "progress_log" not in item:
56
+ item["progress_log"] = "[]"
57
+
58
+ # Ensure timestamps are in ISO format
59
+ if item["created_at"] and "T" not in item["created_at"]:
60
+ try:
61
+ # Convert to ISO format if it's not already
62
+ from dateutil import parser
63
+
64
+ dt = parser.parse(item["created_at"])
65
+ item["created_at"] = dt.isoformat()
66
+ except Exception:
67
+ pass
68
+
69
+ if item["completed_at"] and "T" not in item["completed_at"]:
70
+ try:
71
+ # Convert to ISO format if it's not already
72
+ from dateutil import parser
73
+
74
+ dt = parser.parse(item["completed_at"])
75
+ item["completed_at"] = dt.isoformat()
76
+ except Exception:
77
+ pass
78
+
79
+ # Recalculate duration based on timestamps if it's null but both timestamps exist
80
+ if (
81
+ item["duration_seconds"] is None
82
+ and item["created_at"]
83
+ and item["completed_at"]
84
+ ):
85
+ try:
86
+ from dateutil import parser
87
+
88
+ start_time = parser.parse(item["created_at"])
89
+ end_time = parser.parse(item["completed_at"])
90
+ item["duration_seconds"] = int(
91
+ (end_time - start_time).total_seconds()
92
+ )
93
+ except Exception as e:
94
+ print(f"Error recalculating duration: {str(e)}")
95
+
96
+ history.append(item)
97
+
98
+ # Format response to match what client expects
99
+ response_data = {
100
+ "status": "success",
101
+ "items": history, # Use 'items' key as expected by client
102
+ }
103
+
104
+ # Add CORS headers
105
+ response = make_response(jsonify(response_data))
106
+ response.headers.add("Access-Control-Allow-Origin", "*")
107
+ response.headers.add(
108
+ "Access-Control-Allow-Headers", "Content-Type,Authorization"
109
+ )
110
+ response.headers.add(
111
+ "Access-Control-Allow-Methods", "GET,PUT,POST,DELETE,OPTIONS"
112
+ )
113
+ return response
114
+ except Exception as e:
115
+ print(f"Error getting history: {str(e)}")
116
+ print(traceback.format_exc())
117
+ # Return empty array with CORS headers
118
+ response = make_response(
119
+ jsonify({"status": "error", "items": [], "message": str(e)})
120
+ )
121
+ response.headers.add("Access-Control-Allow-Origin", "*")
122
+ response.headers.add(
123
+ "Access-Control-Allow-Headers", "Content-Type,Authorization"
124
+ )
125
+ response.headers.add(
126
+ "Access-Control-Allow-Methods", "GET,PUT,POST,DELETE,OPTIONS"
127
+ )
128
+ return response
129
+
130
+
131
+ @history_bp.route("/status/<int:research_id>")
132
+ def get_research_status(research_id):
133
+ conn = get_db_connection()
134
+ conn.row_factory = lambda cursor, row: {
135
+ column[0]: row[idx] for idx, column in enumerate(cursor.description)
136
+ }
137
+ cursor = conn.cursor()
138
+ cursor.execute("SELECT * FROM research_history WHERE id = ?", (research_id,))
139
+ result = cursor.fetchone()
140
+ conn.close()
141
+
142
+ 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
147
+
148
+ globals_dict = get_globals()
149
+ active_research = globals_dict["active_research"]
150
+
151
+ # Add progress information
152
+ if research_id in active_research:
153
+ result["progress"] = active_research[research_id]["progress"]
154
+ result["log"] = active_research[research_id]["log"]
155
+ elif result.get("status") == "completed":
156
+ result["progress"] = 100
157
+ try:
158
+ result["log"] = json.loads(result.get("progress_log", "[]"))
159
+ except Exception:
160
+ result["log"] = []
161
+ else:
162
+ result["progress"] = 0
163
+ try:
164
+ result["log"] = json.loads(result.get("progress_log", "[]"))
165
+ except Exception:
166
+ result["log"] = []
167
+
168
+ return jsonify(result)
169
+
170
+
171
+ @history_bp.route("/details/<int:research_id>")
172
+ def get_research_details(research_id):
173
+ """Get detailed progress log for a specific research"""
174
+ conn = get_db_connection()
175
+ conn.row_factory = lambda cursor, row: {
176
+ column[0]: row[idx] for idx, column in enumerate(cursor.description)
177
+ }
178
+ cursor = conn.cursor()
179
+ cursor.execute("SELECT * FROM research_history WHERE id = ?", (research_id,))
180
+ result = cursor.fetchone()
181
+ conn.close()
182
+
183
+ if not result:
184
+ return jsonify({"status": "error", "message": "Research not found"}), 404
185
+
186
+ # Get logs from the dedicated log database
187
+ logs = get_logs_for_research(research_id)
188
+
189
+ # Import globals from research routes
190
+ from .research_routes import get_globals
191
+
192
+ globals_dict = get_globals()
193
+ active_research = globals_dict["active_research"]
194
+
195
+ # If this is an active research, merge with any in-memory logs
196
+ if research_id in active_research:
197
+ # Use the logs from memory temporarily until they're saved to the database
198
+ memory_logs = active_research[research_id]["log"]
199
+
200
+ # Filter out logs that are already in the database by timestamp
201
+ db_timestamps = {log["time"] for log in logs}
202
+ unique_memory_logs = [
203
+ log for log in memory_logs if log["time"] not in db_timestamps
204
+ ]
205
+
206
+ # Add unique memory logs to our return list
207
+ logs.extend(unique_memory_logs)
208
+
209
+ # Sort logs by timestamp
210
+ logs.sort(key=lambda x: x["time"])
211
+
212
+ return jsonify(
213
+ {
214
+ "research_id": research_id,
215
+ "query": result.get("query"),
216
+ "mode": result.get("mode"),
217
+ "status": result.get("status"),
218
+ "progress": active_research.get(research_id, {}).get(
219
+ "progress", 100 if result.get("status") == "completed" else 0
220
+ ),
221
+ "created_at": result.get("created_at"),
222
+ "completed_at": result.get("completed_at"),
223
+ "log": logs,
224
+ }
225
+ )
226
+
227
+
228
+ @history_bp.route("/report/<int:research_id>")
229
+ def get_report(research_id):
230
+ conn = get_db_connection()
231
+ conn.row_factory = lambda cursor, row: {
232
+ column[0]: row[idx] for idx, column in enumerate(cursor.description)
233
+ }
234
+ cursor = conn.cursor()
235
+ cursor.execute("SELECT * FROM research_history WHERE id = ?", (research_id,))
236
+ result = cursor.fetchone()
237
+ conn.close()
238
+
239
+ if not result or not result.get("report_path"):
240
+ return jsonify({"status": "error", "message": "Report not found"}), 404
241
+
242
+ try:
243
+ with open(result["report_path"], "r", encoding="utf-8") as f:
244
+ content = f.read()
245
+
246
+ # Create an enhanced metadata dictionary with database fields
247
+ enhanced_metadata = {
248
+ "query": result.get("query", "Unknown query"),
249
+ "mode": result.get("mode", "quick"),
250
+ "created_at": result.get("created_at"),
251
+ "completed_at": result.get("completed_at"),
252
+ "duration": result.get("duration_seconds"),
253
+ }
254
+
255
+ # Also include any stored metadata
256
+ stored_metadata = json.loads(result.get("metadata", "{}"))
257
+ if stored_metadata and isinstance(stored_metadata, dict):
258
+ enhanced_metadata.update(stored_metadata)
259
+
260
+ return jsonify(
261
+ {
262
+ "status": "success",
263
+ "content": content,
264
+ "query": result.get("query"),
265
+ "mode": result.get("mode"),
266
+ "created_at": result.get("created_at"),
267
+ "completed_at": result.get("completed_at"),
268
+ "metadata": enhanced_metadata,
269
+ }
270
+ )
271
+ except Exception as e:
272
+ return jsonify({"status": "error", "message": str(e)}), 500
273
+
274
+
275
+ @history_bp.route("/markdown/<int:research_id>")
276
+ def get_markdown(research_id):
277
+ """Get markdown export for a specific research"""
278
+ conn = get_db_connection()
279
+ conn.row_factory = lambda cursor, row: {
280
+ column[0]: row[idx] for idx, column in enumerate(cursor.description)
281
+ }
282
+ cursor = conn.cursor()
283
+ cursor.execute("SELECT * FROM research_history WHERE id = ?", (research_id,))
284
+ result = cursor.fetchone()
285
+ conn.close()
286
+
287
+ if not result or not result.get("report_path"):
288
+ return jsonify({"status": "error", "message": "Report not found"}), 404
289
+
290
+ try:
291
+ with open(result["report_path"], "r", encoding="utf-8") as f:
292
+ content = f.read()
293
+ return jsonify({"status": "success", "content": content})
294
+ except Exception as e:
295
+ return jsonify({"status": "error", "message": str(e)}), 500
296
+
297
+
298
+ @history_bp.route("/logs/<int:research_id>")
299
+ def get_research_logs(research_id):
300
+ """Get logs for a specific research ID"""
301
+ # First check if the research exists
302
+ conn = get_db_connection()
303
+ conn.row_factory = lambda cursor, row: {
304
+ column[0]: row[idx] for idx, column in enumerate(cursor.description)
305
+ }
306
+ cursor = conn.cursor()
307
+ cursor.execute("SELECT id FROM research_history WHERE id = ?", (research_id,))
308
+ result = cursor.fetchone()
309
+ conn.close()
310
+
311
+ if not result:
312
+ return jsonify({"status": "error", "message": "Research not found"}), 404
313
+
314
+ # Retrieve logs from the database
315
+ logs = get_logs_for_research(research_id)
316
+
317
+ # Format logs correctly if needed
318
+ formatted_logs = []
319
+ for log in logs:
320
+ # 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
+ }
326
+ formatted_logs.append(log_entry)
327
+
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
+ }
346
+
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
+
351
+ # Sort logs by timestamp
352
+ formatted_logs.sort(key=lambda x: x["time"])
353
+
354
+ return jsonify({"status": "success", "logs": formatted_logs})