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,188 @@
1
+ <% content_for(:title, "Document: #{@document.title}") %>
2
+
3
+ <div class="container-fluid mt-4">
4
+ <div class="row">
5
+ <div class="col-md-8">
6
+ <!-- Document Details -->
7
+ <div class="card mb-4">
8
+ <div class="card-header d-flex justify-content-between align-items-center">
9
+ <h4 class="card-title mb-0">
10
+ <i class="fas fa-file-alt"></i> <%= @document.title %>
11
+ </h4>
12
+ <span class="badge bg-<%= @document.status == 'processed' ? 'success' : @document.status == 'failed' ? 'danger' : 'warning' %> fs-6">
13
+ <%= @document.status.humanize %>
14
+ </span>
15
+ </div>
16
+ <div class="card-body">
17
+ <div class="row">
18
+ <div class="col-md-6">
19
+ <p><strong>Document Type:</strong> <%= @document.document_type %></p>
20
+ <p><strong>Created:</strong> <%= @document.created_at.strftime("%B %d, %Y at %I:%M %p") %></p>
21
+ <p><strong>Last Modified:</strong> <%= @document.file_modified_at&.strftime("%B %d, %Y at %I:%M %p") || "N/A" %></p>
22
+ </div>
23
+ <div class="col-md-6">
24
+ <p><strong>File Size:</strong> <%= number_to_human_size(@document.metadata['file_size']) if @document.metadata && @document.metadata['file_size'] %></p>
25
+ <p><strong>Encoding:</strong> <%= @document.metadata['encoding'] if @document.metadata && @document.metadata['encoding'] %></p>
26
+ <p><strong>Location:</strong> <code class="text-muted small"><%= @document.location %></code></p>
27
+ </div>
28
+ </div>
29
+
30
+ <% if @document.summary.present? %>
31
+ <div class="mt-3">
32
+ <h6><strong>Summary:</strong></h6>
33
+ <p class="text-muted"><%= @document.summary %></p>
34
+ </div>
35
+ <% end %>
36
+
37
+ <% if @document.keywords.present? %>
38
+ <div class="mt-3">
39
+ <h6><strong>Keywords:</strong></h6>
40
+ <% @document.keywords.each do |keyword| %>
41
+ <span class="badge bg-light text-dark me-1"><%= keyword %></span>
42
+ <% end %>
43
+ </div>
44
+ <% end %>
45
+ </div>
46
+ <div class="card-footer">
47
+ <div class="btn-group" role="group">
48
+ <%= link_to "Edit", ragdoll.edit_document_path(@document), class: "btn btn-outline-primary" %>
49
+ <%= link_to "Reprocess", ragdoll.reprocess_document_path(@document), method: :post, class: "btn btn-outline-warning", data: { confirm: "Are you sure you want to reprocess this document?" } %>
50
+ <%= link_to "Download", ragdoll.download_document_path(@document), class: "btn btn-outline-info" %>
51
+ <%= link_to "Delete", ragdoll.document_path(@document), method: :delete, class: "btn btn-outline-danger", data: { confirm: "Are you sure you want to delete '#{@document.title}'?\n\nThis action cannot be undone.", turbo_method: :delete } %>
52
+ <%= link_to "Back to Documents", ragdoll.documents_path, class: "btn btn-secondary" %>
53
+ </div>
54
+ </div>
55
+ </div>
56
+
57
+ <!-- Embeddings -->
58
+ <% if @embeddings && @embeddings.any? %>
59
+ <div class="card">
60
+ <div class="card-header">
61
+ <h5 class="card-title mb-0">
62
+ <i class="fas fa-vector-square"></i> Embeddings
63
+ <span class="badge bg-info"><%= @embeddings.count %></span>
64
+ </h5>
65
+ </div>
66
+ <div class="card-body">
67
+ <div class="table-responsive">
68
+ <table class="table table-sm">
69
+ <thead>
70
+ <tr>
71
+ <th>ID</th>
72
+ <th>Content Preview</th>
73
+ <th>Vector Dimensions</th>
74
+ <th>Created</th>
75
+ </tr>
76
+ </thead>
77
+ <tbody>
78
+ <% @embeddings.limit(10).each do |embedding| %>
79
+ <tr>
80
+ <td><code><%= embedding.id %></code></td>
81
+ <td>
82
+ <%= truncate(embedding.content, length: 100) %>
83
+ </td>
84
+ <td>
85
+ <span class="badge bg-secondary">
86
+ <%= embedding.embedding_vector&.size || "N/A" %>
87
+ </span>
88
+ </td>
89
+ <td>
90
+ <small class="text-muted">
91
+ <%= time_ago_in_words(embedding.created_at) %> ago
92
+ </small>
93
+ </td>
94
+ </tr>
95
+ <% end %>
96
+ </tbody>
97
+ </table>
98
+ </div>
99
+ <% if @embeddings.count > 10 %>
100
+ <p class="text-muted small mt-2">
101
+ Showing first 10 of <%= @embeddings.count %> embeddings.
102
+ </p>
103
+ <% end %>
104
+ </div>
105
+ </div>
106
+ <% else %>
107
+ <div class="card">
108
+ <div class="card-body text-center text-muted">
109
+ <i class="fas fa-info-circle fa-2x mb-3"></i>
110
+ <p>No embeddings available for this document.</p>
111
+ <p class="small">Embeddings will be generated when the document is processed.</p>
112
+ </div>
113
+ </div>
114
+ <% end %>
115
+ </div>
116
+
117
+ <!-- Sidebar -->
118
+ <div class="col-md-4">
119
+ <!-- Document Metadata -->
120
+ <div class="card mb-4">
121
+ <div class="card-header">
122
+ <h6 class="card-title mb-0">
123
+ <i class="fas fa-info-circle"></i> Metadata
124
+ </h6>
125
+ </div>
126
+ <div class="card-body">
127
+ <% if @document.metadata.present? %>
128
+ <% @document.metadata.each do |key, value| %>
129
+ <div class="row mb-2">
130
+ <div class="col-5">
131
+ <small class="text-muted"><%= key.humanize %>:</small>
132
+ </div>
133
+ <div class="col-7">
134
+ <small>
135
+ <% if value.is_a?(String) && value.length > 30 %>
136
+ <span title="<%= value %>"><%= truncate(value, length: 30) %></span>
137
+ <% else %>
138
+ <%= value %>
139
+ <% end %>
140
+ </small>
141
+ </div>
142
+ </div>
143
+ <% end %>
144
+ <% else %>
145
+ <p class="text-muted small">No metadata available.</p>
146
+ <% end %>
147
+ </div>
148
+ </div>
149
+
150
+ <!-- Recent Searches -->
151
+ <% if @recent_searches && @recent_searches.any? %>
152
+ <div class="card">
153
+ <div class="card-header">
154
+ <h6 class="card-title mb-0">
155
+ <i class="fas fa-search"></i> Recent Searches
156
+ </h6>
157
+ </div>
158
+ <div class="card-body">
159
+ <% @recent_searches.each do |search| %>
160
+ <div class="mb-2">
161
+ <small>
162
+ <%= link_to search.query, ragdoll.search_index_path(query: search.query), class: "text-decoration-none" %>
163
+ <span class="text-muted">
164
+ - <%= time_ago_in_words(search.created_at) %> ago
165
+ </span>
166
+ </small>
167
+ </div>
168
+ <% end %>
169
+ </div>
170
+ </div>
171
+ <% end %>
172
+
173
+ <!-- Quick Actions -->
174
+ <div class="card mt-4">
175
+ <div class="card-header">
176
+ <h6 class="card-title mb-0">
177
+ <i class="fas fa-bolt"></i> Quick Actions
178
+ </h6>
179
+ </div>
180
+ <div class="card-body">
181
+ <%= link_to "Search this Document", ragdoll.search_index_path(query: @document.title), class: "btn btn-outline-primary btn-sm w-100 mb-2" %>
182
+ <%= link_to "View Similar Documents", ragdoll.documents_path(document_type: @document.document_type), class: "btn btn-outline-secondary btn-sm w-100 mb-2" %>
183
+ <%= link_to "Upload New Document", ragdoll.new_document_path, class: "btn btn-outline-success btn-sm w-100" %>
184
+ </div>
185
+ </div>
186
+ </div>
187
+ </div>
188
+ </div>
@@ -0,0 +1,248 @@
1
+ <% content_for(:title, "Upload Results") %>
2
+
3
+ <%= render Ragdoll::PageHeaderComponent.new(
4
+ title: "Upload Results",
5
+ subtitle: "Review the results of your document upload",
6
+ icon: "fas fa-check-circle"
7
+ ) %>
8
+
9
+ <div class="row">
10
+ <div class="col-lg-8">
11
+ <div class="card">
12
+ <div class="card-header d-flex justify-content-between align-items-center">
13
+ <h5 class="card-title mb-0">
14
+ <i class="fas fa-list-alt"></i> Upload Summary
15
+ </h5>
16
+ <div>
17
+ <% successful_count = @results&.count { |r| r[:success] } || 0 %>
18
+ <% failed_count = (@results&.length || 0) - successful_count %>
19
+ <% duplicate_count = @results&.count { |r| r[:success] && r[:duplicate] } || 0 %>
20
+ <% new_count = successful_count - duplicate_count %>
21
+
22
+ <span class="badge bg-success me-1"><%= new_count %> new</span>
23
+ <% if duplicate_count > 0 %>
24
+ <span class="badge bg-warning me-1"><%= duplicate_count %> duplicate<%= duplicate_count > 1 ? 's' : '' %></span>
25
+ <% end %>
26
+ <% if failed_count > 0 %>
27
+ <span class="badge bg-danger"><%= failed_count %> failed</span>
28
+ <% end %>
29
+ </div>
30
+ </div>
31
+ <div class="card-body">
32
+ <% if @results&.any? %>
33
+ <% @results.each_with_index do |result, index| %>
34
+ <div class="result-item border-bottom py-3 <%= 'border-bottom-0' if index == @results.count - 1 %>">
35
+ <div class="d-flex align-items-start">
36
+ <div class="result-icon me-3 mt-1">
37
+ <% if result[:success] %>
38
+ <% if result[:duplicate] && !result[:forced] %>
39
+ <i class="fas fa-copy fa-lg text-warning" title="Duplicate detected"></i>
40
+ <% elsif result[:duplicate] && result[:forced] %>
41
+ <i class="fas fa-check-circle fa-lg text-success" title="Duplicate forced"></i>
42
+ <% else %>
43
+ <i class="fas fa-check-circle fa-lg text-success" title="Successfully added"></i>
44
+ <% end %>
45
+ <% else %>
46
+ <i class="fas fa-times-circle fa-lg text-danger" title="Failed"></i>
47
+ <% end %>
48
+ </div>
49
+
50
+ <div class="flex-grow-1">
51
+ <div class="d-flex justify-content-between align-items-start mb-2">
52
+ <h6 class="mb-0">
53
+ <i class="fas fa-file me-1"></i>
54
+ <%= result[:file] %>
55
+ </h6>
56
+ <% if result[:success] %>
57
+ <% if result[:duplicate] && !result[:forced] %>
58
+ <span class="badge bg-warning">
59
+ <i class="fas fa-copy me-1"></i>Duplicate Detected
60
+ </span>
61
+ <% elsif result[:duplicate] && result[:forced] %>
62
+ <span class="badge bg-success">
63
+ <i class="fas fa-exclamation-triangle me-1"></i>Duplicate Forced
64
+ </span>
65
+ <% else %>
66
+ <%= render Ragdoll::StatusBadgeComponent.new(status: "success") %>
67
+ <% end %>
68
+ <% else %>
69
+ <%= render Ragdoll::StatusBadgeComponent.new(status: "failed") %>
70
+ <% end %>
71
+ </div>
72
+
73
+ <% if result[:success] %>
74
+ <% if result[:document] %>
75
+ <p class="text-muted mb-2">
76
+ <% if result[:duplicate] && !result[:forced] %>
77
+ <strong>Duplicate detected:</strong> This document already exists in the system. The existing document was returned instead of creating a duplicate.
78
+ <% elsif result[:duplicate] && result[:forced] %>
79
+ <strong>Duplicate forced:</strong> A new document was created despite an existing duplicate in the system.
80
+ <% else %>
81
+ Document successfully added to the system.
82
+ <% end %>
83
+ <% if result[:message] %>
84
+ <br><em><%= result[:message] %></em>
85
+ <% end %>
86
+ </p>
87
+ <div class="small text-muted">
88
+ <i class="fas fa-id-badge me-1"></i>
89
+ Document ID: <%= result[:document].id %>
90
+ <span class="mx-2">•</span>
91
+ <i class="fas fa-clock me-1"></i>
92
+ Added <%= time_ago_in_words(result[:document].created_at) %> ago
93
+ </div>
94
+ <div class="mt-2">
95
+ <%= link_to ragdoll.document_path(result[:document]),
96
+ class: "btn btn-sm btn-outline-primary" do %>
97
+ <i class="fas fa-eye me-1"></i> View Document
98
+ <% end %>
99
+ </div>
100
+ <% else %>
101
+ <p class="text-success mb-0">
102
+ File processed successfully.
103
+ <% if result[:message] %>
104
+ <%= result[:message] %>
105
+ <% end %>
106
+ </p>
107
+ <% end %>
108
+ <% else %>
109
+ <div class="alert alert-danger mb-0">
110
+ <i class="fas fa-exclamation-triangle me-2"></i>
111
+ <strong>Error:</strong> <%= result[:error] %>
112
+ </div>
113
+ <% end %>
114
+ </div>
115
+ </div>
116
+ </div>
117
+ <% end %>
118
+ <% else %>
119
+ <%= render Ragdoll::EmptyStateComponent.new(
120
+ title: "No results",
121
+ message: "No upload results to display.",
122
+ icon: "fas fa-upload"
123
+ ) %>
124
+ <% end %>
125
+ </div>
126
+
127
+ <% if @results&.any? %>
128
+ <div class="card-footer bg-light">
129
+ <div class="d-flex justify-content-between align-items-center">
130
+ <div class="small text-muted">
131
+ Upload completed at <%= Time.current.strftime("%B %d, %Y at %I:%M %p") %>
132
+ </div>
133
+ <div class="btn-group" role="group">
134
+ <%= link_to ragdoll.new_document_path, class: "btn btn-outline-primary btn-sm" do %>
135
+ <i class="fas fa-plus me-1"></i> Upload More
136
+ <% end %>
137
+ <%= link_to ragdoll.documents_path, class: "btn btn-primary btn-sm" do %>
138
+ <i class="fas fa-list me-1"></i> View All Documents
139
+ <% end %>
140
+ </div>
141
+ </div>
142
+ </div>
143
+ <% end %>
144
+ </div>
145
+ </div>
146
+
147
+ <!-- Sidebar -->
148
+ <div class="col-lg-4">
149
+ <!-- Next Steps -->
150
+ <div class="card mb-4">
151
+ <div class="card-header">
152
+ <h6 class="card-title mb-0">
153
+ <i class="fas fa-arrow-right"></i> Next Steps
154
+ </h6>
155
+ </div>
156
+ <div class="card-body">
157
+ <% if @results&.any? { |r| r[:success] } %>
158
+ <ul class="list-unstyled mb-0">
159
+ <li class="mb-2">
160
+ <i class="fas fa-check text-success me-2"></i>
161
+ Documents are being processed in the background
162
+ </li>
163
+ <li class="mb-2">
164
+ <i class="fas fa-robot text-info me-2"></i>
165
+ AI embeddings will be generated automatically
166
+ </li>
167
+ <li class="mb-2">
168
+ <i class="fas fa-search text-primary me-2"></i>
169
+ Documents will be available for search once processed
170
+ </li>
171
+ <li>
172
+ <i class="fas fa-chart-bar text-warning me-2"></i>
173
+ Monitor processing status in the dashboard
174
+ </li>
175
+ </ul>
176
+ <% else %>
177
+ <p class="text-muted mb-3">
178
+ All uploads failed. Please check the error messages and try again.
179
+ </p>
180
+ <%= link_to ragdoll.new_document_path, class: "btn btn-primary btn-sm w-100" do %>
181
+ <i class="fas fa-retry me-1"></i> Try Again
182
+ <% end %>
183
+ <% end %>
184
+ </div>
185
+ </div>
186
+
187
+ <!-- Processing Status -->
188
+ <% if @results&.any? { |r| r[:success] } %>
189
+ <div class="card">
190
+ <div class="card-header">
191
+ <h6 class="card-title mb-0">
192
+ <i class="fas fa-cogs"></i> Processing Status
193
+ </h6>
194
+ </div>
195
+ <div class="card-body">
196
+ <p class="small text-muted mb-3">
197
+ Your documents are being processed. You can monitor the progress in real-time.
198
+ </p>
199
+
200
+ <div class="d-grid gap-2">
201
+ <%= link_to ragdoll.jobs_path, class: "btn btn-outline-info btn-sm" do %>
202
+ <i class="fas fa-tasks me-1"></i> View Job Queue
203
+ <% end %>
204
+
205
+ <%= link_to ragdoll.dashboard_index_path, class: "btn btn-outline-secondary btn-sm" do %>
206
+ <i class="fas fa-tachometer-alt me-1"></i> Dashboard
207
+ <% end %>
208
+ </div>
209
+ </div>
210
+ </div>
211
+ <% end %>
212
+ </div>
213
+ </div>
214
+
215
+ <script>
216
+ // Auto-refresh successful documents to check processing status
217
+ <% if @results&.any? { |r| r[:success] && r[:document] } %>
218
+ document.addEventListener('DOMContentLoaded', function() {
219
+ // Refresh every 30 seconds to check processing status
220
+ setInterval(function() {
221
+ if (document.visibilityState === 'visible') {
222
+ // Only refresh if page is visible
223
+ const statusElements = document.querySelectorAll('[data-document-id]');
224
+ statusElements.forEach(function(element) {
225
+ // Could add AJAX calls here to update document status
226
+ });
227
+ }
228
+ }, 30000);
229
+ });
230
+ <% end %>
231
+ </script>
232
+
233
+ <style>
234
+ .result-item {
235
+ transition: background-color 0.2s ease;
236
+ }
237
+
238
+ .result-item:hover {
239
+ background-color: #f8f9fa;
240
+ border-radius: 0.375rem;
241
+ margin: -0.5rem;
242
+ padding: 1rem !important;
243
+ }
244
+
245
+ .result-icon {
246
+ min-width: 24px;
247
+ }
248
+ </style>