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,447 @@
1
+ import logging
2
+
3
+ from sqlalchemy import inspect
4
+
5
+ from ..services.settings_manager import SettingsManager
6
+ from .models import Base, Setting, SettingType
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ def migrate_settings_from_files(db_session):
12
+ """
13
+ Migrate settings from files to database
14
+ """
15
+ # Check if settings table is empty
16
+ settings_count = db_session.query(Setting).count()
17
+
18
+ if settings_count == 0:
19
+ logger.info("Settings table is empty, importing from files")
20
+
21
+ # Create settings manager and import settings
22
+ try:
23
+ settings_mgr = SettingsManager(db_session)
24
+ success = settings_mgr.import_from_file()
25
+ if success:
26
+ logger.info("Successfully imported settings from files")
27
+ else:
28
+ logger.warning("Failed to import some settings from files")
29
+ except Exception as e:
30
+ logger.error("Error importing settings from files: %s", e)
31
+ else:
32
+ logger.info(
33
+ "Settings table already has %s rows, skipping import", settings_count
34
+ )
35
+
36
+
37
+ def run_migrations(engine, db_session=None):
38
+ """
39
+ Run any necessary database migrations
40
+
41
+ Args:
42
+ engine: SQLAlchemy engine
43
+ db_session: Optional SQLAlchemy session
44
+ """
45
+ # Create all tables if they don't exist
46
+ inspector = inspect(engine)
47
+ if not inspector.has_table("settings"):
48
+ logger.info("Creating settings table")
49
+ Base.metadata.create_all(engine, tables=[Setting.__table__])
50
+
51
+ # Import existing settings from files
52
+ if db_session:
53
+ migrate_settings_from_files(db_session)
54
+
55
+
56
+ def setup_predefined_settings(db_session):
57
+ """
58
+ Set up predefined settings with UI metadata
59
+
60
+ Args:
61
+ db_session: SQLAlchemy session
62
+ """
63
+ # Define standard UI settings for LLM
64
+ llm_settings = [
65
+ {
66
+ "key": "llm.model",
67
+ "name": "LLM Model",
68
+ "description": "Language model to use for research and analysis",
69
+ "category": "llm_general",
70
+ "ui_element": "select",
71
+ "options": [
72
+ {"value": "gpt-4o", "label": "GPT-4o (OpenAI)"},
73
+ {"value": "gpt-3.5-turbo", "label": "GPT-3.5 Turbo (OpenAI)"},
74
+ {
75
+ "value": "claude-3-5-sonnet-latest",
76
+ "label": "Claude 3.5 Sonnet (Anthropic)",
77
+ },
78
+ {
79
+ "value": "claude-3-opus-20240229",
80
+ "label": "Claude 3 Opus (Anthropic)",
81
+ },
82
+ {"value": "llama3", "label": "Llama 3 (Meta)"},
83
+ {"value": "mistral", "label": "Mistral (Mistral AI)"},
84
+ {"value": "mixtral", "label": "Mixtral (Mistral AI)"},
85
+ ],
86
+ "value": "gpt-3.5-turbo",
87
+ },
88
+ {
89
+ "key": "llm.provider",
90
+ "name": "LLM Provider",
91
+ "description": "Service provider for the language model",
92
+ "category": "llm_general",
93
+ "ui_element": "select",
94
+ "options": [
95
+ {"value": "openai", "label": "OpenAI API"},
96
+ {"value": "anthropic", "label": "Anthropic API"},
97
+ {"value": "ollama", "label": "Ollama (Local)"},
98
+ {"value": "lmstudio", "label": "LM Studio (Local)"},
99
+ {"value": "vllm", "label": "vLLM (Local)"},
100
+ {"value": "openai_endpoint", "label": "Custom OpenAI-compatible API"},
101
+ ],
102
+ "value": "openai",
103
+ },
104
+ {
105
+ "key": "llm.temperature",
106
+ "name": "Temperature",
107
+ "description": "Controls randomness in model outputs (0.0 - 1.0)",
108
+ "category": "llm_parameters",
109
+ "ui_element": "slider",
110
+ "min_value": 0.0,
111
+ "max_value": 1.0,
112
+ "step": 0.05,
113
+ "value": 0.7,
114
+ },
115
+ {
116
+ "key": "llm.max_tokens",
117
+ "name": "Max Tokens",
118
+ "description": "Maximum number of tokens in model responses",
119
+ "category": "llm_parameters",
120
+ "ui_element": "number",
121
+ "min_value": 100,
122
+ "max_value": 4096,
123
+ "value": 1024,
124
+ },
125
+ ]
126
+
127
+ # Define standard UI settings for Search
128
+ search_settings = [
129
+ {
130
+ "key": "search.tool",
131
+ "name": "Search Engine",
132
+ "description": "Web search engine to use for research",
133
+ "category": "search_general",
134
+ "ui_element": "select",
135
+ "options": [
136
+ {"value": "auto", "label": "Auto (Default)"},
137
+ {"value": "arxiv", "label": "Arxiv"},
138
+ {"value": "wikipedia", "label": "Wikipedia"},
139
+ {"value": "pubmed", "label": "Pubmed"},
140
+ {"value": "github", "label": "Github"},
141
+ {"value": "serpapi", "label": "SerpAPI (Google)"},
142
+ {"value": "searxng", "label": "SearXNG (Self-hosted)"},
143
+ {"value": "google_pse", "label": "Google Programmable Search Engine"},
144
+ {"value": "duckduckgo", "label": "DuckDuckGo"},
145
+ {"value": "brave", "label": "Brave"},
146
+ {"value": "wayback", "label": "Wayback"},
147
+ {"value": "local_all", "label": "Local All"},
148
+ ],
149
+ "value": "auto",
150
+ },
151
+ {
152
+ "key": "search.max_results",
153
+ "name": "Max Results",
154
+ "description": "Maximum number of search results to retrieve",
155
+ "category": "search_parameters",
156
+ "ui_element": "number",
157
+ "min_value": 3,
158
+ "max_value": 50,
159
+ "value": 10,
160
+ },
161
+ {
162
+ "key": "search.region",
163
+ "name": "Search Region",
164
+ "description": "Geographic region for search results",
165
+ "category": "search_parameters",
166
+ "ui_element": "select",
167
+ "options": [
168
+ {"value": "us", "label": "United States"},
169
+ {"value": "uk", "label": "United Kingdom"},
170
+ {"value": "fr", "label": "France"},
171
+ {"value": "de", "label": "Germany"},
172
+ {"value": "jp", "label": "Japan"},
173
+ {"value": "wt-wt", "label": "No Region (Worldwide)"},
174
+ ],
175
+ "value": "us",
176
+ },
177
+ {
178
+ "key": "search.time_period",
179
+ "name": "Time Period",
180
+ "description": "Time period for search results",
181
+ "category": "search_parameters",
182
+ "ui_element": "select",
183
+ "options": [
184
+ {"value": "d", "label": "Past 24 hours"},
185
+ {"value": "w", "label": "Past week"},
186
+ {"value": "m", "label": "Past month"},
187
+ {"value": "y", "label": "Past year"},
188
+ {"value": "all", "label": "All time"},
189
+ ],
190
+ "value": "all",
191
+ },
192
+ {
193
+ "key": "search.snippets_only",
194
+ "name": "Snippets Only",
195
+ "description": "Only retrieve snippets instead of full search results",
196
+ "category": "search_parameters",
197
+ "ui_element": "checkbox",
198
+ "value": True,
199
+ },
200
+ ]
201
+
202
+ # Define standard UI settings for Report generation
203
+ report_settings = [
204
+ {
205
+ "key": "report.searches_per_section",
206
+ "name": "Searches Per Section",
207
+ "description": "Number of searches to run per report section",
208
+ "category": "report_parameters",
209
+ "ui_element": "number",
210
+ "min_value": 1,
211
+ "max_value": 5,
212
+ "value": 2,
213
+ },
214
+ {
215
+ "key": "report.enable_fact_checking",
216
+ "name": "Enable Fact Checking",
217
+ "description": "Enable fact checking for report contents",
218
+ "category": "report_parameters",
219
+ "ui_element": "checkbox",
220
+ "value": True,
221
+ },
222
+ {
223
+ "key": "report.detailed_citations",
224
+ "name": "Detailed Citations",
225
+ "description": "Include detailed citations in reports",
226
+ "category": "report_parameters",
227
+ "ui_element": "checkbox",
228
+ "value": True,
229
+ },
230
+ ]
231
+
232
+ # Define standard UI settings for App
233
+ app_settings = [
234
+ {
235
+ "key": "app.debug",
236
+ "name": "Debug Mode",
237
+ "description": "Enable debug mode for the web application",
238
+ "category": "app_interface",
239
+ "ui_element": "checkbox",
240
+ "value": True,
241
+ },
242
+ {
243
+ "key": "app.host",
244
+ "name": "Web Host",
245
+ "description": "Host address to bind the web server",
246
+ "category": "app_interface",
247
+ "ui_element": "text",
248
+ "value": "0.0.0.0",
249
+ },
250
+ {
251
+ "key": "app.port",
252
+ "name": "Web Port",
253
+ "description": "Port for the web server",
254
+ "category": "app_interface",
255
+ "ui_element": "number",
256
+ "min_value": 1,
257
+ "max_value": 65535,
258
+ "value": 5000,
259
+ },
260
+ {
261
+ "key": "app.enable_notifications",
262
+ "name": "Enable Notifications",
263
+ "description": "Enable browser notifications for research events",
264
+ "category": "app_interface",
265
+ "ui_element": "checkbox",
266
+ "value": True,
267
+ },
268
+ {
269
+ "key": "app.theme",
270
+ "name": "UI Theme",
271
+ "description": "User interface theme",
272
+ "category": "app_interface",
273
+ "ui_element": "select",
274
+ "options": [
275
+ {"value": "dark", "label": "Dark"},
276
+ {"value": "light", "label": "Light"},
277
+ {"value": "system", "label": "System Default"},
278
+ ],
279
+ "value": "dark",
280
+ },
281
+ {
282
+ "key": "app.web_interface",
283
+ "name": "Web Interface",
284
+ "description": "Enable the web interface",
285
+ "category": "app_interface",
286
+ "ui_element": "checkbox",
287
+ "value": True,
288
+ },
289
+ {
290
+ "key": "app.enable_web",
291
+ "name": "Enable Web Server",
292
+ "description": "Enable the web server",
293
+ "category": "app_interface",
294
+ "ui_element": "checkbox",
295
+ "value": True,
296
+ },
297
+ ]
298
+
299
+ # Update search settings with research-specific settings
300
+ search_settings.extend(
301
+ [
302
+ {
303
+ "key": "search.research_iterations",
304
+ "name": "Research Iterations",
305
+ "description": "Number of research cycles to perform",
306
+ "category": "search_parameters",
307
+ "ui_element": "number",
308
+ "min_value": 1,
309
+ "max_value": 5,
310
+ "value": 2,
311
+ },
312
+ {
313
+ "key": "search.questions_per_iteration",
314
+ "name": "Questions Per Iteration",
315
+ "description": "Number of questions to generate per research cycle",
316
+ "category": "search_parameters",
317
+ "ui_element": "number",
318
+ "min_value": 1,
319
+ "max_value": 10,
320
+ "value": 3,
321
+ },
322
+ {
323
+ "key": "search.searches_per_section",
324
+ "name": "Searches Per Section",
325
+ "description": "Number of searches to run per report section",
326
+ "category": "search_parameters",
327
+ "ui_element": "number",
328
+ "min_value": 1,
329
+ "max_value": 5,
330
+ "value": 2,
331
+ },
332
+ {
333
+ "key": "search.skip_relevance_filter",
334
+ "name": "Skip Relevance Filter",
335
+ "description": "Skip filtering search results for relevance",
336
+ "category": "search_parameters",
337
+ "ui_element": "checkbox",
338
+ "value": False,
339
+ },
340
+ {
341
+ "key": "search.safe_search",
342
+ "name": "Safe Search",
343
+ "description": "Enable safe search filtering",
344
+ "category": "search_parameters",
345
+ "ui_element": "checkbox",
346
+ "value": True,
347
+ },
348
+ {
349
+ "key": "search.search_language",
350
+ "name": "Search Language",
351
+ "description": "Language for search results",
352
+ "category": "search_parameters",
353
+ "ui_element": "select",
354
+ "options": [
355
+ {"value": "English", "label": "English"},
356
+ {"value": "French", "label": "French"},
357
+ {"value": "German", "label": "German"},
358
+ {"value": "Spanish", "label": "Spanish"},
359
+ {"value": "Italian", "label": "Italian"},
360
+ {"value": "Japanese", "label": "Japanese"},
361
+ {"value": "Chinese", "label": "Chinese"},
362
+ ],
363
+ "value": "English",
364
+ },
365
+ ]
366
+ )
367
+
368
+ # Ensure these predefined settings exist in the database
369
+ # This will update existing settings with the same key
370
+ all_settings = llm_settings + search_settings + report_settings + app_settings
371
+
372
+ # Add/update each setting
373
+ for setting_dict in all_settings:
374
+ try:
375
+ # Convert to correct type based on key prefix
376
+ setting_type = None
377
+ key = setting_dict.get("key", "")
378
+
379
+ if key.startswith("llm."):
380
+ setting_type = SettingType.LLM
381
+ elif key.startswith("search."):
382
+ setting_type = SettingType.SEARCH
383
+ elif key.startswith("report."):
384
+ setting_type = SettingType.REPORT
385
+ elif key.startswith("app."):
386
+ setting_type = SettingType.APP
387
+
388
+ # Skip if no valid type
389
+ if not setting_type:
390
+ logger.warning("Skipping setting %s - unknown type", key)
391
+ continue
392
+
393
+ # Check if setting exists
394
+ existing = db_session.query(Setting).filter(Setting.key == key).first()
395
+
396
+ if existing:
397
+ # Update existing setting
398
+ logger.debug("Updating existing setting: %s", key)
399
+
400
+ # Only update metadata, not the value (to preserve user settings)
401
+ existing.name = setting_dict.get("name", existing.name)
402
+ existing.description = setting_dict.get(
403
+ "description", existing.description
404
+ )
405
+ existing.category = setting_dict.get("category", existing.category)
406
+ existing.ui_element = setting_dict.get(
407
+ "ui_element", existing.ui_element
408
+ )
409
+ existing.options = setting_dict.get("options", existing.options)
410
+ existing.min_value = setting_dict.get("min_value", existing.min_value)
411
+ existing.max_value = setting_dict.get("max_value", existing.max_value)
412
+ existing.step = setting_dict.get("step", existing.step)
413
+
414
+ # Only set value if it's not already set
415
+ if existing.value is None and "value" in setting_dict:
416
+ existing.value = setting_dict["value"]
417
+ else:
418
+ # Create new setting
419
+ logger.info("Creating new setting: %s", key)
420
+ setting = Setting(
421
+ key=key,
422
+ value=setting_dict.get("value"),
423
+ type=setting_type,
424
+ name=setting_dict.get(
425
+ "name", key.split(".")[-1].replace("_", " ").title()
426
+ ),
427
+ description=setting_dict.get("description", f"Setting for {key}"),
428
+ category=setting_dict.get("category"),
429
+ ui_element=setting_dict.get("ui_element", "text"),
430
+ options=setting_dict.get("options"),
431
+ min_value=setting_dict.get("min_value"),
432
+ max_value=setting_dict.get("max_value"),
433
+ step=setting_dict.get("step"),
434
+ visible=setting_dict.get("visible", True),
435
+ editable=setting_dict.get("editable", True),
436
+ )
437
+ db_session.add(setting)
438
+
439
+ # Commit after each successful setting
440
+ db_session.commit()
441
+
442
+ except Exception as e:
443
+ logger.error("Error ensuring setting %s: %s", setting_dict.get("key"), e)
444
+ db_session.rollback()
445
+
446
+ # Log completion
447
+ logger.info("Predefined settings setup complete")
@@ -0,0 +1,117 @@
1
+ import enum
2
+
3
+ from sqlalchemy import (
4
+ JSON,
5
+ Boolean,
6
+ Column,
7
+ DateTime,
8
+ Enum,
9
+ Float,
10
+ ForeignKey,
11
+ Integer,
12
+ String,
13
+ Text,
14
+ UniqueConstraint,
15
+ )
16
+ from sqlalchemy.ext.declarative import declarative_base
17
+ from sqlalchemy.orm import relationship
18
+ from sqlalchemy.sql import func
19
+
20
+ Base = declarative_base()
21
+
22
+
23
+ class ResearchMode(enum.Enum):
24
+ QUICK = "quick"
25
+ DETAILED = "detailed"
26
+
27
+
28
+ class ResearchStatus(enum.Enum):
29
+ PENDING = "pending"
30
+ IN_PROGRESS = "in_progress"
31
+ COMPLETED = "completed"
32
+ FAILED = "failed"
33
+ CANCELLED = "cancelled"
34
+
35
+
36
+ class Research(Base):
37
+ __tablename__ = "research"
38
+
39
+ id = Column(Integer, primary_key=True, index=True)
40
+ query = Column(String, nullable=False)
41
+ status = Column(
42
+ Enum(ResearchStatus), default=ResearchStatus.PENDING, nullable=False
43
+ )
44
+ mode = Column(Enum(ResearchMode), default=ResearchMode.QUICK, nullable=False)
45
+ created_at = Column(DateTime, server_default=func.now(), nullable=False)
46
+ updated_at = Column(
47
+ DateTime, server_default=func.now(), onupdate=func.now(), nullable=False
48
+ )
49
+ progress = Column(Float, default=0.0, nullable=False)
50
+ start_time = Column(DateTime, nullable=True)
51
+ end_time = Column(DateTime, nullable=True)
52
+ error_message = Column(Text, nullable=True)
53
+
54
+ # Relationships
55
+ report = relationship(
56
+ "ResearchReport",
57
+ back_populates="research",
58
+ uselist=False,
59
+ cascade="all, delete-orphan",
60
+ )
61
+
62
+
63
+ class ResearchReport(Base):
64
+ __tablename__ = "research_report"
65
+
66
+ id = Column(Integer, primary_key=True, index=True)
67
+ research_id = Column(
68
+ Integer,
69
+ ForeignKey("research.id", ondelete="CASCADE"),
70
+ nullable=False,
71
+ unique=True,
72
+ )
73
+ content = Column(Text, nullable=True)
74
+ created_at = Column(DateTime, server_default=func.now(), nullable=False)
75
+ updated_at = Column(
76
+ DateTime, server_default=func.now(), onupdate=func.now(), nullable=False
77
+ )
78
+ report_metadata = Column(
79
+ JSON, nullable=True
80
+ ) # Additional metadata about the report
81
+
82
+ # Relationships
83
+ research = relationship("Research", back_populates="report")
84
+
85
+
86
+ class SettingType(enum.Enum):
87
+ APP = "app"
88
+ LLM = "llm"
89
+ SEARCH = "search"
90
+ REPORT = "report"
91
+
92
+
93
+ class Setting(Base):
94
+ """Database model for storing settings"""
95
+
96
+ __tablename__ = "settings"
97
+
98
+ id = Column(Integer, primary_key=True, index=True)
99
+ key = Column(String(255), nullable=False, unique=True, index=True)
100
+ value = Column(JSON, nullable=True)
101
+ type = Column(Enum(SettingType), nullable=False, index=True)
102
+ name = Column(String(255), nullable=False)
103
+ description = Column(Text, nullable=True)
104
+ category = Column(String(100), nullable=True, index=True)
105
+ ui_element = Column(String(50), default="text", nullable=False)
106
+ options = Column(JSON, nullable=True) # For select elements
107
+ min_value = Column(Float, nullable=True) # For numeric inputs
108
+ max_value = Column(Float, nullable=True) # For numeric inputs
109
+ step = Column(Float, nullable=True) # For sliders
110
+ visible = Column(Boolean, default=True, nullable=False)
111
+ editable = Column(Boolean, default=True, nullable=False)
112
+ created_at = Column(DateTime, server_default=func.now(), nullable=False)
113
+ updated_at = Column(
114
+ DateTime, server_default=func.now(), onupdate=func.now(), nullable=False
115
+ )
116
+
117
+ __table_args__ = (UniqueConstraint("key", name="uix_settings_key"),)
@@ -0,0 +1,107 @@
1
+ """
2
+ Schema upgrade script for Local Deep Research database.
3
+ Handles schema upgrades for existing ldr.db databases.
4
+ """
5
+
6
+ import logging
7
+ import os
8
+ import sqlite3
9
+ import sys
10
+
11
+ # Set up logging
12
+ logging.basicConfig(level=logging.INFO)
13
+ logger = logging.getLogger(__name__)
14
+
15
+ # Add the parent directory to sys.path to allow relative imports
16
+ sys.path.append(
17
+ os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
18
+ )
19
+
20
+ try:
21
+ from src.local_deep_research.web.models.database import DB_PATH
22
+ except ImportError:
23
+ # Fallback path if import fails
24
+ current_dir = os.path.dirname(os.path.abspath(__file__))
25
+ project_root = os.path.abspath(os.path.join(current_dir, "..", "..", "..", ".."))
26
+ DB_PATH = os.path.join(project_root, "src", "data", "ldr.db")
27
+
28
+
29
+ def check_table_exists(conn, table_name):
30
+ """
31
+ Check if a table exists in the database
32
+
33
+ Args:
34
+ conn: SQLite connection
35
+ table_name: Name of the table
36
+
37
+ Returns:
38
+ bool: True if table exists, False otherwise
39
+ """
40
+ cursor = conn.cursor()
41
+ cursor.execute(
42
+ "SELECT name FROM sqlite_master WHERE type='table' AND name=?", (table_name,)
43
+ )
44
+ return cursor.fetchone() is not None
45
+
46
+
47
+ def remove_research_log_table(conn):
48
+ """
49
+ Remove the redundant research_log table if it exists
50
+
51
+ Args:
52
+ conn: SQLite connection
53
+
54
+ Returns:
55
+ bool: True if operation was successful, False otherwise
56
+ """
57
+ try:
58
+ cursor = conn.cursor()
59
+
60
+ # Check if table exists
61
+ if check_table_exists(conn, "research_log"):
62
+ # For SQLite, DROP TABLE is the way to remove a table
63
+ cursor.execute("DROP TABLE research_log")
64
+ conn.commit()
65
+ logger.info("Successfully removed redundant 'research_log' table")
66
+ return True
67
+ else:
68
+ logger.info("Table 'research_log' does not exist, no action needed")
69
+ return True
70
+ except Exception as e:
71
+ logger.error(f"Error removing research_log table: {e}")
72
+ return False
73
+
74
+
75
+ def run_schema_upgrades():
76
+ """
77
+ Run all schema upgrade operations on the database
78
+
79
+ Returns:
80
+ bool: True if all upgrades successful, False otherwise
81
+ """
82
+ # Check if database exists
83
+ if not os.path.exists(DB_PATH):
84
+ logger.warning(f"Database not found at {DB_PATH}, skipping schema upgrades")
85
+ return False
86
+
87
+ logger.info(f"Running schema upgrades on {DB_PATH}")
88
+
89
+ try:
90
+ # Connect to the database
91
+ conn = sqlite3.connect(DB_PATH)
92
+
93
+ # 1. Remove the redundant research_log table
94
+ remove_research_log_table(conn)
95
+
96
+ # Close connection
97
+ conn.close()
98
+
99
+ logger.info("Schema upgrades completed successfully")
100
+ return True
101
+ except Exception as e:
102
+ logger.error(f"Error during schema upgrades: {e}")
103
+ return False
104
+
105
+
106
+ if __name__ == "__main__":
107
+ run_schema_upgrades()