ragdoll-rails 0.1.8 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +18 -21
- data/app/assets/javascripts/ragdoll/application.js +129 -0
- data/app/assets/javascripts/ragdoll/bulk_upload_status.js +454 -0
- data/app/assets/stylesheets/ragdoll/application.css +84 -0
- data/app/assets/stylesheets/ragdoll/bulk_upload_status.css +379 -0
- data/app/channels/application_cable/channel.rb +6 -0
- data/app/channels/application_cable/connection.rb +6 -0
- data/app/channels/ragdoll/bulk_upload_status_channel.rb +27 -0
- data/app/channels/ragdoll/file_processing_channel.rb +26 -0
- data/app/components/ragdoll/alert_component.html.erb +4 -0
- data/app/components/ragdoll/alert_component.rb +32 -0
- data/app/components/ragdoll/application_component.rb +6 -0
- data/app/components/ragdoll/card_component.html.erb +15 -0
- data/app/components/ragdoll/card_component.rb +21 -0
- data/app/components/ragdoll/document_list_component.html.erb +41 -0
- data/app/components/ragdoll/document_list_component.rb +13 -0
- data/app/components/ragdoll/document_table_component.html.erb +76 -0
- data/app/components/ragdoll/document_table_component.rb +13 -0
- data/app/components/ragdoll/empty_state_component.html.erb +12 -0
- data/app/components/ragdoll/empty_state_component.rb +17 -0
- data/app/components/ragdoll/flash_messages_component.html.erb +3 -0
- data/app/components/ragdoll/flash_messages_component.rb +37 -0
- data/app/components/ragdoll/navbar_component.html.erb +24 -0
- data/app/components/ragdoll/navbar_component.rb +31 -0
- data/app/components/ragdoll/page_header_component.html.erb +13 -0
- data/app/components/ragdoll/page_header_component.rb +15 -0
- data/app/components/ragdoll/stats_card_component.html.erb +11 -0
- data/app/components/ragdoll/stats_card_component.rb +17 -0
- data/app/components/ragdoll/status_badge_component.html.erb +3 -0
- data/app/components/ragdoll/status_badge_component.rb +30 -0
- data/app/controllers/ragdoll/api/v1/analytics_controller.rb +72 -0
- data/app/controllers/ragdoll/api/v1/base_controller.rb +29 -0
- data/app/controllers/ragdoll/api/v1/documents_controller.rb +148 -0
- data/app/controllers/ragdoll/api/v1/search_controller.rb +87 -0
- data/app/controllers/ragdoll/api/v1/system_controller.rb +97 -0
- data/app/controllers/ragdoll/application_controller.rb +17 -0
- data/app/controllers/ragdoll/configuration_controller.rb +82 -0
- data/app/controllers/ragdoll/dashboard_controller.rb +98 -0
- data/app/controllers/ragdoll/documents_controller.rb +460 -0
- data/app/controllers/ragdoll/documents_controller_backup.rb +68 -0
- data/app/controllers/ragdoll/jobs_controller.rb +116 -0
- data/app/controllers/ragdoll/search_controller.rb +368 -0
- data/app/jobs/application_job.rb +9 -0
- data/app/jobs/ragdoll/bulk_document_processing_job.rb +280 -0
- data/app/jobs/ragdoll/process_file_job.rb +166 -0
- data/app/services/ragdoll/worker_health_service.rb +111 -0
- data/app/views/layouts/ragdoll/application.html.erb +162 -0
- data/app/views/ragdoll/dashboard/analytics.html.erb +333 -0
- data/app/views/ragdoll/dashboard/index.html.erb +208 -0
- data/app/views/ragdoll/documents/edit.html.erb +91 -0
- data/app/views/ragdoll/documents/index.html.erb +302 -0
- data/app/views/ragdoll/documents/new.html.erb +1518 -0
- data/app/views/ragdoll/documents/show.html.erb +188 -0
- data/app/views/ragdoll/documents/upload_results.html.erb +248 -0
- data/app/views/ragdoll/jobs/index.html.erb +669 -0
- data/app/views/ragdoll/jobs/show.html.erb +129 -0
- data/app/views/ragdoll/search/index.html.erb +324 -0
- data/config/cable.yml +12 -0
- data/config/routes.rb +57 -2
- data/lib/generators/ragdoll/init/templates/INSTALL +3 -2
- data/lib/generators/ragdoll/init_generator.rb +68 -0
- data/lib/ragdoll/rails/engine.rb +48 -0
- data/lib/ragdoll/rails/version.rb +1 -1
- metadata +231 -6
- data/lib/generators/ragdoll/init/init_generator.rb +0 -26
@@ -0,0 +1,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
|
-
Ragdoll::Engine.routes.draw do
|
4
|
-
#
|
3
|
+
Ragdoll::Rails::Engine.routes.draw do
|
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
|
@@ -21,10 +21,11 @@ Next steps:
|
|
21
21
|
export GOOGLE_API_KEY="your-api-key-here"
|
22
22
|
export GOOGLE_PROJECT_ID="your-project-id"
|
23
23
|
|
24
|
-
2.
|
24
|
+
2. Run database migrations:
|
25
25
|
|
26
|
-
rails ragdoll:install:migrations
|
27
26
|
rails db:migrate
|
27
|
+
|
28
|
+
Note: Migrations have been automatically copied to your db/migrate directory.
|
28
29
|
|
29
30
|
3. Ensure you have PostgreSQL with pgvector extension installed:
|
30
31
|
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails/generators'
|
4
|
+
require 'rails/generators/active_record'
|
5
|
+
|
6
|
+
module Ragdoll
|
7
|
+
class InitGenerator < ::Rails::Generators::Base
|
8
|
+
include ::Rails::Generators::Migration
|
9
|
+
|
10
|
+
desc "Create Ragdoll configuration initializer and install migrations"
|
11
|
+
source_root File.expand_path("init/templates", __dir__)
|
12
|
+
|
13
|
+
def create_initializer_file
|
14
|
+
template "ragdoll_config.rb", "config/initializers/ragdoll_config.rb"
|
15
|
+
end
|
16
|
+
|
17
|
+
def copy_migrations
|
18
|
+
say "Installing Ragdoll migrations..."
|
19
|
+
|
20
|
+
# Find the ragdoll gem path
|
21
|
+
begin
|
22
|
+
ragdoll_path = Gem::Specification.find_by_name('ragdoll').gem_dir
|
23
|
+
rescue Gem::MissingSpecError
|
24
|
+
# If not found as a gem (e.g., using path in Gemfile), try to find it relative to this engine
|
25
|
+
ragdoll_path = File.expand_path("../../../../../ragdoll", __dir__)
|
26
|
+
end
|
27
|
+
|
28
|
+
migration_source_path = File.join(ragdoll_path, 'db/migrate')
|
29
|
+
|
30
|
+
if File.exist?(migration_source_path)
|
31
|
+
Dir.glob("#{migration_source_path}/*.rb").each do |migration_file|
|
32
|
+
migration_name = File.basename(migration_file)
|
33
|
+
|
34
|
+
# Check if migration already exists in the app
|
35
|
+
if migration_exists?(migration_name)
|
36
|
+
say_status "skip", migration_name, :yellow
|
37
|
+
else
|
38
|
+
copy_file migration_file, "db/migrate/#{migration_name}"
|
39
|
+
say_status "copied", migration_name, :green
|
40
|
+
end
|
41
|
+
end
|
42
|
+
else
|
43
|
+
say "Warning: Could not find Ragdoll migrations at #{migration_source_path}", :red
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def show_readme
|
48
|
+
readme "INSTALL" if behavior == :invoke
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def application_name
|
54
|
+
::Rails.application.class.name.split("::").first.underscore
|
55
|
+
end
|
56
|
+
|
57
|
+
def migration_exists?(filename)
|
58
|
+
Dir.glob("#{destination_root}/db/migrate/*.rb").any? do |existing_file|
|
59
|
+
File.basename(existing_file) == filename
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Required for Rails::Generators::Migration
|
64
|
+
def self.next_migration_number(dirname)
|
65
|
+
::ActiveRecord::Generators::Base.next_migration_number(dirname)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|