ragdoll-rails 0.1.8 → 0.1.11
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.
- checksums.yaml +4 -4
- data/README.md +18 -21
- data/app/assets/javascripts/ragdoll/application.js +129 -0
- data/app/assets/javascripts/ragdoll/bulk_upload_status.js +454 -0
- data/app/assets/stylesheets/ragdoll/application.css +84 -0
- data/app/assets/stylesheets/ragdoll/bulk_upload_status.css +379 -0
- data/app/channels/application_cable/channel.rb +6 -0
- data/app/channels/application_cable/connection.rb +6 -0
- data/app/channels/ragdoll/bulk_upload_status_channel.rb +27 -0
- data/app/channels/ragdoll/file_processing_channel.rb +26 -0
- data/app/components/ragdoll/alert_component.html.erb +4 -0
- data/app/components/ragdoll/alert_component.rb +32 -0
- data/app/components/ragdoll/application_component.rb +6 -0
- data/app/components/ragdoll/card_component.html.erb +15 -0
- data/app/components/ragdoll/card_component.rb +21 -0
- data/app/components/ragdoll/document_list_component.html.erb +41 -0
- data/app/components/ragdoll/document_list_component.rb +13 -0
- data/app/components/ragdoll/document_table_component.html.erb +76 -0
- data/app/components/ragdoll/document_table_component.rb +13 -0
- data/app/components/ragdoll/empty_state_component.html.erb +12 -0
- data/app/components/ragdoll/empty_state_component.rb +17 -0
- data/app/components/ragdoll/flash_messages_component.html.erb +3 -0
- data/app/components/ragdoll/flash_messages_component.rb +37 -0
- data/app/components/ragdoll/navbar_component.html.erb +24 -0
- data/app/components/ragdoll/navbar_component.rb +31 -0
- data/app/components/ragdoll/page_header_component.html.erb +13 -0
- data/app/components/ragdoll/page_header_component.rb +15 -0
- data/app/components/ragdoll/stats_card_component.html.erb +11 -0
- data/app/components/ragdoll/stats_card_component.rb +17 -0
- data/app/components/ragdoll/status_badge_component.html.erb +3 -0
- data/app/components/ragdoll/status_badge_component.rb +30 -0
- data/app/controllers/ragdoll/api/v1/analytics_controller.rb +72 -0
- data/app/controllers/ragdoll/api/v1/base_controller.rb +29 -0
- data/app/controllers/ragdoll/api/v1/documents_controller.rb +148 -0
- data/app/controllers/ragdoll/api/v1/search_controller.rb +87 -0
- data/app/controllers/ragdoll/api/v1/system_controller.rb +97 -0
- data/app/controllers/ragdoll/application_controller.rb +17 -0
- data/app/controllers/ragdoll/configuration_controller.rb +82 -0
- data/app/controllers/ragdoll/dashboard_controller.rb +98 -0
- data/app/controllers/ragdoll/documents_controller.rb +460 -0
- data/app/controllers/ragdoll/documents_controller_backup.rb +68 -0
- data/app/controllers/ragdoll/jobs_controller.rb +116 -0
- data/app/controllers/ragdoll/search_controller.rb +368 -0
- data/app/jobs/application_job.rb +9 -0
- data/app/jobs/ragdoll/bulk_document_processing_job.rb +280 -0
- data/app/jobs/ragdoll/process_file_job.rb +166 -0
- data/app/services/ragdoll/worker_health_service.rb +111 -0
- data/app/views/layouts/ragdoll/application.html.erb +162 -0
- data/app/views/ragdoll/dashboard/analytics.html.erb +333 -0
- data/app/views/ragdoll/dashboard/index.html.erb +208 -0
- data/app/views/ragdoll/documents/edit.html.erb +91 -0
- data/app/views/ragdoll/documents/index.html.erb +302 -0
- data/app/views/ragdoll/documents/new.html.erb +1518 -0
- data/app/views/ragdoll/documents/show.html.erb +188 -0
- data/app/views/ragdoll/documents/upload_results.html.erb +248 -0
- data/app/views/ragdoll/jobs/index.html.erb +669 -0
- data/app/views/ragdoll/jobs/show.html.erb +129 -0
- data/app/views/ragdoll/search/index.html.erb +324 -0
- data/config/cable.yml +12 -0
- data/config/routes.rb +57 -2
- data/lib/generators/ragdoll/init/templates/INSTALL +3 -2
- data/lib/generators/ragdoll/init_generator.rb +68 -0
- data/lib/ragdoll/rails/engine.rb +48 -0
- data/lib/ragdoll/rails/version.rb +1 -1
- metadata +231 -6
- data/lib/generators/ragdoll/init/init_generator.rb +0 -26
@@ -0,0 +1,333 @@
|
|
1
|
+
<% content_for(:title, "Analytics Dashboard") %>
|
2
|
+
|
3
|
+
<%= render Ragdoll::PageHeaderComponent.new(
|
4
|
+
title: "Analytics Dashboard",
|
5
|
+
subtitle: "Search patterns, usage statistics, and system insights",
|
6
|
+
icon: "fas fa-chart-line"
|
7
|
+
) %>
|
8
|
+
|
9
|
+
<!-- Search Analytics Overview -->
|
10
|
+
<div class="row mb-4">
|
11
|
+
<div class="col-md-3">
|
12
|
+
<%= render Ragdoll::StatsCardComponent.new(
|
13
|
+
title: "Total Searches",
|
14
|
+
value: @search_analytics[:total_searches],
|
15
|
+
icon: "fas fa-search",
|
16
|
+
color: "primary"
|
17
|
+
) %>
|
18
|
+
</div>
|
19
|
+
<div class="col-md-3">
|
20
|
+
<%= render Ragdoll::StatsCardComponent.new(
|
21
|
+
title: "Unique Queries",
|
22
|
+
value: @search_analytics[:unique_queries],
|
23
|
+
icon: "fas fa-question-circle",
|
24
|
+
color: "info"
|
25
|
+
) %>
|
26
|
+
</div>
|
27
|
+
<div class="col-md-3">
|
28
|
+
<%= render Ragdoll::StatsCardComponent.new(
|
29
|
+
title: "Avg Results",
|
30
|
+
value: @search_analytics[:average_results],
|
31
|
+
icon: "fas fa-list",
|
32
|
+
color: "success"
|
33
|
+
) %>
|
34
|
+
</div>
|
35
|
+
<div class="col-md-3">
|
36
|
+
<%= render Ragdoll::StatsCardComponent.new(
|
37
|
+
title: "Avg Similarity",
|
38
|
+
value: number_to_percentage(@search_analytics[:average_similarity] * 100, precision: 1),
|
39
|
+
icon: "fas fa-percentage",
|
40
|
+
color: "warning"
|
41
|
+
) %>
|
42
|
+
</div>
|
43
|
+
</div>
|
44
|
+
|
45
|
+
<!-- Time-based Analytics -->
|
46
|
+
<div class="row mb-4">
|
47
|
+
<div class="col-md-4">
|
48
|
+
<%= render Ragdoll::StatsCardComponent.new(
|
49
|
+
title: "Today",
|
50
|
+
value: @search_analytics[:searches_today],
|
51
|
+
icon: "fas fa-calendar-day",
|
52
|
+
color: "primary"
|
53
|
+
) %>
|
54
|
+
</div>
|
55
|
+
<div class="col-md-4">
|
56
|
+
<%= render Ragdoll::StatsCardComponent.new(
|
57
|
+
title: "This Week",
|
58
|
+
value: @search_analytics[:searches_this_week],
|
59
|
+
icon: "fas fa-calendar-week",
|
60
|
+
color: "success"
|
61
|
+
) %>
|
62
|
+
</div>
|
63
|
+
<div class="col-md-4">
|
64
|
+
<%= render Ragdoll::StatsCardComponent.new(
|
65
|
+
title: "This Month",
|
66
|
+
value: @search_analytics[:searches_this_month],
|
67
|
+
icon: "fas fa-calendar-alt",
|
68
|
+
color: "info"
|
69
|
+
) %>
|
70
|
+
</div>
|
71
|
+
</div>
|
72
|
+
|
73
|
+
<div class="row">
|
74
|
+
<!-- Search Trends Chart -->
|
75
|
+
<div class="col-lg-8">
|
76
|
+
<div class="card mb-4">
|
77
|
+
<div class="card-header">
|
78
|
+
<h5 class="card-title mb-0">
|
79
|
+
<i class="fas fa-chart-line"></i> Search Trends (Last 7 Days)
|
80
|
+
</h5>
|
81
|
+
</div>
|
82
|
+
<div class="card-body">
|
83
|
+
<canvas id="searchTrendsChart" height="100"></canvas>
|
84
|
+
</div>
|
85
|
+
</div>
|
86
|
+
</div>
|
87
|
+
|
88
|
+
<!-- Search Types Distribution -->
|
89
|
+
<div class="col-lg-4">
|
90
|
+
<div class="card mb-4">
|
91
|
+
<div class="card-header">
|
92
|
+
<h5 class="card-title mb-0">
|
93
|
+
<i class="fas fa-chart-pie"></i> Search Types
|
94
|
+
</h5>
|
95
|
+
</div>
|
96
|
+
<div class="card-body">
|
97
|
+
<canvas id="searchTypesChart" height="150"></canvas>
|
98
|
+
</div>
|
99
|
+
</div>
|
100
|
+
</div>
|
101
|
+
</div>
|
102
|
+
|
103
|
+
<div class="row">
|
104
|
+
<!-- Top Queries -->
|
105
|
+
<div class="col-lg-6">
|
106
|
+
<div class="card mb-4">
|
107
|
+
<div class="card-header">
|
108
|
+
<h5 class="card-title mb-0">
|
109
|
+
<i class="fas fa-fire"></i> Top Search Queries
|
110
|
+
</h5>
|
111
|
+
</div>
|
112
|
+
<div class="card-body">
|
113
|
+
<% if @top_queries.any? %>
|
114
|
+
<% @top_queries.each_with_index do |(query, count), index| %>
|
115
|
+
<div class="d-flex justify-content-between align-items-center mb-2 <%= 'border-bottom pb-2' unless index == @top_queries.count - 1 %>">
|
116
|
+
<div class="flex-grow-1">
|
117
|
+
<div class="fw-medium"><%= truncate(query, length: 40) %></div>
|
118
|
+
</div>
|
119
|
+
<div class="ms-2">
|
120
|
+
<span class="badge bg-primary"><%= count %></span>
|
121
|
+
</div>
|
122
|
+
</div>
|
123
|
+
<% end %>
|
124
|
+
<% else %>
|
125
|
+
<%= render Ragdoll::EmptyStateComponent.new(
|
126
|
+
title: "No queries yet",
|
127
|
+
message: "Search data will appear here once users start searching.",
|
128
|
+
icon: "fas fa-search"
|
129
|
+
) %>
|
130
|
+
<% end %>
|
131
|
+
</div>
|
132
|
+
</div>
|
133
|
+
</div>
|
134
|
+
|
135
|
+
<!-- Top Documents -->
|
136
|
+
<div class="col-lg-6">
|
137
|
+
<div class="card mb-4">
|
138
|
+
<div class="card-header">
|
139
|
+
<h5 class="card-title mb-0">
|
140
|
+
<i class="fas fa-star"></i> Most Accessed Documents
|
141
|
+
</h5>
|
142
|
+
</div>
|
143
|
+
<div class="card-body">
|
144
|
+
<% if @top_documents.any? %>
|
145
|
+
<% @top_documents.each_with_index do |(title, usage_count), index| %>
|
146
|
+
<div class="d-flex justify-content-between align-items-center mb-2 <%= 'border-bottom pb-2' unless index == @top_documents.count - 1 %>">
|
147
|
+
<div class="flex-grow-1">
|
148
|
+
<div class="fw-medium"><%= truncate(title, length: 35) %></div>
|
149
|
+
<small class="text-muted">Used <%= usage_count %> times</small>
|
150
|
+
</div>
|
151
|
+
<div class="ms-2">
|
152
|
+
<span class="badge bg-success"><%= usage_count %></span>
|
153
|
+
</div>
|
154
|
+
</div>
|
155
|
+
<% end %>
|
156
|
+
<% else %>
|
157
|
+
<%= render Ragdoll::EmptyStateComponent.new(
|
158
|
+
title: "No document usage yet",
|
159
|
+
message: "Document access statistics will appear here.",
|
160
|
+
icon: "fas fa-file-alt"
|
161
|
+
) %>
|
162
|
+
<% end %>
|
163
|
+
</div>
|
164
|
+
</div>
|
165
|
+
</div>
|
166
|
+
</div>
|
167
|
+
|
168
|
+
<!-- Similarity Score Distribution -->
|
169
|
+
<div class="row">
|
170
|
+
<div class="col-lg-8">
|
171
|
+
<div class="card mb-4">
|
172
|
+
<div class="card-header">
|
173
|
+
<h5 class="card-title mb-0">
|
174
|
+
<i class="fas fa-chart-bar"></i> Similarity Score Distribution
|
175
|
+
</h5>
|
176
|
+
</div>
|
177
|
+
<div class="card-body">
|
178
|
+
<canvas id="similarityChart" height="100"></canvas>
|
179
|
+
</div>
|
180
|
+
</div>
|
181
|
+
</div>
|
182
|
+
|
183
|
+
<!-- System Statistics -->
|
184
|
+
<div class="col-lg-4">
|
185
|
+
<div class="card mb-4">
|
186
|
+
<div class="card-header">
|
187
|
+
<h5 class="card-title mb-0">
|
188
|
+
<i class="fas fa-server"></i> System Statistics
|
189
|
+
</h5>
|
190
|
+
</div>
|
191
|
+
<div class="card-body">
|
192
|
+
<dl class="row small">
|
193
|
+
<dt class="col-sm-7">Total Documents:</dt>
|
194
|
+
<dd class="col-sm-5"><%= @system_stats[:total_documents] %></dd>
|
195
|
+
|
196
|
+
<dt class="col-sm-7">Processed:</dt>
|
197
|
+
<dd class="col-sm-5">
|
198
|
+
<span class="badge bg-success"><%= @system_stats[:processed_documents] %></span>
|
199
|
+
</dd>
|
200
|
+
|
201
|
+
<dt class="col-sm-7">Failed:</dt>
|
202
|
+
<dd class="col-sm-5">
|
203
|
+
<span class="badge bg-danger"><%= @system_stats[:failed_documents] %></span>
|
204
|
+
</dd>
|
205
|
+
|
206
|
+
<dt class="col-sm-7">Pending:</dt>
|
207
|
+
<dd class="col-sm-5">
|
208
|
+
<span class="badge bg-warning"><%= @system_stats[:pending_documents] %></span>
|
209
|
+
</dd>
|
210
|
+
|
211
|
+
<dt class="col-sm-7">Total Embeddings:</dt>
|
212
|
+
<dd class="col-sm-5"><%= @system_stats[:total_embeddings] %></dd>
|
213
|
+
|
214
|
+
<dt class="col-sm-7">Embedding Usage:</dt>
|
215
|
+
<dd class="col-sm-5"><%= @system_stats[:total_embedding_usage] %></dd>
|
216
|
+
</dl>
|
217
|
+
</div>
|
218
|
+
</div>
|
219
|
+
</div>
|
220
|
+
</div>
|
221
|
+
|
222
|
+
<!-- Chart.js Scripts -->
|
223
|
+
<script>
|
224
|
+
document.addEventListener('DOMContentLoaded', function() {
|
225
|
+
// Search Trends Chart
|
226
|
+
const trendsCtx = document.getElementById('searchTrendsChart').getContext('2d');
|
227
|
+
new Chart(trendsCtx, {
|
228
|
+
type: 'line',
|
229
|
+
data: {
|
230
|
+
labels: <%= @search_trends.keys.to_json.html_safe %>,
|
231
|
+
datasets: [{
|
232
|
+
label: 'Searches',
|
233
|
+
data: <%= @search_trends.values.to_json.html_safe %>,
|
234
|
+
borderColor: '#0d6efd',
|
235
|
+
backgroundColor: 'rgba(13, 110, 253, 0.1)',
|
236
|
+
fill: true,
|
237
|
+
tension: 0.4
|
238
|
+
}]
|
239
|
+
},
|
240
|
+
options: {
|
241
|
+
responsive: true,
|
242
|
+
maintainAspectRatio: false,
|
243
|
+
plugins: {
|
244
|
+
legend: {
|
245
|
+
display: false
|
246
|
+
}
|
247
|
+
},
|
248
|
+
scales: {
|
249
|
+
y: {
|
250
|
+
beginAtZero: true,
|
251
|
+
ticks: {
|
252
|
+
stepSize: 1
|
253
|
+
}
|
254
|
+
}
|
255
|
+
}
|
256
|
+
}
|
257
|
+
});
|
258
|
+
|
259
|
+
// Search Types Chart
|
260
|
+
const typesCtx = document.getElementById('searchTypesChart').getContext('2d');
|
261
|
+
const searchTypesData = <%= @search_analytics[:search_types].to_json.html_safe %>;
|
262
|
+
new Chart(typesCtx, {
|
263
|
+
type: 'doughnut',
|
264
|
+
data: {
|
265
|
+
labels: Object.keys(searchTypesData).map(type => type.charAt(0).toUpperCase() + type.slice(1)),
|
266
|
+
datasets: [{
|
267
|
+
data: Object.values(searchTypesData),
|
268
|
+
backgroundColor: [
|
269
|
+
'#0d6efd',
|
270
|
+
'#198754',
|
271
|
+
'#ffc107',
|
272
|
+
'#dc3545',
|
273
|
+
'#6f42c1'
|
274
|
+
]
|
275
|
+
}]
|
276
|
+
},
|
277
|
+
options: {
|
278
|
+
responsive: true,
|
279
|
+
maintainAspectRatio: false,
|
280
|
+
plugins: {
|
281
|
+
legend: {
|
282
|
+
position: 'bottom'
|
283
|
+
}
|
284
|
+
}
|
285
|
+
}
|
286
|
+
});
|
287
|
+
|
288
|
+
// Similarity Distribution Chart
|
289
|
+
const similarityCtx = document.getElementById('similarityChart').getContext('2d');
|
290
|
+
const similarityData = <%= @similarity_distribution.to_json.html_safe %>;
|
291
|
+
new Chart(similarityCtx, {
|
292
|
+
type: 'bar',
|
293
|
+
data: {
|
294
|
+
labels: Object.keys(similarityData),
|
295
|
+
datasets: [{
|
296
|
+
label: 'Search Count',
|
297
|
+
data: Object.values(similarityData),
|
298
|
+
backgroundColor: [
|
299
|
+
'#198754',
|
300
|
+
'#20c997',
|
301
|
+
'#0dcaf0',
|
302
|
+
'#6f42c1',
|
303
|
+
'#fd7e14',
|
304
|
+
'#dc3545'
|
305
|
+
]
|
306
|
+
}]
|
307
|
+
},
|
308
|
+
options: {
|
309
|
+
responsive: true,
|
310
|
+
maintainAspectRatio: false,
|
311
|
+
plugins: {
|
312
|
+
legend: {
|
313
|
+
display: false
|
314
|
+
}
|
315
|
+
},
|
316
|
+
scales: {
|
317
|
+
y: {
|
318
|
+
beginAtZero: true,
|
319
|
+
ticks: {
|
320
|
+
stepSize: 1
|
321
|
+
}
|
322
|
+
},
|
323
|
+
x: {
|
324
|
+
title: {
|
325
|
+
display: true,
|
326
|
+
text: 'Similarity Score Range'
|
327
|
+
}
|
328
|
+
}
|
329
|
+
}
|
330
|
+
}
|
331
|
+
});
|
332
|
+
});
|
333
|
+
</script>
|
@@ -0,0 +1,208 @@
|
|
1
|
+
<% content_for :title, "Dashboard - Ragdoll Engine" %>
|
2
|
+
|
3
|
+
<%= render Ragdoll::PageHeaderComponent.new(
|
4
|
+
title: "Ragdoll Engine Dashboard",
|
5
|
+
icon: "fas fa-tachometer-alt",
|
6
|
+
subtitle: "Overview of your document processing and search system"
|
7
|
+
) %>
|
8
|
+
|
9
|
+
<div class="row mb-4">
|
10
|
+
<div class="col-md-3">
|
11
|
+
<%= render Ragdoll::StatsCardComponent.new(
|
12
|
+
title: "Documents",
|
13
|
+
value: @stats[:total_documents],
|
14
|
+
icon: "fas fa-file-alt",
|
15
|
+
color: "primary",
|
16
|
+
description: "Total documents"
|
17
|
+
) %>
|
18
|
+
</div>
|
19
|
+
<div class="col-md-3">
|
20
|
+
<%= render Ragdoll::StatsCardComponent.new(
|
21
|
+
title: "Processed",
|
22
|
+
value: @stats[:processed_documents],
|
23
|
+
icon: "fas fa-check-circle",
|
24
|
+
color: "success",
|
25
|
+
description: "Successfully processed"
|
26
|
+
) %>
|
27
|
+
</div>
|
28
|
+
<div class="col-md-3">
|
29
|
+
<%= render Ragdoll::StatsCardComponent.new(
|
30
|
+
title: "Embeddings",
|
31
|
+
value: @stats[:total_embeddings],
|
32
|
+
icon: "fas fa-vector-square",
|
33
|
+
color: "info",
|
34
|
+
description: "Vector embeddings"
|
35
|
+
) %>
|
36
|
+
</div>
|
37
|
+
<div class="col-md-3">
|
38
|
+
<%= render Ragdoll::StatsCardComponent.new(
|
39
|
+
title: "Searches",
|
40
|
+
value: @stats[:total_searches],
|
41
|
+
icon: "fas fa-search",
|
42
|
+
color: "warning",
|
43
|
+
description: "Total searches"
|
44
|
+
) %>
|
45
|
+
</div>
|
46
|
+
</div>
|
47
|
+
|
48
|
+
<div class="row">
|
49
|
+
<div class="col-md-6">
|
50
|
+
<%= render Ragdoll::CardComponent.new(title: "Document Types", icon: "fas fa-chart-pie") do %>
|
51
|
+
<% if @document_types.any? %>
|
52
|
+
<canvas id="documentTypesChart" width="400" height="200"></canvas>
|
53
|
+
<% else %>
|
54
|
+
<p class="text-muted">No documents yet. <%= link_to "Add your first document", ragdoll.new_document_path, class: "btn btn-primary btn-sm" %></p>
|
55
|
+
<% end %>
|
56
|
+
<% end %>
|
57
|
+
</div>
|
58
|
+
|
59
|
+
<div class="col-md-6">
|
60
|
+
<%= render Ragdoll::CardComponent.new(title: "Most Searched Documents", icon: "fas fa-fire") do %>
|
61
|
+
<% if @top_searched_documents.any? %>
|
62
|
+
<% @top_searched_documents.each do |title, count| %>
|
63
|
+
<div class="d-flex justify-content-between align-items-center mb-2">
|
64
|
+
<span class="text-truncate"><%= title %></span>
|
65
|
+
<span class="badge bg-primary"><%= count %></span>
|
66
|
+
</div>
|
67
|
+
<% end %>
|
68
|
+
<% else %>
|
69
|
+
<p class="text-muted">No search data yet. <%= link_to "Try searching", ragdoll.search_index_path, class: "btn btn-primary btn-sm" %></p>
|
70
|
+
<% end %>
|
71
|
+
<% end %>
|
72
|
+
</div>
|
73
|
+
</div>
|
74
|
+
|
75
|
+
<div class="row mt-4">
|
76
|
+
<div class="col-md-6">
|
77
|
+
<div class="card">
|
78
|
+
<div class="card-header">
|
79
|
+
<h5><i class="fas fa-clock"></i> Recent Documents</h5>
|
80
|
+
</div>
|
81
|
+
<div class="card-body">
|
82
|
+
<% if @recent_documents.any? %>
|
83
|
+
<% @recent_documents.each do |document| %>
|
84
|
+
<div class="d-flex justify-content-between align-items-center mb-2">
|
85
|
+
<div>
|
86
|
+
<%= link_to document.title, ragdoll.document_path(document), class: "text-decoration-none" %>
|
87
|
+
<small class="text-muted d-block">
|
88
|
+
<%= document.document_type&.upcase %> •
|
89
|
+
<%= render Ragdoll::StatusBadgeComponent.new(status: document.status) %>
|
90
|
+
</small>
|
91
|
+
</div>
|
92
|
+
<small class="text-muted"><%= time_ago_in_words(document.created_at) %> ago</small>
|
93
|
+
</div>
|
94
|
+
<% end %>
|
95
|
+
<div class="mt-3">
|
96
|
+
<%= link_to "View all documents", ragdoll.documents_path, class: "btn btn-outline-primary btn-sm" %>
|
97
|
+
</div>
|
98
|
+
<% else %>
|
99
|
+
<p class="text-muted">No documents yet. <%= link_to "Add your first document", ragdoll.new_document_path, class: "btn btn-primary btn-sm" %></p>
|
100
|
+
<% end %>
|
101
|
+
</div>
|
102
|
+
</div>
|
103
|
+
</div>
|
104
|
+
|
105
|
+
<div class="col-md-6">
|
106
|
+
<div class="card">
|
107
|
+
<div class="card-header">
|
108
|
+
<h5><i class="fas fa-history"></i> Recent Searches</h5>
|
109
|
+
</div>
|
110
|
+
<div class="card-body">
|
111
|
+
<% if @stats[:recent_searches].any? %>
|
112
|
+
<% @stats[:recent_searches].each do |search| %>
|
113
|
+
<div class="d-flex justify-content-between align-items-center mb-2">
|
114
|
+
<div>
|
115
|
+
<a href="<%= ragdoll.search_index_path %>?search_id=<%= search.id %>" class="text-decoration-none">
|
116
|
+
<i class="fas fa-search me-1"></i><strong><%= search.query %></strong>
|
117
|
+
</a>
|
118
|
+
<small class="text-muted d-block">
|
119
|
+
<%= search.search_type.titleize %>
|
120
|
+
• <%= search.results_count %> results
|
121
|
+
<% if search.execution_time_ms %>
|
122
|
+
• <%= number_with_precision(search.execution_time_ms / 1000.0, precision: 3) %>s
|
123
|
+
<% end %>
|
124
|
+
</small>
|
125
|
+
</div>
|
126
|
+
<small class="text-muted"><%= time_ago_in_words(search.created_at) %> ago</small>
|
127
|
+
</div>
|
128
|
+
<% end %>
|
129
|
+
<div class="mt-3">
|
130
|
+
<%= link_to "View search analytics", ragdoll.analytics_path, class: "btn btn-outline-primary btn-sm" %>
|
131
|
+
</div>
|
132
|
+
<% else %>
|
133
|
+
<p class="text-muted">No searches yet. <%= link_to "Try searching", ragdoll.search_index_path, class: "btn btn-primary btn-sm" %></p>
|
134
|
+
<% end %>
|
135
|
+
</div>
|
136
|
+
</div>
|
137
|
+
</div>
|
138
|
+
</div>
|
139
|
+
|
140
|
+
<div class="row mt-4">
|
141
|
+
<div class="col-12">
|
142
|
+
<div class="card">
|
143
|
+
<div class="card-header">
|
144
|
+
<h5><i class="fas fa-tools"></i> Quick Actions</h5>
|
145
|
+
</div>
|
146
|
+
<div class="card-body">
|
147
|
+
<div class="row">
|
148
|
+
<div class="col-md-3 mb-2">
|
149
|
+
<%= link_to ragdoll.new_document_path, class: "btn btn-primary w-100" do %>
|
150
|
+
<i class="fas fa-plus"></i> Add Document
|
151
|
+
<% end %>
|
152
|
+
</div>
|
153
|
+
<div class="col-md-3 mb-2">
|
154
|
+
<%= link_to ragdoll.search_index_path, class: "btn btn-success w-100" do %>
|
155
|
+
<i class="fas fa-search"></i> Search Documents
|
156
|
+
<% end %>
|
157
|
+
</div>
|
158
|
+
<div class="col-md-3 mb-2">
|
159
|
+
<%= link_to ragdoll.analytics_path, class: "btn btn-info w-100" do %>
|
160
|
+
<i class="fas fa-chart-line"></i> View Analytics
|
161
|
+
<% end %>
|
162
|
+
</div>
|
163
|
+
<div class="col-md-3 mb-2">
|
164
|
+
<%= link_to ragdoll.configuration_path, class: "btn btn-warning w-100" do %>
|
165
|
+
<i class="fas fa-cog"></i> Configuration
|
166
|
+
<% end %>
|
167
|
+
</div>
|
168
|
+
</div>
|
169
|
+
</div>
|
170
|
+
</div>
|
171
|
+
</div>
|
172
|
+
</div>
|
173
|
+
|
174
|
+
<% if @document_types.any? %>
|
175
|
+
<%= content_for :javascript do %>
|
176
|
+
<script>
|
177
|
+
document.addEventListener('DOMContentLoaded', function() {
|
178
|
+
const ctx = document.getElementById('documentTypesChart').getContext('2d');
|
179
|
+
const documentTypesChart = new Chart(ctx, {
|
180
|
+
type: 'doughnut',
|
181
|
+
data: {
|
182
|
+
labels: <%= @document_types.keys.to_json.html_safe %>,
|
183
|
+
datasets: [{
|
184
|
+
data: <%= @document_types.values.to_json.html_safe %>,
|
185
|
+
backgroundColor: [
|
186
|
+
'#FF6384',
|
187
|
+
'#36A2EB',
|
188
|
+
'#FFCE56',
|
189
|
+
'#4BC0C0',
|
190
|
+
'#9966FF',
|
191
|
+
'#FF9F40'
|
192
|
+
]
|
193
|
+
}]
|
194
|
+
},
|
195
|
+
options: {
|
196
|
+
responsive: true,
|
197
|
+
maintainAspectRatio: false,
|
198
|
+
plugins: {
|
199
|
+
legend: {
|
200
|
+
position: 'bottom'
|
201
|
+
}
|
202
|
+
}
|
203
|
+
}
|
204
|
+
});
|
205
|
+
});
|
206
|
+
</script>
|
207
|
+
<% end %>
|
208
|
+
<% end %>
|
@@ -0,0 +1,91 @@
|
|
1
|
+
<% content_for(:title, "Edit Document: #{@document.title}") %>
|
2
|
+
|
3
|
+
<div class="container-fluid mt-4">
|
4
|
+
<div class="row">
|
5
|
+
<div class="col-md-8 mx-auto">
|
6
|
+
<div class="card">
|
7
|
+
<div class="card-header">
|
8
|
+
<h4 class="card-title mb-0">
|
9
|
+
<i class="fas fa-edit"></i> Edit Document
|
10
|
+
</h4>
|
11
|
+
</div>
|
12
|
+
<div class="card-body">
|
13
|
+
<%= form_with model: @document, url: ragdoll.document_path(@document), local: true do |form| %>
|
14
|
+
<% if @document.errors.any? %>
|
15
|
+
<div class="alert alert-danger" role="alert">
|
16
|
+
<h6 class="alert-heading">Please fix the following errors:</h6>
|
17
|
+
<ul class="mb-0">
|
18
|
+
<% @document.errors.full_messages.each do |message| %>
|
19
|
+
<li><%= message %></li>
|
20
|
+
<% end %>
|
21
|
+
</ul>
|
22
|
+
</div>
|
23
|
+
<% end %>
|
24
|
+
|
25
|
+
<div class="mb-3">
|
26
|
+
<%= form.label :title, class: "form-label" %>
|
27
|
+
<%= form.text_field :title, class: "form-control", placeholder: "Enter document title" %>
|
28
|
+
</div>
|
29
|
+
|
30
|
+
<div class="mb-3">
|
31
|
+
<%= form.label :summary, class: "form-label" %>
|
32
|
+
<%= form.text_area :summary, class: "form-control", rows: 3, placeholder: "Enter document summary (optional)" %>
|
33
|
+
</div>
|
34
|
+
|
35
|
+
<div class="mb-3">
|
36
|
+
<%= form.label :keywords, class: "form-label" %>
|
37
|
+
<%= form.text_field :keywords, class: "form-control", placeholder: "Enter keywords separated by commas" %>
|
38
|
+
<small class="form-text text-muted">Separate keywords with commas</small>
|
39
|
+
</div>
|
40
|
+
|
41
|
+
<div class="mb-3">
|
42
|
+
<%= form.label :status, class: "form-label" %>
|
43
|
+
<%= form.select :status, options_for_select([
|
44
|
+
['Pending', 'pending'],
|
45
|
+
['Processing', 'processing'],
|
46
|
+
['Processed', 'processed'],
|
47
|
+
['Failed', 'failed']
|
48
|
+
], @document.status), {}, class: "form-select" %>
|
49
|
+
</div>
|
50
|
+
|
51
|
+
<% if @document.location.present? %>
|
52
|
+
<div class="mb-3">
|
53
|
+
<label class="form-label">File Location</label>
|
54
|
+
<div class="input-group">
|
55
|
+
<input type="text" class="form-control" value="<%= @document.location %>" readonly>
|
56
|
+
<span class="input-group-text"><i class="fas fa-lock"></i></span>
|
57
|
+
</div>
|
58
|
+
<small class="form-text text-muted">File location cannot be changed</small>
|
59
|
+
</div>
|
60
|
+
<% end %>
|
61
|
+
|
62
|
+
<% if @document.metadata.present? %>
|
63
|
+
<div class="mb-3">
|
64
|
+
<label class="form-label">Metadata</label>
|
65
|
+
<div class="card bg-light">
|
66
|
+
<div class="card-body">
|
67
|
+
<% @document.metadata.each do |key, value| %>
|
68
|
+
<div class="row mb-2">
|
69
|
+
<div class="col-sm-4">
|
70
|
+
<small class="text-muted"><%= key.humanize %>:</small>
|
71
|
+
</div>
|
72
|
+
<div class="col-sm-8">
|
73
|
+
<small><%= value %></small>
|
74
|
+
</div>
|
75
|
+
</div>
|
76
|
+
<% end %>
|
77
|
+
</div>
|
78
|
+
</div>
|
79
|
+
</div>
|
80
|
+
<% end %>
|
81
|
+
|
82
|
+
<div class="d-flex justify-content-between">
|
83
|
+
<%= link_to "Cancel", ragdoll.document_path(@document), class: "btn btn-secondary" %>
|
84
|
+
<%= form.submit "Update Document", class: "btn btn-primary" %>
|
85
|
+
</div>
|
86
|
+
<% end %>
|
87
|
+
</div>
|
88
|
+
</div>
|
89
|
+
</div>
|
90
|
+
</div>
|
91
|
+
</div>
|