local-deep-research 0.4.3__py3-none-any.whl → 0.5.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.
- 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/defaults/default_settings.json +35 -35
- 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 +5 -3
- local_deep_research/web/database/models.py +51 -2
- local_deep_research/web/database/schema_upgrade.py +49 -29
- local_deep_research/web/models/database.py +51 -61
- 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 +227 -41
- local_deep_research/web/routes/settings_routes.py +156 -55
- local_deep_research/web/services/research_service.py +310 -103
- local_deep_research/web/services/resource_service.py +36 -11
- local_deep_research/web/services/settings_manager.py +58 -18
- 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/components/settings.js +70 -47
- 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.3.dist-info → local_deep_research-0.5.0.dist-info}/METADATA +8 -2
- local_deep_research-0.5.0.dist-info/RECORD +265 -0
- local_deep_research-0.4.3.dist-info/RECORD +0 -177
- {local_deep_research-0.4.3.dist-info → local_deep_research-0.5.0.dist-info}/WHEEL +0 -0
- {local_deep_research-0.4.3.dist-info → local_deep_research-0.5.0.dist-info}/entry_points.txt +0 -0
- {local_deep_research-0.4.3.dist-info → local_deep_research-0.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -395,16 +395,28 @@
|
|
395
395
|
shouldSaveImmediately = true;
|
396
396
|
}
|
397
397
|
}
|
398
|
-
|
399
|
-
|
398
|
+
// Handle range/slider (save on change/input or blur)
|
399
|
+
else if (input.type === 'range') {
|
400
400
|
value = input.value;
|
401
401
|
if (eventType === 'change' || eventType === 'input' || eventType === 'blur') {
|
402
402
|
shouldSaveImmediately = true;
|
403
403
|
}
|
404
|
-
|
404
|
+
}
|
405
405
|
// Handle other inputs (text, number, textarea) - Save on Enter or Blur
|
406
406
|
else {
|
407
407
|
value = input.value;
|
408
|
+
|
409
|
+
// Handle JSON.
|
410
|
+
if (input.classList.contains('json-content')) {
|
411
|
+
try {
|
412
|
+
// Validate
|
413
|
+
value = JSON.parse(input.value);
|
414
|
+
} catch (e) {
|
415
|
+
markInvalidInput(input, 'Invalid JSON');
|
416
|
+
return;
|
417
|
+
}
|
418
|
+
}
|
419
|
+
|
408
420
|
// Basic validation for number
|
409
421
|
if (input.type === 'number') {
|
410
422
|
try {
|
@@ -1429,31 +1441,32 @@
|
|
1429
1441
|
// Generate the appropriate input element based on UI element type
|
1430
1442
|
switch(setting.ui_element) {
|
1431
1443
|
case 'textarea':
|
1432
|
-
|
1433
|
-
|
1434
|
-
|
1444
|
+
inputElement = `
|
1445
|
+
<textarea id="${settingId}" name="${setting.key}"
|
1446
|
+
class="settings-textarea"
|
1447
|
+
${!setting.editable ? 'disabled' : ''}
|
1448
|
+
>${setting.value !== null ? setting.value : ''}</textarea>
|
1449
|
+
`;
|
1450
|
+
break;
|
1451
|
+
|
1452
|
+
case 'json':
|
1453
|
+
let jsonClass = ' json-content';
|
1435
1454
|
|
1436
|
-
|
1437
|
-
|
1438
|
-
|
1439
|
-
|
1455
|
+
// Try to format the JSON for better display
|
1456
|
+
try {
|
1457
|
+
setting.value = JSON.stringify(JSON.parse(setting.value), null, 2);
|
1458
|
+
} catch (e) {
|
1459
|
+
// If parsing fails, keep the original value
|
1460
|
+
console.log('Error formatting JSON:', e);
|
1461
|
+
}
|
1440
1462
|
|
1441
|
-
|
1463
|
+
// If it's an object (not an array), render individual controls
|
1464
|
+
if (setting.value.startsWith('{')) {
|
1442
1465
|
try {
|
1443
|
-
|
1466
|
+
const jsonObj = JSON.parse(setting.value);
|
1467
|
+
return renderExpandedJsonControls(setting, settingId, jsonObj);
|
1444
1468
|
} catch (e) {
|
1445
|
-
|
1446
|
-
console.log('Error formatting JSON:', e);
|
1447
|
-
}
|
1448
|
-
|
1449
|
-
// If it's an object (not an array), render individual controls
|
1450
|
-
if (setting.value.startsWith('{')) {
|
1451
|
-
try {
|
1452
|
-
const jsonObj = JSON.parse(setting.value);
|
1453
|
-
return renderExpandedJsonControls(setting, settingId, jsonObj);
|
1454
|
-
} catch (e) {
|
1455
|
-
console.log('Error parsing JSON for controls:', e);
|
1456
|
-
}
|
1469
|
+
console.log('Error parsing JSON for controls:', e);
|
1457
1470
|
}
|
1458
1471
|
}
|
1459
1472
|
|
@@ -1581,26 +1594,15 @@
|
|
1581
1594
|
|
1582
1595
|
default:
|
1583
1596
|
// Handle llm.model here explicitly if not handled by ui_element
|
1584
|
-
|
1585
|
-
|
1586
|
-
|
1587
|
-
|
1588
|
-
|
1589
|
-
|
1590
|
-
|
1591
|
-
|
1592
|
-
|
1593
|
-
} else {
|
1594
|
-
// Default to text input
|
1595
|
-
inputElement = `
|
1596
|
-
<input type="${setting.ui_element === 'password' ? 'password' : 'text'}"
|
1597
|
-
id="${settingId}" name="${setting.key}"
|
1598
|
-
class="settings-input form-control"
|
1599
|
-
value="${setting.value !== null ? setting.value : ''}"
|
1600
|
-
${!setting.editable ? 'disabled' : ''}
|
1601
|
-
>
|
1602
|
-
`;
|
1603
|
-
}
|
1597
|
+
// Default to text input
|
1598
|
+
inputElement = `
|
1599
|
+
<input type="${setting.ui_element === 'password' ? 'password' : 'text'}"
|
1600
|
+
id="${settingId}" name="${setting.key}"
|
1601
|
+
class="settings-input form-control"
|
1602
|
+
value="${setting.value !== null ? setting.value : ''}"
|
1603
|
+
${!setting.editable ? 'disabled' : ''}
|
1604
|
+
>
|
1605
|
+
`;
|
1604
1606
|
break;
|
1605
1607
|
}
|
1606
1608
|
|
@@ -1945,6 +1947,22 @@
|
|
1945
1947
|
}, 1500);
|
1946
1948
|
}
|
1947
1949
|
|
1950
|
+
/**
|
1951
|
+
* Validates user-specified JSON data and shows and error if it is not
|
1952
|
+
* valid JSON.
|
1953
|
+
* @param content The content to validate.
|
1954
|
+
* @return True if the content is valid.
|
1955
|
+
*/
|
1956
|
+
function validateJsonContent(content) {
|
1957
|
+
try {
|
1958
|
+
JSON.parse(content);
|
1959
|
+
return true;
|
1960
|
+
} catch (e) {
|
1961
|
+
showMessage('Setting value must be valid JSON.', 'error', 5000);
|
1962
|
+
return false;
|
1963
|
+
}
|
1964
|
+
}
|
1965
|
+
|
1948
1966
|
/**
|
1949
1967
|
* Submit settings data to the API
|
1950
1968
|
* @param {Object} formData - The settings to save
|
@@ -1962,6 +1980,11 @@
|
|
1962
1980
|
} else if (sourceElement.classList.contains('json-property-control')) {
|
1963
1981
|
// For JSON property controls, use the property item
|
1964
1982
|
loadingContainer = sourceElement.closest('.json-property-item') || sourceElement;
|
1983
|
+
} else if (sourceElement.classList.contains('json-content')) {
|
1984
|
+
// For JSON content, validate it before saving.
|
1985
|
+
if (!validateJsonContent(sourceElement.value)) {
|
1986
|
+
return;
|
1987
|
+
}
|
1965
1988
|
} else {
|
1966
1989
|
// For other inputs, use the form-group or settings-item
|
1967
1990
|
loadingContainer = sourceElement.closest('.form-group') ||
|
@@ -3688,16 +3711,16 @@
|
|
3688
3711
|
|
3689
3712
|
return false;
|
3690
3713
|
}
|
3691
|
-
|
3714
|
+
|
3692
3715
|
if (providerUpper === 'OPENAI_ENDPOINT') {
|
3693
3716
|
if (model.provider && model.provider.toUpperCase() === 'OPENAI_ENDPOINT') {
|
3694
3717
|
return true;
|
3695
3718
|
}
|
3696
|
-
|
3719
|
+
|
3697
3720
|
if (model.label && model.label.toLowerCase().includes('custom')) {
|
3698
3721
|
return true;
|
3699
3722
|
}
|
3700
|
-
|
3723
|
+
|
3701
3724
|
return false;
|
3702
3725
|
}
|
3703
3726
|
|
@@ -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
|
+
})();
|