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,162 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title><%= content_for(:title) || "Ragdoll Dashboard" %></title>
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
6
|
+
<meta name="apple-mobile-web-app-capable" content="yes">
|
7
|
+
<meta name="mobile-web-app-capable" content="yes">
|
8
|
+
<%= csrf_meta_tags %>
|
9
|
+
<%= csp_meta_tag %>
|
10
|
+
<meta name="csrf-token" content="<%= form_authenticity_token %>">
|
11
|
+
|
12
|
+
<%= yield :head %>
|
13
|
+
|
14
|
+
<%= favicon_link_tag "ragdoll.png", type: "image/png" %>
|
15
|
+
<%= favicon_link_tag "ragdoll.png", rel: "apple-touch-icon" %>
|
16
|
+
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
|
17
|
+
<%= stylesheet_link_tag "ragdoll/bulk_upload_status", "data-turbo-track": "reload" %>
|
18
|
+
|
19
|
+
<!-- Bootstrap CSS from CDN -->
|
20
|
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
21
|
+
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
22
|
+
|
23
|
+
<!-- Rails UJS for handling method: :delete and data-confirm -->
|
24
|
+
<script src="https://cdn.jsdelivr.net/npm/@rails/ujs@7.0.0/lib/assets/compiled/rails-ujs.js"></script>
|
25
|
+
|
26
|
+
<!-- ActionCable from CDN with fallback handling -->
|
27
|
+
<script src="https://cdn.jsdelivr.net/gh/rails/rails@7-1-stable/actioncable/app/assets/javascripts/action_cable.js"></script>
|
28
|
+
|
29
|
+
<!-- Fallback CSS for when CDN is blocked -->
|
30
|
+
<style>
|
31
|
+
.container { max-width: 1200px; margin: 0 auto; padding: 0 15px; }
|
32
|
+
.row { display: flex; flex-wrap: wrap; margin: 0 -15px; }
|
33
|
+
.col-md-8 { flex: 0 0 66.666667%; max-width: 66.666667%; padding: 0 15px; }
|
34
|
+
.col-md-4 { flex: 0 0 33.333333%; max-width: 33.333333%; padding: 0 15px; }
|
35
|
+
.col-12 { flex: 0 0 100%; max-width: 100%; padding: 0 15px; }
|
36
|
+
.card { border: 1px solid #dee2e6; border-radius: 0.375rem; margin-bottom: 1rem; }
|
37
|
+
.card-header { padding: 0.75rem 1.25rem; background-color: #f8f9fa; border-bottom: 1px solid #dee2e6; }
|
38
|
+
.card-body { padding: 1.25rem; }
|
39
|
+
.btn { display: inline-block; padding: 0.375rem 0.75rem; font-size: 1rem; line-height: 1.5; border-radius: 0.375rem; border: 1px solid; text-decoration: none; cursor: pointer; }
|
40
|
+
.btn-primary { color: #fff; background-color: #007bff; border-color: #007bff; }
|
41
|
+
.navbar { padding: 0.5rem 0; background-color: #007bff !important; }
|
42
|
+
.navbar-brand { color: #fff !important; text-decoration: none; font-weight: bold; }
|
43
|
+
.nav-link { color: #fff !important; text-decoration: none; padding: 0.5rem 1rem; }
|
44
|
+
.alert { padding: 0.75rem 1.25rem; margin-bottom: 1rem; border: 1px solid; border-radius: 0.375rem; }
|
45
|
+
.alert-success { color: #155724; background-color: #d4edda; border-color: #c3e6cb; }
|
46
|
+
.alert-danger { color: #721c24; background-color: #f8d7da; border-color: #f5c6cb; }
|
47
|
+
.alert-info { color: #0c5460; background-color: #d1ecf1; border-color: #bee5eb; }
|
48
|
+
</style>
|
49
|
+
</head>
|
50
|
+
|
51
|
+
<body>
|
52
|
+
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
53
|
+
<div class="container">
|
54
|
+
<%= link_to "Ragdoll Dashboard", ragdoll.root_path, class: "navbar-brand" %>
|
55
|
+
|
56
|
+
<div class="navbar-nav ms-auto">
|
57
|
+
<%= link_to "Dashboard", ragdoll.dashboard_index_path, class: "nav-link" %>
|
58
|
+
<%= link_to "Documents", ragdoll.documents_path, class: "nav-link" %>
|
59
|
+
<%= link_to "Jobs", ragdoll.jobs_path, class: "nav-link" %>
|
60
|
+
<%= link_to "Search", ragdoll.search_index_path, class: "nav-link" %>
|
61
|
+
<%= link_to "Analytics", ragdoll.analytics_path, class: "nav-link" %>
|
62
|
+
<%= link_to "Back to Demo", main_app.root_path, class: "nav-link" %>
|
63
|
+
</div>
|
64
|
+
</div>
|
65
|
+
</nav>
|
66
|
+
|
67
|
+
<% if flash.any? %>
|
68
|
+
<div class="container mt-3">
|
69
|
+
<% flash.each do |type, message| %>
|
70
|
+
<div class="alert alert-<%= type == 'notice' ? 'success' : type == 'alert' ? 'danger' : 'info' %> alert-dismissible fade show" role="alert">
|
71
|
+
<%= message %>
|
72
|
+
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
73
|
+
</div>
|
74
|
+
<% end %>
|
75
|
+
</div>
|
76
|
+
<% end %>
|
77
|
+
|
78
|
+
<main>
|
79
|
+
<%= yield %>
|
80
|
+
</main>
|
81
|
+
|
82
|
+
<!-- JavaScript with fallback support -->
|
83
|
+
<script>
|
84
|
+
// Check if CDN resources loaded, provide fallbacks if not
|
85
|
+
console.log('🔧 Checking JavaScript dependencies...');
|
86
|
+
|
87
|
+
// ActionCable fallback and App global setup
|
88
|
+
window.App = window.App || {};
|
89
|
+
|
90
|
+
if (typeof ActionCable === 'undefined') {
|
91
|
+
console.warn('ActionCable not loaded from CDN, using minimal fallback');
|
92
|
+
window.ActionCable = {
|
93
|
+
createConsumer: function(url) {
|
94
|
+
console.log('ActionCable fallback: WebSocket functionality disabled');
|
95
|
+
return {
|
96
|
+
subscriptions: {
|
97
|
+
create: function(channel, callbacks) {
|
98
|
+
console.log('ActionCable fallback: Channel subscription disabled', channel);
|
99
|
+
if (callbacks && callbacks.connected) {
|
100
|
+
setTimeout(() => callbacks.connected(), 100);
|
101
|
+
}
|
102
|
+
return {
|
103
|
+
unsubscribe: function() { console.log('ActionCable fallback: Unsubscribe'); }
|
104
|
+
};
|
105
|
+
}
|
106
|
+
},
|
107
|
+
disconnect: function() { console.log('ActionCable fallback: Disconnect'); }
|
108
|
+
};
|
109
|
+
}
|
110
|
+
};
|
111
|
+
} else {
|
112
|
+
console.log('✅ ActionCable loaded from CDN');
|
113
|
+
}
|
114
|
+
|
115
|
+
// Ensure App.cable is available regardless of loading method
|
116
|
+
if (!window.App) {
|
117
|
+
window.App = {};
|
118
|
+
}
|
119
|
+
|
120
|
+
if (typeof ActionCable !== 'undefined' && !window.App.cable) {
|
121
|
+
try {
|
122
|
+
window.App.cable = ActionCable.createConsumer('/cable');
|
123
|
+
console.log('✅ App.cable initialized from CDN ActionCable');
|
124
|
+
console.log('✅ App.cable consumer ready:', window.App.cable);
|
125
|
+
} catch (e) {
|
126
|
+
console.error('❌ Failed to initialize App.cable:', e);
|
127
|
+
}
|
128
|
+
} else if (window.App.cable) {
|
129
|
+
console.log('✅ App.cable already initialized');
|
130
|
+
}
|
131
|
+
|
132
|
+
console.log('🚀 JavaScript dependencies check complete');
|
133
|
+
|
134
|
+
// Additional CDN loading diagnostics
|
135
|
+
console.log('📊 CDN Resource Status:');
|
136
|
+
console.log('- ActionCable CDN loaded:', typeof ActionCable !== 'undefined');
|
137
|
+
console.log('- Bootstrap CDN loaded:', typeof bootstrap !== 'undefined');
|
138
|
+
console.log('- Chart.js CDN loaded:', typeof Chart !== 'undefined');
|
139
|
+
|
140
|
+
console.log('🔧 Fallback system status:');
|
141
|
+
console.log('- ActionCable fallback:', window.ActionCable ? 'active' : 'inactive');
|
142
|
+
|
143
|
+
// Initialize Rails UJS
|
144
|
+
if (typeof Rails !== 'undefined') {
|
145
|
+
Rails.start();
|
146
|
+
console.log('✅ Rails UJS initialized');
|
147
|
+
} else {
|
148
|
+
console.warn('⚠️ Rails UJS not available');
|
149
|
+
}
|
150
|
+
</script>
|
151
|
+
|
152
|
+
<!-- JavaScript from CDN -->
|
153
|
+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
154
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
155
|
+
|
156
|
+
<%= javascript_importmap_tags %>
|
157
|
+
<%= javascript_include_tag 'ragdoll/application', 'data-turbo-track': 'reload' %>
|
158
|
+
<%= javascript_include_tag 'ragdoll/bulk_upload_status', 'data-turbo-track': 'reload' %>
|
159
|
+
|
160
|
+
<%= yield :javascript %>
|
161
|
+
</body>
|
162
|
+
</html>
|
@@ -0,0 +1,333 @@
|
|
1
|
+
<% content_for(:title, "Analytics Dashboard") %>
|
2
|
+
|
3
|
+
<%= render Ragdoll::PageHeaderComponent.new(
|
4
|
+
title: "Analytics Dashboard",
|
5
|
+
subtitle: "Search patterns, usage statistics, and system insights",
|
6
|
+
icon: "fas fa-chart-line"
|
7
|
+
) %>
|
8
|
+
|
9
|
+
<!-- Search Analytics Overview -->
|
10
|
+
<div class="row mb-4">
|
11
|
+
<div class="col-md-3">
|
12
|
+
<%= render Ragdoll::StatsCardComponent.new(
|
13
|
+
title: "Total Searches",
|
14
|
+
value: @search_analytics[:total_searches],
|
15
|
+
icon: "fas fa-search",
|
16
|
+
color: "primary"
|
17
|
+
) %>
|
18
|
+
</div>
|
19
|
+
<div class="col-md-3">
|
20
|
+
<%= render Ragdoll::StatsCardComponent.new(
|
21
|
+
title: "Unique Queries",
|
22
|
+
value: @search_analytics[:unique_queries],
|
23
|
+
icon: "fas fa-question-circle",
|
24
|
+
color: "info"
|
25
|
+
) %>
|
26
|
+
</div>
|
27
|
+
<div class="col-md-3">
|
28
|
+
<%= render Ragdoll::StatsCardComponent.new(
|
29
|
+
title: "Avg Results",
|
30
|
+
value: @search_analytics[:average_results],
|
31
|
+
icon: "fas fa-list",
|
32
|
+
color: "success"
|
33
|
+
) %>
|
34
|
+
</div>
|
35
|
+
<div class="col-md-3">
|
36
|
+
<%= render Ragdoll::StatsCardComponent.new(
|
37
|
+
title: "Avg Similarity",
|
38
|
+
value: number_to_percentage(@search_analytics[:average_similarity] * 100, precision: 1),
|
39
|
+
icon: "fas fa-percentage",
|
40
|
+
color: "warning"
|
41
|
+
) %>
|
42
|
+
</div>
|
43
|
+
</div>
|
44
|
+
|
45
|
+
<!-- Time-based Analytics -->
|
46
|
+
<div class="row mb-4">
|
47
|
+
<div class="col-md-4">
|
48
|
+
<%= render Ragdoll::StatsCardComponent.new(
|
49
|
+
title: "Today",
|
50
|
+
value: @search_analytics[:searches_today],
|
51
|
+
icon: "fas fa-calendar-day",
|
52
|
+
color: "primary"
|
53
|
+
) %>
|
54
|
+
</div>
|
55
|
+
<div class="col-md-4">
|
56
|
+
<%= render Ragdoll::StatsCardComponent.new(
|
57
|
+
title: "This Week",
|
58
|
+
value: @search_analytics[:searches_this_week],
|
59
|
+
icon: "fas fa-calendar-week",
|
60
|
+
color: "success"
|
61
|
+
) %>
|
62
|
+
</div>
|
63
|
+
<div class="col-md-4">
|
64
|
+
<%= render Ragdoll::StatsCardComponent.new(
|
65
|
+
title: "This Month",
|
66
|
+
value: @search_analytics[:searches_this_month],
|
67
|
+
icon: "fas fa-calendar-alt",
|
68
|
+
color: "info"
|
69
|
+
) %>
|
70
|
+
</div>
|
71
|
+
</div>
|
72
|
+
|
73
|
+
<div class="row">
|
74
|
+
<!-- Search Trends Chart -->
|
75
|
+
<div class="col-lg-8">
|
76
|
+
<div class="card mb-4">
|
77
|
+
<div class="card-header">
|
78
|
+
<h5 class="card-title mb-0">
|
79
|
+
<i class="fas fa-chart-line"></i> Search Trends (Last 7 Days)
|
80
|
+
</h5>
|
81
|
+
</div>
|
82
|
+
<div class="card-body">
|
83
|
+
<canvas id="searchTrendsChart" height="100"></canvas>
|
84
|
+
</div>
|
85
|
+
</div>
|
86
|
+
</div>
|
87
|
+
|
88
|
+
<!-- Search Types Distribution -->
|
89
|
+
<div class="col-lg-4">
|
90
|
+
<div class="card mb-4">
|
91
|
+
<div class="card-header">
|
92
|
+
<h5 class="card-title mb-0">
|
93
|
+
<i class="fas fa-chart-pie"></i> Search Types
|
94
|
+
</h5>
|
95
|
+
</div>
|
96
|
+
<div class="card-body">
|
97
|
+
<canvas id="searchTypesChart" height="150"></canvas>
|
98
|
+
</div>
|
99
|
+
</div>
|
100
|
+
</div>
|
101
|
+
</div>
|
102
|
+
|
103
|
+
<div class="row">
|
104
|
+
<!-- Top Queries -->
|
105
|
+
<div class="col-lg-6">
|
106
|
+
<div class="card mb-4">
|
107
|
+
<div class="card-header">
|
108
|
+
<h5 class="card-title mb-0">
|
109
|
+
<i class="fas fa-fire"></i> Top Search Queries
|
110
|
+
</h5>
|
111
|
+
</div>
|
112
|
+
<div class="card-body">
|
113
|
+
<% if @top_queries.any? %>
|
114
|
+
<% @top_queries.each_with_index do |(query, count), index| %>
|
115
|
+
<div class="d-flex justify-content-between align-items-center mb-2 <%= 'border-bottom pb-2' unless index == @top_queries.count - 1 %>">
|
116
|
+
<div class="flex-grow-1">
|
117
|
+
<div class="fw-medium"><%= truncate(query, length: 40) %></div>
|
118
|
+
</div>
|
119
|
+
<div class="ms-2">
|
120
|
+
<span class="badge bg-primary"><%= count %></span>
|
121
|
+
</div>
|
122
|
+
</div>
|
123
|
+
<% end %>
|
124
|
+
<% else %>
|
125
|
+
<%= render Ragdoll::EmptyStateComponent.new(
|
126
|
+
title: "No queries yet",
|
127
|
+
message: "Search data will appear here once users start searching.",
|
128
|
+
icon: "fas fa-search"
|
129
|
+
) %>
|
130
|
+
<% end %>
|
131
|
+
</div>
|
132
|
+
</div>
|
133
|
+
</div>
|
134
|
+
|
135
|
+
<!-- Top Documents -->
|
136
|
+
<div class="col-lg-6">
|
137
|
+
<div class="card mb-4">
|
138
|
+
<div class="card-header">
|
139
|
+
<h5 class="card-title mb-0">
|
140
|
+
<i class="fas fa-star"></i> Most Accessed Documents
|
141
|
+
</h5>
|
142
|
+
</div>
|
143
|
+
<div class="card-body">
|
144
|
+
<% if @top_documents.any? %>
|
145
|
+
<% @top_documents.each_with_index do |(title, usage_count), index| %>
|
146
|
+
<div class="d-flex justify-content-between align-items-center mb-2 <%= 'border-bottom pb-2' unless index == @top_documents.count - 1 %>">
|
147
|
+
<div class="flex-grow-1">
|
148
|
+
<div class="fw-medium"><%= truncate(title, length: 35) %></div>
|
149
|
+
<small class="text-muted">Used <%= usage_count %> times</small>
|
150
|
+
</div>
|
151
|
+
<div class="ms-2">
|
152
|
+
<span class="badge bg-success"><%= usage_count %></span>
|
153
|
+
</div>
|
154
|
+
</div>
|
155
|
+
<% end %>
|
156
|
+
<% else %>
|
157
|
+
<%= render Ragdoll::EmptyStateComponent.new(
|
158
|
+
title: "No document usage yet",
|
159
|
+
message: "Document access statistics will appear here.",
|
160
|
+
icon: "fas fa-file-alt"
|
161
|
+
) %>
|
162
|
+
<% end %>
|
163
|
+
</div>
|
164
|
+
</div>
|
165
|
+
</div>
|
166
|
+
</div>
|
167
|
+
|
168
|
+
<!-- Similarity Score Distribution -->
|
169
|
+
<div class="row">
|
170
|
+
<div class="col-lg-8">
|
171
|
+
<div class="card mb-4">
|
172
|
+
<div class="card-header">
|
173
|
+
<h5 class="card-title mb-0">
|
174
|
+
<i class="fas fa-chart-bar"></i> Similarity Score Distribution
|
175
|
+
</h5>
|
176
|
+
</div>
|
177
|
+
<div class="card-body">
|
178
|
+
<canvas id="similarityChart" height="100"></canvas>
|
179
|
+
</div>
|
180
|
+
</div>
|
181
|
+
</div>
|
182
|
+
|
183
|
+
<!-- System Statistics -->
|
184
|
+
<div class="col-lg-4">
|
185
|
+
<div class="card mb-4">
|
186
|
+
<div class="card-header">
|
187
|
+
<h5 class="card-title mb-0">
|
188
|
+
<i class="fas fa-server"></i> System Statistics
|
189
|
+
</h5>
|
190
|
+
</div>
|
191
|
+
<div class="card-body">
|
192
|
+
<dl class="row small">
|
193
|
+
<dt class="col-sm-7">Total Documents:</dt>
|
194
|
+
<dd class="col-sm-5"><%= @system_stats[:total_documents] %></dd>
|
195
|
+
|
196
|
+
<dt class="col-sm-7">Processed:</dt>
|
197
|
+
<dd class="col-sm-5">
|
198
|
+
<span class="badge bg-success"><%= @system_stats[:processed_documents] %></span>
|
199
|
+
</dd>
|
200
|
+
|
201
|
+
<dt class="col-sm-7">Failed:</dt>
|
202
|
+
<dd class="col-sm-5">
|
203
|
+
<span class="badge bg-danger"><%= @system_stats[:failed_documents] %></span>
|
204
|
+
</dd>
|
205
|
+
|
206
|
+
<dt class="col-sm-7">Pending:</dt>
|
207
|
+
<dd class="col-sm-5">
|
208
|
+
<span class="badge bg-warning"><%= @system_stats[:pending_documents] %></span>
|
209
|
+
</dd>
|
210
|
+
|
211
|
+
<dt class="col-sm-7">Total Embeddings:</dt>
|
212
|
+
<dd class="col-sm-5"><%= @system_stats[:total_embeddings] %></dd>
|
213
|
+
|
214
|
+
<dt class="col-sm-7">Embedding Usage:</dt>
|
215
|
+
<dd class="col-sm-5"><%= @system_stats[:total_embedding_usage] %></dd>
|
216
|
+
</dl>
|
217
|
+
</div>
|
218
|
+
</div>
|
219
|
+
</div>
|
220
|
+
</div>
|
221
|
+
|
222
|
+
<!-- Chart.js Scripts -->
|
223
|
+
<script>
|
224
|
+
document.addEventListener('DOMContentLoaded', function() {
|
225
|
+
// Search Trends Chart
|
226
|
+
const trendsCtx = document.getElementById('searchTrendsChart').getContext('2d');
|
227
|
+
new Chart(trendsCtx, {
|
228
|
+
type: 'line',
|
229
|
+
data: {
|
230
|
+
labels: <%= @search_trends.keys.to_json.html_safe %>,
|
231
|
+
datasets: [{
|
232
|
+
label: 'Searches',
|
233
|
+
data: <%= @search_trends.values.to_json.html_safe %>,
|
234
|
+
borderColor: '#0d6efd',
|
235
|
+
backgroundColor: 'rgba(13, 110, 253, 0.1)',
|
236
|
+
fill: true,
|
237
|
+
tension: 0.4
|
238
|
+
}]
|
239
|
+
},
|
240
|
+
options: {
|
241
|
+
responsive: true,
|
242
|
+
maintainAspectRatio: false,
|
243
|
+
plugins: {
|
244
|
+
legend: {
|
245
|
+
display: false
|
246
|
+
}
|
247
|
+
},
|
248
|
+
scales: {
|
249
|
+
y: {
|
250
|
+
beginAtZero: true,
|
251
|
+
ticks: {
|
252
|
+
stepSize: 1
|
253
|
+
}
|
254
|
+
}
|
255
|
+
}
|
256
|
+
}
|
257
|
+
});
|
258
|
+
|
259
|
+
// Search Types Chart
|
260
|
+
const typesCtx = document.getElementById('searchTypesChart').getContext('2d');
|
261
|
+
const searchTypesData = <%= @search_analytics[:search_types].to_json.html_safe %>;
|
262
|
+
new Chart(typesCtx, {
|
263
|
+
type: 'doughnut',
|
264
|
+
data: {
|
265
|
+
labels: Object.keys(searchTypesData).map(type => type.charAt(0).toUpperCase() + type.slice(1)),
|
266
|
+
datasets: [{
|
267
|
+
data: Object.values(searchTypesData),
|
268
|
+
backgroundColor: [
|
269
|
+
'#0d6efd',
|
270
|
+
'#198754',
|
271
|
+
'#ffc107',
|
272
|
+
'#dc3545',
|
273
|
+
'#6f42c1'
|
274
|
+
]
|
275
|
+
}]
|
276
|
+
},
|
277
|
+
options: {
|
278
|
+
responsive: true,
|
279
|
+
maintainAspectRatio: false,
|
280
|
+
plugins: {
|
281
|
+
legend: {
|
282
|
+
position: 'bottom'
|
283
|
+
}
|
284
|
+
}
|
285
|
+
}
|
286
|
+
});
|
287
|
+
|
288
|
+
// Similarity Distribution Chart
|
289
|
+
const similarityCtx = document.getElementById('similarityChart').getContext('2d');
|
290
|
+
const similarityData = <%= @similarity_distribution.to_json.html_safe %>;
|
291
|
+
new Chart(similarityCtx, {
|
292
|
+
type: 'bar',
|
293
|
+
data: {
|
294
|
+
labels: Object.keys(similarityData),
|
295
|
+
datasets: [{
|
296
|
+
label: 'Search Count',
|
297
|
+
data: Object.values(similarityData),
|
298
|
+
backgroundColor: [
|
299
|
+
'#198754',
|
300
|
+
'#20c997',
|
301
|
+
'#0dcaf0',
|
302
|
+
'#6f42c1',
|
303
|
+
'#fd7e14',
|
304
|
+
'#dc3545'
|
305
|
+
]
|
306
|
+
}]
|
307
|
+
},
|
308
|
+
options: {
|
309
|
+
responsive: true,
|
310
|
+
maintainAspectRatio: false,
|
311
|
+
plugins: {
|
312
|
+
legend: {
|
313
|
+
display: false
|
314
|
+
}
|
315
|
+
},
|
316
|
+
scales: {
|
317
|
+
y: {
|
318
|
+
beginAtZero: true,
|
319
|
+
ticks: {
|
320
|
+
stepSize: 1
|
321
|
+
}
|
322
|
+
},
|
323
|
+
x: {
|
324
|
+
title: {
|
325
|
+
display: true,
|
326
|
+
text: 'Similarity Score Range'
|
327
|
+
}
|
328
|
+
}
|
329
|
+
}
|
330
|
+
}
|
331
|
+
});
|
332
|
+
});
|
333
|
+
</script>
|