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,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ragdoll
|
4
|
+
module Api
|
5
|
+
module V1
|
6
|
+
class SystemController < BaseController
|
7
|
+
def stats
|
8
|
+
begin
|
9
|
+
client = ::Ragdoll::Client.new
|
10
|
+
ragdoll_stats = client.stats
|
11
|
+
|
12
|
+
system_stats = {
|
13
|
+
ragdoll_version: ::Ragdoll::VERSION,
|
14
|
+
rails_version: Rails.version,
|
15
|
+
ruby_version: RUBY_VERSION,
|
16
|
+
|
17
|
+
database_stats: {
|
18
|
+
documents: ::Ragdoll::Document.count,
|
19
|
+
embeddings: ::Ragdoll::Embedding.count,
|
20
|
+
searches: ::Ragdoll::Search.count,
|
21
|
+
database_size: calculate_database_size
|
22
|
+
},
|
23
|
+
|
24
|
+
configuration: {
|
25
|
+
llm_provider: ::Ragdoll.configuration.llm_provider,
|
26
|
+
embedding_provider: ::Ragdoll.configuration.embedding_provider,
|
27
|
+
embedding_model: ::Ragdoll.configuration.embedding_model,
|
28
|
+
chunk_size: ::Ragdoll.configuration.chunk_size,
|
29
|
+
chunk_overlap: ::Ragdoll.configuration.chunk_overlap,
|
30
|
+
max_search_results: ::Ragdoll.configuration.max_search_results,
|
31
|
+
search_similarity_threshold: ::Ragdoll.configuration.search_similarity_threshold,
|
32
|
+
enable_search_analytics: ::Ragdoll.configuration.enable_search_analytics,
|
33
|
+
enable_document_summarization: ::Ragdoll.configuration.enable_document_summarization,
|
34
|
+
enable_usage_tracking: ::Ragdoll.configuration.enable_usage_tracking,
|
35
|
+
usage_ranking_enabled: ::Ragdoll.configuration.usage_ranking_enabled
|
36
|
+
},
|
37
|
+
|
38
|
+
performance_metrics: {
|
39
|
+
average_search_time: calculate_average_search_time,
|
40
|
+
embedding_dimensions: ::Ragdoll::Embedding.first&.embedding_dimensions || 0,
|
41
|
+
average_document_size: ::Ragdoll::Document.average('LENGTH(summary)')&.round || 0,
|
42
|
+
average_chunks_per_document: ::Ragdoll::Document.count > 0 ? (::Ragdoll::Embedding.count.to_f / ::Ragdoll::Document.count).round || 0 : 0
|
43
|
+
},
|
44
|
+
|
45
|
+
health_check: {
|
46
|
+
database_connection: database_healthy?,
|
47
|
+
embedding_service: embedding_service_healthy?
|
48
|
+
}
|
49
|
+
}.merge(ragdoll_stats)
|
50
|
+
|
51
|
+
render json: system_stats
|
52
|
+
rescue => e
|
53
|
+
render_error("Error retrieving system stats: #{e.message}")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def calculate_database_size
|
60
|
+
# Simple approximation - in production you might want more sophisticated calculation
|
61
|
+
result = ActiveRecord::Base.connection.execute(
|
62
|
+
"SELECT pg_size_pretty(pg_database_size(current_database()))"
|
63
|
+
)
|
64
|
+
result.first['pg_size_pretty']
|
65
|
+
rescue
|
66
|
+
"Unknown"
|
67
|
+
end
|
68
|
+
|
69
|
+
def calculate_average_search_time
|
70
|
+
# Calculate from actual search execution times if available
|
71
|
+
avg_time = ::Ragdoll::Search.where.not(execution_time_ms: nil).average(:execution_time_ms)
|
72
|
+
if avg_time
|
73
|
+
"#{avg_time.round}ms"
|
74
|
+
else
|
75
|
+
"< 100ms"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def database_healthy?
|
80
|
+
ActiveRecord::Base.connection.active?
|
81
|
+
rescue
|
82
|
+
false
|
83
|
+
end
|
84
|
+
|
85
|
+
def embedding_service_healthy?
|
86
|
+
begin
|
87
|
+
client = ::Ragdoll::Client.new
|
88
|
+
# Try a simple test to see if the embedding service is available
|
89
|
+
true
|
90
|
+
rescue
|
91
|
+
false
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ragdoll
|
4
|
+
class ApplicationController < ActionController::Base
|
5
|
+
protect_from_forgery with: :exception
|
6
|
+
before_action :set_current_user
|
7
|
+
|
8
|
+
layout 'ragdoll/application'
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def set_current_user
|
13
|
+
# This can be overridden in the host application
|
14
|
+
# to set the current user for the Ragdoll engine
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ragdoll
|
4
|
+
class ConfigurationController < ApplicationController
|
5
|
+
def index
|
6
|
+
@configuration = ::Ragdoll.configuration
|
7
|
+
@available_providers = %w[openai anthropic google azure ollama huggingface]
|
8
|
+
@available_models = {
|
9
|
+
openai: ['text-embedding-3-small', 'text-embedding-3-large', 'text-embedding-ada-002'],
|
10
|
+
anthropic: ['claude-3-haiku-20240307', 'claude-3-sonnet-20240229', 'claude-3-opus-20240229'],
|
11
|
+
google: ['gemini-pro', 'gemini-1.5-flash', 'gemini-1.5-pro'],
|
12
|
+
azure: ['text-embedding-3-small', 'text-embedding-3-large'],
|
13
|
+
ollama: ['llama2', 'mistral', 'codellama'],
|
14
|
+
huggingface: ['sentence-transformers/all-MiniLM-L6-v2', 'sentence-transformers/all-mpnet-base-v2']
|
15
|
+
}
|
16
|
+
|
17
|
+
@current_stats = {
|
18
|
+
total_documents: ::Ragdoll::Document.count,
|
19
|
+
total_embeddings: ::Ragdoll::Embedding.count,
|
20
|
+
embedding_dimensions: ::Ragdoll::Embedding.first&.embedding_dimensions || 0,
|
21
|
+
average_chunk_size: ::Ragdoll::Embedding.average('LENGTH(content)')&.round || 0
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
def update
|
26
|
+
config_params = params.require(:configuration).permit(
|
27
|
+
:llm_provider,
|
28
|
+
:embedding_provider,
|
29
|
+
:embedding_model,
|
30
|
+
:chunk_size,
|
31
|
+
:chunk_overlap,
|
32
|
+
:max_search_results,
|
33
|
+
:search_similarity_threshold,
|
34
|
+
:enable_search_analytics,
|
35
|
+
:enable_document_summarization,
|
36
|
+
:enable_usage_tracking,
|
37
|
+
:usage_ranking_enabled,
|
38
|
+
:openai_api_key,
|
39
|
+
:anthropic_api_key,
|
40
|
+
:google_api_key,
|
41
|
+
:azure_api_key,
|
42
|
+
:ollama_url,
|
43
|
+
:huggingface_api_key
|
44
|
+
)
|
45
|
+
|
46
|
+
begin
|
47
|
+
# Update configuration
|
48
|
+
config = ::Ragdoll.configuration
|
49
|
+
|
50
|
+
config_params.each do |key, value|
|
51
|
+
# Convert string values to appropriate types
|
52
|
+
case key
|
53
|
+
when 'chunk_size', 'chunk_overlap', 'max_search_results'
|
54
|
+
config.send("#{key}=", value.to_i)
|
55
|
+
when 'search_similarity_threshold'
|
56
|
+
config.send("#{key}=", value.to_f)
|
57
|
+
when 'enable_search_analytics', 'enable_document_summarization', 'enable_usage_tracking', 'usage_ranking_enabled'
|
58
|
+
config.send("#{key}=", value == '1' || value == 'true')
|
59
|
+
else
|
60
|
+
config.send("#{key}=", value) if value.present?
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
flash[:notice] = 'Configuration updated successfully.'
|
65
|
+
|
66
|
+
# Test the configuration
|
67
|
+
begin
|
68
|
+
client = ::Ragdoll::Client.new
|
69
|
+
test_result = client.stats
|
70
|
+
flash[:notice] += " Configuration test successful."
|
71
|
+
rescue => e
|
72
|
+
flash[:warning] = "Configuration saved but test failed: #{e.message}"
|
73
|
+
end
|
74
|
+
|
75
|
+
rescue => e
|
76
|
+
flash[:alert] = "Error updating configuration: #{e.message}"
|
77
|
+
end
|
78
|
+
|
79
|
+
redirect_to ragdoll.configuration_path
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ragdoll
|
4
|
+
class DashboardController < ApplicationController
|
5
|
+
def index
|
6
|
+
@stats = {
|
7
|
+
total_documents: ::Ragdoll::Document.count,
|
8
|
+
processed_documents: ::Ragdoll::Document.where(status: 'processed').count,
|
9
|
+
failed_documents: ::Ragdoll::Document.where(status: 'failed').count,
|
10
|
+
pending_documents: ::Ragdoll::Document.where(status: 'pending').count,
|
11
|
+
total_embeddings: ::Ragdoll::Embedding.count,
|
12
|
+
total_searches: ::Ragdoll::Search.count,
|
13
|
+
recent_searches: ::Ragdoll::Search.order(created_at: :desc).limit(5)
|
14
|
+
}
|
15
|
+
|
16
|
+
@document_types = ::Ragdoll::Document.group(:document_type).count
|
17
|
+
@recent_documents = ::Ragdoll::Document.order(created_at: :desc).limit(10)
|
18
|
+
|
19
|
+
# Usage analytics - join through embeddable (Content) to get to documents
|
20
|
+
@top_searched_documents = ::Ragdoll::Embedding
|
21
|
+
.joins("JOIN ragdoll_contents ON ragdoll_contents.id = ragdoll_embeddings.embeddable_id")
|
22
|
+
.joins("JOIN ragdoll_documents ON ragdoll_documents.id = ragdoll_contents.document_id")
|
23
|
+
.group('ragdoll_documents.title')
|
24
|
+
.order(Arel.sql('SUM(ragdoll_embeddings.usage_count) DESC'))
|
25
|
+
.limit(5)
|
26
|
+
.sum(:usage_count)
|
27
|
+
end
|
28
|
+
|
29
|
+
def analytics
|
30
|
+
today = Date.current
|
31
|
+
week_start = today.beginning_of_week
|
32
|
+
month_start = today.beginning_of_month
|
33
|
+
|
34
|
+
# Calculate search statistics
|
35
|
+
all_searches = ::Ragdoll::Search.all
|
36
|
+
searches_today = all_searches.where(created_at: today.beginning_of_day..today.end_of_day)
|
37
|
+
searches_this_week = all_searches.where(created_at: week_start.beginning_of_day..today.end_of_day)
|
38
|
+
searches_this_month = all_searches.where(created_at: month_start.beginning_of_day..today.end_of_day)
|
39
|
+
|
40
|
+
# Comprehensive search analytics combining both pages
|
41
|
+
@search_analytics = {
|
42
|
+
total_searches: all_searches.count,
|
43
|
+
unique_queries: all_searches.distinct.count(:query),
|
44
|
+
searches_today: searches_today.count,
|
45
|
+
searches_this_week: searches_this_week.count,
|
46
|
+
searches_this_month: searches_this_month.count,
|
47
|
+
average_results: all_searches.average(:results_count)&.round(1) || 0,
|
48
|
+
average_similarity: all_searches.where.not(avg_similarity_score: nil).average(:avg_similarity_score)&.round(3) || 0,
|
49
|
+
avg_execution_time: all_searches.average(:execution_time_ms)&.round(1) || 0,
|
50
|
+
search_types: all_searches.group(:search_type).count
|
51
|
+
}
|
52
|
+
|
53
|
+
# Top queries (most frequent)
|
54
|
+
@top_queries = all_searches
|
55
|
+
.group(:query)
|
56
|
+
.count
|
57
|
+
.sort_by { |query, count| -count }
|
58
|
+
.first(10)
|
59
|
+
.to_h
|
60
|
+
|
61
|
+
# Search trends by day for the last 7 days
|
62
|
+
@search_trends = (6.days.ago.to_date..today).map do |date|
|
63
|
+
count = all_searches.where(created_at: date.beginning_of_day..date.end_of_day).count
|
64
|
+
[date.strftime('%m/%d'), count]
|
65
|
+
end.to_h
|
66
|
+
|
67
|
+
# Most searched documents (using embedding usage as proxy)
|
68
|
+
@top_documents = ::Ragdoll::Embedding
|
69
|
+
.joins("JOIN ragdoll_contents ON ragdoll_contents.id = ragdoll_embeddings.embeddable_id")
|
70
|
+
.joins("JOIN ragdoll_documents ON ragdoll_documents.id = ragdoll_contents.document_id")
|
71
|
+
.group('ragdoll_documents.title')
|
72
|
+
.order(Arel.sql('SUM(ragdoll_embeddings.usage_count) DESC'))
|
73
|
+
.limit(10)
|
74
|
+
.sum(:usage_count)
|
75
|
+
|
76
|
+
# Similarity score distribution
|
77
|
+
similarity_scores = all_searches.where.not(avg_similarity_score: nil).pluck(:avg_similarity_score)
|
78
|
+
@similarity_distribution = {
|
79
|
+
"0.9-1.0" => similarity_scores.count { |s| s >= 0.9 },
|
80
|
+
"0.8-0.9" => similarity_scores.count { |s| s >= 0.8 && s < 0.9 },
|
81
|
+
"0.7-0.8" => similarity_scores.count { |s| s >= 0.7 && s < 0.8 },
|
82
|
+
"0.6-0.7" => similarity_scores.count { |s| s >= 0.6 && s < 0.7 },
|
83
|
+
"0.5-0.6" => similarity_scores.count { |s| s >= 0.5 && s < 0.6 },
|
84
|
+
"< 0.5" => similarity_scores.count { |s| s < 0.5 }
|
85
|
+
}
|
86
|
+
|
87
|
+
# System statistics
|
88
|
+
@system_stats = {
|
89
|
+
total_documents: ::Ragdoll::Document.count,
|
90
|
+
processed_documents: ::Ragdoll::Document.where(status: 'processed').count,
|
91
|
+
failed_documents: ::Ragdoll::Document.where(status: 'failed').count,
|
92
|
+
pending_documents: ::Ragdoll::Document.where(status: 'pending').count,
|
93
|
+
total_embeddings: ::Ragdoll::Embedding.count,
|
94
|
+
total_embedding_usage: ::Ragdoll::Embedding.sum(:usage_count)
|
95
|
+
}
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|