ragdoll-rails 0.1.9 → 0.1.12

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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -2
  3. data/app/assets/javascripts/ragdoll/application.js +129 -0
  4. data/app/assets/javascripts/ragdoll/bulk_upload_status.js +454 -0
  5. data/app/assets/stylesheets/ragdoll/application.css +84 -0
  6. data/app/assets/stylesheets/ragdoll/bulk_upload_status.css +379 -0
  7. data/app/channels/application_cable/channel.rb +6 -0
  8. data/app/channels/application_cable/connection.rb +6 -0
  9. data/app/channels/ragdoll/bulk_upload_status_channel.rb +27 -0
  10. data/app/channels/ragdoll/file_processing_channel.rb +26 -0
  11. data/app/components/ragdoll/alert_component.html.erb +4 -0
  12. data/app/components/ragdoll/alert_component.rb +32 -0
  13. data/app/components/ragdoll/application_component.rb +6 -0
  14. data/app/components/ragdoll/card_component.html.erb +15 -0
  15. data/app/components/ragdoll/card_component.rb +21 -0
  16. data/app/components/ragdoll/document_list_component.html.erb +41 -0
  17. data/app/components/ragdoll/document_list_component.rb +13 -0
  18. data/app/components/ragdoll/document_table_component.html.erb +76 -0
  19. data/app/components/ragdoll/document_table_component.rb +13 -0
  20. data/app/components/ragdoll/empty_state_component.html.erb +12 -0
  21. data/app/components/ragdoll/empty_state_component.rb +17 -0
  22. data/app/components/ragdoll/flash_messages_component.html.erb +3 -0
  23. data/app/components/ragdoll/flash_messages_component.rb +37 -0
  24. data/app/components/ragdoll/navbar_component.html.erb +24 -0
  25. data/app/components/ragdoll/navbar_component.rb +31 -0
  26. data/app/components/ragdoll/page_header_component.html.erb +13 -0
  27. data/app/components/ragdoll/page_header_component.rb +15 -0
  28. data/app/components/ragdoll/stats_card_component.html.erb +11 -0
  29. data/app/components/ragdoll/stats_card_component.rb +17 -0
  30. data/app/components/ragdoll/status_badge_component.html.erb +3 -0
  31. data/app/components/ragdoll/status_badge_component.rb +30 -0
  32. data/app/controllers/ragdoll/api/v1/analytics_controller.rb +72 -0
  33. data/app/controllers/ragdoll/api/v1/base_controller.rb +29 -0
  34. data/app/controllers/ragdoll/api/v1/documents_controller.rb +148 -0
  35. data/app/controllers/ragdoll/api/v1/search_controller.rb +87 -0
  36. data/app/controllers/ragdoll/api/v1/system_controller.rb +97 -0
  37. data/app/controllers/ragdoll/application_controller.rb +17 -0
  38. data/app/controllers/ragdoll/configuration_controller.rb +82 -0
  39. data/app/controllers/ragdoll/dashboard_controller.rb +98 -0
  40. data/app/controllers/ragdoll/documents_controller.rb +460 -0
  41. data/app/controllers/ragdoll/documents_controller_backup.rb +68 -0
  42. data/app/controllers/ragdoll/jobs_controller.rb +116 -0
  43. data/app/controllers/ragdoll/search_controller.rb +374 -0
  44. data/app/jobs/application_job.rb +9 -0
  45. data/app/jobs/ragdoll/bulk_document_processing_job.rb +280 -0
  46. data/app/jobs/ragdoll/process_file_job.rb +166 -0
  47. data/app/services/ragdoll/worker_health_service.rb +111 -0
  48. data/app/views/layouts/ragdoll/application.html.erb +162 -0
  49. data/app/views/ragdoll/dashboard/analytics.html.erb +333 -0
  50. data/app/views/ragdoll/dashboard/index.html.erb +208 -0
  51. data/app/views/ragdoll/documents/edit.html.erb +91 -0
  52. data/app/views/ragdoll/documents/index.html.erb +305 -0
  53. data/app/views/ragdoll/documents/new.html.erb +1518 -0
  54. data/app/views/ragdoll/documents/show.html.erb +188 -0
  55. data/app/views/ragdoll/documents/upload_results.html.erb +248 -0
  56. data/app/views/ragdoll/jobs/index.html.erb +669 -0
  57. data/app/views/ragdoll/jobs/show.html.erb +129 -0
  58. data/app/views/ragdoll/search/index.html.erb +327 -0
  59. data/config/cable.yml +12 -0
  60. data/config/routes.rb +56 -1
  61. data/lib/generators/ragdoll/init/templates/ragdoll_config.rb +17 -4
  62. data/lib/ragdoll/rails/configuration.rb +2 -1
  63. data/lib/ragdoll/rails/engine.rb +32 -1
  64. data/lib/ragdoll/rails/version.rb +1 -1
  65. metadata +90 -4
@@ -0,0 +1,129 @@
1
+ <% content_for(:title, "Job Details") %>
2
+
3
+ <%= render Ragdoll::PageHeaderComponent.new(
4
+ title: "Job Details",
5
+ subtitle: "View job information and execution details",
6
+ icon: "fas fa-info-circle"
7
+ ) %>
8
+
9
+ <div class="row">
10
+ <div class="col-md-8">
11
+ <div class="card">
12
+ <div class="card-header">
13
+ <h5 class="card-title mb-0">
14
+ <i class="fas fa-cog"></i> Job Information
15
+ </h5>
16
+ </div>
17
+ <div class="card-body">
18
+ <dl class="row">
19
+ <dt class="col-sm-3">Job ID:</dt>
20
+ <dd class="col-sm-9"><%= @job.id %></dd>
21
+
22
+ <dt class="col-sm-3">Class:</dt>
23
+ <dd class="col-sm-9"><code><%= @job.class_name %></code></dd>
24
+
25
+ <dt class="col-sm-3">Status:</dt>
26
+ <dd class="col-sm-9">
27
+ <% if @job.finished_at %>
28
+ <%= render Ragdoll::StatusBadgeComponent.new(status: "completed") %>
29
+ <% else %>
30
+ <%= render Ragdoll::StatusBadgeComponent.new(status: "pending") %>
31
+ <% end %>
32
+ </dd>
33
+
34
+ <dt class="col-sm-3">Priority:</dt>
35
+ <dd class="col-sm-9"><%= @job.priority %></dd>
36
+
37
+ <dt class="col-sm-3">Queue:</dt>
38
+ <dd class="col-sm-9"><%= @job.queue_name %></dd>
39
+
40
+ <dt class="col-sm-3">Created:</dt>
41
+ <dd class="col-sm-9">
42
+ <%= @job.created_at.strftime("%B %d, %Y at %I:%M %p") %>
43
+ <small class="text-muted">(<%= time_ago_in_words(@job.created_at) %> ago)</small>
44
+ </dd>
45
+
46
+ <% if @job.scheduled_at %>
47
+ <dt class="col-sm-3">Scheduled For:</dt>
48
+ <dd class="col-sm-9">
49
+ <%= @job.scheduled_at.strftime("%B %d, %Y at %I:%M %p") %>
50
+ <% if @job.scheduled_at > Time.current %>
51
+ <small class="text-muted">(in <%= time_ago_in_words(@job.scheduled_at) %>)</small>
52
+ <% end %>
53
+ </dd>
54
+ <% end %>
55
+
56
+ <% if @job.finished_at %>
57
+ <dt class="col-sm-3">Finished:</dt>
58
+ <dd class="col-sm-9">
59
+ <%= @job.finished_at.strftime("%B %d, %Y at %I:%M %p") %>
60
+ <small class="text-muted">(<%= time_ago_in_words(@job.finished_at) %> ago)</small>
61
+ </dd>
62
+
63
+ <dt class="col-sm-3">Duration:</dt>
64
+ <dd class="col-sm-9">
65
+ <%= distance_of_time_in_words(@job.created_at, @job.finished_at) %>
66
+ </dd>
67
+ <% end %>
68
+ </dl>
69
+ </div>
70
+ </div>
71
+
72
+ <% if @job.arguments.present? %>
73
+ <div class="card mt-4">
74
+ <div class="card-header">
75
+ <h5 class="card-title mb-0">
76
+ <i class="fas fa-list"></i> Arguments
77
+ </h5>
78
+ </div>
79
+ <div class="card-body">
80
+ <pre class="bg-light p-3 rounded"><code><%= JSON.pretty_generate(@job.arguments) %></code></pre>
81
+ </div>
82
+ </div>
83
+ <% end %>
84
+ </div>
85
+
86
+ <div class="col-md-4">
87
+ <div class="card">
88
+ <div class="card-header">
89
+ <h5 class="card-title mb-0">
90
+ <i class="fas fa-tools"></i> Actions
91
+ </h5>
92
+ </div>
93
+ <div class="card-body">
94
+ <div class="d-grid gap-2">
95
+ <%= link_to ragdoll.jobs_path, class: "btn btn-outline-secondary" do %>
96
+ <i class="fas fa-arrow-left"></i> Back to Jobs
97
+ <% end %>
98
+
99
+ <% unless @job.finished_at %>
100
+ <%= link_to ragdoll.job_path(@job), method: :delete,
101
+ class: "btn btn-outline-danger",
102
+ data: { confirm: "Are you sure you want to delete this job?" } do %>
103
+ <i class="fas fa-trash"></i> Delete Job
104
+ <% end %>
105
+ <% end %>
106
+ </div>
107
+ </div>
108
+ </div>
109
+
110
+ <div class="card mt-4">
111
+ <div class="card-header">
112
+ <h5 class="card-title mb-0">
113
+ <i class="fas fa-info"></i> Job Stats
114
+ </h5>
115
+ </div>
116
+ <div class="card-body">
117
+ <small class="text-muted">
118
+ <% if @job.finished_at %>
119
+ This job completed successfully.
120
+ <% elsif @job.scheduled_at && @job.scheduled_at > Time.current %>
121
+ This job is scheduled to run in the future.
122
+ <% else %>
123
+ This job is waiting to be processed.
124
+ <% end %>
125
+ </small>
126
+ </div>
127
+ </div>
128
+ </div>
129
+ </div>
@@ -0,0 +1,327 @@
1
+ <% content_for :title, "Search - Ragdoll Engine" %>
2
+
3
+ <div class="row">
4
+ <div class="col-12">
5
+ <h1><i class="fas fa-search"></i> Search Documents</h1>
6
+ <p class="text-muted">Use semantic search to find relevant content across all your documents</p>
7
+
8
+ <% if @reconstructed_search %>
9
+ <div class="alert alert-info">
10
+ <i class="fas fa-history me-2"></i>
11
+ <strong>Previous Search Loaded:</strong> All search parameters have been restored from your search history.
12
+ <small class="d-block mt-1">
13
+ Original search from <%= time_ago_in_words(@reconstructed_search.created_at) %> ago
14
+ • <%= @reconstructed_search.search_type.titleize %> search
15
+ • <%= @reconstructed_search.results_count %> results found
16
+ </small>
17
+ </div>
18
+ <% end %>
19
+ </div>
20
+ </div>
21
+
22
+ <div class="row">
23
+ <div class="col-md-8">
24
+ <div class="card">
25
+ <div class="card-header">
26
+ <h5><i class="fas fa-search"></i> Search Query</h5>
27
+ </div>
28
+ <div class="card-body">
29
+ <%= form_with url: ragdoll.search_path, method: :post, local: true, class: "search-form", data: { turbo: false } do |form| %>
30
+ <div class="input-group mb-3">
31
+ <%= form.text_field :query, placeholder: "Enter your search query...", value: @query, class: "form-control form-control-lg", required: true %>
32
+ <div class="input-group-append">
33
+ <%= form.submit "Search", class: "btn btn-primary btn-lg" %>
34
+ </div>
35
+ </div>
36
+
37
+ <div class="row">
38
+ <div class="col-md-6">
39
+ <div class="mb-3">
40
+ <label class="form-label">Document Type</label>
41
+ <%= form.select :document_type,
42
+ options_for_select([["All Types", ""]] + ::Ragdoll::Document.distinct.pluck(:document_type).compact.map { |type| [type.titleize, type] }, @filters[:document_type]),
43
+ {},
44
+ { class: "form-select", title: "Document Type filtering is deprecated in unified text-based architecture" } %>
45
+ <small class="form-text text-warning">
46
+ <i class="fas fa-exclamation-triangle"></i> Note: All media types are now converted to text for unified cross-modal search
47
+ </small>
48
+ </div>
49
+ </div>
50
+
51
+ <div class="col-md-6">
52
+ <div class="mb-3">
53
+ <label class="form-label">Status</label>
54
+ <%= form.select :status,
55
+ options_for_select([["All Statuses", ""], ["Processed", "processed"], ["Failed", "failed"], ["Pending", "pending"]], @filters[:status]),
56
+ {},
57
+ { class: "form-select" } %>
58
+ </div>
59
+ </div>
60
+ </div>
61
+
62
+ <div class="row">
63
+ <div class="col-md-3">
64
+ <div class="mb-3">
65
+ <label class="form-label">Search Types</label>
66
+ <div class="form-check">
67
+ <%= form.check_box :use_similarity_search, { checked: @use_similarity_search != 'false', class: "form-check-input" }, "true", "false" %>
68
+ <%= form.label :use_similarity_search, "Similarity Search", class: "form-check-label" %>
69
+ <small class="form-text text-muted d-block">AI-powered semantic search</small>
70
+ </div>
71
+ <div class="form-check mt-2">
72
+ <%= form.check_box :use_fulltext_search, { checked: @use_fulltext_search != 'false', class: "form-check-input" }, "true", "false" %>
73
+ <%= form.label :use_fulltext_search, "Full Text Search", class: "form-check-label" %>
74
+ <small class="form-text text-muted d-block">Traditional keyword matching</small>
75
+ </div>
76
+ </div>
77
+ </div>
78
+
79
+ <div class="col-md-3">
80
+ <div class="mb-3">
81
+ <label class="form-label">Max Results</label>
82
+ <%= form.number_field :limit, value: @filters[:limit], min: 1, max: 50, class: "form-control" %>
83
+ </div>
84
+ </div>
85
+
86
+ <div class="col-md-3">
87
+ <div class="mb-3">
88
+ <label class="form-label">Similarity Threshold</label>
89
+ <%= form.number_field :threshold, value: @filters[:threshold], min: 0.001, max: 1.0, step: 0.001, class: "form-control" %>
90
+ </div>
91
+ </div>
92
+
93
+ <div class="col-md-3">
94
+ <div class="mb-3">
95
+ <label class="form-label">Additional Options</label>
96
+ <div class="form-check">
97
+ <%= form.check_box :use_usage_ranking, { checked: params[:use_usage_ranking] == 'true', class: "form-check-input" }, "true", "false" %>
98
+ <%= form.label :use_usage_ranking, "Use Usage Ranking", class: "form-check-label" %>
99
+ </div>
100
+ </div>
101
+ </div>
102
+ </div>
103
+ <% end %>
104
+ </div>
105
+ </div>
106
+
107
+ <% if @search_performed %>
108
+ <div class="card mt-4">
109
+ <div class="card-header">
110
+ <h5><i class="fas fa-list"></i> Search Results</h5>
111
+ <% if @detailed_results %>
112
+ <span class="badge bg-primary"><%= @detailed_results.count %> results</span>
113
+ <% end %>
114
+ </div>
115
+ <div class="card-body">
116
+ <% if @error %>
117
+ <div class="alert alert-danger">
118
+ <i class="fas fa-exclamation-circle"></i> Error: <%= @error %>
119
+ </div>
120
+ <% elsif @detailed_results && @detailed_results.any? %>
121
+ <% @detailed_results.each_with_index do |result, index| %>
122
+ <div class="card mb-3">
123
+ <div class="card-body">
124
+ <div class="d-flex justify-content-between align-items-start">
125
+ <div class="flex-grow-1">
126
+ <h6 class="card-title">
127
+ <%= link_to result[:document].title, ragdoll.document_path(result[:document]), class: "text-decoration-none" %>
128
+ <span class="badge bg-secondary ms-2"><%= result[:document].document_type&.upcase %></span>
129
+ <% if result[:search_type] == 'similarity' %>
130
+ <span class="badge bg-info ms-1" title="Found via AI-powered similarity search">
131
+ <i class="fas fa-brain"></i> Similarity
132
+ </span>
133
+ <% elsif result[:search_type] == 'fulltext' %>
134
+ <span class="badge bg-success ms-1" title="Found via full-text keyword search">
135
+ <i class="fas fa-font"></i> Full Text
136
+ </span>
137
+ <% end %>
138
+ </h6>
139
+ <p class="card-text"><%= result[:content] %></p>
140
+ <div class="d-flex align-items-center">
141
+ <small class="text-muted">
142
+ <% if result[:embedding] %>
143
+ Chunk <%= result[:embedding].chunk_index %>
144
+ <% else %>
145
+ Document summary
146
+ <% end %>
147
+ </small>
148
+ <% if result[:usage_count] && result[:usage_count] > 0 %>
149
+ <span class="badge bg-success ms-2"><%= result[:usage_count] %> uses</span>
150
+ <% end %>
151
+ <% if result[:last_used] %>
152
+ <small class="text-muted ms-2">
153
+ Last used: <%= time_ago_in_words(result[:last_used]) %> ago
154
+ </small>
155
+ <% end %>
156
+ </div>
157
+ </div>
158
+ <div class="text-end">
159
+ <% if result[:similarity] %>
160
+ <div class="similarity-score">
161
+ <span class="badge bg-info">
162
+ <%= number_with_precision(result[:similarity], precision: 3) %>
163
+ </span>
164
+ <br>
165
+ <small class="text-muted">similarity</small>
166
+ </div>
167
+ <% else %>
168
+ <div class="text-muted">
169
+ <i class="fas fa-font"></i><br>
170
+ <small>Keyword Match</small>
171
+ </div>
172
+ <% end %>
173
+ </div>
174
+ </div>
175
+ </div>
176
+ </div>
177
+ <% end %>
178
+ <% else %>
179
+ <div class="text-center py-4">
180
+ <i class="fas fa-search fa-3x text-muted mb-3"></i>
181
+ <h5>No results found</h5>
182
+ <p class="text-muted">Try adjusting your search query or filters.</p>
183
+
184
+ <% if @below_threshold_stats && @below_threshold_stats[:highest] %>
185
+ <div class="alert alert-warning mt-3">
186
+ <h6><i class="fas fa-exclamation-triangle"></i>
187
+ <% if @below_threshold_stats[:count] && @below_threshold_stats[:count] > 0 %>
188
+ Results exist below your threshold!
189
+ <% else %>
190
+ All results are below your threshold!
191
+ <% end %>
192
+ </h6>
193
+ <p class="mb-3">
194
+ <% if @below_threshold_stats[:count] && @below_threshold_stats[:count] > 0 %>
195
+ Found <strong><%= @below_threshold_stats[:count] %></strong> potential matches below your threshold of
196
+ <% else %>
197
+ All matches found are below your threshold of
198
+ <% end %>
199
+ <strong><%= number_with_precision(@similarity_threshold_used, precision: 3) %></strong>
200
+ <% search_type = params[:use_similarity_search] != 'false' && params[:use_fulltext_search] != 'false' ? 'hybrid' :
201
+ params[:use_similarity_search] != 'false' ? 'semantic' : 'fulltext' %>
202
+ <small class="text-muted d-block">(<%= search_type %> search)</small>
203
+ </p>
204
+
205
+ <div class="row text-center mb-3">
206
+ <div class="col-md-4">
207
+ <strong>Highest Match</strong><br>
208
+ <span class="badge bg-success fs-6"><%= number_with_precision(@below_threshold_stats[:highest], precision: 3) %></span>
209
+ </div>
210
+ <div class="col-md-4">
211
+ <strong>Average Match</strong><br>
212
+ <span class="badge bg-info fs-6"><%= number_with_precision(@below_threshold_stats[:average], precision: 3) %></span>
213
+ </div>
214
+ <div class="col-md-4">
215
+ <strong>Lowest Match</strong><br>
216
+ <span class="badge bg-secondary fs-6"><%= number_with_precision(@below_threshold_stats[:lowest], precision: 3) %></span>
217
+ </div>
218
+ </div>
219
+
220
+ <% if @below_threshold_stats[:suggested_threshold] && @below_threshold_stats[:suggested_threshold] > 0 %>
221
+ <div class="d-grid gap-2">
222
+ <%= form_with url: ragdoll.search_path, method: :post, local: true, data: { turbo: false } do |form| %>
223
+ <%= form.hidden_field :query, value: @query %>
224
+ <%= form.hidden_field :threshold, value: @below_threshold_stats[:suggested_threshold] %>
225
+ <%= form.hidden_field :document_type, value: @filters[:document_type] %>
226
+ <%= form.hidden_field :status, value: @filters[:status] %>
227
+ <%= form.hidden_field :limit, value: @filters[:limit] %>
228
+ <%= form.hidden_field :use_similarity_search, value: params[:use_similarity_search] %>
229
+ <%= form.hidden_field :use_fulltext_search, value: params[:use_fulltext_search] %>
230
+ <%= form.button type: :submit, class: "btn btn-warning w-100" do %>
231
+ <i class="fas fa-redo"></i>
232
+ Search again with threshold: <%= number_with_precision(@below_threshold_stats[:suggested_threshold], precision: 3) %>
233
+ <% end %>
234
+ <% end %>
235
+ </div>
236
+ <% end %>
237
+ </div>
238
+ <% elsif @similarity_search_attempted && @similarity_stats && @similarity_stats[:total_similarities_calculated] && @similarity_stats[:total_similarities_calculated] > 0 %>
239
+ <div class="alert alert-info mt-3">
240
+ <h6><i class="fas fa-chart-line"></i> Similarity Search Statistics</h6>
241
+ <p class="mb-2">No results exceeded the similarity threshold of <strong><%= number_with_precision(@similarity_threshold_used, precision: 3) %></strong></p>
242
+ <div class="row text-center">
243
+ <div class="col-md-4">
244
+ <strong>Highest Similarity</strong><br>
245
+ <span class="badge bg-success"><%= number_with_precision(@similarity_stats[:highest_similarity] || 0, precision: 3) %></span>
246
+ </div>
247
+ <div class="col-md-4">
248
+ <strong>Average Similarity</strong><br>
249
+ <span class="badge bg-info"><%= number_with_precision(@similarity_stats[:average_similarity] || 0, precision: 3) %></span>
250
+ </div>
251
+ <div class="col-md-4">
252
+ <strong>Lowest Similarity</strong><br>
253
+ <span class="badge bg-secondary"><%= number_with_precision(@similarity_stats[:lowest_similarity] || 0, precision: 3) %></span>
254
+ </div>
255
+ </div>
256
+ <small class="text-muted d-block mt-2">
257
+ Checked <%= @similarity_stats[:total_similarities_calculated] %> embeddings
258
+ <% if @similarity_stats[:similarities_above_threshold] && @similarity_stats[:similarities_above_threshold] > 0 %>
259
+ • <%= @similarity_stats[:similarities_above_threshold] %> were above threshold but filtered by other criteria
260
+ <% end %>
261
+ </small>
262
+ </div>
263
+ <% end %>
264
+ </div>
265
+ <% end %>
266
+ </div>
267
+ </div>
268
+ <% end %>
269
+ </div>
270
+
271
+ <div class="col-md-4">
272
+ <div class="card">
273
+ <div class="card-header">
274
+ <h5><i class="fas fa-info-circle"></i> Search Tips</h5>
275
+ </div>
276
+ <div class="card-body">
277
+ <h6>How to Search:</h6>
278
+ <ul class="list-unstyled">
279
+ <li><i class="fas fa-lightbulb text-warning"></i> Use natural language queries</li>
280
+ <li><i class="fas fa-quote-left text-info"></i> Ask questions or describe concepts</li>
281
+ <li><i class="fas fa-filter text-success"></i> Use filters to narrow results</li>
282
+ <li><i class="fas fa-sliders-h text-primary"></i> Adjust similarity threshold</li>
283
+ </ul>
284
+
285
+ <hr>
286
+
287
+ <h6>Search Features:</h6>
288
+ <ul class="list-unstyled">
289
+ <li><i class="fas fa-brain text-primary"></i> Semantic understanding</li>
290
+ <li><i class="fas fa-chart-line text-success"></i> Usage-based ranking</li>
291
+ <li><i class="fas fa-bullseye text-info"></i> Similarity scoring</li>
292
+ <li><i class="fas fa-clock text-warning"></i> Recency weighting</li>
293
+ </ul>
294
+ </div>
295
+ </div>
296
+
297
+ <div class="card mt-4">
298
+ <div class="card-header">
299
+ <h5><i class="fas fa-star"></i> Popular Queries</h5>
300
+ </div>
301
+ <div class="card-body">
302
+ <% if @popular_queries&.any? %>
303
+ <% @popular_queries.each do |query, count| %>
304
+ <div class="d-flex justify-content-between align-items-center mb-2">
305
+ <%= form_with url: ragdoll.search_path, method: :post, local: true, class: "d-inline", data: { turbo: false } do |form| %>
306
+ <%= form.hidden_field :query, value: query %>
307
+ <%= form.hidden_field :threshold, value: (::Rails.env.development? ? 0.001 : 0.7) %>
308
+ <%= form.hidden_field :document_type, value: '' %>
309
+ <%= form.hidden_field :status, value: '' %>
310
+ <%= form.hidden_field :limit, value: 10 %>
311
+ <%= form.hidden_field :use_similarity_search, value: 'true' %>
312
+ <%= form.hidden_field :use_fulltext_search, value: 'true' %>
313
+ <%= form.button type: :submit, class: "btn btn-link p-0 text-decoration-none border-0 text-start", style: "font: inherit;" do %>
314
+ <%= query %>
315
+ <% end %>
316
+ <% end %>
317
+ <span class="badge bg-secondary"><%= count %></span>
318
+ </div>
319
+ <% end %>
320
+ <% else %>
321
+ <p class="text-muted">No popular queries yet.</p>
322
+ <% end %>
323
+ </div>
324
+ </div>
325
+
326
+ </div>
327
+ </div>
data/config/cable.yml ADDED
@@ -0,0 +1,12 @@
1
+ development:
2
+ # Use Redis adapter for reliable cross-process communication
3
+ adapter: redis
4
+ url: redis://localhost:6379/0
5
+
6
+ test:
7
+ adapter: test
8
+
9
+ production:
10
+ adapter: redis
11
+ url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
12
+ channel_prefix: ragdoll_rails_production
data/config/routes.rb CHANGED
@@ -1,5 +1,60 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  Ragdoll::Rails::Engine.routes.draw do
4
- # Define your engine routes here
4
+ # Dashboard and Analytics
5
+ get "/" => "dashboard#index", as: :root
6
+ get "dashboard" => "dashboard#index", as: :dashboard_index
7
+ get "analytics" => "dashboard#analytics", as: :analytics
8
+
9
+ # Job Queue Dashboard
10
+ resources :jobs, only: [:index, :show, :destroy] do
11
+ member do
12
+ post :retry
13
+ end
14
+ collection do
15
+ get :health
16
+ post :restart_workers
17
+ post :bulk_delete
18
+ post :bulk_retry
19
+ delete :cancel_all_pending
20
+ end
21
+ end
22
+
23
+ # Document Management
24
+ resources :documents do
25
+ member do
26
+ get :preview
27
+ post :reprocess
28
+ get :download
29
+ end
30
+ collection do
31
+ post :bulk_upload
32
+ post :bulk_delete
33
+ post :bulk_reprocess
34
+ get :status
35
+ post :upload_async
36
+ end
37
+ end
38
+
39
+ # Search Interface
40
+ get "search" => "search#index", as: :search_index
41
+ post "search" => "search#search", as: :search
42
+
43
+ # Configuration
44
+ get "configuration" => "configuration#index", as: :configuration
45
+ patch "configuration" => "configuration#update"
46
+
47
+ # API endpoints for AJAX interactions
48
+ namespace :api do
49
+ namespace :v1 do
50
+ resources :documents, only: [:index, :show, :create, :update, :destroy] do
51
+ member do
52
+ post :reprocess
53
+ end
54
+ end
55
+ post "search" => "search#search"
56
+ get "analytics" => "analytics#index"
57
+ get "system_stats" => "system#stats"
58
+ end
59
+ end
5
60
  end
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Ragdoll RAG (Retrieval-Augmented Generation) Configuration
3
+ # Ragdoll Unified Text-Based RAG Configuration
4
4
  # This initializer configures the Ragdoll Rails engine for your application.
5
+ # All media types (images, audio, documents) are converted to searchable text.
5
6
 
6
7
  Ragdoll.configure do |config|
7
8
  # LLM Provider Configuration
@@ -39,9 +40,13 @@ Ragdoll.configure do |config|
39
40
  }
40
41
  }
41
42
 
42
- # Embedding Model Configuration
43
+ # Unified Embedding Model Configuration
44
+ # Single model for all content types (converted to text)
43
45
  # Examples: 'text-embedding-3-small', 'text-embedding-3-large', 'text-embedding-ada-002'
44
- config.embedding_model = 'text-embedding-3-small'
46
+ config.embedding_model = 'text-embedding-3-large'
47
+
48
+ # Enable unified text-based architecture
49
+ config.use_unified_content = true
45
50
 
46
51
  # Default model for chat/completion
47
52
  config.default_model = 'gpt-4o-mini'
@@ -50,6 +55,13 @@ Ragdoll.configure do |config|
50
55
  config.chunk_size = 1000
51
56
  config.chunk_overlap = 200
52
57
 
58
+ # Text Conversion Settings (for unified architecture)
59
+ config.text_conversion = {
60
+ image_detail_level: :comprehensive, # :minimal, :standard, :comprehensive, :analytical
61
+ audio_transcription_provider: :openai, # :openai, :azure, :google, :whisper_local
62
+ enable_fallback_descriptions: true
63
+ }
64
+
53
65
  # Search Configuration
54
66
  config.search_similarity_threshold = 0.7
55
67
  config.max_search_results = 10
@@ -92,5 +104,6 @@ Ragdoll::Rails.configure do |config|
92
104
  config.max_file_size = 10.megabytes
93
105
 
94
106
  # Allowed file types for document upload
95
- config.allowed_file_types = %w[pdf docx txt md html htm json xml csv]
107
+ # All types are converted to text: images -> descriptions, audio -> transcripts
108
+ config.allowed_file_types = %w[pdf docx txt md html htm json xml csv jpg jpeg png gif mp3 wav m4a]
96
109
  end
@@ -12,7 +12,8 @@ module Ragdoll
12
12
  @job_adapter = :sidekiq
13
13
  @queue_name = :ragdoll
14
14
  @max_file_size = 10 * 1024 * 1024 # 10MB
15
- @allowed_file_types = %w[pdf docx txt md html htm json xml csv]
15
+ # Unified text-based architecture supports all media types converted to text
16
+ @allowed_file_types = %w[pdf docx txt md html htm json xml csv jpg jpeg png gif mp3 wav m4a]
16
17
  end
17
18
 
18
19
  def configure_core
@@ -5,7 +5,7 @@ require 'rails/engine'
5
5
  module Ragdoll
6
6
  module Rails
7
7
  class Engine < ::Rails::Engine
8
- isolate_namespace Ragdoll::Rails
8
+ isolate_namespace Ragdoll
9
9
  engine_name 'ragdoll'
10
10
 
11
11
  # Configure the engine to use migrations from the ragdoll gem
@@ -34,8 +34,39 @@ module Ragdoll
34
34
  initializer "ragdoll.configure" do |app|
35
35
  # Configure Rails-specific functionality
36
36
  # Core functionality is provided by the ragdoll gem
37
+
38
+ # Ensure ViewComponent autoloading for engine components
39
+ app.config.autoload_paths += ["#{root}/app/components"]
40
+
41
+ # Ensure Services autoloading for engine services
42
+ app.config.autoload_paths += ["#{root}/app/services"]
43
+
44
+ # Configure ViewComponent
45
+ if ::Rails.env.development? && app.config.respond_to?(:view_component)
46
+ app.config.view_component.preview_paths ||= []
47
+ app.config.view_component.preview_paths << "#{root}/spec/components/previews"
48
+ end
49
+
50
+ # Configure ActionCable for the engine
51
+ # Provide default ActionCable configuration if not already set
52
+ app.config.action_cable.mount_path ||= '/cable'
53
+
54
+ # Set default adapter if not already configured
55
+ unless app.config.action_cable.adapter
56
+ if ::Rails.env.development?
57
+ app.config.action_cable.adapter = 'redis'
58
+ app.config.action_cable.url = 'redis://localhost:6379/0'
59
+ elsif ::Rails.env.test?
60
+ app.config.action_cable.adapter = 'test'
61
+ elsif ::Rails.env.production?
62
+ app.config.action_cable.adapter = 'redis'
63
+ app.config.action_cable.url = ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" }
64
+ app.config.action_cable.channel_prefix = 'ragdoll_rails_production'
65
+ end
66
+ end
37
67
  end
38
68
 
69
+
39
70
  # Ensure models are eager loaded in production
40
71
  initializer "ragdoll.eager_load", after: "finisher_hook" do |app|
41
72
  if ::Rails.env.production?
@@ -4,6 +4,6 @@
4
4
 
5
5
  module Ragdoll
6
6
  module Rails
7
- VERSION = "0.1.9"
7
+ VERSION = "0.1.12"
8
8
  end
9
9
  end