local-deep-research 0.1.26__py3-none-any.whl → 0.2.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- local_deep_research/__init__.py +23 -22
- local_deep_research/__main__.py +16 -0
- local_deep_research/advanced_search_system/__init__.py +7 -0
- local_deep_research/advanced_search_system/filters/__init__.py +8 -0
- local_deep_research/advanced_search_system/filters/base_filter.py +38 -0
- local_deep_research/advanced_search_system/filters/cross_engine_filter.py +200 -0
- local_deep_research/advanced_search_system/findings/base_findings.py +81 -0
- local_deep_research/advanced_search_system/findings/repository.py +452 -0
- local_deep_research/advanced_search_system/knowledge/__init__.py +1 -0
- local_deep_research/advanced_search_system/knowledge/base_knowledge.py +151 -0
- local_deep_research/advanced_search_system/knowledge/standard_knowledge.py +159 -0
- local_deep_research/advanced_search_system/questions/__init__.py +1 -0
- local_deep_research/advanced_search_system/questions/base_question.py +64 -0
- local_deep_research/advanced_search_system/questions/decomposition_question.py +445 -0
- local_deep_research/advanced_search_system/questions/standard_question.py +119 -0
- local_deep_research/advanced_search_system/repositories/__init__.py +7 -0
- local_deep_research/advanced_search_system/strategies/__init__.py +1 -0
- local_deep_research/advanced_search_system/strategies/base_strategy.py +118 -0
- local_deep_research/advanced_search_system/strategies/iterdrag_strategy.py +450 -0
- local_deep_research/advanced_search_system/strategies/parallel_search_strategy.py +312 -0
- local_deep_research/advanced_search_system/strategies/rapid_search_strategy.py +270 -0
- local_deep_research/advanced_search_system/strategies/standard_strategy.py +300 -0
- local_deep_research/advanced_search_system/tools/__init__.py +1 -0
- local_deep_research/advanced_search_system/tools/base_tool.py +100 -0
- local_deep_research/advanced_search_system/tools/knowledge_tools/__init__.py +1 -0
- local_deep_research/advanced_search_system/tools/question_tools/__init__.py +1 -0
- local_deep_research/advanced_search_system/tools/search_tools/__init__.py +1 -0
- local_deep_research/api/__init__.py +5 -5
- local_deep_research/api/research_functions.py +154 -160
- local_deep_research/app.py +8 -0
- local_deep_research/citation_handler.py +25 -16
- local_deep_research/{config.py → config/config_files.py} +102 -110
- local_deep_research/config/llm_config.py +472 -0
- local_deep_research/config/search_config.py +77 -0
- local_deep_research/defaults/__init__.py +10 -5
- local_deep_research/defaults/main.toml +2 -2
- local_deep_research/defaults/search_engines.toml +60 -34
- local_deep_research/main.py +121 -19
- local_deep_research/migrate_db.py +147 -0
- local_deep_research/report_generator.py +87 -45
- local_deep_research/search_system.py +153 -283
- local_deep_research/setup_data_dir.py +35 -0
- local_deep_research/test_migration.py +178 -0
- local_deep_research/utilities/__init__.py +0 -0
- local_deep_research/utilities/db_utils.py +49 -0
- local_deep_research/{utilties → utilities}/enums.py +2 -2
- local_deep_research/{utilties → utilities}/llm_utils.py +63 -29
- local_deep_research/utilities/search_utilities.py +242 -0
- local_deep_research/{utilties → utilities}/setup_utils.py +4 -2
- local_deep_research/web/__init__.py +0 -1
- local_deep_research/web/app.py +86 -1709
- local_deep_research/web/app_factory.py +289 -0
- local_deep_research/web/database/README.md +70 -0
- local_deep_research/web/database/migrate_to_ldr_db.py +289 -0
- local_deep_research/web/database/migrations.py +447 -0
- local_deep_research/web/database/models.py +117 -0
- local_deep_research/web/database/schema_upgrade.py +107 -0
- local_deep_research/web/models/database.py +294 -0
- local_deep_research/web/models/settings.py +94 -0
- local_deep_research/web/routes/api_routes.py +559 -0
- local_deep_research/web/routes/history_routes.py +354 -0
- local_deep_research/web/routes/research_routes.py +715 -0
- local_deep_research/web/routes/settings_routes.py +1583 -0
- local_deep_research/web/services/research_service.py +947 -0
- local_deep_research/web/services/resource_service.py +149 -0
- local_deep_research/web/services/settings_manager.py +669 -0
- local_deep_research/web/services/settings_service.py +187 -0
- local_deep_research/web/services/socket_service.py +210 -0
- local_deep_research/web/static/css/custom_dropdown.css +277 -0
- local_deep_research/web/static/css/settings.css +1223 -0
- local_deep_research/web/static/css/styles.css +525 -48
- local_deep_research/web/static/js/components/custom_dropdown.js +428 -0
- local_deep_research/web/static/js/components/detail.js +348 -0
- local_deep_research/web/static/js/components/fallback/formatting.js +122 -0
- local_deep_research/web/static/js/components/fallback/ui.js +215 -0
- local_deep_research/web/static/js/components/history.js +487 -0
- local_deep_research/web/static/js/components/logpanel.js +949 -0
- local_deep_research/web/static/js/components/progress.js +1107 -0
- local_deep_research/web/static/js/components/research.js +1865 -0
- local_deep_research/web/static/js/components/results.js +766 -0
- local_deep_research/web/static/js/components/settings.js +3981 -0
- local_deep_research/web/static/js/components/settings_sync.js +106 -0
- local_deep_research/web/static/js/main.js +226 -0
- local_deep_research/web/static/js/services/api.js +253 -0
- local_deep_research/web/static/js/services/audio.js +31 -0
- local_deep_research/web/static/js/services/formatting.js +119 -0
- local_deep_research/web/static/js/services/pdf.js +622 -0
- local_deep_research/web/static/js/services/socket.js +882 -0
- local_deep_research/web/static/js/services/ui.js +546 -0
- local_deep_research/web/templates/base.html +72 -0
- local_deep_research/web/templates/components/custom_dropdown.html +47 -0
- local_deep_research/web/templates/components/log_panel.html +32 -0
- local_deep_research/web/templates/components/mobile_nav.html +22 -0
- local_deep_research/web/templates/components/settings_form.html +299 -0
- local_deep_research/web/templates/components/sidebar.html +21 -0
- local_deep_research/web/templates/pages/details.html +73 -0
- local_deep_research/web/templates/pages/history.html +51 -0
- local_deep_research/web/templates/pages/progress.html +57 -0
- local_deep_research/web/templates/pages/research.html +139 -0
- local_deep_research/web/templates/pages/results.html +59 -0
- local_deep_research/web/templates/settings_dashboard.html +78 -192
- local_deep_research/web/utils/__init__.py +0 -0
- local_deep_research/web/utils/formatters.py +76 -0
- local_deep_research/web_search_engines/engines/full_search.py +18 -16
- local_deep_research/web_search_engines/engines/meta_search_engine.py +182 -131
- local_deep_research/web_search_engines/engines/search_engine_arxiv.py +224 -139
- local_deep_research/web_search_engines/engines/search_engine_brave.py +88 -71
- local_deep_research/web_search_engines/engines/search_engine_ddg.py +48 -39
- local_deep_research/web_search_engines/engines/search_engine_github.py +415 -204
- local_deep_research/web_search_engines/engines/search_engine_google_pse.py +123 -90
- local_deep_research/web_search_engines/engines/search_engine_guardian.py +210 -157
- local_deep_research/web_search_engines/engines/search_engine_local.py +532 -369
- local_deep_research/web_search_engines/engines/search_engine_local_all.py +42 -36
- local_deep_research/web_search_engines/engines/search_engine_pubmed.py +358 -266
- local_deep_research/web_search_engines/engines/search_engine_searxng.py +212 -160
- local_deep_research/web_search_engines/engines/search_engine_semantic_scholar.py +213 -170
- local_deep_research/web_search_engines/engines/search_engine_serpapi.py +84 -68
- local_deep_research/web_search_engines/engines/search_engine_wayback.py +186 -154
- local_deep_research/web_search_engines/engines/search_engine_wikipedia.py +115 -77
- local_deep_research/web_search_engines/search_engine_base.py +174 -99
- local_deep_research/web_search_engines/search_engine_factory.py +192 -102
- local_deep_research/web_search_engines/search_engines_config.py +22 -15
- {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.2.dist-info}/METADATA +177 -97
- local_deep_research-0.2.2.dist-info/RECORD +135 -0
- {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.2.dist-info}/WHEEL +1 -2
- {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.2.dist-info}/entry_points.txt +3 -0
- local_deep_research/defaults/llm_config.py +0 -338
- local_deep_research/utilties/search_utilities.py +0 -114
- local_deep_research/web/static/js/app.js +0 -3763
- local_deep_research/web/templates/api_keys_config.html +0 -82
- local_deep_research/web/templates/collections_config.html +0 -90
- local_deep_research/web/templates/index.html +0 -348
- local_deep_research/web/templates/llm_config.html +0 -120
- local_deep_research/web/templates/main_config.html +0 -89
- local_deep_research/web/templates/search_engines_config.html +0 -154
- local_deep_research/web/templates/settings.html +0 -519
- local_deep_research-0.1.26.dist-info/RECORD +0 -61
- local_deep_research-0.1.26.dist-info/top_level.txt +0 -1
- /local_deep_research/{utilties → config}/__init__.py +0 -0
- {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,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})
|