local-deep-research 0.1.26__py3-none-any.whl → 0.2.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 (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 +96 -84
  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 +72 -44
  41. local_deep_research/search_system.py +147 -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 +1592 -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 +211 -159
  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.0.dist-info}/METADATA +177 -97
  124. local_deep_research-0.2.0.dist-info/RECORD +135 -0
  125. {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.0.dist-info}/WHEEL +1 -2
  126. {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.0.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.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,294 @@
1
+ import json
2
+ import logging
3
+ import os
4
+ import sqlite3
5
+ import traceback
6
+ from datetime import datetime
7
+
8
+ # Initialize logger
9
+ logger = logging.getLogger(__name__)
10
+
11
+ # Database path
12
+ # Use unified database in data directory
13
+ DATA_DIR = os.path.abspath(
14
+ os.path.join(os.path.dirname(__file__), "..", "..", "..", "data")
15
+ )
16
+ os.makedirs(DATA_DIR, exist_ok=True)
17
+ DB_PATH = os.path.join(DATA_DIR, "ldr.db")
18
+
19
+ # Legacy database paths (for migration)
20
+ LEGACY_RESEARCH_HISTORY_DB = os.path.abspath(
21
+ os.path.join(os.path.dirname(__file__), "..", "..", "..", "research_history.db")
22
+ )
23
+ LEGACY_DEEP_RESEARCH_DB = os.path.join(
24
+ os.path.abspath(
25
+ os.path.join(os.path.dirname(__file__), "..", "..", "..", "..", "data")
26
+ ),
27
+ "deep_research.db",
28
+ )
29
+
30
+
31
+ def get_db_connection():
32
+ """
33
+ Get a connection to the SQLite database.
34
+ Allows for custom row factory if needed.
35
+ """
36
+ conn = sqlite3.connect(DB_PATH)
37
+ return conn
38
+
39
+
40
+ def init_db():
41
+ """Initialize the database with necessary tables."""
42
+ conn = get_db_connection()
43
+ cursor = conn.cursor()
44
+
45
+ # Create the table if it doesn't exist
46
+ cursor.execute(
47
+ """
48
+ CREATE TABLE IF NOT EXISTS research_history (
49
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
50
+ query TEXT NOT NULL,
51
+ mode TEXT NOT NULL,
52
+ status TEXT NOT NULL,
53
+ created_at TEXT NOT NULL,
54
+ completed_at TEXT,
55
+ duration_seconds INTEGER,
56
+ report_path TEXT,
57
+ metadata TEXT,
58
+ progress_log TEXT,
59
+ progress INTEGER,
60
+ title TEXT
61
+ )
62
+ """
63
+ )
64
+
65
+ # Create a dedicated table for research logs
66
+ cursor.execute(
67
+ """
68
+ CREATE TABLE IF NOT EXISTS research_logs (
69
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
70
+ research_id INTEGER NOT NULL,
71
+ timestamp TEXT NOT NULL,
72
+ message TEXT NOT NULL,
73
+ log_type TEXT NOT NULL,
74
+ progress INTEGER,
75
+ metadata TEXT,
76
+ FOREIGN KEY (research_id) REFERENCES research_history (id) ON DELETE CASCADE
77
+ )
78
+ """
79
+ )
80
+
81
+ # Create a dedicated table for research resources
82
+ cursor.execute(
83
+ """
84
+ CREATE TABLE IF NOT EXISTS research_resources (
85
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
86
+ research_id INTEGER NOT NULL,
87
+ title TEXT,
88
+ url TEXT,
89
+ content_preview TEXT,
90
+ source_type TEXT,
91
+ metadata TEXT,
92
+ created_at TEXT NOT NULL,
93
+ FOREIGN KEY (research_id) REFERENCES research_history (id) ON DELETE CASCADE
94
+ )
95
+ """
96
+ )
97
+
98
+ # Check if the duration_seconds column exists, add it if missing
99
+ cursor.execute("PRAGMA table_info(research_history)")
100
+ columns = [column[1] for column in cursor.fetchall()]
101
+
102
+ if "duration_seconds" not in columns:
103
+ print("Adding missing 'duration_seconds' column to research_history table")
104
+ cursor.execute(
105
+ "ALTER TABLE research_history ADD COLUMN duration_seconds INTEGER"
106
+ )
107
+
108
+ # Check if the progress column exists, add it if missing
109
+ if "progress" not in columns:
110
+ print("Adding missing 'progress' column to research_history table")
111
+ cursor.execute("ALTER TABLE research_history ADD COLUMN progress INTEGER")
112
+
113
+ # Check if the title column exists, add it if missing
114
+ if "title" not in columns:
115
+ print("Adding missing 'title' column to research_history table")
116
+ cursor.execute("ALTER TABLE research_history ADD COLUMN title TEXT")
117
+
118
+ # Enable foreign key support
119
+ cursor.execute("PRAGMA foreign_keys = ON")
120
+
121
+ conn.commit()
122
+ conn.close()
123
+
124
+
125
+ def calculate_duration(created_at_str, completed_at_str=None):
126
+ """
127
+ Calculate duration in seconds between created_at timestamp and completed_at or now.
128
+ Handles various timestamp formats and returns None if calculation fails.
129
+
130
+ Args:
131
+ created_at_str: The start timestamp
132
+ completed_at_str: Optional end timestamp, defaults to current time if None
133
+
134
+ Returns:
135
+ Duration in seconds or None if calculation fails
136
+ """
137
+ if not created_at_str:
138
+ return None
139
+
140
+ end_time = None
141
+ if completed_at_str:
142
+ # Use completed_at time if provided
143
+ try:
144
+ if "T" in completed_at_str: # ISO format with T separator
145
+ end_time = datetime.fromisoformat(completed_at_str)
146
+ else: # Older format without T
147
+ # Try different formats
148
+ try:
149
+ end_time = datetime.strptime(
150
+ completed_at_str, "%Y-%m-%d %H:%M:%S.%f"
151
+ )
152
+ except ValueError:
153
+ try:
154
+ end_time = datetime.strptime(
155
+ completed_at_str, "%Y-%m-%d %H:%M:%S"
156
+ )
157
+ except ValueError:
158
+ # Last resort fallback
159
+ end_time = datetime.fromisoformat(
160
+ completed_at_str.replace(" ", "T")
161
+ )
162
+ except Exception as e:
163
+ print(f"Error parsing completed_at timestamp: {str(e)}")
164
+ try:
165
+ from dateutil import parser
166
+
167
+ end_time = parser.parse(completed_at_str)
168
+ except Exception:
169
+ print(
170
+ f"Fallback parsing also failed for completed_at: {completed_at_str}"
171
+ )
172
+ # Fall back to current time
173
+ end_time = datetime.utcnow()
174
+ else:
175
+ # Use current time if no completed_at provided
176
+ end_time = datetime.utcnow()
177
+
178
+ start_time = None
179
+ try:
180
+ # Proper parsing of ISO format
181
+ if "T" in created_at_str: # ISO format with T separator
182
+ start_time = datetime.fromisoformat(created_at_str)
183
+ else: # Older format without T
184
+ # Try different formats
185
+ try:
186
+ start_time = datetime.strptime(created_at_str, "%Y-%m-%d %H:%M:%S.%f")
187
+ except ValueError:
188
+ try:
189
+ start_time = datetime.strptime(created_at_str, "%Y-%m-%d %H:%M:%S")
190
+ except ValueError:
191
+ # Last resort fallback
192
+ start_time = datetime.fromisoformat(
193
+ created_at_str.replace(" ", "T")
194
+ )
195
+ except Exception as e:
196
+ print(f"Error parsing created_at timestamp: {str(e)}")
197
+ # Fallback method if parsing fails
198
+ try:
199
+ from dateutil import parser
200
+
201
+ start_time = parser.parse(created_at_str)
202
+ except Exception:
203
+ print(f"Fallback parsing also failed for created_at: {created_at_str}")
204
+ return None
205
+
206
+ # Calculate duration if both timestamps are valid
207
+ if start_time and end_time:
208
+ try:
209
+ return int((end_time - start_time).total_seconds())
210
+ except Exception as e:
211
+ print(f"Error calculating duration: {str(e)}")
212
+
213
+ return None
214
+
215
+
216
+ def add_log_to_db(research_id, message, log_type="info", progress=None, metadata=None):
217
+ """
218
+ Store a log entry in the database
219
+
220
+ Args:
221
+ research_id: ID of the research
222
+ message: Log message text
223
+ log_type: Type of log (info, error, milestone)
224
+ progress: Progress percentage (0-100)
225
+ metadata: Additional metadata as dictionary (will be stored as JSON)
226
+ """
227
+ try:
228
+ timestamp = datetime.utcnow().isoformat()
229
+ metadata_json = json.dumps(metadata) if metadata else None
230
+
231
+ conn = get_db_connection()
232
+ cursor = conn.cursor()
233
+ cursor.execute(
234
+ "INSERT INTO research_logs (research_id, timestamp, message, log_type, progress, metadata) "
235
+ "VALUES (?, ?, ?, ?, ?, ?)",
236
+ (research_id, timestamp, message, log_type, progress, metadata_json),
237
+ )
238
+ conn.commit()
239
+ conn.close()
240
+ return True
241
+ except Exception as e:
242
+ print(f"Error adding log to database: {str(e)}")
243
+ print(traceback.format_exc())
244
+ return False
245
+
246
+
247
+ def get_logs_for_research(research_id):
248
+ """
249
+ Retrieve all logs for a specific research ID
250
+
251
+ Args:
252
+ research_id: ID of the research
253
+
254
+ Returns:
255
+ List of log entries as dictionaries
256
+ """
257
+ try:
258
+ conn = get_db_connection()
259
+ conn.row_factory = sqlite3.Row
260
+ cursor = conn.cursor()
261
+ cursor.execute(
262
+ "SELECT * FROM research_logs WHERE research_id = ? ORDER BY timestamp ASC",
263
+ (research_id,),
264
+ )
265
+ results = cursor.fetchall()
266
+ conn.close()
267
+
268
+ logs = []
269
+ for result in results:
270
+ log_entry = dict(result)
271
+ # Parse metadata JSON if it exists
272
+ if log_entry.get("metadata"):
273
+ try:
274
+ log_entry["metadata"] = json.loads(log_entry["metadata"])
275
+ except Exception:
276
+ log_entry["metadata"] = {}
277
+ else:
278
+ log_entry["metadata"] = {}
279
+
280
+ # Convert entry for frontend consumption
281
+ formatted_entry = {
282
+ "time": log_entry["timestamp"],
283
+ "message": log_entry["message"],
284
+ "progress": log_entry["progress"],
285
+ "metadata": log_entry["metadata"],
286
+ "type": log_entry["log_type"],
287
+ }
288
+ logs.append(formatted_entry)
289
+
290
+ return logs
291
+ except Exception as e:
292
+ print(f"Error retrieving logs from database: {str(e)}")
293
+ print(traceback.format_exc())
294
+ return []
@@ -0,0 +1,94 @@
1
+ from enum import Enum
2
+ from typing import Any, Dict, List, Optional
3
+
4
+ from pydantic import BaseModel, field_validator
5
+
6
+
7
+ class SettingType(str, Enum):
8
+ """Types of settings in the system"""
9
+
10
+ APP = "app"
11
+ LLM = "llm"
12
+ SEARCH = "search"
13
+ REPORT = "report"
14
+
15
+
16
+ class BaseSetting(BaseModel):
17
+ """Base model for all settings"""
18
+
19
+ key: str
20
+ value: Any
21
+ type: SettingType
22
+ name: str
23
+ description: Optional[str] = None
24
+ category: Optional[str] = None
25
+ ui_element: Optional[str] = "text" # text, select, checkbox, slider, etc.
26
+ options: Optional[List[Dict[str, Any]]] = None # For select elements
27
+ min_value: Optional[float] = None # For numeric inputs
28
+ max_value: Optional[float] = None # For numeric inputs
29
+ step: Optional[float] = None # For sliders
30
+ visible: bool = True
31
+ editable: bool = True
32
+
33
+ class Config:
34
+ from_attributes = True
35
+
36
+
37
+ class LLMSetting(BaseSetting):
38
+ """LLM-specific settings"""
39
+
40
+ type: SettingType = SettingType.LLM
41
+
42
+ @field_validator("key")
43
+ def validate_llm_key(cls, v):
44
+ # Ensure LLM settings follow a convention
45
+ if not v.startswith("llm."):
46
+ return f"llm.{v}"
47
+ return v
48
+
49
+
50
+ class SearchSetting(BaseSetting):
51
+ """Search-specific settings"""
52
+
53
+ type: SettingType = SettingType.SEARCH
54
+
55
+ @field_validator("key")
56
+ def validate_search_key(cls, v):
57
+ # Ensure search settings follow a convention
58
+ if not v.startswith("search."):
59
+ return f"search.{v}"
60
+ return v
61
+
62
+
63
+ class ReportSetting(BaseSetting):
64
+ """Report generation settings"""
65
+
66
+ type: SettingType = SettingType.REPORT
67
+
68
+ @field_validator("key")
69
+ def validate_report_key(cls, v):
70
+ # Ensure report settings follow a convention
71
+ if not v.startswith("report."):
72
+ return f"report.{v}"
73
+ return v
74
+
75
+
76
+ class AppSetting(BaseSetting):
77
+ """Application-wide settings"""
78
+
79
+ type: SettingType = SettingType.APP
80
+
81
+ @field_validator("key")
82
+ def validate_app_key(cls, v):
83
+ # Ensure app settings follow a convention
84
+ if not v.startswith("app."):
85
+ return f"app.{v}"
86
+ return v
87
+
88
+
89
+ class SettingsGroup(BaseModel):
90
+ """A group of related settings"""
91
+
92
+ name: str
93
+ description: Optional[str] = None
94
+ settings: List[BaseSetting]