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.
- checksums.yaml +4 -4
- 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 +56 -1
- data/lib/ragdoll/rails/engine.rb +32 -1
- data/lib/ragdoll/rails/version.rb +1 -1
- metadata +86 -1
@@ -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,324 @@
|
|
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" } %>
|
45
|
+
</div>
|
46
|
+
</div>
|
47
|
+
|
48
|
+
<div class="col-md-6">
|
49
|
+
<div class="mb-3">
|
50
|
+
<label class="form-label">Status</label>
|
51
|
+
<%= form.select :status,
|
52
|
+
options_for_select([["All Statuses", ""], ["Processed", "processed"], ["Failed", "failed"], ["Pending", "pending"]], @filters[:status]),
|
53
|
+
{},
|
54
|
+
{ class: "form-select" } %>
|
55
|
+
</div>
|
56
|
+
</div>
|
57
|
+
</div>
|
58
|
+
|
59
|
+
<div class="row">
|
60
|
+
<div class="col-md-3">
|
61
|
+
<div class="mb-3">
|
62
|
+
<label class="form-label">Search Types</label>
|
63
|
+
<div class="form-check">
|
64
|
+
<%= form.check_box :use_similarity_search, { checked: @use_similarity_search != 'false', class: "form-check-input" }, "true", "false" %>
|
65
|
+
<%= form.label :use_similarity_search, "Similarity Search", class: "form-check-label" %>
|
66
|
+
<small class="form-text text-muted d-block">AI-powered semantic search</small>
|
67
|
+
</div>
|
68
|
+
<div class="form-check mt-2">
|
69
|
+
<%= form.check_box :use_fulltext_search, { checked: @use_fulltext_search != 'false', class: "form-check-input" }, "true", "false" %>
|
70
|
+
<%= form.label :use_fulltext_search, "Full Text Search", class: "form-check-label" %>
|
71
|
+
<small class="form-text text-muted d-block">Traditional keyword matching</small>
|
72
|
+
</div>
|
73
|
+
</div>
|
74
|
+
</div>
|
75
|
+
|
76
|
+
<div class="col-md-3">
|
77
|
+
<div class="mb-3">
|
78
|
+
<label class="form-label">Max Results</label>
|
79
|
+
<%= form.number_field :limit, value: @filters[:limit], min: 1, max: 50, class: "form-control" %>
|
80
|
+
</div>
|
81
|
+
</div>
|
82
|
+
|
83
|
+
<div class="col-md-3">
|
84
|
+
<div class="mb-3">
|
85
|
+
<label class="form-label">Similarity Threshold</label>
|
86
|
+
<%= form.number_field :threshold, value: @filters[:threshold], min: 0.001, max: 1.0, step: 0.001, class: "form-control" %>
|
87
|
+
</div>
|
88
|
+
</div>
|
89
|
+
|
90
|
+
<div class="col-md-3">
|
91
|
+
<div class="mb-3">
|
92
|
+
<label class="form-label">Additional Options</label>
|
93
|
+
<div class="form-check">
|
94
|
+
<%= form.check_box :use_usage_ranking, { checked: params[:use_usage_ranking] == 'true', class: "form-check-input" }, "true", "false" %>
|
95
|
+
<%= form.label :use_usage_ranking, "Use Usage Ranking", class: "form-check-label" %>
|
96
|
+
</div>
|
97
|
+
</div>
|
98
|
+
</div>
|
99
|
+
</div>
|
100
|
+
<% end %>
|
101
|
+
</div>
|
102
|
+
</div>
|
103
|
+
|
104
|
+
<% if @search_performed %>
|
105
|
+
<div class="card mt-4">
|
106
|
+
<div class="card-header">
|
107
|
+
<h5><i class="fas fa-list"></i> Search Results</h5>
|
108
|
+
<% if @detailed_results %>
|
109
|
+
<span class="badge bg-primary"><%= @detailed_results.count %> results</span>
|
110
|
+
<% end %>
|
111
|
+
</div>
|
112
|
+
<div class="card-body">
|
113
|
+
<% if @error %>
|
114
|
+
<div class="alert alert-danger">
|
115
|
+
<i class="fas fa-exclamation-circle"></i> Error: <%= @error %>
|
116
|
+
</div>
|
117
|
+
<% elsif @detailed_results && @detailed_results.any? %>
|
118
|
+
<% @detailed_results.each_with_index do |result, index| %>
|
119
|
+
<div class="card mb-3">
|
120
|
+
<div class="card-body">
|
121
|
+
<div class="d-flex justify-content-between align-items-start">
|
122
|
+
<div class="flex-grow-1">
|
123
|
+
<h6 class="card-title">
|
124
|
+
<%= link_to result[:document].title, ragdoll.document_path(result[:document]), class: "text-decoration-none" %>
|
125
|
+
<span class="badge bg-secondary ms-2"><%= result[:document].document_type&.upcase %></span>
|
126
|
+
<% if result[:search_type] == 'similarity' %>
|
127
|
+
<span class="badge bg-info ms-1" title="Found via AI-powered similarity search">
|
128
|
+
<i class="fas fa-brain"></i> Similarity
|
129
|
+
</span>
|
130
|
+
<% elsif result[:search_type] == 'fulltext' %>
|
131
|
+
<span class="badge bg-success ms-1" title="Found via full-text keyword search">
|
132
|
+
<i class="fas fa-font"></i> Full Text
|
133
|
+
</span>
|
134
|
+
<% end %>
|
135
|
+
</h6>
|
136
|
+
<p class="card-text"><%= result[:content] %></p>
|
137
|
+
<div class="d-flex align-items-center">
|
138
|
+
<small class="text-muted">
|
139
|
+
<% if result[:embedding] %>
|
140
|
+
Chunk <%= result[:embedding].chunk_index %>
|
141
|
+
<% else %>
|
142
|
+
Document summary
|
143
|
+
<% end %>
|
144
|
+
</small>
|
145
|
+
<% if result[:usage_count] && result[:usage_count] > 0 %>
|
146
|
+
<span class="badge bg-success ms-2"><%= result[:usage_count] %> uses</span>
|
147
|
+
<% end %>
|
148
|
+
<% if result[:last_used] %>
|
149
|
+
<small class="text-muted ms-2">
|
150
|
+
Last used: <%= time_ago_in_words(result[:last_used]) %> ago
|
151
|
+
</small>
|
152
|
+
<% end %>
|
153
|
+
</div>
|
154
|
+
</div>
|
155
|
+
<div class="text-end">
|
156
|
+
<% if result[:similarity] %>
|
157
|
+
<div class="similarity-score">
|
158
|
+
<span class="badge bg-info">
|
159
|
+
<%= number_with_precision(result[:similarity], precision: 3) %>
|
160
|
+
</span>
|
161
|
+
<br>
|
162
|
+
<small class="text-muted">similarity</small>
|
163
|
+
</div>
|
164
|
+
<% else %>
|
165
|
+
<div class="text-muted">
|
166
|
+
<i class="fas fa-font"></i><br>
|
167
|
+
<small>Keyword Match</small>
|
168
|
+
</div>
|
169
|
+
<% end %>
|
170
|
+
</div>
|
171
|
+
</div>
|
172
|
+
</div>
|
173
|
+
</div>
|
174
|
+
<% end %>
|
175
|
+
<% else %>
|
176
|
+
<div class="text-center py-4">
|
177
|
+
<i class="fas fa-search fa-3x text-muted mb-3"></i>
|
178
|
+
<h5>No results found</h5>
|
179
|
+
<p class="text-muted">Try adjusting your search query or filters.</p>
|
180
|
+
|
181
|
+
<% if @below_threshold_stats && @below_threshold_stats[:highest] %>
|
182
|
+
<div class="alert alert-warning mt-3">
|
183
|
+
<h6><i class="fas fa-exclamation-triangle"></i>
|
184
|
+
<% if @below_threshold_stats[:count] && @below_threshold_stats[:count] > 0 %>
|
185
|
+
Results exist below your threshold!
|
186
|
+
<% else %>
|
187
|
+
All results are below your threshold!
|
188
|
+
<% end %>
|
189
|
+
</h6>
|
190
|
+
<p class="mb-3">
|
191
|
+
<% if @below_threshold_stats[:count] && @below_threshold_stats[:count] > 0 %>
|
192
|
+
Found <strong><%= @below_threshold_stats[:count] %></strong> potential matches below your threshold of
|
193
|
+
<% else %>
|
194
|
+
All matches found are below your threshold of
|
195
|
+
<% end %>
|
196
|
+
<strong><%= number_with_precision(@similarity_threshold_used, precision: 3) %></strong>
|
197
|
+
<% search_type = params[:use_similarity_search] != 'false' && params[:use_fulltext_search] != 'false' ? 'hybrid' :
|
198
|
+
params[:use_similarity_search] != 'false' ? 'semantic' : 'fulltext' %>
|
199
|
+
<small class="text-muted d-block">(<%= search_type %> search)</small>
|
200
|
+
</p>
|
201
|
+
|
202
|
+
<div class="row text-center mb-3">
|
203
|
+
<div class="col-md-4">
|
204
|
+
<strong>Highest Match</strong><br>
|
205
|
+
<span class="badge bg-success fs-6"><%= number_with_precision(@below_threshold_stats[:highest], precision: 3) %></span>
|
206
|
+
</div>
|
207
|
+
<div class="col-md-4">
|
208
|
+
<strong>Average Match</strong><br>
|
209
|
+
<span class="badge bg-info fs-6"><%= number_with_precision(@below_threshold_stats[:average], precision: 3) %></span>
|
210
|
+
</div>
|
211
|
+
<div class="col-md-4">
|
212
|
+
<strong>Lowest Match</strong><br>
|
213
|
+
<span class="badge bg-secondary fs-6"><%= number_with_precision(@below_threshold_stats[:lowest], precision: 3) %></span>
|
214
|
+
</div>
|
215
|
+
</div>
|
216
|
+
|
217
|
+
<% if @below_threshold_stats[:suggested_threshold] && @below_threshold_stats[:suggested_threshold] > 0 %>
|
218
|
+
<div class="d-grid gap-2">
|
219
|
+
<%= form_with url: ragdoll.search_path, method: :post, local: true, data: { turbo: false } do |form| %>
|
220
|
+
<%= form.hidden_field :query, value: @query %>
|
221
|
+
<%= form.hidden_field :threshold, value: @below_threshold_stats[:suggested_threshold] %>
|
222
|
+
<%= form.hidden_field :document_type, value: @filters[:document_type] %>
|
223
|
+
<%= form.hidden_field :status, value: @filters[:status] %>
|
224
|
+
<%= form.hidden_field :limit, value: @filters[:limit] %>
|
225
|
+
<%= form.hidden_field :use_similarity_search, value: params[:use_similarity_search] %>
|
226
|
+
<%= form.hidden_field :use_fulltext_search, value: params[:use_fulltext_search] %>
|
227
|
+
<%= form.button type: :submit, class: "btn btn-warning w-100" do %>
|
228
|
+
<i class="fas fa-redo"></i>
|
229
|
+
Search again with threshold: <%= number_with_precision(@below_threshold_stats[:suggested_threshold], precision: 3) %>
|
230
|
+
<% end %>
|
231
|
+
<% end %>
|
232
|
+
</div>
|
233
|
+
<% end %>
|
234
|
+
</div>
|
235
|
+
<% elsif @similarity_search_attempted && @similarity_stats && @similarity_stats[:total_similarities_calculated] && @similarity_stats[:total_similarities_calculated] > 0 %>
|
236
|
+
<div class="alert alert-info mt-3">
|
237
|
+
<h6><i class="fas fa-chart-line"></i> Similarity Search Statistics</h6>
|
238
|
+
<p class="mb-2">No results exceeded the similarity threshold of <strong><%= number_with_precision(@similarity_threshold_used, precision: 3) %></strong></p>
|
239
|
+
<div class="row text-center">
|
240
|
+
<div class="col-md-4">
|
241
|
+
<strong>Highest Similarity</strong><br>
|
242
|
+
<span class="badge bg-success"><%= number_with_precision(@similarity_stats[:highest_similarity] || 0, precision: 3) %></span>
|
243
|
+
</div>
|
244
|
+
<div class="col-md-4">
|
245
|
+
<strong>Average Similarity</strong><br>
|
246
|
+
<span class="badge bg-info"><%= number_with_precision(@similarity_stats[:average_similarity] || 0, precision: 3) %></span>
|
247
|
+
</div>
|
248
|
+
<div class="col-md-4">
|
249
|
+
<strong>Lowest Similarity</strong><br>
|
250
|
+
<span class="badge bg-secondary"><%= number_with_precision(@similarity_stats[:lowest_similarity] || 0, precision: 3) %></span>
|
251
|
+
</div>
|
252
|
+
</div>
|
253
|
+
<small class="text-muted d-block mt-2">
|
254
|
+
Checked <%= @similarity_stats[:total_similarities_calculated] %> embeddings
|
255
|
+
<% if @similarity_stats[:similarities_above_threshold] && @similarity_stats[:similarities_above_threshold] > 0 %>
|
256
|
+
• <%= @similarity_stats[:similarities_above_threshold] %> were above threshold but filtered by other criteria
|
257
|
+
<% end %>
|
258
|
+
</small>
|
259
|
+
</div>
|
260
|
+
<% end %>
|
261
|
+
</div>
|
262
|
+
<% end %>
|
263
|
+
</div>
|
264
|
+
</div>
|
265
|
+
<% end %>
|
266
|
+
</div>
|
267
|
+
|
268
|
+
<div class="col-md-4">
|
269
|
+
<div class="card">
|
270
|
+
<div class="card-header">
|
271
|
+
<h5><i class="fas fa-info-circle"></i> Search Tips</h5>
|
272
|
+
</div>
|
273
|
+
<div class="card-body">
|
274
|
+
<h6>How to Search:</h6>
|
275
|
+
<ul class="list-unstyled">
|
276
|
+
<li><i class="fas fa-lightbulb text-warning"></i> Use natural language queries</li>
|
277
|
+
<li><i class="fas fa-quote-left text-info"></i> Ask questions or describe concepts</li>
|
278
|
+
<li><i class="fas fa-filter text-success"></i> Use filters to narrow results</li>
|
279
|
+
<li><i class="fas fa-sliders-h text-primary"></i> Adjust similarity threshold</li>
|
280
|
+
</ul>
|
281
|
+
|
282
|
+
<hr>
|
283
|
+
|
284
|
+
<h6>Search Features:</h6>
|
285
|
+
<ul class="list-unstyled">
|
286
|
+
<li><i class="fas fa-brain text-primary"></i> Semantic understanding</li>
|
287
|
+
<li><i class="fas fa-chart-line text-success"></i> Usage-based ranking</li>
|
288
|
+
<li><i class="fas fa-bullseye text-info"></i> Similarity scoring</li>
|
289
|
+
<li><i class="fas fa-clock text-warning"></i> Recency weighting</li>
|
290
|
+
</ul>
|
291
|
+
</div>
|
292
|
+
</div>
|
293
|
+
|
294
|
+
<div class="card mt-4">
|
295
|
+
<div class="card-header">
|
296
|
+
<h5><i class="fas fa-star"></i> Popular Queries</h5>
|
297
|
+
</div>
|
298
|
+
<div class="card-body">
|
299
|
+
<% if @popular_queries&.any? %>
|
300
|
+
<% @popular_queries.each do |query, count| %>
|
301
|
+
<div class="d-flex justify-content-between align-items-center mb-2">
|
302
|
+
<%= form_with url: ragdoll.search_path, method: :post, local: true, class: "d-inline", data: { turbo: false } do |form| %>
|
303
|
+
<%= form.hidden_field :query, value: query %>
|
304
|
+
<%= form.hidden_field :threshold, value: (::Rails.env.development? ? 0.001 : 0.7) %>
|
305
|
+
<%= form.hidden_field :document_type, value: '' %>
|
306
|
+
<%= form.hidden_field :status, value: '' %>
|
307
|
+
<%= form.hidden_field :limit, value: 10 %>
|
308
|
+
<%= form.hidden_field :use_similarity_search, value: 'true' %>
|
309
|
+
<%= form.hidden_field :use_fulltext_search, value: 'true' %>
|
310
|
+
<%= form.button type: :submit, class: "btn btn-link p-0 text-decoration-none border-0 text-start", style: "font: inherit;" do %>
|
311
|
+
<%= query %>
|
312
|
+
<% end %>
|
313
|
+
<% end %>
|
314
|
+
<span class="badge bg-secondary"><%= count %></span>
|
315
|
+
</div>
|
316
|
+
<% end %>
|
317
|
+
<% else %>
|
318
|
+
<p class="text-muted">No popular queries yet.</p>
|
319
|
+
<% end %>
|
320
|
+
</div>
|
321
|
+
</div>
|
322
|
+
|
323
|
+
</div>
|
324
|
+
</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
|
-
#
|
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
|
data/lib/ragdoll/rails/engine.rb
CHANGED
@@ -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
|
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?
|