local-deep-research 0.4.4__py3-none-any.whl → 0.5.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 +7 -0
- local_deep_research/__version__.py +1 -1
- local_deep_research/advanced_search_system/answer_decoding/__init__.py +5 -0
- local_deep_research/advanced_search_system/answer_decoding/browsecomp_answer_decoder.py +421 -0
- local_deep_research/advanced_search_system/candidate_exploration/README.md +219 -0
- local_deep_research/advanced_search_system/candidate_exploration/__init__.py +25 -0
- local_deep_research/advanced_search_system/candidate_exploration/adaptive_explorer.py +329 -0
- local_deep_research/advanced_search_system/candidate_exploration/base_explorer.py +341 -0
- local_deep_research/advanced_search_system/candidate_exploration/constraint_guided_explorer.py +436 -0
- local_deep_research/advanced_search_system/candidate_exploration/diversity_explorer.py +457 -0
- local_deep_research/advanced_search_system/candidate_exploration/parallel_explorer.py +250 -0
- local_deep_research/advanced_search_system/candidate_exploration/progressive_explorer.py +255 -0
- local_deep_research/advanced_search_system/candidates/__init__.py +5 -0
- local_deep_research/advanced_search_system/candidates/base_candidate.py +59 -0
- local_deep_research/advanced_search_system/constraint_checking/README.md +150 -0
- local_deep_research/advanced_search_system/constraint_checking/__init__.py +35 -0
- local_deep_research/advanced_search_system/constraint_checking/base_constraint_checker.py +122 -0
- local_deep_research/advanced_search_system/constraint_checking/constraint_checker.py +223 -0
- local_deep_research/advanced_search_system/constraint_checking/constraint_satisfaction_tracker.py +387 -0
- local_deep_research/advanced_search_system/constraint_checking/dual_confidence_checker.py +424 -0
- local_deep_research/advanced_search_system/constraint_checking/evidence_analyzer.py +174 -0
- local_deep_research/advanced_search_system/constraint_checking/intelligent_constraint_relaxer.py +503 -0
- local_deep_research/advanced_search_system/constraint_checking/rejection_engine.py +143 -0
- local_deep_research/advanced_search_system/constraint_checking/strict_checker.py +259 -0
- local_deep_research/advanced_search_system/constraint_checking/threshold_checker.py +213 -0
- local_deep_research/advanced_search_system/constraints/__init__.py +6 -0
- local_deep_research/advanced_search_system/constraints/base_constraint.py +58 -0
- local_deep_research/advanced_search_system/constraints/constraint_analyzer.py +143 -0
- local_deep_research/advanced_search_system/evidence/__init__.py +12 -0
- local_deep_research/advanced_search_system/evidence/base_evidence.py +57 -0
- local_deep_research/advanced_search_system/evidence/evaluator.py +159 -0
- local_deep_research/advanced_search_system/evidence/requirements.py +122 -0
- local_deep_research/advanced_search_system/filters/base_filter.py +3 -1
- local_deep_research/advanced_search_system/filters/cross_engine_filter.py +8 -2
- local_deep_research/advanced_search_system/filters/journal_reputation_filter.py +43 -29
- local_deep_research/advanced_search_system/findings/repository.py +54 -17
- local_deep_research/advanced_search_system/knowledge/standard_knowledge.py +3 -1
- local_deep_research/advanced_search_system/query_generation/adaptive_query_generator.py +405 -0
- local_deep_research/advanced_search_system/questions/__init__.py +16 -0
- local_deep_research/advanced_search_system/questions/atomic_fact_question.py +171 -0
- local_deep_research/advanced_search_system/questions/browsecomp_question.py +287 -0
- local_deep_research/advanced_search_system/questions/decomposition_question.py +13 -4
- local_deep_research/advanced_search_system/questions/entity_aware_question.py +184 -0
- local_deep_research/advanced_search_system/questions/standard_question.py +9 -3
- local_deep_research/advanced_search_system/search_optimization/cross_constraint_manager.py +624 -0
- local_deep_research/advanced_search_system/source_management/diversity_manager.py +613 -0
- local_deep_research/advanced_search_system/strategies/__init__.py +42 -0
- local_deep_research/advanced_search_system/strategies/adaptive_decomposition_strategy.py +564 -0
- local_deep_research/advanced_search_system/strategies/base_strategy.py +4 -4
- local_deep_research/advanced_search_system/strategies/browsecomp_entity_strategy.py +1031 -0
- local_deep_research/advanced_search_system/strategies/browsecomp_optimized_strategy.py +778 -0
- local_deep_research/advanced_search_system/strategies/concurrent_dual_confidence_strategy.py +446 -0
- local_deep_research/advanced_search_system/strategies/constrained_search_strategy.py +1348 -0
- local_deep_research/advanced_search_system/strategies/constraint_parallel_strategy.py +522 -0
- local_deep_research/advanced_search_system/strategies/direct_search_strategy.py +217 -0
- local_deep_research/advanced_search_system/strategies/dual_confidence_strategy.py +320 -0
- local_deep_research/advanced_search_system/strategies/dual_confidence_with_rejection.py +219 -0
- local_deep_research/advanced_search_system/strategies/early_stop_constrained_strategy.py +369 -0
- local_deep_research/advanced_search_system/strategies/entity_aware_source_strategy.py +140 -0
- local_deep_research/advanced_search_system/strategies/evidence_based_strategy.py +1248 -0
- local_deep_research/advanced_search_system/strategies/evidence_based_strategy_v2.py +1337 -0
- local_deep_research/advanced_search_system/strategies/focused_iteration_strategy.py +537 -0
- local_deep_research/advanced_search_system/strategies/improved_evidence_based_strategy.py +782 -0
- local_deep_research/advanced_search_system/strategies/iterative_reasoning_strategy.py +760 -0
- local_deep_research/advanced_search_system/strategies/iterdrag_strategy.py +55 -21
- local_deep_research/advanced_search_system/strategies/llm_driven_modular_strategy.py +865 -0
- local_deep_research/advanced_search_system/strategies/modular_strategy.py +1142 -0
- local_deep_research/advanced_search_system/strategies/parallel_constrained_strategy.py +506 -0
- local_deep_research/advanced_search_system/strategies/parallel_search_strategy.py +34 -16
- local_deep_research/advanced_search_system/strategies/rapid_search_strategy.py +29 -9
- local_deep_research/advanced_search_system/strategies/recursive_decomposition_strategy.py +492 -0
- local_deep_research/advanced_search_system/strategies/smart_decomposition_strategy.py +284 -0
- local_deep_research/advanced_search_system/strategies/smart_query_strategy.py +515 -0
- local_deep_research/advanced_search_system/strategies/source_based_strategy.py +48 -24
- local_deep_research/advanced_search_system/strategies/standard_strategy.py +34 -14
- local_deep_research/advanced_search_system/tools/base_tool.py +7 -2
- local_deep_research/api/benchmark_functions.py +6 -2
- local_deep_research/api/research_functions.py +10 -4
- local_deep_research/benchmarks/__init__.py +9 -7
- local_deep_research/benchmarks/benchmark_functions.py +6 -2
- local_deep_research/benchmarks/cli/benchmark_commands.py +27 -10
- local_deep_research/benchmarks/cli.py +38 -13
- local_deep_research/benchmarks/comparison/__init__.py +4 -2
- local_deep_research/benchmarks/comparison/evaluator.py +316 -239
- local_deep_research/benchmarks/datasets/__init__.py +1 -1
- local_deep_research/benchmarks/datasets/base.py +91 -72
- local_deep_research/benchmarks/datasets/browsecomp.py +54 -33
- local_deep_research/benchmarks/datasets/custom_dataset_template.py +19 -19
- local_deep_research/benchmarks/datasets/simpleqa.py +14 -14
- local_deep_research/benchmarks/datasets/utils.py +48 -29
- local_deep_research/benchmarks/datasets.py +4 -11
- local_deep_research/benchmarks/efficiency/__init__.py +8 -4
- local_deep_research/benchmarks/efficiency/resource_monitor.py +223 -171
- local_deep_research/benchmarks/efficiency/speed_profiler.py +62 -48
- local_deep_research/benchmarks/evaluators/browsecomp.py +3 -1
- local_deep_research/benchmarks/evaluators/composite.py +6 -2
- local_deep_research/benchmarks/evaluators/simpleqa.py +36 -13
- local_deep_research/benchmarks/graders.py +32 -10
- local_deep_research/benchmarks/metrics/README.md +1 -1
- local_deep_research/benchmarks/metrics/calculation.py +25 -10
- local_deep_research/benchmarks/metrics/reporting.py +7 -3
- local_deep_research/benchmarks/metrics/visualization.py +42 -23
- local_deep_research/benchmarks/metrics.py +1 -1
- local_deep_research/benchmarks/optimization/__init__.py +3 -1
- local_deep_research/benchmarks/optimization/api.py +7 -1
- local_deep_research/benchmarks/optimization/optuna_optimizer.py +75 -26
- local_deep_research/benchmarks/runners.py +48 -15
- local_deep_research/citation_handler.py +65 -92
- local_deep_research/citation_handlers/__init__.py +15 -0
- local_deep_research/citation_handlers/base_citation_handler.py +70 -0
- local_deep_research/citation_handlers/forced_answer_citation_handler.py +179 -0
- local_deep_research/citation_handlers/precision_extraction_handler.py +550 -0
- local_deep_research/citation_handlers/standard_citation_handler.py +80 -0
- local_deep_research/config/llm_config.py +271 -169
- local_deep_research/config/search_config.py +14 -5
- local_deep_research/defaults/__init__.py +0 -1
- local_deep_research/metrics/__init__.py +13 -0
- local_deep_research/metrics/database.py +58 -0
- local_deep_research/metrics/db_models.py +115 -0
- local_deep_research/metrics/migrate_add_provider_to_token_usage.py +148 -0
- local_deep_research/metrics/migrate_call_stack_tracking.py +105 -0
- local_deep_research/metrics/migrate_enhanced_tracking.py +75 -0
- local_deep_research/metrics/migrate_research_ratings.py +31 -0
- local_deep_research/metrics/models.py +61 -0
- local_deep_research/metrics/pricing/__init__.py +12 -0
- local_deep_research/metrics/pricing/cost_calculator.py +237 -0
- local_deep_research/metrics/pricing/pricing_cache.py +143 -0
- local_deep_research/metrics/pricing/pricing_fetcher.py +240 -0
- local_deep_research/metrics/query_utils.py +51 -0
- local_deep_research/metrics/search_tracker.py +380 -0
- local_deep_research/metrics/token_counter.py +1078 -0
- local_deep_research/migrate_db.py +3 -1
- local_deep_research/report_generator.py +22 -8
- local_deep_research/search_system.py +390 -9
- local_deep_research/test_migration.py +15 -5
- local_deep_research/utilities/db_utils.py +7 -4
- local_deep_research/utilities/es_utils.py +115 -104
- local_deep_research/utilities/llm_utils.py +15 -5
- local_deep_research/utilities/log_utils.py +151 -0
- local_deep_research/utilities/search_cache.py +387 -0
- local_deep_research/utilities/search_utilities.py +14 -6
- local_deep_research/utilities/threading_utils.py +92 -0
- local_deep_research/utilities/url_utils.py +6 -0
- local_deep_research/web/api.py +347 -0
- local_deep_research/web/app.py +13 -17
- local_deep_research/web/app_factory.py +71 -66
- local_deep_research/web/database/migrate_to_ldr_db.py +12 -4
- local_deep_research/web/database/migrations.py +20 -3
- local_deep_research/web/database/models.py +74 -25
- local_deep_research/web/database/schema_upgrade.py +49 -29
- local_deep_research/web/models/database.py +63 -83
- local_deep_research/web/routes/api_routes.py +56 -22
- local_deep_research/web/routes/benchmark_routes.py +4 -1
- local_deep_research/web/routes/globals.py +22 -0
- local_deep_research/web/routes/history_routes.py +71 -46
- local_deep_research/web/routes/metrics_routes.py +1155 -0
- local_deep_research/web/routes/research_routes.py +192 -54
- local_deep_research/web/routes/settings_routes.py +156 -55
- local_deep_research/web/services/research_service.py +412 -251
- local_deep_research/web/services/resource_service.py +36 -11
- local_deep_research/web/services/settings_manager.py +55 -17
- local_deep_research/web/services/settings_service.py +12 -4
- local_deep_research/web/services/socket_service.py +295 -188
- local_deep_research/web/static/css/custom_dropdown.css +180 -0
- local_deep_research/web/static/css/styles.css +39 -1
- local_deep_research/web/static/js/components/detail.js +633 -267
- local_deep_research/web/static/js/components/details.js +751 -0
- local_deep_research/web/static/js/components/fallback/formatting.js +11 -11
- local_deep_research/web/static/js/components/fallback/ui.js +23 -23
- local_deep_research/web/static/js/components/history.js +76 -76
- local_deep_research/web/static/js/components/logpanel.js +61 -13
- local_deep_research/web/static/js/components/progress.js +13 -2
- local_deep_research/web/static/js/components/research.js +99 -12
- local_deep_research/web/static/js/components/results.js +239 -106
- local_deep_research/web/static/js/main.js +40 -40
- local_deep_research/web/static/js/services/audio.js +1 -1
- local_deep_research/web/static/js/services/formatting.js +11 -11
- local_deep_research/web/static/js/services/keyboard.js +157 -0
- local_deep_research/web/static/js/services/pdf.js +80 -80
- local_deep_research/web/static/sounds/README.md +1 -1
- local_deep_research/web/templates/base.html +1 -0
- local_deep_research/web/templates/components/log_panel.html +7 -1
- local_deep_research/web/templates/components/mobile_nav.html +1 -1
- local_deep_research/web/templates/components/sidebar.html +3 -0
- local_deep_research/web/templates/pages/cost_analytics.html +1245 -0
- local_deep_research/web/templates/pages/details.html +325 -24
- local_deep_research/web/templates/pages/history.html +1 -1
- local_deep_research/web/templates/pages/metrics.html +1929 -0
- local_deep_research/web/templates/pages/progress.html +2 -2
- local_deep_research/web/templates/pages/research.html +53 -17
- local_deep_research/web/templates/pages/results.html +12 -1
- local_deep_research/web/templates/pages/star_reviews.html +803 -0
- local_deep_research/web/utils/formatters.py +9 -3
- local_deep_research/web_search_engines/default_search_engines.py +5 -3
- local_deep_research/web_search_engines/engines/full_search.py +8 -2
- local_deep_research/web_search_engines/engines/meta_search_engine.py +59 -20
- local_deep_research/web_search_engines/engines/search_engine_arxiv.py +19 -6
- local_deep_research/web_search_engines/engines/search_engine_brave.py +6 -2
- local_deep_research/web_search_engines/engines/search_engine_ddg.py +3 -1
- local_deep_research/web_search_engines/engines/search_engine_elasticsearch.py +81 -58
- local_deep_research/web_search_engines/engines/search_engine_github.py +46 -15
- local_deep_research/web_search_engines/engines/search_engine_google_pse.py +16 -6
- local_deep_research/web_search_engines/engines/search_engine_guardian.py +39 -15
- local_deep_research/web_search_engines/engines/search_engine_local.py +58 -25
- local_deep_research/web_search_engines/engines/search_engine_local_all.py +15 -5
- local_deep_research/web_search_engines/engines/search_engine_pubmed.py +63 -21
- local_deep_research/web_search_engines/engines/search_engine_searxng.py +37 -11
- local_deep_research/web_search_engines/engines/search_engine_semantic_scholar.py +27 -9
- local_deep_research/web_search_engines/engines/search_engine_serpapi.py +12 -4
- local_deep_research/web_search_engines/engines/search_engine_wayback.py +31 -10
- local_deep_research/web_search_engines/engines/search_engine_wikipedia.py +12 -3
- local_deep_research/web_search_engines/search_engine_base.py +83 -35
- local_deep_research/web_search_engines/search_engine_factory.py +25 -8
- local_deep_research/web_search_engines/search_engines_config.py +9 -3
- {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.2.dist-info}/METADATA +7 -1
- local_deep_research-0.5.2.dist-info/RECORD +265 -0
- local_deep_research-0.4.4.dist-info/RECORD +0 -177
- {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.2.dist-info}/WHEEL +0 -0
- {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.2.dist-info}/entry_points.txt +0 -0
- {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.2.dist-info}/licenses/LICENSE +0 -0
@@ -12,7 +12,7 @@
|
|
12
12
|
'page-history': ['history.js'],
|
13
13
|
'page-settings': ['settings.js']
|
14
14
|
};
|
15
|
-
|
15
|
+
|
16
16
|
// Core services to always load
|
17
17
|
const coreServices = [
|
18
18
|
'formatting.js',
|
@@ -21,53 +21,53 @@
|
|
21
21
|
'socket.js'
|
22
22
|
// 'audio.js' - Removed from here, loaded separately
|
23
23
|
];
|
24
|
-
|
24
|
+
|
25
25
|
// Optional services to load when needed
|
26
26
|
const optionalServices = {
|
27
27
|
'page-results': ['pdf.js'],
|
28
28
|
'page-detail': ['pdf.js']
|
29
29
|
};
|
30
|
-
|
30
|
+
|
31
31
|
/**
|
32
32
|
* Initialize the application
|
33
33
|
*/
|
34
34
|
function initializeApp() {
|
35
35
|
// Detect current page
|
36
36
|
const currentPage = detectCurrentPage();
|
37
|
-
|
37
|
+
|
38
38
|
if (!currentPage) {
|
39
39
|
console.error('Cannot detect current page type');
|
40
40
|
return;
|
41
41
|
}
|
42
|
-
|
42
|
+
|
43
43
|
console.log('Current page detected:', currentPage);
|
44
|
-
|
44
|
+
|
45
45
|
// IMPORTANT: Load audio.js first, before ANY other scripts
|
46
46
|
loadAudioServiceFirst(() => {
|
47
47
|
// Continue loading other scripts after audio service is loaded
|
48
48
|
console.log('Audio service script loaded, continuing with other scripts');
|
49
|
-
|
49
|
+
|
50
50
|
// Load UI and formatting utils
|
51
51
|
loadScripts('utils', coreServices.filter(s => s.includes('formatting') || s.includes('ui')));
|
52
|
-
|
52
|
+
|
53
53
|
// Then load the rest
|
54
54
|
loadScripts('services', coreServices.filter(s => s.includes('api') || s.includes('socket')));
|
55
|
-
|
55
|
+
|
56
56
|
// Load optional services for this page
|
57
57
|
if (optionalServices[currentPage]) {
|
58
58
|
loadScripts('services', optionalServices[currentPage]);
|
59
59
|
}
|
60
|
-
|
60
|
+
|
61
61
|
// Load components for this page AFTER all services
|
62
62
|
if (pageComponents[currentPage]) {
|
63
63
|
loadScripts('components', pageComponents[currentPage]);
|
64
64
|
}
|
65
|
-
|
65
|
+
|
66
66
|
// Initialize tooltips and other global UI elements
|
67
67
|
initializeGlobalUI();
|
68
68
|
});
|
69
69
|
}
|
70
|
-
|
70
|
+
|
71
71
|
/**
|
72
72
|
* Load audio service first and separately to ensure it's fully loaded before other scripts
|
73
73
|
* @param {Function} callback - Function to call after audio service is loaded
|
@@ -77,11 +77,11 @@
|
|
77
77
|
const audioScript = document.createElement('script');
|
78
78
|
audioScript.src = `/research/static/js/services/audio.js?t=${new Date().getTime()}`; // Add timestamp to avoid cache
|
79
79
|
audioScript.async = false;
|
80
|
-
|
80
|
+
|
81
81
|
// Set up callback for when script loads
|
82
82
|
audioScript.onload = function() {
|
83
83
|
console.log('Audio service script loaded successfully');
|
84
|
-
|
84
|
+
|
85
85
|
// Check if audio service is available in window object
|
86
86
|
setTimeout(() => {
|
87
87
|
if (window.audio) {
|
@@ -89,39 +89,39 @@
|
|
89
89
|
} else {
|
90
90
|
console.warn('Audio service not available in window object after script load');
|
91
91
|
}
|
92
|
-
|
92
|
+
|
93
93
|
// Continue regardless
|
94
94
|
callback();
|
95
95
|
}, 100); // Small delay to ensure script executes
|
96
96
|
};
|
97
|
-
|
97
|
+
|
98
98
|
// Error handling
|
99
99
|
audioScript.onerror = function() {
|
100
100
|
console.error('Failed to load audio service script');
|
101
101
|
// Continue with other scripts even if audio fails
|
102
102
|
callback();
|
103
103
|
};
|
104
|
-
|
104
|
+
|
105
105
|
// Add to document
|
106
106
|
document.body.appendChild(audioScript);
|
107
107
|
}
|
108
|
-
|
108
|
+
|
109
109
|
/**
|
110
110
|
* Detect the current page based on body class
|
111
111
|
* @returns {string|null} The page identifier or null if not found
|
112
112
|
*/
|
113
113
|
function detectCurrentPage() {
|
114
114
|
const bodyClasses = document.body.classList;
|
115
|
-
|
115
|
+
|
116
116
|
for (const pageId in pageComponents) {
|
117
117
|
if (bodyClasses.contains(pageId)) {
|
118
118
|
return pageId;
|
119
119
|
}
|
120
120
|
}
|
121
|
-
|
121
|
+
|
122
122
|
// Check URL patterns as fallback
|
123
123
|
const path = window.location.pathname;
|
124
|
-
|
124
|
+
|
125
125
|
if (path === '/' || path === '/index' || path === '/home' || path === '/research/') {
|
126
126
|
return 'page-home';
|
127
127
|
} else if (path.includes('/research/progress')) {
|
@@ -135,10 +135,10 @@
|
|
135
135
|
} else if (path.includes('/research/settings')) {
|
136
136
|
return 'page-settings';
|
137
137
|
}
|
138
|
-
|
138
|
+
|
139
139
|
return null;
|
140
140
|
}
|
141
|
-
|
141
|
+
|
142
142
|
/**
|
143
143
|
* Load scripts dynamically
|
144
144
|
* @param {string} folder - The folder containing the scripts
|
@@ -146,7 +146,7 @@
|
|
146
146
|
*/
|
147
147
|
function loadScripts(folder, scripts) {
|
148
148
|
if (!scripts || !scripts.length) return;
|
149
|
-
|
149
|
+
|
150
150
|
scripts.forEach(script => {
|
151
151
|
const scriptElement = document.createElement('script');
|
152
152
|
scriptElement.src = `/research/static/js/${folder}/${script}`;
|
@@ -154,7 +154,7 @@
|
|
154
154
|
document.body.appendChild(scriptElement);
|
155
155
|
});
|
156
156
|
}
|
157
|
-
|
157
|
+
|
158
158
|
/**
|
159
159
|
* Initialize global UI elements
|
160
160
|
*/
|
@@ -164,7 +164,7 @@
|
|
164
164
|
// Only ask for permission when user interacts with the page
|
165
165
|
document.addEventListener('click', requestNotificationPermission, { once: true });
|
166
166
|
}
|
167
|
-
|
167
|
+
|
168
168
|
// Initialize theme toggle
|
169
169
|
const themeToggle = document.getElementById('theme-toggle');
|
170
170
|
if (themeToggle) {
|
@@ -172,42 +172,42 @@
|
|
172
172
|
const savedTheme = localStorage.getItem('theme');
|
173
173
|
const systemDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
174
174
|
const isDarkMode = savedTheme === 'dark' || (savedTheme === null && systemDarkMode);
|
175
|
-
|
175
|
+
|
176
176
|
// Set initial theme
|
177
177
|
document.documentElement.setAttribute('data-theme', isDarkMode ? 'dark' : 'light');
|
178
|
-
themeToggle.innerHTML = isDarkMode ?
|
179
|
-
'<i class="fas fa-sun"></i>' :
|
178
|
+
themeToggle.innerHTML = isDarkMode ?
|
179
|
+
'<i class="fas fa-sun"></i>' :
|
180
180
|
'<i class="fas fa-moon"></i>';
|
181
|
-
|
181
|
+
|
182
182
|
// Listen for theme toggle click
|
183
183
|
themeToggle.addEventListener('click', function() {
|
184
184
|
const currentTheme = document.documentElement.getAttribute('data-theme');
|
185
185
|
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
186
|
-
|
186
|
+
|
187
187
|
document.documentElement.setAttribute('data-theme', newTheme);
|
188
188
|
localStorage.setItem('theme', newTheme);
|
189
|
-
|
190
|
-
themeToggle.innerHTML = newTheme === 'dark' ?
|
191
|
-
'<i class="fas fa-sun"></i>' :
|
189
|
+
|
190
|
+
themeToggle.innerHTML = newTheme === 'dark' ?
|
191
|
+
'<i class="fas fa-sun"></i>' :
|
192
192
|
'<i class="fas fa-moon"></i>';
|
193
193
|
});
|
194
194
|
}
|
195
|
-
|
195
|
+
|
196
196
|
// Initialize mobile menu toggle
|
197
197
|
const mobileMenuToggle = document.getElementById('mobile-menu-toggle');
|
198
198
|
const navMenu = document.getElementById('main-nav');
|
199
|
-
|
199
|
+
|
200
200
|
if (mobileMenuToggle && navMenu) {
|
201
201
|
mobileMenuToggle.addEventListener('click', function() {
|
202
202
|
navMenu.classList.toggle('open');
|
203
203
|
mobileMenuToggle.setAttribute(
|
204
|
-
'aria-expanded',
|
204
|
+
'aria-expanded',
|
205
205
|
navMenu.classList.contains('open') ? 'true' : 'false'
|
206
206
|
);
|
207
207
|
});
|
208
208
|
}
|
209
209
|
}
|
210
|
-
|
210
|
+
|
211
211
|
/**
|
212
212
|
* Request notification permission
|
213
213
|
*/
|
@@ -216,11 +216,11 @@
|
|
216
216
|
Notification.requestPermission();
|
217
217
|
}
|
218
218
|
}
|
219
|
-
|
219
|
+
|
220
220
|
// Initialize when DOM is ready
|
221
221
|
if (document.readyState === 'loading') {
|
222
222
|
document.addEventListener('DOMContentLoaded', initializeApp);
|
223
223
|
} else {
|
224
224
|
initializeApp();
|
225
225
|
}
|
226
|
-
})();
|
226
|
+
})();
|
@@ -38,31 +38,31 @@ function formatMode(mode) {
|
|
38
38
|
*/
|
39
39
|
function formatDate(date, duration = null) {
|
40
40
|
if (!date) return 'Unknown';
|
41
|
-
|
41
|
+
|
42
42
|
try {
|
43
43
|
const dateObj = new Date(date);
|
44
|
-
const options = {
|
45
|
-
year: 'numeric',
|
46
|
-
month: 'short',
|
44
|
+
const options = {
|
45
|
+
year: 'numeric',
|
46
|
+
month: 'short',
|
47
47
|
day: 'numeric',
|
48
48
|
hour: '2-digit',
|
49
49
|
minute: '2-digit'
|
50
50
|
};
|
51
|
-
|
51
|
+
|
52
52
|
let formattedDate = dateObj.toLocaleDateString('en-US', options);
|
53
|
-
|
53
|
+
|
54
54
|
if (duration) {
|
55
55
|
// Format the duration
|
56
56
|
const minutes = Math.floor(duration / 60);
|
57
57
|
const seconds = duration % 60;
|
58
|
-
|
58
|
+
|
59
59
|
if (minutes > 0) {
|
60
60
|
formattedDate += ` (${minutes}m ${seconds}s)`;
|
61
61
|
} else {
|
62
62
|
formattedDate += ` (${seconds}s)`;
|
63
63
|
}
|
64
64
|
}
|
65
|
-
|
65
|
+
|
66
66
|
return formattedDate;
|
67
67
|
} catch (e) {
|
68
68
|
console.error('Error formatting date:', e);
|
@@ -77,10 +77,10 @@ function formatDate(date, duration = null) {
|
|
77
77
|
*/
|
78
78
|
function formatDuration(seconds) {
|
79
79
|
if (!seconds || isNaN(seconds)) return 'Unknown';
|
80
|
-
|
80
|
+
|
81
81
|
const minutes = Math.floor(seconds / 60);
|
82
82
|
const remainingSeconds = Math.floor(seconds % 60);
|
83
|
-
|
83
|
+
|
84
84
|
if (minutes === 0) {
|
85
85
|
return `${remainingSeconds}s`;
|
86
86
|
} else {
|
@@ -116,4 +116,4 @@ window.formatting = {
|
|
116
116
|
formatDuration,
|
117
117
|
formatNumber,
|
118
118
|
capitalizeFirstLetter
|
119
|
-
};
|
119
|
+
};
|
@@ -0,0 +1,157 @@
|
|
1
|
+
/**
|
2
|
+
* Global Keyboard Shortcuts Service
|
3
|
+
* Provides consistent keyboard shortcuts across the application
|
4
|
+
*/
|
5
|
+
(function() {
|
6
|
+
'use strict';
|
7
|
+
|
8
|
+
// Keyboard shortcut registry - simplified to just the essential ones
|
9
|
+
const shortcuts = {
|
10
|
+
'newSearch': {
|
11
|
+
keys: ['ctrl+enter', 'cmd+enter'],
|
12
|
+
description: 'Return to main search',
|
13
|
+
handler: () => {
|
14
|
+
// Always navigate to main research page
|
15
|
+
window.location.href = '/';
|
16
|
+
}
|
17
|
+
}
|
18
|
+
};
|
19
|
+
|
20
|
+
/**
|
21
|
+
* Check if a keyboard event matches a shortcut pattern
|
22
|
+
*/
|
23
|
+
function matchesShortcut(event, pattern) {
|
24
|
+
const parts = pattern.toLowerCase().split('+');
|
25
|
+
const key = parts[parts.length - 1];
|
26
|
+
|
27
|
+
// Check modifiers
|
28
|
+
const requiresCtrl = parts.includes('ctrl');
|
29
|
+
const requiresCmd = parts.includes('cmd');
|
30
|
+
const requiresShift = parts.includes('shift');
|
31
|
+
const requiresAlt = parts.includes('alt');
|
32
|
+
|
33
|
+
// For single key shortcuts, ensure NO modifiers are pressed
|
34
|
+
if (parts.length === 1) {
|
35
|
+
if (event.ctrlKey || event.metaKey || event.shiftKey || event.altKey) {
|
36
|
+
return false;
|
37
|
+
}
|
38
|
+
}
|
39
|
+
|
40
|
+
// Match key
|
41
|
+
const eventKey = event.key.toLowerCase();
|
42
|
+
if (eventKey !== key && event.code.toLowerCase() !== `key${key}`) {
|
43
|
+
// Special handling for special keys
|
44
|
+
if (key === '/' && eventKey !== '/') return false;
|
45
|
+
else if (key === ',' && eventKey !== ',') return false;
|
46
|
+
else if (key !== '/' && key !== ',' && eventKey !== key) return false;
|
47
|
+
}
|
48
|
+
|
49
|
+
// Match modifiers (only if required)
|
50
|
+
if (requiresCtrl && !event.ctrlKey) return false;
|
51
|
+
if (requiresCmd && !event.metaKey) return false;
|
52
|
+
if (requiresShift && !event.shiftKey) return false;
|
53
|
+
if (requiresAlt && !event.altKey) return false;
|
54
|
+
|
55
|
+
return true;
|
56
|
+
}
|
57
|
+
|
58
|
+
/**
|
59
|
+
* Initialize keyboard shortcuts
|
60
|
+
*/
|
61
|
+
function initializeKeyboardShortcuts() {
|
62
|
+
console.log('Keyboard shortcuts initialized');
|
63
|
+
|
64
|
+
document.addEventListener('keydown', function(event) {
|
65
|
+
// Skip if user is typing in an input field
|
66
|
+
const activeElement = document.activeElement;
|
67
|
+
const isTyping = activeElement && (
|
68
|
+
activeElement.tagName === 'INPUT' ||
|
69
|
+
activeElement.tagName === 'TEXTAREA' ||
|
70
|
+
activeElement.contentEditable === 'true'
|
71
|
+
);
|
72
|
+
|
73
|
+
// Allow Escape even when typing
|
74
|
+
if (isTyping && event.key !== 'Escape') {
|
75
|
+
return;
|
76
|
+
}
|
77
|
+
|
78
|
+
// Debug log
|
79
|
+
if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
|
80
|
+
console.log('Key pressed:', event.key, 'Code:', event.code);
|
81
|
+
}
|
82
|
+
|
83
|
+
// Check each shortcut
|
84
|
+
for (const [name, shortcut] of Object.entries(shortcuts)) {
|
85
|
+
for (const pattern of shortcut.keys) {
|
86
|
+
if (matchesShortcut(event, pattern)) {
|
87
|
+
console.log('Shortcut matched:', name, pattern);
|
88
|
+
event.preventDefault();
|
89
|
+
shortcut.handler(event);
|
90
|
+
return;
|
91
|
+
}
|
92
|
+
}
|
93
|
+
}
|
94
|
+
});
|
95
|
+
|
96
|
+
// Add help text to footer if on main pages
|
97
|
+
addKeyboardHints();
|
98
|
+
}
|
99
|
+
|
100
|
+
/**
|
101
|
+
* Add subtle keyboard hints to the UI
|
102
|
+
*/
|
103
|
+
function addKeyboardHints() {
|
104
|
+
// Keyboard hints disabled
|
105
|
+
}
|
106
|
+
|
107
|
+
/**
|
108
|
+
* Get list of available shortcuts for current page
|
109
|
+
*/
|
110
|
+
function getAvailableShortcuts() {
|
111
|
+
const currentPath = window.location.pathname;
|
112
|
+
const allShortcuts = { ...shortcuts };
|
113
|
+
|
114
|
+
// Add page-specific shortcuts
|
115
|
+
if (currentPath.includes('/research/progress/')) {
|
116
|
+
allShortcuts.viewResults = {
|
117
|
+
keys: ['enter'],
|
118
|
+
description: 'View results (when complete)',
|
119
|
+
handler: () => {
|
120
|
+
const viewBtn = document.getElementById('view-results-btn');
|
121
|
+
if (viewBtn && viewBtn.style.display !== 'none') {
|
122
|
+
window.location.href = viewBtn.href;
|
123
|
+
}
|
124
|
+
}
|
125
|
+
};
|
126
|
+
}
|
127
|
+
|
128
|
+
if (currentPath.includes('/research/results/')) {
|
129
|
+
allShortcuts.escape = {
|
130
|
+
keys: ['escape'],
|
131
|
+
description: 'Back to new search',
|
132
|
+
handler: () => window.location.href = '/'
|
133
|
+
};
|
134
|
+
}
|
135
|
+
|
136
|
+
return allShortcuts;
|
137
|
+
}
|
138
|
+
|
139
|
+
// Initialize keyboard shortcuts on all pages
|
140
|
+
if (document.readyState === 'loading') {
|
141
|
+
document.addEventListener('DOMContentLoaded', initializeKeyboardShortcuts);
|
142
|
+
} else {
|
143
|
+
initializeKeyboardShortcuts();
|
144
|
+
}
|
145
|
+
|
146
|
+
// Expose API for other components
|
147
|
+
window.KeyboardService = {
|
148
|
+
shortcuts: getAvailableShortcuts,
|
149
|
+
addShortcut: (name, config) => {
|
150
|
+
shortcuts[name] = config;
|
151
|
+
},
|
152
|
+
removeShortcut: (name) => {
|
153
|
+
delete shortcuts[name];
|
154
|
+
}
|
155
|
+
};
|
156
|
+
|
157
|
+
})();
|