ragdoll-rails 0.1.9 → 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.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/ragdoll/application.js +129 -0
  3. data/app/assets/javascripts/ragdoll/bulk_upload_status.js +454 -0
  4. data/app/assets/stylesheets/ragdoll/application.css +84 -0
  5. data/app/assets/stylesheets/ragdoll/bulk_upload_status.css +379 -0
  6. data/app/channels/application_cable/channel.rb +6 -0
  7. data/app/channels/application_cable/connection.rb +6 -0
  8. data/app/channels/ragdoll/bulk_upload_status_channel.rb +27 -0
  9. data/app/channels/ragdoll/file_processing_channel.rb +26 -0
  10. data/app/components/ragdoll/alert_component.html.erb +4 -0
  11. data/app/components/ragdoll/alert_component.rb +32 -0
  12. data/app/components/ragdoll/application_component.rb +6 -0
  13. data/app/components/ragdoll/card_component.html.erb +15 -0
  14. data/app/components/ragdoll/card_component.rb +21 -0
  15. data/app/components/ragdoll/document_list_component.html.erb +41 -0
  16. data/app/components/ragdoll/document_list_component.rb +13 -0
  17. data/app/components/ragdoll/document_table_component.html.erb +76 -0
  18. data/app/components/ragdoll/document_table_component.rb +13 -0
  19. data/app/components/ragdoll/empty_state_component.html.erb +12 -0
  20. data/app/components/ragdoll/empty_state_component.rb +17 -0
  21. data/app/components/ragdoll/flash_messages_component.html.erb +3 -0
  22. data/app/components/ragdoll/flash_messages_component.rb +37 -0
  23. data/app/components/ragdoll/navbar_component.html.erb +24 -0
  24. data/app/components/ragdoll/navbar_component.rb +31 -0
  25. data/app/components/ragdoll/page_header_component.html.erb +13 -0
  26. data/app/components/ragdoll/page_header_component.rb +15 -0
  27. data/app/components/ragdoll/stats_card_component.html.erb +11 -0
  28. data/app/components/ragdoll/stats_card_component.rb +17 -0
  29. data/app/components/ragdoll/status_badge_component.html.erb +3 -0
  30. data/app/components/ragdoll/status_badge_component.rb +30 -0
  31. data/app/controllers/ragdoll/api/v1/analytics_controller.rb +72 -0
  32. data/app/controllers/ragdoll/api/v1/base_controller.rb +29 -0
  33. data/app/controllers/ragdoll/api/v1/documents_controller.rb +148 -0
  34. data/app/controllers/ragdoll/api/v1/search_controller.rb +87 -0
  35. data/app/controllers/ragdoll/api/v1/system_controller.rb +97 -0
  36. data/app/controllers/ragdoll/application_controller.rb +17 -0
  37. data/app/controllers/ragdoll/configuration_controller.rb +82 -0
  38. data/app/controllers/ragdoll/dashboard_controller.rb +98 -0
  39. data/app/controllers/ragdoll/documents_controller.rb +460 -0
  40. data/app/controllers/ragdoll/documents_controller_backup.rb +68 -0
  41. data/app/controllers/ragdoll/jobs_controller.rb +116 -0
  42. data/app/controllers/ragdoll/search_controller.rb +368 -0
  43. data/app/jobs/application_job.rb +9 -0
  44. data/app/jobs/ragdoll/bulk_document_processing_job.rb +280 -0
  45. data/app/jobs/ragdoll/process_file_job.rb +166 -0
  46. data/app/services/ragdoll/worker_health_service.rb +111 -0
  47. data/app/views/layouts/ragdoll/application.html.erb +162 -0
  48. data/app/views/ragdoll/dashboard/analytics.html.erb +333 -0
  49. data/app/views/ragdoll/dashboard/index.html.erb +208 -0
  50. data/app/views/ragdoll/documents/edit.html.erb +91 -0
  51. data/app/views/ragdoll/documents/index.html.erb +302 -0
  52. data/app/views/ragdoll/documents/new.html.erb +1518 -0
  53. data/app/views/ragdoll/documents/show.html.erb +188 -0
  54. data/app/views/ragdoll/documents/upload_results.html.erb +248 -0
  55. data/app/views/ragdoll/jobs/index.html.erb +669 -0
  56. data/app/views/ragdoll/jobs/show.html.erb +129 -0
  57. data/app/views/ragdoll/search/index.html.erb +324 -0
  58. data/config/cable.yml +12 -0
  59. data/config/routes.rb +56 -1
  60. data/lib/ragdoll/rails/engine.rb +32 -1
  61. data/lib/ragdoll/rails/version.rb +1 -1
  62. metadata +86 -1
@@ -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>
@@ -0,0 +1,302 @@
1
+ <% content_for :title, "Documents - Ragdoll Engine" %>
2
+
3
+ <div class="row">
4
+ <div class="col-12">
5
+ <div class="d-flex justify-content-between align-items-center mb-4">
6
+ <h1><i class="fas fa-file-alt"></i> Documents</h1>
7
+ <%= link_to "Add Document", ragdoll.new_document_path, class: "btn btn-primary" %>
8
+ </div>
9
+ </div>
10
+ </div>
11
+
12
+ <div class="row mb-4">
13
+ <div class="col-12">
14
+ <div class="card">
15
+ <div class="card-header">
16
+ <h5><i class="fas fa-filter"></i> Filters</h5>
17
+ </div>
18
+ <div class="card-body">
19
+ <%= form_with url: ragdoll.documents_path, method: :get, local: true, class: "row g-3" do |form| %>
20
+ <div class="col-md-3">
21
+ <%= form.text_field :search, placeholder: "Search documents...", value: params[:search], class: "form-control" %>
22
+ </div>
23
+ <div class="col-md-3">
24
+ <%= form.select :document_type, options_for_select([["All Types", ""]] + @document_types.map { |type| [type.titleize, type] }, params[:document_type]), {}, { class: "form-select" } %>
25
+ </div>
26
+ <div class="col-md-3">
27
+ <%= form.select :status, options_for_select([["All Statuses", ""]] + @statuses.map { |status| [status.titleize, status] }, params[:status]), {}, { class: "form-select" } %>
28
+ </div>
29
+ <div class="col-md-3">
30
+ <%= form.submit "Filter", class: "btn btn-outline-primary" %>
31
+ <%= link_to "Clear", ragdoll.documents_path, class: "btn btn-outline-secondary" %>
32
+ </div>
33
+ <% end %>
34
+ </div>
35
+ </div>
36
+ </div>
37
+ </div>
38
+
39
+ <% if @documents.any? %>
40
+ <div class="row">
41
+ <div class="col-12">
42
+ <div class="card">
43
+ <div class="card-header">
44
+ <div class="d-flex justify-content-between align-items-center">
45
+ <h5 class="mb-0"><i class="fas fa-list"></i> Document List</h5>
46
+ <div class="d-flex align-items-center gap-2">
47
+ <!-- View Toggle Buttons -->
48
+ <div class="btn-group btn-group-sm me-3" role="group" aria-label="View toggle">
49
+ <button type="button" class="btn btn-outline-secondary" id="card-view-btn" onclick="switchToCardView()">
50
+ <i class="fas fa-th-large"></i> Cards
51
+ </button>
52
+ <button type="button" class="btn btn-outline-secondary" id="table-view-btn" onclick="switchToTableView()">
53
+ <i class="fas fa-table"></i> Table
54
+ </button>
55
+ </div>
56
+
57
+ <!-- Bulk Action Buttons -->
58
+ <button class="btn btn-outline-danger btn-sm" onclick="bulkDelete()">
59
+ <i class="fas fa-trash"></i> Delete Selected
60
+ </button>
61
+ <button class="btn btn-outline-warning btn-sm" onclick="bulkReprocess()">
62
+ <i class="fas fa-sync"></i> Reprocess Selected
63
+ </button>
64
+ </div>
65
+ </div>
66
+ </div>
67
+ <div class="card-body">
68
+ <!-- Card View -->
69
+ <div id="card-view" style="display: block;">
70
+ <%= render Ragdoll::DocumentListComponent.new(documents: @documents) %>
71
+ </div>
72
+
73
+ <!-- Table View -->
74
+ <div id="table-view" style="display: none;">
75
+ <%= render Ragdoll::DocumentTableComponent.new(documents: @documents) %>
76
+ </div>
77
+ </div>
78
+ </div>
79
+ </div>
80
+ </div>
81
+ <% else %>
82
+ <div class="row">
83
+ <div class="col-12">
84
+ <%= render Ragdoll::EmptyStateComponent.new(
85
+ title: "No documents found",
86
+ message: "Get started by adding your first document to the system.",
87
+ icon: "fas fa-file-alt",
88
+ action_path: ragdoll.new_document_path,
89
+ action_text: "Add Document"
90
+ ) %>
91
+ </div>
92
+ </div>
93
+ <% end %>
94
+
95
+ <%= content_for :javascript do %>
96
+ <script>
97
+ // View switching functions
98
+ function switchToCardView() {
99
+ document.getElementById('card-view').style.display = 'block';
100
+ document.getElementById('table-view').style.display = 'none';
101
+ document.getElementById('card-view-btn').classList.add('active');
102
+ document.getElementById('table-view-btn').classList.remove('active');
103
+ localStorage.setItem('ragdoll-document-view', 'card');
104
+ }
105
+
106
+ function switchToTableView() {
107
+ document.getElementById('card-view').style.display = 'none';
108
+ document.getElementById('table-view').style.display = 'block';
109
+ document.getElementById('table-view-btn').classList.add('active');
110
+ document.getElementById('card-view-btn').classList.remove('active');
111
+ localStorage.setItem('ragdoll-document-view', 'table');
112
+ }
113
+
114
+ // Attach event listeners to document checkboxes
115
+ function attachCheckboxListeners() {
116
+ document.querySelectorAll('.document-checkbox').forEach(checkbox => {
117
+ checkbox.removeEventListener('change', updateSelectAllState); // Remove first to avoid duplicates
118
+ checkbox.addEventListener('change', updateSelectAllState);
119
+ });
120
+ updateSelectAllState(); // Initialize state
121
+ }
122
+
123
+ // Initialize view based on saved preference
124
+ document.addEventListener('DOMContentLoaded', function() {
125
+ const savedView = localStorage.getItem('ragdoll-document-view') || 'card';
126
+ if (savedView === 'table') {
127
+ switchToTableView();
128
+ } else {
129
+ switchToCardView();
130
+ }
131
+
132
+ // Attach listeners after view is set
133
+ setTimeout(attachCheckboxListeners, 100);
134
+ });
135
+
136
+ function toggleAll(source) {
137
+ // Get all document checkboxes
138
+ const checkboxes = document.querySelectorAll('.document-checkbox');
139
+ const selectAllCard = document.getElementById('select-all');
140
+ const selectAllTable = document.getElementById('select-all-table');
141
+
142
+ // Set all checkboxes to match the source checkbox state
143
+ checkboxes.forEach(checkbox => {
144
+ checkbox.checked = source.checked;
145
+ });
146
+
147
+ // Sync both select-all checkboxes
148
+ if (selectAllCard) {
149
+ selectAllCard.checked = source.checked;
150
+ selectAllCard.indeterminate = false;
151
+ }
152
+ if (selectAllTable) {
153
+ selectAllTable.checked = source.checked;
154
+ selectAllTable.indeterminate = false;
155
+ }
156
+ }
157
+
158
+ function updateSelectAllState() {
159
+ // Get all document checkboxes
160
+ const checkboxes = document.querySelectorAll('.document-checkbox');
161
+ const selectAllCard = document.getElementById('select-all');
162
+ const selectAllTable = document.getElementById('select-all-table');
163
+
164
+ if (checkboxes.length === 0) return;
165
+
166
+ // Count checked boxes
167
+ const checkedCount = Array.from(checkboxes).filter(cb => cb.checked).length;
168
+
169
+ // Determine state
170
+ let isChecked = false;
171
+ let isIndeterminate = false;
172
+
173
+ if (checkedCount === 0) {
174
+ // None checked
175
+ isChecked = false;
176
+ isIndeterminate = false;
177
+ } else if (checkedCount === checkboxes.length) {
178
+ // All checked
179
+ isChecked = true;
180
+ isIndeterminate = false;
181
+ } else {
182
+ // Some checked
183
+ isChecked = false;
184
+ isIndeterminate = true;
185
+ }
186
+
187
+ // Update both select-all checkboxes
188
+ if (selectAllCard) {
189
+ selectAllCard.checked = isChecked;
190
+ selectAllCard.indeterminate = isIndeterminate;
191
+ }
192
+ if (selectAllTable) {
193
+ selectAllTable.checked = isChecked;
194
+ selectAllTable.indeterminate = isIndeterminate;
195
+ }
196
+ }
197
+
198
+
199
+ function reprocessDocument(documentId) {
200
+ if (confirm('Are you sure you want to reprocess this document?')) {
201
+ // Create a form with proper CSRF token for individual reprocess action
202
+ const form = document.createElement('form');
203
+ form.method = 'POST';
204
+ form.action = `<%= ragdoll.documents_path %>/${documentId}/reprocess`;
205
+
206
+ // Add CSRF token
207
+ const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
208
+ const csrfInput = document.createElement('input');
209
+ csrfInput.type = 'hidden';
210
+ csrfInput.name = 'authenticity_token';
211
+ csrfInput.value = csrfToken;
212
+ form.appendChild(csrfInput);
213
+
214
+ document.body.appendChild(form);
215
+ form.submit();
216
+ }
217
+ }
218
+
219
+ function bulkDelete() {
220
+ const checkedBoxes = document.querySelectorAll('.document-checkbox:checked');
221
+ if (checkedBoxes.length === 0) {
222
+ alert('Please select at least one document to delete.');
223
+ return;
224
+ }
225
+
226
+ if (confirm(`Are you sure you want to delete ${checkedBoxes.length} selected documents?`)) {
227
+ // Create a new form with proper CSRF token for delete action
228
+ const form = document.createElement('form');
229
+ form.method = 'POST';
230
+ form.action = '<%= ragdoll.bulk_delete_documents_path %>';
231
+
232
+ // Add CSRF token
233
+ const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
234
+ const csrfInput = document.createElement('input');
235
+ csrfInput.type = 'hidden';
236
+ csrfInput.name = 'authenticity_token';
237
+ csrfInput.value = csrfToken;
238
+ form.appendChild(csrfInput);
239
+
240
+ // Add selected document IDs
241
+ checkedBoxes.forEach(checkbox => {
242
+ const input = document.createElement('input');
243
+ input.type = 'hidden';
244
+ input.name = 'document_ids[]';
245
+ input.value = checkbox.value;
246
+ form.appendChild(input);
247
+ });
248
+
249
+ document.body.appendChild(form);
250
+ form.submit();
251
+ }
252
+ }
253
+
254
+ function bulkReprocess() {
255
+ const checkedBoxes = document.querySelectorAll('.document-checkbox:checked');
256
+ if (checkedBoxes.length === 0) {
257
+ alert('Please select at least one document to reprocess.');
258
+ return;
259
+ }
260
+
261
+ if (confirm(`Are you sure you want to reprocess ${checkedBoxes.length} selected documents?`)) {
262
+ // Create a new form with proper CSRF token for reprocess action
263
+ const form = document.createElement('form');
264
+ form.method = 'POST';
265
+ form.action = '<%= ragdoll.bulk_reprocess_documents_path %>';
266
+
267
+ // Add CSRF token
268
+ const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
269
+ const csrfInput = document.createElement('input');
270
+ csrfInput.type = 'hidden';
271
+ csrfInput.name = 'authenticity_token';
272
+ csrfInput.value = csrfToken;
273
+ form.appendChild(csrfInput);
274
+
275
+ // Add selected document IDs
276
+ checkedBoxes.forEach(checkbox => {
277
+ const input = document.createElement('input');
278
+ input.type = 'hidden';
279
+ input.name = 'document_ids[]';
280
+ input.value = checkbox.value;
281
+ form.appendChild(input);
282
+ });
283
+
284
+ document.body.appendChild(form);
285
+ form.submit();
286
+ }
287
+ }
288
+
289
+ // Initialize Bootstrap tooltips
290
+ document.addEventListener('DOMContentLoaded', function () {
291
+ var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
292
+ var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
293
+ return new bootstrap.Tooltip(tooltipTriggerEl, {
294
+ delay: { "show": 500, "hide": 100 },
295
+ placement: 'top',
296
+ boundary: 'viewport',
297
+ fallbackPlacements: ['top', 'bottom']
298
+ });
299
+ });
300
+ });
301
+ </script>
302
+ <% end %>