local-deep-research 0.4.4__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.
Files changed (220) hide show
  1. local_deep_research/__init__.py +7 -0
  2. local_deep_research/__version__.py +1 -1
  3. local_deep_research/advanced_search_system/answer_decoding/__init__.py +5 -0
  4. local_deep_research/advanced_search_system/answer_decoding/browsecomp_answer_decoder.py +421 -0
  5. local_deep_research/advanced_search_system/candidate_exploration/README.md +219 -0
  6. local_deep_research/advanced_search_system/candidate_exploration/__init__.py +25 -0
  7. local_deep_research/advanced_search_system/candidate_exploration/adaptive_explorer.py +329 -0
  8. local_deep_research/advanced_search_system/candidate_exploration/base_explorer.py +341 -0
  9. local_deep_research/advanced_search_system/candidate_exploration/constraint_guided_explorer.py +436 -0
  10. local_deep_research/advanced_search_system/candidate_exploration/diversity_explorer.py +457 -0
  11. local_deep_research/advanced_search_system/candidate_exploration/parallel_explorer.py +250 -0
  12. local_deep_research/advanced_search_system/candidate_exploration/progressive_explorer.py +255 -0
  13. local_deep_research/advanced_search_system/candidates/__init__.py +5 -0
  14. local_deep_research/advanced_search_system/candidates/base_candidate.py +59 -0
  15. local_deep_research/advanced_search_system/constraint_checking/README.md +150 -0
  16. local_deep_research/advanced_search_system/constraint_checking/__init__.py +35 -0
  17. local_deep_research/advanced_search_system/constraint_checking/base_constraint_checker.py +122 -0
  18. local_deep_research/advanced_search_system/constraint_checking/constraint_checker.py +223 -0
  19. local_deep_research/advanced_search_system/constraint_checking/constraint_satisfaction_tracker.py +387 -0
  20. local_deep_research/advanced_search_system/constraint_checking/dual_confidence_checker.py +424 -0
  21. local_deep_research/advanced_search_system/constraint_checking/evidence_analyzer.py +174 -0
  22. local_deep_research/advanced_search_system/constraint_checking/intelligent_constraint_relaxer.py +503 -0
  23. local_deep_research/advanced_search_system/constraint_checking/rejection_engine.py +143 -0
  24. local_deep_research/advanced_search_system/constraint_checking/strict_checker.py +259 -0
  25. local_deep_research/advanced_search_system/constraint_checking/threshold_checker.py +213 -0
  26. local_deep_research/advanced_search_system/constraints/__init__.py +6 -0
  27. local_deep_research/advanced_search_system/constraints/base_constraint.py +58 -0
  28. local_deep_research/advanced_search_system/constraints/constraint_analyzer.py +143 -0
  29. local_deep_research/advanced_search_system/evidence/__init__.py +12 -0
  30. local_deep_research/advanced_search_system/evidence/base_evidence.py +57 -0
  31. local_deep_research/advanced_search_system/evidence/evaluator.py +159 -0
  32. local_deep_research/advanced_search_system/evidence/requirements.py +122 -0
  33. local_deep_research/advanced_search_system/filters/base_filter.py +3 -1
  34. local_deep_research/advanced_search_system/filters/cross_engine_filter.py +8 -2
  35. local_deep_research/advanced_search_system/filters/journal_reputation_filter.py +43 -29
  36. local_deep_research/advanced_search_system/findings/repository.py +54 -17
  37. local_deep_research/advanced_search_system/knowledge/standard_knowledge.py +3 -1
  38. local_deep_research/advanced_search_system/query_generation/adaptive_query_generator.py +405 -0
  39. local_deep_research/advanced_search_system/questions/__init__.py +16 -0
  40. local_deep_research/advanced_search_system/questions/atomic_fact_question.py +171 -0
  41. local_deep_research/advanced_search_system/questions/browsecomp_question.py +287 -0
  42. local_deep_research/advanced_search_system/questions/decomposition_question.py +13 -4
  43. local_deep_research/advanced_search_system/questions/entity_aware_question.py +184 -0
  44. local_deep_research/advanced_search_system/questions/standard_question.py +9 -3
  45. local_deep_research/advanced_search_system/search_optimization/cross_constraint_manager.py +624 -0
  46. local_deep_research/advanced_search_system/source_management/diversity_manager.py +613 -0
  47. local_deep_research/advanced_search_system/strategies/__init__.py +42 -0
  48. local_deep_research/advanced_search_system/strategies/adaptive_decomposition_strategy.py +564 -0
  49. local_deep_research/advanced_search_system/strategies/base_strategy.py +4 -4
  50. local_deep_research/advanced_search_system/strategies/browsecomp_entity_strategy.py +1031 -0
  51. local_deep_research/advanced_search_system/strategies/browsecomp_optimized_strategy.py +778 -0
  52. local_deep_research/advanced_search_system/strategies/concurrent_dual_confidence_strategy.py +446 -0
  53. local_deep_research/advanced_search_system/strategies/constrained_search_strategy.py +1348 -0
  54. local_deep_research/advanced_search_system/strategies/constraint_parallel_strategy.py +522 -0
  55. local_deep_research/advanced_search_system/strategies/direct_search_strategy.py +217 -0
  56. local_deep_research/advanced_search_system/strategies/dual_confidence_strategy.py +320 -0
  57. local_deep_research/advanced_search_system/strategies/dual_confidence_with_rejection.py +219 -0
  58. local_deep_research/advanced_search_system/strategies/early_stop_constrained_strategy.py +369 -0
  59. local_deep_research/advanced_search_system/strategies/entity_aware_source_strategy.py +140 -0
  60. local_deep_research/advanced_search_system/strategies/evidence_based_strategy.py +1248 -0
  61. local_deep_research/advanced_search_system/strategies/evidence_based_strategy_v2.py +1337 -0
  62. local_deep_research/advanced_search_system/strategies/focused_iteration_strategy.py +537 -0
  63. local_deep_research/advanced_search_system/strategies/improved_evidence_based_strategy.py +782 -0
  64. local_deep_research/advanced_search_system/strategies/iterative_reasoning_strategy.py +760 -0
  65. local_deep_research/advanced_search_system/strategies/iterdrag_strategy.py +55 -21
  66. local_deep_research/advanced_search_system/strategies/llm_driven_modular_strategy.py +865 -0
  67. local_deep_research/advanced_search_system/strategies/modular_strategy.py +1142 -0
  68. local_deep_research/advanced_search_system/strategies/parallel_constrained_strategy.py +506 -0
  69. local_deep_research/advanced_search_system/strategies/parallel_search_strategy.py +34 -16
  70. local_deep_research/advanced_search_system/strategies/rapid_search_strategy.py +29 -9
  71. local_deep_research/advanced_search_system/strategies/recursive_decomposition_strategy.py +492 -0
  72. local_deep_research/advanced_search_system/strategies/smart_decomposition_strategy.py +284 -0
  73. local_deep_research/advanced_search_system/strategies/smart_query_strategy.py +515 -0
  74. local_deep_research/advanced_search_system/strategies/source_based_strategy.py +48 -24
  75. local_deep_research/advanced_search_system/strategies/standard_strategy.py +34 -14
  76. local_deep_research/advanced_search_system/tools/base_tool.py +7 -2
  77. local_deep_research/api/benchmark_functions.py +6 -2
  78. local_deep_research/api/research_functions.py +10 -4
  79. local_deep_research/benchmarks/__init__.py +9 -7
  80. local_deep_research/benchmarks/benchmark_functions.py +6 -2
  81. local_deep_research/benchmarks/cli/benchmark_commands.py +27 -10
  82. local_deep_research/benchmarks/cli.py +38 -13
  83. local_deep_research/benchmarks/comparison/__init__.py +4 -2
  84. local_deep_research/benchmarks/comparison/evaluator.py +316 -239
  85. local_deep_research/benchmarks/datasets/__init__.py +1 -1
  86. local_deep_research/benchmarks/datasets/base.py +91 -72
  87. local_deep_research/benchmarks/datasets/browsecomp.py +54 -33
  88. local_deep_research/benchmarks/datasets/custom_dataset_template.py +19 -19
  89. local_deep_research/benchmarks/datasets/simpleqa.py +14 -14
  90. local_deep_research/benchmarks/datasets/utils.py +48 -29
  91. local_deep_research/benchmarks/datasets.py +4 -11
  92. local_deep_research/benchmarks/efficiency/__init__.py +8 -4
  93. local_deep_research/benchmarks/efficiency/resource_monitor.py +223 -171
  94. local_deep_research/benchmarks/efficiency/speed_profiler.py +62 -48
  95. local_deep_research/benchmarks/evaluators/browsecomp.py +3 -1
  96. local_deep_research/benchmarks/evaluators/composite.py +6 -2
  97. local_deep_research/benchmarks/evaluators/simpleqa.py +36 -13
  98. local_deep_research/benchmarks/graders.py +32 -10
  99. local_deep_research/benchmarks/metrics/README.md +1 -1
  100. local_deep_research/benchmarks/metrics/calculation.py +25 -10
  101. local_deep_research/benchmarks/metrics/reporting.py +7 -3
  102. local_deep_research/benchmarks/metrics/visualization.py +42 -23
  103. local_deep_research/benchmarks/metrics.py +1 -1
  104. local_deep_research/benchmarks/optimization/__init__.py +3 -1
  105. local_deep_research/benchmarks/optimization/api.py +7 -1
  106. local_deep_research/benchmarks/optimization/optuna_optimizer.py +75 -26
  107. local_deep_research/benchmarks/runners.py +48 -15
  108. local_deep_research/citation_handler.py +65 -92
  109. local_deep_research/citation_handlers/__init__.py +15 -0
  110. local_deep_research/citation_handlers/base_citation_handler.py +70 -0
  111. local_deep_research/citation_handlers/forced_answer_citation_handler.py +179 -0
  112. local_deep_research/citation_handlers/precision_extraction_handler.py +550 -0
  113. local_deep_research/citation_handlers/standard_citation_handler.py +80 -0
  114. local_deep_research/config/llm_config.py +271 -169
  115. local_deep_research/config/search_config.py +14 -5
  116. local_deep_research/defaults/__init__.py +0 -1
  117. local_deep_research/metrics/__init__.py +13 -0
  118. local_deep_research/metrics/database.py +58 -0
  119. local_deep_research/metrics/db_models.py +115 -0
  120. local_deep_research/metrics/migrate_add_provider_to_token_usage.py +148 -0
  121. local_deep_research/metrics/migrate_call_stack_tracking.py +105 -0
  122. local_deep_research/metrics/migrate_enhanced_tracking.py +75 -0
  123. local_deep_research/metrics/migrate_research_ratings.py +31 -0
  124. local_deep_research/metrics/models.py +61 -0
  125. local_deep_research/metrics/pricing/__init__.py +12 -0
  126. local_deep_research/metrics/pricing/cost_calculator.py +237 -0
  127. local_deep_research/metrics/pricing/pricing_cache.py +143 -0
  128. local_deep_research/metrics/pricing/pricing_fetcher.py +240 -0
  129. local_deep_research/metrics/query_utils.py +51 -0
  130. local_deep_research/metrics/search_tracker.py +380 -0
  131. local_deep_research/metrics/token_counter.py +1078 -0
  132. local_deep_research/migrate_db.py +3 -1
  133. local_deep_research/report_generator.py +22 -8
  134. local_deep_research/search_system.py +390 -9
  135. local_deep_research/test_migration.py +15 -5
  136. local_deep_research/utilities/db_utils.py +7 -4
  137. local_deep_research/utilities/es_utils.py +115 -104
  138. local_deep_research/utilities/llm_utils.py +15 -5
  139. local_deep_research/utilities/log_utils.py +151 -0
  140. local_deep_research/utilities/search_cache.py +387 -0
  141. local_deep_research/utilities/search_utilities.py +14 -6
  142. local_deep_research/utilities/threading_utils.py +92 -0
  143. local_deep_research/utilities/url_utils.py +6 -0
  144. local_deep_research/web/api.py +347 -0
  145. local_deep_research/web/app.py +13 -17
  146. local_deep_research/web/app_factory.py +71 -66
  147. local_deep_research/web/database/migrate_to_ldr_db.py +12 -4
  148. local_deep_research/web/database/migrations.py +5 -3
  149. local_deep_research/web/database/models.py +51 -2
  150. local_deep_research/web/database/schema_upgrade.py +49 -29
  151. local_deep_research/web/models/database.py +51 -61
  152. local_deep_research/web/routes/api_routes.py +56 -22
  153. local_deep_research/web/routes/benchmark_routes.py +4 -1
  154. local_deep_research/web/routes/globals.py +22 -0
  155. local_deep_research/web/routes/history_routes.py +71 -46
  156. local_deep_research/web/routes/metrics_routes.py +1155 -0
  157. local_deep_research/web/routes/research_routes.py +227 -41
  158. local_deep_research/web/routes/settings_routes.py +156 -55
  159. local_deep_research/web/services/research_service.py +310 -103
  160. local_deep_research/web/services/resource_service.py +36 -11
  161. local_deep_research/web/services/settings_manager.py +55 -17
  162. local_deep_research/web/services/settings_service.py +12 -4
  163. local_deep_research/web/services/socket_service.py +295 -188
  164. local_deep_research/web/static/css/custom_dropdown.css +180 -0
  165. local_deep_research/web/static/css/styles.css +39 -1
  166. local_deep_research/web/static/js/components/detail.js +633 -267
  167. local_deep_research/web/static/js/components/details.js +751 -0
  168. local_deep_research/web/static/js/components/fallback/formatting.js +11 -11
  169. local_deep_research/web/static/js/components/fallback/ui.js +23 -23
  170. local_deep_research/web/static/js/components/history.js +76 -76
  171. local_deep_research/web/static/js/components/logpanel.js +61 -13
  172. local_deep_research/web/static/js/components/progress.js +13 -2
  173. local_deep_research/web/static/js/components/research.js +99 -12
  174. local_deep_research/web/static/js/components/results.js +239 -106
  175. local_deep_research/web/static/js/main.js +40 -40
  176. local_deep_research/web/static/js/services/audio.js +1 -1
  177. local_deep_research/web/static/js/services/formatting.js +11 -11
  178. local_deep_research/web/static/js/services/keyboard.js +157 -0
  179. local_deep_research/web/static/js/services/pdf.js +80 -80
  180. local_deep_research/web/static/sounds/README.md +1 -1
  181. local_deep_research/web/templates/base.html +1 -0
  182. local_deep_research/web/templates/components/log_panel.html +7 -1
  183. local_deep_research/web/templates/components/mobile_nav.html +1 -1
  184. local_deep_research/web/templates/components/sidebar.html +3 -0
  185. local_deep_research/web/templates/pages/cost_analytics.html +1245 -0
  186. local_deep_research/web/templates/pages/details.html +325 -24
  187. local_deep_research/web/templates/pages/history.html +1 -1
  188. local_deep_research/web/templates/pages/metrics.html +1929 -0
  189. local_deep_research/web/templates/pages/progress.html +2 -2
  190. local_deep_research/web/templates/pages/research.html +53 -17
  191. local_deep_research/web/templates/pages/results.html +12 -1
  192. local_deep_research/web/templates/pages/star_reviews.html +803 -0
  193. local_deep_research/web/utils/formatters.py +9 -3
  194. local_deep_research/web_search_engines/default_search_engines.py +5 -3
  195. local_deep_research/web_search_engines/engines/full_search.py +8 -2
  196. local_deep_research/web_search_engines/engines/meta_search_engine.py +59 -20
  197. local_deep_research/web_search_engines/engines/search_engine_arxiv.py +19 -6
  198. local_deep_research/web_search_engines/engines/search_engine_brave.py +6 -2
  199. local_deep_research/web_search_engines/engines/search_engine_ddg.py +3 -1
  200. local_deep_research/web_search_engines/engines/search_engine_elasticsearch.py +81 -58
  201. local_deep_research/web_search_engines/engines/search_engine_github.py +46 -15
  202. local_deep_research/web_search_engines/engines/search_engine_google_pse.py +16 -6
  203. local_deep_research/web_search_engines/engines/search_engine_guardian.py +39 -15
  204. local_deep_research/web_search_engines/engines/search_engine_local.py +58 -25
  205. local_deep_research/web_search_engines/engines/search_engine_local_all.py +15 -5
  206. local_deep_research/web_search_engines/engines/search_engine_pubmed.py +63 -21
  207. local_deep_research/web_search_engines/engines/search_engine_searxng.py +37 -11
  208. local_deep_research/web_search_engines/engines/search_engine_semantic_scholar.py +27 -9
  209. local_deep_research/web_search_engines/engines/search_engine_serpapi.py +12 -4
  210. local_deep_research/web_search_engines/engines/search_engine_wayback.py +31 -10
  211. local_deep_research/web_search_engines/engines/search_engine_wikipedia.py +12 -3
  212. local_deep_research/web_search_engines/search_engine_base.py +83 -35
  213. local_deep_research/web_search_engines/search_engine_factory.py +25 -8
  214. local_deep_research/web_search_engines/search_engines_config.py +9 -3
  215. {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.0.dist-info}/METADATA +7 -1
  216. local_deep_research-0.5.0.dist-info/RECORD +265 -0
  217. local_deep_research-0.4.4.dist-info/RECORD +0 -177
  218. {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.0.dist-info}/WHEEL +0 -0
  219. {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.0.dist-info}/entry_points.txt +0 -0
  220. {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -9,7 +9,7 @@
9
9
  let pdfBtn = null;
10
10
  let researchId = null;
11
11
  let researchData = null;
12
-
12
+
13
13
  /**
14
14
  * Initialize the results component
15
15
  */
@@ -18,41 +18,52 @@
18
18
  resultsContainer = document.getElementById('results-content');
19
19
  exportBtn = document.getElementById('export-markdown-btn');
20
20
  pdfBtn = document.getElementById('download-pdf-btn');
21
-
21
+
22
22
  if (!resultsContainer) {
23
23
  console.error('Results container not found');
24
24
  return;
25
25
  }
26
-
26
+
27
27
  console.log('Results component initialized');
28
-
28
+
29
29
  // Get research ID from URL
30
30
  researchId = getResearchIdFromUrl();
31
-
31
+
32
32
  if (!researchId) {
33
33
  showError('Research ID not found in URL');
34
34
  return;
35
35
  }
36
-
36
+
37
37
  // Set up event listeners
38
38
  setupEventListeners();
39
-
39
+
40
40
  // Load research results
41
41
  loadResearchResults();
42
-
42
+
43
+ // Initialize star rating
44
+ initializeStarRating();
45
+
43
46
  // Note: Log panel is now automatically initialized by logpanel.js
44
47
  // No need to manually initialize it here
45
48
  }
46
-
49
+
47
50
  /**
48
51
  * Set up event listeners
49
52
  */
50
53
  function setupEventListeners() {
54
+ // View metrics button
55
+ const metricsBtn = document.getElementById('view-metrics-btn');
56
+ if (metricsBtn) {
57
+ metricsBtn.addEventListener('click', () => {
58
+ window.location.href = `/research/details/${researchId}`;
59
+ });
60
+ }
61
+
51
62
  // Export button
52
63
  if (exportBtn) {
53
64
  exportBtn.addEventListener('click', handleExport);
54
65
  }
55
-
66
+
56
67
  // PDF button
57
68
  if (pdfBtn) {
58
69
  pdfBtn.addEventListener('click', handlePdfExport);
@@ -65,8 +76,9 @@
65
76
  window.location.href = '/research/history';
66
77
  });
67
78
  }
79
+
68
80
  }
69
-
81
+
70
82
  /**
71
83
  * Get research ID from URL
72
84
  * @returns {string|null} Research ID
@@ -76,7 +88,7 @@
76
88
  const match = path.match(/\/research\/results\/(\d+)/);
77
89
  return match ? match[1] : null;
78
90
  }
79
-
91
+
80
92
  /**
81
93
  * Load research results from API
82
94
  */
@@ -84,25 +96,25 @@
84
96
  try {
85
97
  // Show loading state
86
98
  resultsContainer.innerHTML = '<div class="text-center my-5"><i class="fas fa-spinner fa-pulse"></i><p class="mt-3">Loading research results...</p></div>';
87
-
99
+
88
100
  // Fetch result from API
89
101
  const response = await fetch(`/research/api/report/${researchId}`);
90
-
102
+
91
103
  if (!response.ok) {
92
104
  throw new Error(`HTTP error ${response.status}`);
93
105
  }
94
-
106
+
95
107
  const responseData = await response.json();
96
108
  console.log('Original API response:', responseData);
97
-
109
+
98
110
  // Store data for export
99
111
  researchData = responseData;
100
-
112
+
101
113
  // Check if we have data to display
102
114
  if (!responseData) {
103
115
  throw new Error('No data received from server');
104
116
  }
105
-
117
+
106
118
  // Use the API metadata directly
107
119
  if (responseData.metadata && typeof responseData.metadata === 'object') {
108
120
  console.log('Using metadata directly from API response:', responseData.metadata);
@@ -111,7 +123,7 @@
111
123
  // Fallback to content extraction if no metadata in response
112
124
  populateMetadata(responseData);
113
125
  }
114
-
126
+
115
127
  // Render the content
116
128
  if (responseData.content && typeof responseData.content === 'string') {
117
129
  console.log('Rendering content from API response');
@@ -121,21 +133,21 @@
121
133
  console.log('No direct content found, trying to find content in response');
122
134
  findAndRenderContent(responseData);
123
135
  }
124
-
136
+
125
137
  // Enable export buttons
126
138
  if (exportBtn) exportBtn.disabled = false;
127
139
  if (pdfBtn) pdfBtn.disabled = false;
128
-
140
+
129
141
  } catch (error) {
130
142
  console.error('Error loading research results:', error);
131
143
  showError(`Error loading research results: ${error.message}`);
132
-
144
+
133
145
  // Disable export buttons
134
146
  if (exportBtn) exportBtn.disabled = true;
135
147
  if (pdfBtn) pdfBtn.disabled = true;
136
148
  }
137
149
  }
138
-
150
+
139
151
  /**
140
152
  * Populate metadata directly from API response metadata
141
153
  * @param {Object} data - API response with metadata
@@ -143,7 +155,7 @@
143
155
  function populateMetadataFromApiResponse(data) {
144
156
  const metadata = data.metadata || {};
145
157
  console.log('Using API response metadata:', metadata);
146
-
158
+
147
159
  // Query field
148
160
  const queryElement = document.getElementById('result-query');
149
161
  if (queryElement) {
@@ -152,18 +164,18 @@
152
164
  console.log('Setting query to:', query);
153
165
  queryElement.textContent = query;
154
166
  }
155
-
167
+
156
168
  // Generated date field
157
169
  const dateElement = document.getElementById('result-date');
158
170
  if (dateElement) {
159
171
  let dateStr = 'Unknown date';
160
-
172
+
161
173
  // Try multiple sources for the timestamp - first from the API response directly, then from metadata
162
- const timestamp = data.created_at || data.timestamp || data.date ||
174
+ const timestamp = data.created_at || data.timestamp || data.date ||
163
175
  metadata.created_at || metadata.timestamp || metadata.date;
164
-
176
+
165
177
  console.log('Found timestamp:', timestamp);
166
-
178
+
167
179
  if (timestamp) {
168
180
  if (window.formatting && typeof window.formatting.formatDate === 'function') {
169
181
  dateStr = window.formatting.formatDate(timestamp);
@@ -177,11 +189,11 @@
177
189
  console.error('Error parsing date:', e);
178
190
  }
179
191
  }
180
-
192
+
181
193
  // Add duration if available - format as "Xm Ys" for values over 60 seconds
182
194
  if (metadata.duration || metadata.duration_seconds || data.duration_seconds) {
183
195
  const durationSeconds = parseInt(metadata.duration || metadata.duration_seconds || data.duration_seconds, 10);
184
-
196
+
185
197
  if (!isNaN(durationSeconds)) {
186
198
  let durationStr;
187
199
  if (durationSeconds < 60) {
@@ -195,39 +207,39 @@
195
207
  }
196
208
  }
197
209
  }
198
-
210
+
199
211
  console.log('Setting date to:', dateStr);
200
212
  dateElement.textContent = dateStr;
201
213
  }
202
-
214
+
203
215
  // Mode field
204
216
  const modeElement = document.getElementById('result-mode');
205
217
  if (modeElement) {
206
218
  // Get mode from metadata or main response
207
- let mode = metadata.mode || metadata.research_mode || metadata.type ||
219
+ let mode = metadata.mode || metadata.research_mode || metadata.type ||
208
220
  data.mode || data.research_mode || data.type;
209
-
221
+
210
222
  // Detect if this is a detailed report based on content structure
211
223
  if (!mode && data.content) {
212
- if (data.content.toLowerCase().includes('table of contents') ||
224
+ if (data.content.toLowerCase().includes('table of contents') ||
213
225
  data.content.match(/^#.*\n+##.*\n+###/m)) {
214
226
  mode = 'detailed';
215
227
  } else {
216
228
  mode = 'quick';
217
229
  }
218
230
  }
219
-
231
+
220
232
  // Format mode using available formatter
221
233
  if (window.formatting && typeof window.formatting.formatMode === 'function') {
222
234
  mode = window.formatting.formatMode(mode);
223
235
  console.log('Formatted mode:', mode);
224
236
  }
225
-
237
+
226
238
  console.log('Setting mode to:', mode || 'Quick');
227
239
  modeElement.textContent = mode || 'Quick';
228
240
  }
229
241
  }
230
-
242
+
231
243
  /**
232
244
  * Find and render content from various response formats
233
245
  * @param {Object} data - Research data to extract content from
@@ -261,7 +273,7 @@
261
273
  // Look for any property that might contain the content
262
274
  const contentProps = ['markdown', 'text', 'summary', 'output', 'research_output'];
263
275
  let foundContent = false;
264
-
276
+
265
277
  for (const prop of contentProps) {
266
278
  if (data[prop] && typeof data[prop] === 'string') {
267
279
  console.log(`Rendering from data.${prop}`);
@@ -270,7 +282,7 @@
270
282
  break;
271
283
  }
272
284
  }
273
-
285
+
274
286
  if (!foundContent) {
275
287
  // Last resort: try to render the entire data object
276
288
  console.log('No clear content found, rendering entire data object');
@@ -278,7 +290,7 @@
278
290
  }
279
291
  }
280
292
  }
281
-
293
+
282
294
  /**
283
295
  * Populate metadata fields with information from the research data
284
296
  * @param {Object} data - Research data with metadata
@@ -288,18 +300,18 @@
288
300
  console.log('API response data:', data);
289
301
  console.log('Data type:', typeof data);
290
302
  console.log('Available top-level keys:', Object.keys(data));
291
-
303
+
292
304
  // Direct extraction from content
293
305
  if (data.content && typeof data.content === 'string') {
294
306
  console.log('Attempting to extract metadata from content');
295
-
307
+
296
308
  // Extract the query from content first line or header
297
309
  // Avoid matching "Table of Contents" as query
298
310
  const queryMatch = data.content.match(/^#\s*([^\n]+)/m) || // First heading
299
311
  data.content.match(/Query:\s*([^\n]+)/i) || // Explicit query label
300
312
  data.content.match(/Question:\s*([^\n]+)/i) || // Question label
301
313
  data.content.match(/^([^\n#]+)(?=\n)/); // First line if not starting with #
302
-
314
+
303
315
  if (queryMatch && queryMatch[1] && !queryMatch[1].toLowerCase().includes('table of contents')) {
304
316
  const queryElement = document.getElementById('result-query');
305
317
  if (queryElement) {
@@ -319,58 +331,58 @@
319
331
  }
320
332
  }
321
333
  }
322
-
334
+
323
335
  // Extract generated date/time - Try multiple formats
324
- const dateMatch = data.content.match(/Generated at:\s*([^\n]+)/i) ||
336
+ const dateMatch = data.content.match(/Generated at:\s*([^\n]+)/i) ||
325
337
  data.content.match(/Date:\s*([^\n]+)/i) ||
326
338
  data.content.match(/Generated:\s*([^\n]+)/i) ||
327
339
  data.content.match(/Created:\s*([^\n]+)/i);
328
-
340
+
329
341
  if (dateMatch && dateMatch[1]) {
330
342
  const dateElement = document.getElementById('result-date');
331
343
  if (dateElement) {
332
344
  const extractedDate = dateMatch[1].trim();
333
345
  console.log('Extracted date from content:', extractedDate);
334
-
346
+
335
347
  // Format the date using the available formatter
336
348
  let formattedDate = extractedDate;
337
349
  if (window.formatting && typeof window.formatting.formatDate === 'function') {
338
350
  formattedDate = window.formatting.formatDate(extractedDate);
339
351
  console.log('Date formatted using formatter:', formattedDate);
340
352
  }
341
-
353
+
342
354
  dateElement.textContent = formattedDate || new Date().toLocaleString();
343
355
  }
344
356
  }
345
-
357
+
346
358
  // Extract mode
347
- const modeMatch = data.content.match(/Mode:\s*([^\n]+)/i) ||
359
+ const modeMatch = data.content.match(/Mode:\s*([^\n]+)/i) ||
348
360
  data.content.match(/Research type:\s*([^\n]+)/i);
349
-
361
+
350
362
  if (modeMatch && modeMatch[1]) {
351
363
  const modeElement = document.getElementById('result-mode');
352
364
  if (modeElement) {
353
365
  const extractedMode = modeMatch[1].trim();
354
366
  console.log('Extracted mode from content:', extractedMode);
355
-
367
+
356
368
  // Format mode using available formatter
357
369
  let formattedMode = extractedMode;
358
370
  if (window.formatting && typeof window.formatting.formatMode === 'function') {
359
371
  formattedMode = window.formatting.formatMode(extractedMode);
360
372
  console.log('Mode formatted using formatter:', formattedMode);
361
373
  }
362
-
374
+
363
375
  modeElement.textContent = formattedMode || 'Standard';
364
376
  }
365
377
  } else {
366
378
  // Detect mode based on content structure and keywords
367
379
  const modeElement = document.getElementById('result-mode');
368
380
  if (modeElement) {
369
- if (data.content.toLowerCase().includes('table of contents') ||
381
+ if (data.content.toLowerCase().includes('table of contents') ||
370
382
  data.content.toLowerCase().includes('detailed report') ||
371
383
  data.content.match(/^#.*\n+##.*\n+###/m)) { // Has H1, H2, H3 structure
372
384
  modeElement.textContent = 'Detailed';
373
- } else if (data.content.toLowerCase().includes('quick research') ||
385
+ } else if (data.content.toLowerCase().includes('quick research') ||
374
386
  data.content.toLowerCase().includes('summary')) {
375
387
  modeElement.textContent = 'Quick';
376
388
  } else {
@@ -378,32 +390,32 @@
378
390
  }
379
391
  }
380
392
  }
381
-
393
+
382
394
  return; // Exit early since we've handled extraction from content
383
395
  }
384
-
396
+
385
397
  // Also check the metadata field which likely contains the actual metadata
386
398
  const metadata = data.metadata || {};
387
399
  console.log('Metadata object:', metadata);
388
400
  if (metadata) {
389
401
  console.log('Metadata keys:', Object.keys(metadata));
390
402
  }
391
-
403
+
392
404
  // Extract research object if nested
393
405
  const researchData = data.research || data;
394
-
406
+
395
407
  // Debug nested structure if exists
396
408
  if (data.research) {
397
409
  console.log('Nested research data:', data.research);
398
410
  console.log('Research keys:', Object.keys(data.research));
399
411
  }
400
-
412
+
401
413
  // Query field
402
414
  const queryElement = document.getElementById('result-query');
403
415
  if (queryElement) {
404
416
  // Try different possible locations for query data
405
417
  let query = 'Unknown query';
406
-
418
+
407
419
  if (metadata.query) {
408
420
  query = metadata.query;
409
421
  } else if (metadata.title) {
@@ -419,17 +431,17 @@
419
431
  } else if (researchData.input) {
420
432
  query = researchData.input;
421
433
  }
422
-
434
+
423
435
  console.log('Setting query to:', query);
424
436
  queryElement.textContent = query;
425
437
  }
426
-
438
+
427
439
  // Generated date field
428
440
  const dateElement = document.getElementById('result-date');
429
441
  if (dateElement) {
430
442
  let dateStr = 'Unknown date';
431
443
  let timestampField = null;
432
-
444
+
433
445
  // Try different possible date fields
434
446
  if (metadata.created_at) {
435
447
  timestampField = metadata.created_at;
@@ -446,7 +458,7 @@
446
458
  } else if (researchData.time) {
447
459
  timestampField = researchData.time;
448
460
  }
449
-
461
+
450
462
  // Format the date using the available formatter
451
463
  if (timestampField) {
452
464
  if (window.formatting && typeof window.formatting.formatDate === 'function') {
@@ -462,7 +474,7 @@
462
474
  }
463
475
  }
464
476
  }
465
-
477
+
466
478
  // Add duration if available
467
479
  if (metadata.duration) {
468
480
  dateStr += ` (${metadata.duration} seconds)`;
@@ -471,16 +483,16 @@
471
483
  } else if (researchData.duration) {
472
484
  dateStr += ` (${researchData.duration} seconds)`;
473
485
  }
474
-
486
+
475
487
  console.log('Setting date to:', dateStr);
476
488
  dateElement.textContent = dateStr;
477
489
  }
478
-
490
+
479
491
  // Mode field
480
492
  const modeElement = document.getElementById('result-mode');
481
493
  if (modeElement) {
482
494
  let mode = 'Quick'; // Default to Quick
483
-
495
+
484
496
  if (metadata.mode) {
485
497
  mode = metadata.mode;
486
498
  } else if (metadata.research_mode) {
@@ -494,17 +506,17 @@
494
506
  } else if (researchData.type) {
495
507
  mode = researchData.type;
496
508
  }
497
-
509
+
498
510
  // Format mode using available formatter
499
511
  if (window.formatting && typeof window.formatting.formatMode === 'function') {
500
512
  mode = window.formatting.formatMode(mode);
501
513
  }
502
-
514
+
503
515
  console.log('Setting mode to:', mode);
504
516
  modeElement.textContent = mode;
505
517
  }
506
518
  }
507
-
519
+
508
520
  /**
509
521
  * Render research results in the container
510
522
  * @param {Object|string} data - Research data to render
@@ -513,10 +525,10 @@
513
525
  try {
514
526
  // Clear container
515
527
  resultsContainer.innerHTML = '';
516
-
528
+
517
529
  // Determine the content to render
518
530
  let content = '';
519
-
531
+
520
532
  if (typeof data === 'string') {
521
533
  // Direct string content
522
534
  content = data;
@@ -544,7 +556,7 @@
544
556
  // Last resort: stringify the entire object
545
557
  content = JSON.stringify(data, null, 2);
546
558
  }
547
-
559
+
548
560
  // Render the content as Markdown if possible
549
561
  if (window.ui && window.ui.renderMarkdown) {
550
562
  const renderedHtml = window.ui.renderMarkdown(content);
@@ -554,18 +566,18 @@
554
566
  content = content.replace(/\n/g, '<br>');
555
567
  resultsContainer.innerHTML = `<div class="markdown-content">${content}</div>`;
556
568
  }
557
-
569
+
558
570
  // Add syntax highlighting if Prism is available
559
571
  if (window.Prism) {
560
572
  window.Prism.highlightAllUnder(resultsContainer);
561
573
  }
562
-
574
+
563
575
  } catch (error) {
564
576
  console.error('Error rendering results:', error);
565
577
  showError(`Error rendering results: ${error.message}`);
566
578
  }
567
579
  }
568
-
580
+
569
581
  /**
570
582
  * Show error message in the results container
571
583
  * @param {string} message - Error message
@@ -582,7 +594,7 @@
582
594
  </p>
583
595
  `;
584
596
  }
585
-
597
+
586
598
  /**
587
599
  * Handle export button click
588
600
  */
@@ -591,21 +603,21 @@
591
603
  if (!researchData) {
592
604
  throw new Error('No research data available');
593
605
  }
594
-
606
+
595
607
  // Get metadata from DOM (which should be populated by now)
596
608
  const query = document.getElementById('result-query')?.textContent || 'Unknown query';
597
609
  const generated = document.getElementById('result-date')?.textContent || 'Unknown date';
598
610
  const mode = document.getElementById('result-mode')?.textContent || 'Quick';
599
-
611
+
600
612
  // Create markdown header with metadata
601
613
  let markdownHeader = `# Research Results: ${query}\n\n`;
602
614
  markdownHeader += `- **Generated:** ${generated}\n`;
603
615
  markdownHeader += `- **Mode:** ${mode}\n\n`;
604
616
  markdownHeader += `---\n\n`;
605
-
617
+
606
618
  // Extract the content to export
607
619
  let markdownContent = '';
608
-
620
+
609
621
  // Try to extract the markdown content from various possible locations
610
622
  if (typeof researchData === 'string') {
611
623
  markdownContent = researchData;
@@ -620,9 +632,9 @@
620
632
  'output',
621
633
  'research_output'
622
634
  ];
623
-
635
+
624
636
  let found = false;
625
-
637
+
626
638
  // First check direct properties
627
639
  for (const prop of contentProps) {
628
640
  if (researchData[prop] && typeof researchData[prop] === 'string') {
@@ -632,7 +644,7 @@
632
644
  break;
633
645
  }
634
646
  }
635
-
647
+
636
648
  // Then check nested properties
637
649
  if (!found && researchData.research) {
638
650
  for (const prop of contentProps) {
@@ -644,7 +656,7 @@
644
656
  }
645
657
  }
646
658
  }
647
-
659
+
648
660
  // Check results property
649
661
  if (!found && researchData.results) {
650
662
  if (typeof researchData.results === 'string') {
@@ -661,34 +673,35 @@
661
673
  }
662
674
  }
663
675
  }
664
-
676
+
665
677
  // Last resort
666
678
  if (!markdownContent) {
667
679
  console.warn('Could not extract markdown content, using JSON');
668
680
  markdownContent = "```json\n" + JSON.stringify(researchData, null, 2) + "\n```";
669
681
  }
670
682
  }
671
-
683
+
672
684
  // Combine header and content
673
685
  const fullMarkdown = markdownHeader + markdownContent;
674
-
686
+
675
687
  // Create blob and trigger download
676
688
  const blob = new Blob([fullMarkdown], { type: 'text/markdown' });
677
689
  const link = document.createElement('a');
678
690
  link.href = URL.createObjectURL(blob);
679
691
  link.download = `research_${researchId}.md`;
680
-
692
+
681
693
  // Trigger download
682
694
  document.body.appendChild(link);
683
695
  link.click();
684
696
  document.body.removeChild(link);
685
-
697
+
686
698
  } catch (error) {
687
699
  console.error('Error exporting markdown:', error);
688
700
  alert(`Error exporting markdown: ${error.message}`);
689
701
  }
690
702
  }
691
-
703
+
704
+
692
705
  /**
693
706
  * Handle PDF export button click
694
707
  */
@@ -697,21 +710,21 @@
697
710
  if (!researchData) {
698
711
  throw new Error('No research data available');
699
712
  }
700
-
713
+
701
714
  console.log('PDF export initiated for research ID:', researchId);
702
-
715
+
703
716
  // Show loading indicator
704
717
  pdfBtn.disabled = true;
705
718
  pdfBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Generating PDF...';
706
-
719
+
707
720
  // Get metadata from DOM (which should be populated correctly by now)
708
721
  const title = document.getElementById('result-query')?.textContent || `Research ${researchId}`;
709
722
  console.log('Using title for PDF:', title);
710
-
723
+
711
724
  // Check if PDF service is available
712
725
  if (window.pdfService && window.pdfService.downloadPdf) {
713
726
  console.log('PDF service available, calling downloadPdf');
714
-
727
+
715
728
  // Add the metadata to the researchData for PDF generation
716
729
  const pdfData = {
717
730
  ...researchData,
@@ -723,7 +736,7 @@
723
736
  mode: document.getElementById('result-mode')?.textContent || 'Standard'
724
737
  }
725
738
  };
726
-
739
+
727
740
  // Use the PDF service to generate and download the PDF
728
741
  window.pdfService.downloadPdf(pdfData, researchId)
729
742
  .then(() => {
@@ -735,7 +748,7 @@
735
748
  .catch(error => {
736
749
  console.error('Error generating PDF:', error);
737
750
  alert(`Error generating PDF: ${error.message || 'Unknown error'}`);
738
-
751
+
739
752
  // Reset button
740
753
  pdfBtn.disabled = false;
741
754
  pdfBtn.innerHTML = '<i class="fas fa-file-pdf"></i> Download PDF';
@@ -744,11 +757,11 @@
744
757
  console.error('PDF service not available');
745
758
  throw new Error('PDF service not available');
746
759
  }
747
-
760
+
748
761
  } catch (error) {
749
762
  console.error('Error exporting PDF:', error);
750
763
  alert(`Error exporting PDF: ${error.message || 'Unknown error'}`);
751
-
764
+
752
765
  // Reset button
753
766
  if (pdfBtn) {
754
767
  pdfBtn.disabled = false;
@@ -756,11 +769,131 @@
756
769
  }
757
770
  }
758
771
  }
759
-
772
+
773
+ /**
774
+ * Initialize star rating functionality
775
+ */
776
+ function initializeStarRating() {
777
+ const starRating = document.getElementById('research-rating');
778
+ if (!starRating || !researchId) return;
779
+
780
+ const stars = starRating.querySelectorAll('.star');
781
+ let currentRating = 0;
782
+
783
+ // Load existing rating
784
+ loadExistingRating();
785
+
786
+ // Add hover effects
787
+ stars.forEach((star, index) => {
788
+ star.addEventListener('mouseenter', () => {
789
+ highlightStars(index + 1);
790
+ });
791
+
792
+ star.addEventListener('click', () => {
793
+ const rating = index + 1;
794
+ setRating(rating);
795
+ saveRating(rating);
796
+
797
+ // Visual feedback for saving
798
+ starRating.style.opacity = '0.7';
799
+ setTimeout(() => {
800
+ starRating.style.opacity = '1';
801
+ }, 500);
802
+ });
803
+ });
804
+
805
+ starRating.addEventListener('mouseleave', () => {
806
+ // Restore the permanent rating when mouse leaves
807
+ setRating(currentRating);
808
+ });
809
+
810
+ function highlightStars(rating) {
811
+ stars.forEach((star, index) => {
812
+ // Clear all classes first
813
+ star.classList.remove('hover', 'active');
814
+ // Add hover class for preview
815
+ if (index < rating) {
816
+ star.classList.add('hover');
817
+ }
818
+ });
819
+ }
820
+
821
+ function setRating(rating) {
822
+ currentRating = rating;
823
+ stars.forEach((star, index) => {
824
+ // Clear all classes first
825
+ star.classList.remove('hover', 'active');
826
+ // Set active state for permanent rating
827
+ if (index < rating) {
828
+ star.classList.add('active');
829
+ }
830
+ });
831
+ }
832
+
833
+ async function loadExistingRating() {
834
+ try {
835
+ const response = await fetch(`/metrics/api/ratings/${researchId}`);
836
+ if (response.ok) {
837
+ const data = await response.json();
838
+ if (data.rating) {
839
+ setRating(data.rating);
840
+ }
841
+ }
842
+ } catch (error) {
843
+ console.log('No existing rating found');
844
+ }
845
+ }
846
+
847
+ async function saveRating(rating) {
848
+ try {
849
+ console.log('Attempting to save rating:', rating);
850
+
851
+ // Get CSRF token from meta tag
852
+ const csrfToken = document.querySelector('meta[name=csrf-token]')?.getAttribute('content');
853
+ console.log('CSRF token:', csrfToken ? 'found' : 'missing');
854
+
855
+ const headers = {
856
+ 'Content-Type': 'application/json',
857
+ };
858
+
859
+ // Add CSRF token if available
860
+ if (csrfToken) {
861
+ headers['X-CSRFToken'] = csrfToken;
862
+ }
863
+
864
+ const response = await fetch(`/metrics/api/ratings/${researchId}`, {
865
+ method: 'POST',
866
+ headers: headers,
867
+ body: JSON.stringify({ rating: rating })
868
+ });
869
+
870
+ console.log('Response status:', response.status);
871
+ const responseText = await response.text();
872
+ console.log('Response:', responseText);
873
+
874
+ if (response.ok) {
875
+ console.log('Rating saved successfully');
876
+ try {
877
+ const responseData = JSON.parse(responseText);
878
+ if (responseData.status === 'success') {
879
+ console.log('✅ Rating confirmed saved:', responseData.rating);
880
+ }
881
+ } catch (e) {
882
+ console.log('✅ Rating saved (non-JSON response)');
883
+ }
884
+ } else {
885
+ console.error('❌ Failed to save rating:', response.status, responseText);
886
+ }
887
+ } catch (error) {
888
+ console.error('❌ Error saving rating:', error);
889
+ }
890
+ }
891
+ }
892
+
760
893
  // Initialize on DOM content loaded
761
894
  if (document.readyState === 'loading') {
762
895
  document.addEventListener('DOMContentLoaded', initializeResults);
763
896
  } else {
764
897
  initializeResults();
765
898
  }
766
- })();
899
+ })();