ruby_llm-agents 1.3.3 → 2.0.0
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 +101 -334
- data/app/controllers/concerns/ruby_llm/agents/sortable.rb +0 -1
- data/app/controllers/ruby_llm/agents/agents_controller.rb +5 -56
- data/app/controllers/ruby_llm/agents/dashboard_controller.rb +22 -106
- data/app/controllers/ruby_llm/agents/executions_controller.rb +4 -114
- data/app/controllers/ruby_llm/agents/tenants_controller.rb +30 -2
- data/app/helpers/ruby_llm/agents/application_helper.rb +19 -53
- data/app/models/ruby_llm/agents/execution/analytics.rb +13 -54
- data/app/models/ruby_llm/agents/execution/scopes.rb +61 -14
- data/app/models/ruby_llm/agents/execution.rb +46 -10
- data/app/models/ruby_llm/agents/execution_detail.rb +18 -0
- data/app/models/ruby_llm/agents/tenant/budgetable.rb +132 -24
- data/app/models/ruby_llm/agents/tenant/incrementable.rb +117 -0
- data/app/models/ruby_llm/agents/tenant/resettable.rb +128 -0
- data/app/models/ruby_llm/agents/tenant/trackable.rb +46 -12
- data/app/models/ruby_llm/agents/tenant.rb +2 -3
- data/app/models/ruby_llm/agents/tenant_budget.rb +6 -3
- data/app/services/ruby_llm/agents/agent_registry.rb +6 -112
- data/app/views/layouts/ruby_llm/agents/application.html.erb +87 -252
- data/app/views/ruby_llm/agents/agents/_config_agent.html.erb +71 -218
- data/app/views/ruby_llm/agents/agents/_config_embedder.html.erb +20 -63
- data/app/views/ruby_llm/agents/agents/_config_image_generator.html.erb +44 -131
- data/app/views/ruby_llm/agents/agents/_config_moderator.html.erb +16 -57
- data/app/views/ruby_llm/agents/agents/_config_speaker.html.erb +39 -104
- data/app/views/ruby_llm/agents/agents/_config_transcriber.html.erb +29 -82
- data/app/views/ruby_llm/agents/agents/_empty_state.html.erb +4 -14
- data/app/views/ruby_llm/agents/agents/index.html.erb +105 -274
- data/app/views/ruby_llm/agents/agents/show.html.erb +248 -378
- data/app/views/ruby_llm/agents/dashboard/_action_center.html.erb +29 -52
- data/app/views/ruby_llm/agents/dashboard/_tenant_budget.html.erb +73 -99
- data/app/views/ruby_llm/agents/dashboard/index.html.erb +228 -433
- data/app/views/ruby_llm/agents/executions/_execution.html.erb +1 -1
- data/app/views/ruby_llm/agents/executions/_filters.html.erb +4 -25
- data/app/views/ruby_llm/agents/executions/_list.html.erb +111 -152
- data/app/views/ruby_llm/agents/executions/index.html.erb +5 -7
- data/app/views/ruby_llm/agents/executions/show.html.erb +528 -989
- data/app/views/ruby_llm/agents/shared/_agent_type_badge.html.erb +5 -21
- data/app/views/ruby_llm/agents/shared/_executions_table.html.erb +70 -191
- data/app/views/ruby_llm/agents/shared/_filter_dropdown.html.erb +16 -44
- data/app/views/ruby_llm/agents/shared/_select_dropdown.html.erb +12 -41
- data/app/views/ruby_llm/agents/shared/_status_badge.html.erb +11 -65
- data/app/views/ruby_llm/agents/shared/_tenant_filter.html.erb +6 -5
- data/app/views/ruby_llm/agents/system_config/show.html.erb +240 -351
- data/app/views/ruby_llm/agents/tenants/_form.html.erb +67 -77
- data/app/views/ruby_llm/agents/tenants/edit.html.erb +7 -9
- data/app/views/ruby_llm/agents/tenants/index.html.erb +100 -122
- data/app/views/ruby_llm/agents/tenants/show.html.erb +146 -336
- data/config/routes.rb +0 -13
- data/lib/generators/ruby_llm_agents/install_generator.rb +9 -14
- data/lib/generators/ruby_llm_agents/migrate_structure_generator.rb +2 -12
- data/lib/generators/ruby_llm_agents/restructure_generator.rb +0 -2
- data/lib/generators/ruby_llm_agents/templates/add_usage_counters_to_tenants_migration.rb.tt +37 -0
- data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +1 -2
- data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +1 -1
- data/lib/generators/ruby_llm_agents/templates/application_image_pipeline.rb.tt +0 -1
- data/lib/generators/ruby_llm_agents/templates/create_execution_details_migration.rb.tt +27 -0
- data/lib/generators/ruby_llm_agents/templates/create_tenants_migration.rb.tt +25 -0
- data/lib/generators/ruby_llm_agents/templates/image_pipeline.rb.tt +0 -1
- data/lib/generators/ruby_llm_agents/templates/initializer.rb.tt +9 -12
- data/lib/generators/ruby_llm_agents/templates/migration.rb.tt +40 -71
- data/lib/generators/ruby_llm_agents/templates/remove_agent_version_migration.rb.tt +13 -0
- data/lib/generators/ruby_llm_agents/templates/remove_workflow_columns_migration.rb.tt +19 -0
- data/lib/generators/ruby_llm_agents/templates/skills/AGENTS.md.tt +2 -4
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_PIPELINES.md.tt +0 -1
- data/lib/generators/ruby_llm_agents/templates/split_execution_details_migration.rb.tt +232 -0
- data/lib/generators/ruby_llm_agents/upgrade_generator.rb +58 -262
- data/lib/ruby_llm/agents/audio/speaker.rb +0 -1
- data/lib/ruby_llm/agents/audio/transcriber.rb +0 -1
- data/lib/ruby_llm/agents/base_agent.rb +52 -6
- data/lib/ruby_llm/agents/core/base/callbacks.rb +142 -0
- data/lib/ruby_llm/agents/core/base.rb +23 -55
- data/lib/ruby_llm/agents/core/configuration.rb +58 -117
- data/lib/ruby_llm/agents/core/errors.rb +0 -58
- data/lib/ruby_llm/agents/core/instrumentation.rb +157 -110
- data/lib/ruby_llm/agents/core/llm_tenant.rb +8 -7
- data/lib/ruby_llm/agents/core/version.rb +1 -1
- data/lib/ruby_llm/agents/dsl/base.rb +157 -17
- data/lib/ruby_llm/agents/dsl/caching.rb +33 -2
- data/lib/ruby_llm/agents/dsl/reliability.rb +148 -0
- data/lib/ruby_llm/agents/dsl.rb +1 -2
- data/lib/ruby_llm/agents/image/analyzer/execution.rb +1 -2
- data/lib/ruby_llm/agents/image/background_remover/execution.rb +1 -2
- data/lib/ruby_llm/agents/image/concerns/image_operation_dsl.rb +1 -13
- data/lib/ruby_llm/agents/image/concerns/image_operation_execution.rb +2 -2
- data/lib/ruby_llm/agents/image/editor/dsl.rb +0 -14
- data/lib/ruby_llm/agents/image/editor/execution.rb +1 -10
- data/lib/ruby_llm/agents/image/editor.rb +0 -1
- data/lib/ruby_llm/agents/image/generator.rb +0 -21
- data/lib/ruby_llm/agents/image/pipeline/dsl.rb +0 -13
- data/lib/ruby_llm/agents/image/pipeline/execution.rb +0 -1
- data/lib/ruby_llm/agents/image/transformer/dsl.rb +0 -13
- data/lib/ruby_llm/agents/image/transformer/execution.rb +1 -10
- data/lib/ruby_llm/agents/image/transformer.rb +0 -1
- data/lib/ruby_llm/agents/image/upscaler/execution.rb +1 -2
- data/lib/ruby_llm/agents/image/variator/execution.rb +1 -2
- data/lib/ruby_llm/agents/infrastructure/alert_manager.rb +78 -173
- data/lib/ruby_llm/agents/infrastructure/attempt_tracker.rb +1 -0
- data/lib/ruby_llm/agents/infrastructure/budget/budget_query.rb +66 -2
- data/lib/ruby_llm/agents/infrastructure/budget/spend_recorder.rb +0 -12
- data/lib/ruby_llm/agents/infrastructure/circuit_breaker.rb +10 -13
- data/lib/ruby_llm/agents/infrastructure/reliability.rb +37 -2
- data/lib/ruby_llm/agents/pipeline/context.rb +0 -1
- data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +28 -4
- data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +3 -10
- data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +88 -55
- data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +5 -41
- data/lib/ruby_llm/agents/rails/engine.rb +6 -6
- data/lib/ruby_llm/agents/results/base.rb +1 -49
- data/lib/ruby_llm/agents/text/embedder.rb +0 -1
- data/lib/ruby_llm/agents.rb +1 -9
- data/lib/tasks/ruby_llm_agents.rake +34 -0
- metadata +12 -81
- data/app/controllers/ruby_llm/agents/api_configurations_controller.rb +0 -214
- data/app/controllers/ruby_llm/agents/workflows_controller.rb +0 -544
- data/app/mailers/ruby_llm/agents/alert_mailer.rb +0 -84
- data/app/mailers/ruby_llm/agents/application_mailer.rb +0 -28
- data/app/models/ruby_llm/agents/api_configuration.rb +0 -386
- data/app/models/ruby_llm/agents/execution/workflow.rb +0 -170
- data/app/models/ruby_llm/agents/tenant/configurable.rb +0 -135
- data/app/views/ruby_llm/agents/agents/_agent.html.erb +0 -98
- data/app/views/ruby_llm/agents/agents/_version_comparison.html.erb +0 -186
- data/app/views/ruby_llm/agents/agents/_workflow.html.erb +0 -126
- data/app/views/ruby_llm/agents/alert_mailer/alert_notification.html.erb +0 -107
- data/app/views/ruby_llm/agents/alert_mailer/alert_notification.text.erb +0 -18
- data/app/views/ruby_llm/agents/api_configurations/_api_key_field.html.erb +0 -34
- data/app/views/ruby_llm/agents/api_configurations/_form.html.erb +0 -288
- data/app/views/ruby_llm/agents/api_configurations/edit.html.erb +0 -95
- data/app/views/ruby_llm/agents/api_configurations/edit_tenant.html.erb +0 -97
- data/app/views/ruby_llm/agents/api_configurations/show.html.erb +0 -214
- data/app/views/ruby_llm/agents/api_configurations/tenant.html.erb +0 -179
- data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +0 -73
- data/app/views/ruby_llm/agents/dashboard/_alerts_feed.html.erb +0 -62
- data/app/views/ruby_llm/agents/dashboard/_breaker_strip.html.erb +0 -47
- data/app/views/ruby_llm/agents/dashboard/_budgets_bar.html.erb +0 -75
- data/app/views/ruby_llm/agents/dashboard/_model_comparison.html.erb +0 -56
- data/app/views/ruby_llm/agents/dashboard/_model_cost_breakdown.html.erb +0 -115
- data/app/views/ruby_llm/agents/dashboard/_now_strip.html.erb +0 -59
- data/app/views/ruby_llm/agents/dashboard/_top_errors.html.erb +0 -60
- data/app/views/ruby_llm/agents/executions/_workflow_summary.html.erb +0 -86
- data/app/views/ruby_llm/agents/executions/dry_run.html.erb +0 -149
- data/app/views/ruby_llm/agents/shared/_breadcrumbs.html.erb +0 -48
- data/app/views/ruby_llm/agents/shared/_nav_link.html.erb +0 -27
- data/app/views/ruby_llm/agents/shared/_stat_card.html.erb +0 -14
- data/app/views/ruby_llm/agents/shared/_workflow_type_badge.html.erb +0 -35
- data/app/views/ruby_llm/agents/workflows/_empty_state.html.erb +0 -22
- data/app/views/ruby_llm/agents/workflows/_step_performance.html.erb +0 -228
- data/app/views/ruby_llm/agents/workflows/_structure_dsl.html.erb +0 -539
- data/app/views/ruby_llm/agents/workflows/_structure_parallel.html.erb +0 -76
- data/app/views/ruby_llm/agents/workflows/_structure_pipeline.html.erb +0 -74
- data/app/views/ruby_llm/agents/workflows/_structure_router.html.erb +0 -108
- data/app/views/ruby_llm/agents/workflows/_workflow_diagram.html.erb +0 -920
- data/app/views/ruby_llm/agents/workflows/index.html.erb +0 -179
- data/app/views/ruby_llm/agents/workflows/show.html.erb +0 -467
- data/lib/generators/ruby_llm_agents/api_configuration_generator.rb +0 -100
- data/lib/generators/ruby_llm_agents/templates/add_workflow_migration.rb.tt +0 -38
- data/lib/generators/ruby_llm_agents/templates/application_workflow.rb.tt +0 -48
- data/lib/generators/ruby_llm_agents/templates/create_api_configurations_migration.rb.tt +0 -90
- data/lib/generators/ruby_llm_agents/templates/skills/WORKFLOWS.md.tt +0 -551
- data/lib/ruby_llm/agents/core/base/moderation_dsl.rb +0 -181
- data/lib/ruby_llm/agents/core/base/moderation_execution.rb +0 -274
- data/lib/ruby_llm/agents/core/resolved_config.rb +0 -348
- data/lib/ruby_llm/agents/image/generator/content_policy.rb +0 -95
- data/lib/ruby_llm/agents/infrastructure/redactor.rb +0 -130
- data/lib/ruby_llm/agents/results/moderation_result.rb +0 -158
- data/lib/ruby_llm/agents/text/moderator.rb +0 -237
- data/lib/ruby_llm/agents/workflow/approval.rb +0 -205
- data/lib/ruby_llm/agents/workflow/approval_store.rb +0 -179
- data/lib/ruby_llm/agents/workflow/async.rb +0 -220
- data/lib/ruby_llm/agents/workflow/async_executor.rb +0 -156
- data/lib/ruby_llm/agents/workflow/dsl/executor.rb +0 -467
- data/lib/ruby_llm/agents/workflow/dsl/input_schema.rb +0 -244
- data/lib/ruby_llm/agents/workflow/dsl/iteration_executor.rb +0 -289
- data/lib/ruby_llm/agents/workflow/dsl/parallel_group.rb +0 -107
- data/lib/ruby_llm/agents/workflow/dsl/route_builder.rb +0 -150
- data/lib/ruby_llm/agents/workflow/dsl/schedule_helpers.rb +0 -187
- data/lib/ruby_llm/agents/workflow/dsl/step_config.rb +0 -352
- data/lib/ruby_llm/agents/workflow/dsl/step_executor.rb +0 -415
- data/lib/ruby_llm/agents/workflow/dsl/wait_config.rb +0 -257
- data/lib/ruby_llm/agents/workflow/dsl/wait_executor.rb +0 -317
- data/lib/ruby_llm/agents/workflow/dsl.rb +0 -576
- data/lib/ruby_llm/agents/workflow/instrumentation.rb +0 -249
- data/lib/ruby_llm/agents/workflow/notifiers/base.rb +0 -117
- data/lib/ruby_llm/agents/workflow/notifiers/email.rb +0 -117
- data/lib/ruby_llm/agents/workflow/notifiers/slack.rb +0 -180
- data/lib/ruby_llm/agents/workflow/notifiers/webhook.rb +0 -121
- data/lib/ruby_llm/agents/workflow/notifiers.rb +0 -70
- data/lib/ruby_llm/agents/workflow/orchestrator.rb +0 -416
- data/lib/ruby_llm/agents/workflow/result.rb +0 -592
- data/lib/ruby_llm/agents/workflow/thread_pool.rb +0 -185
- data/lib/ruby_llm/agents/workflow/throttle_manager.rb +0 -206
- data/lib/ruby_llm/agents/workflow/wait_result.rb +0 -213
|
@@ -32,37 +32,11 @@ module RubyLLM
|
|
|
32
32
|
|
|
33
33
|
# Returns chart data as JSON for live updates
|
|
34
34
|
#
|
|
35
|
-
# @param range [String] Time range: "today", "7d",
|
|
36
|
-
# @
|
|
37
|
-
# @return [JSON] Chart data with series (and optional comparison series)
|
|
35
|
+
# @param range [String] Time range: "today", "7d", or "30d"
|
|
36
|
+
# @return [JSON] Chart data with series
|
|
38
37
|
def chart_data
|
|
39
38
|
range = params[:range].presence || "today"
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if custom_range?(range)
|
|
43
|
-
from_date, to_date = parse_custom_range(range)
|
|
44
|
-
data = tenant_scoped_executions.activity_chart_json_for_dates(from: from_date, to: to_date)
|
|
45
|
-
else
|
|
46
|
-
data = tenant_scoped_executions.activity_chart_json(range: range)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
if compare
|
|
50
|
-
offset_days = range_to_days(range)
|
|
51
|
-
comparison_data = if custom_range?(range)
|
|
52
|
-
from_date, to_date = parse_custom_range(range)
|
|
53
|
-
tenant_scoped_executions.activity_chart_json_for_dates(
|
|
54
|
-
from: from_date - offset_days.days,
|
|
55
|
-
to: to_date - offset_days.days
|
|
56
|
-
)
|
|
57
|
-
else
|
|
58
|
-
tenant_scoped_executions.activity_chart_json(
|
|
59
|
-
range: range,
|
|
60
|
-
offset_days: offset_days
|
|
61
|
-
)
|
|
62
|
-
end
|
|
63
|
-
data[:comparison] = comparison_data
|
|
64
|
-
end
|
|
65
|
-
|
|
39
|
+
data = tenant_scoped_executions.activity_chart_json(range: range)
|
|
66
40
|
render json: data
|
|
67
41
|
end
|
|
68
42
|
|
|
@@ -70,49 +44,17 @@ module RubyLLM
|
|
|
70
44
|
|
|
71
45
|
# Converts range parameter to number of days
|
|
72
46
|
#
|
|
73
|
-
# @param range [String] Range parameter (today, 7d, 30d
|
|
47
|
+
# @param range [String] Range parameter (today, 7d, 30d)
|
|
74
48
|
# @return [Integer] Number of days
|
|
75
49
|
def range_to_days(range)
|
|
76
50
|
case range
|
|
77
51
|
when "today" then 1
|
|
78
52
|
when "7d" then 7
|
|
79
53
|
when "30d" then 30
|
|
80
|
-
|
|
81
|
-
when "90d" then 90
|
|
82
|
-
else
|
|
83
|
-
# Handle custom range format "YYYY-MM-DD_YYYY-MM-DD"
|
|
84
|
-
if range&.include?("_")
|
|
85
|
-
from_str, to_str = range.split("_")
|
|
86
|
-
from_date = Date.parse(from_str) rescue nil
|
|
87
|
-
to_date = Date.parse(to_str) rescue nil
|
|
88
|
-
if from_date && to_date
|
|
89
|
-
(to_date - from_date).to_i + 1
|
|
90
|
-
else
|
|
91
|
-
1
|
|
92
|
-
end
|
|
93
|
-
else
|
|
94
|
-
1
|
|
95
|
-
end
|
|
54
|
+
else 1
|
|
96
55
|
end
|
|
97
56
|
end
|
|
98
57
|
|
|
99
|
-
# Checks if a range is a custom date range
|
|
100
|
-
#
|
|
101
|
-
# @param range [String] Range parameter
|
|
102
|
-
# @return [Boolean] True if custom date range format
|
|
103
|
-
def custom_range?(range)
|
|
104
|
-
range&.match?(/\A\d{4}-\d{2}-\d{2}_\d{4}-\d{2}-\d{2}\z/)
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
# Parses a custom range string into date objects
|
|
108
|
-
#
|
|
109
|
-
# @param range [String] Custom range in format "YYYY-MM-DD_YYYY-MM-DD"
|
|
110
|
-
# @return [Array<Date>] [from_date, to_date]
|
|
111
|
-
def parse_custom_range(range)
|
|
112
|
-
from_str, to_str = range.split("_")
|
|
113
|
-
[Date.parse(from_str), Date.parse(to_str)]
|
|
114
|
-
end
|
|
115
|
-
|
|
116
58
|
# Builds per-agent comparison statistics for all agent types
|
|
117
59
|
#
|
|
118
60
|
# Creates separate instance variables for each agent type:
|
|
@@ -121,8 +63,6 @@ module RubyLLM
|
|
|
121
63
|
# - @transcriber_stats: Transcribers
|
|
122
64
|
# - @speaker_stats: Speakers
|
|
123
65
|
# - @image_generator_stats: Image generators
|
|
124
|
-
# - @moderator_stats: Moderators
|
|
125
|
-
# - @workflow_stats: Workflows
|
|
126
66
|
#
|
|
127
67
|
# @param base_scope [ActiveRecord::Relation] Base scope to filter from
|
|
128
68
|
# @return [Array<Hash>] Array of base agent stats (for backward compatibility)
|
|
@@ -138,7 +78,6 @@ module RubyLLM
|
|
|
138
78
|
all_stats = all_agent_types.map do |agent_type|
|
|
139
79
|
agent_class = AgentRegistry.find(agent_type)
|
|
140
80
|
detected_type = AgentRegistry.send(:detect_agent_type, agent_class)
|
|
141
|
-
workflow_type = detected_type == "workflow" ? detect_workflow_type(agent_class) : nil
|
|
142
81
|
|
|
143
82
|
# Get stats from batch or use zeros for never-executed agents
|
|
144
83
|
stats = execution_stats[agent_type] || {
|
|
@@ -152,43 +91,21 @@ module RubyLLM
|
|
|
152
91
|
total_cost: stats[:total_cost],
|
|
153
92
|
avg_cost: stats[:avg_cost],
|
|
154
93
|
avg_duration_ms: stats[:avg_duration_ms],
|
|
155
|
-
success_rate: stats[:success_rate]
|
|
156
|
-
is_workflow: detected_type == "workflow",
|
|
157
|
-
workflow_type: workflow_type
|
|
94
|
+
success_rate: stats[:success_rate]
|
|
158
95
|
}
|
|
159
96
|
end.sort_by { |a| [-(a[:executions] || 0), -(a[:total_cost] || 0)] }
|
|
160
97
|
|
|
161
|
-
# Split stats by agent type for
|
|
98
|
+
# Split stats by agent type for 5-tab display
|
|
162
99
|
@agent_stats = all_stats.select { |a| a[:detected_type] == "agent" }
|
|
163
100
|
@embedder_stats = all_stats.select { |a| a[:detected_type] == "embedder" }
|
|
164
101
|
@transcriber_stats = all_stats.select { |a| a[:detected_type] == "transcriber" }
|
|
165
102
|
@speaker_stats = all_stats.select { |a| a[:detected_type] == "speaker" }
|
|
166
103
|
@image_generator_stats = all_stats.select { |a| a[:detected_type] == "image_generator" }
|
|
167
|
-
@moderator_stats = all_stats.select { |a| a[:detected_type] == "moderator" }
|
|
168
|
-
@workflow_stats = all_stats.select { |a| a[:detected_type] == "workflow" }
|
|
169
104
|
|
|
170
105
|
# Return base agents for backward compatibility
|
|
171
106
|
@agent_stats
|
|
172
107
|
end
|
|
173
108
|
|
|
174
|
-
# Detects workflow type from class hierarchy
|
|
175
|
-
#
|
|
176
|
-
# @param agent_class [Class] The agent class
|
|
177
|
-
# @return [String, nil] "pipeline", "parallel", "router", or nil
|
|
178
|
-
def detect_workflow_type(agent_class)
|
|
179
|
-
return nil unless agent_class
|
|
180
|
-
|
|
181
|
-
ancestors = agent_class.ancestors.map { |a| a.name.to_s }
|
|
182
|
-
|
|
183
|
-
if ancestors.include?("RubyLLM::Agents::Workflow::Pipeline")
|
|
184
|
-
"pipeline"
|
|
185
|
-
elsif ancestors.include?("RubyLLM::Agents::Workflow::Parallel")
|
|
186
|
-
"parallel"
|
|
187
|
-
elsif ancestors.include?("RubyLLM::Agents::Workflow::Router")
|
|
188
|
-
"router"
|
|
189
|
-
end
|
|
190
|
-
end
|
|
191
|
-
|
|
192
109
|
# Builds per-model statistics for model comparison and cost breakdown
|
|
193
110
|
#
|
|
194
111
|
# @param base_scope [ActiveRecord::Relation] Base scope to filter from
|
|
@@ -337,34 +254,33 @@ module RubyLLM
|
|
|
337
254
|
[]
|
|
338
255
|
end
|
|
339
256
|
|
|
340
|
-
# Loads tenant budget info for the current tenant
|
|
257
|
+
# Loads tenant budget info for the current tenant using counter columns
|
|
341
258
|
#
|
|
342
|
-
# @param base_scope [ActiveRecord::Relation] Base scope for
|
|
259
|
+
# @param base_scope [ActiveRecord::Relation] Base scope (unused, kept for backward compat)
|
|
343
260
|
# @return [Hash, nil] Tenant budget data with usage info, or nil if not applicable
|
|
344
261
|
def load_tenant_budget(base_scope)
|
|
345
262
|
return nil unless tenant_filter_enabled? && current_tenant_id.present?
|
|
346
|
-
return nil unless
|
|
263
|
+
return nil unless Tenant.table_exists?
|
|
347
264
|
|
|
348
|
-
|
|
349
|
-
return nil unless
|
|
265
|
+
tenant = Tenant.for(current_tenant_id)
|
|
266
|
+
return nil unless tenant
|
|
350
267
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
month_scope = base_scope.where("created_at >= ?", Time.current.beginning_of_month)
|
|
268
|
+
tenant.ensure_daily_reset!
|
|
269
|
+
tenant.ensure_monthly_reset!
|
|
354
270
|
|
|
355
|
-
daily_spend =
|
|
356
|
-
monthly_spend =
|
|
271
|
+
daily_spend = tenant.daily_cost_spent
|
|
272
|
+
monthly_spend = tenant.monthly_cost_spent
|
|
357
273
|
|
|
358
274
|
{
|
|
359
275
|
tenant_id: current_tenant_id,
|
|
360
|
-
daily_limit:
|
|
361
|
-
monthly_limit:
|
|
276
|
+
daily_limit: tenant.effective_daily_limit,
|
|
277
|
+
monthly_limit: tenant.effective_monthly_limit,
|
|
362
278
|
daily_spend: daily_spend,
|
|
363
279
|
monthly_spend: monthly_spend,
|
|
364
|
-
daily_percentage:
|
|
365
|
-
monthly_percentage:
|
|
366
|
-
enforcement:
|
|
367
|
-
per_agent_daily:
|
|
280
|
+
daily_percentage: tenant.effective_daily_limit.to_f > 0 ? (daily_spend / tenant.effective_daily_limit * 100).round(1) : 0,
|
|
281
|
+
monthly_percentage: tenant.effective_monthly_limit.to_f > 0 ? (monthly_spend / tenant.effective_monthly_limit * 100).round(1) : 0,
|
|
282
|
+
enforcement: tenant.effective_enforcement,
|
|
283
|
+
per_agent_daily: tenant.per_agent_daily || {}
|
|
368
284
|
}
|
|
369
285
|
end
|
|
370
286
|
|
|
@@ -16,7 +16,7 @@ module RubyLLM
|
|
|
16
16
|
include Filterable
|
|
17
17
|
include Sortable
|
|
18
18
|
|
|
19
|
-
CSV_COLUMNS = %w[id agent_type
|
|
19
|
+
CSV_COLUMNS = %w[id agent_type status model_id total_tokens total_cost
|
|
20
20
|
duration_ms created_at error_class error_message].freeze
|
|
21
21
|
|
|
22
22
|
# Lists all executions with filtering and pagination
|
|
@@ -61,49 +61,6 @@ module RubyLLM
|
|
|
61
61
|
end
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
-
# Reruns an execution with the same parameters
|
|
65
|
-
#
|
|
66
|
-
# Supports both dry-run mode (returns prompt info without API call)
|
|
67
|
-
# and real reruns that create a new execution.
|
|
68
|
-
#
|
|
69
|
-
# @return [void]
|
|
70
|
-
def rerun
|
|
71
|
-
@execution = Execution.find(params[:id])
|
|
72
|
-
dry_run = params[:dry_run] == "true"
|
|
73
|
-
|
|
74
|
-
agent_class = AgentRegistry.find(@execution.agent_type)
|
|
75
|
-
|
|
76
|
-
unless agent_class
|
|
77
|
-
flash[:alert] = "Agent class '#{@execution.agent_type}' not found. Cannot rerun."
|
|
78
|
-
redirect_to execution_path(@execution)
|
|
79
|
-
return
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
# Prepare parameters from original execution
|
|
83
|
-
original_params = @execution.parameters&.symbolize_keys || {}
|
|
84
|
-
|
|
85
|
-
if dry_run
|
|
86
|
-
# Dry run mode - show what would be sent without making API call
|
|
87
|
-
result = agent_class.call(**original_params, dry_run: true)
|
|
88
|
-
@dry_run_result = result
|
|
89
|
-
|
|
90
|
-
respond_to do |format|
|
|
91
|
-
format.html { render :dry_run }
|
|
92
|
-
format.json { render json: result }
|
|
93
|
-
end
|
|
94
|
-
else
|
|
95
|
-
# Real rerun - execute the agent
|
|
96
|
-
begin
|
|
97
|
-
agent_class.call(**original_params)
|
|
98
|
-
flash[:notice] = "Execution rerun successfully! Check the executions list for the new result."
|
|
99
|
-
rescue StandardError => e
|
|
100
|
-
flash[:alert] = "Rerun failed: #{e.message}"
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
redirect_to executions_path
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
|
|
107
64
|
# Exports filtered executions as CSV
|
|
108
65
|
#
|
|
109
66
|
# Streams CSV data with redacted error messages to protect
|
|
@@ -141,7 +98,6 @@ module RubyLLM
|
|
|
141
98
|
CSV.generate_line([
|
|
142
99
|
execution.id,
|
|
143
100
|
execution.agent_type,
|
|
144
|
-
execution.agent_version,
|
|
145
101
|
execution.status,
|
|
146
102
|
execution.model_id,
|
|
147
103
|
execution.total_tokens,
|
|
@@ -156,14 +112,12 @@ module RubyLLM
|
|
|
156
112
|
# Loads available options for filter dropdowns
|
|
157
113
|
#
|
|
158
114
|
# Populates @agent_types with all agent types that have executions,
|
|
159
|
-
# @model_ids with all distinct models used, @
|
|
160
|
-
# workflow patterns used, and @statuses with all possible status values.
|
|
115
|
+
# @model_ids with all distinct models used, and @statuses with all possible status values.
|
|
161
116
|
#
|
|
162
117
|
# @return [void]
|
|
163
118
|
def load_filter_options
|
|
164
119
|
@agent_types = available_agent_types
|
|
165
120
|
@model_ids = available_model_ids
|
|
166
|
-
@workflow_types = available_workflow_types
|
|
167
121
|
@statuses = Execution.statuses.keys
|
|
168
122
|
end
|
|
169
123
|
|
|
@@ -187,24 +141,6 @@ module RubyLLM
|
|
|
187
141
|
@available_model_ids ||= tenant_scoped_executions.where.not(model_id: nil).distinct.pluck(:model_id).sort
|
|
188
142
|
end
|
|
189
143
|
|
|
190
|
-
# Returns distinct workflow types from execution history
|
|
191
|
-
#
|
|
192
|
-
# Memoized to avoid duplicate queries within a request.
|
|
193
|
-
# Returns empty array if workflow_type column doesn't exist yet.
|
|
194
|
-
# Uses tenant_scoped_executions to respect multi-tenancy filtering.
|
|
195
|
-
#
|
|
196
|
-
# @return [Array<String>] Workflow types (pipeline, parallel, router)
|
|
197
|
-
def available_workflow_types
|
|
198
|
-
return @available_workflow_types if defined?(@available_workflow_types)
|
|
199
|
-
|
|
200
|
-
@available_workflow_types = if Execution.column_names.include?("workflow_type")
|
|
201
|
-
tenant_scoped_executions.where.not(workflow_type: [nil, ""])
|
|
202
|
-
.distinct.pluck(:workflow_type).sort
|
|
203
|
-
else
|
|
204
|
-
[]
|
|
205
|
-
end
|
|
206
|
-
end
|
|
207
|
-
|
|
208
144
|
# Loads paginated executions and associated statistics
|
|
209
145
|
#
|
|
210
146
|
# Sets @executions, @pagination, @sort_params, and @filter_stats instance variables
|
|
@@ -264,63 +200,17 @@ module RubyLLM
|
|
|
264
200
|
model_ids = parse_array_param(:model_ids)
|
|
265
201
|
scope = scope.where(model_id: model_ids) if model_ids.any?
|
|
266
202
|
|
|
267
|
-
# Apply workflow type filter (only if column exists)
|
|
268
|
-
if Execution.column_names.include?("workflow_type")
|
|
269
|
-
workflow_types = parse_array_param(:workflow_types)
|
|
270
|
-
if workflow_types.any?
|
|
271
|
-
includes_single = workflow_types.include?("single")
|
|
272
|
-
other_types = workflow_types - ["single"]
|
|
273
|
-
|
|
274
|
-
if includes_single && other_types.any?
|
|
275
|
-
# Include both single (null workflow_type) and specific workflow types
|
|
276
|
-
scope = scope.where(workflow_type: [nil, ""] + other_types)
|
|
277
|
-
elsif includes_single
|
|
278
|
-
# Only single executions (non-workflow)
|
|
279
|
-
scope = scope.where(workflow_type: [nil, ""])
|
|
280
|
-
else
|
|
281
|
-
# Only specific workflow types
|
|
282
|
-
scope = scope.where(workflow_type: workflow_types)
|
|
283
|
-
end
|
|
284
|
-
end
|
|
285
|
-
end
|
|
286
|
-
|
|
287
|
-
# Apply execution type tab filter (agents vs workflows)
|
|
288
|
-
scope = apply_execution_type_filter(scope)
|
|
289
|
-
|
|
290
203
|
# Apply retries filter (show only executions with multiple attempts)
|
|
291
204
|
scope = scope.where("attempts_count > 1") if params[:has_retries].present?
|
|
292
205
|
|
|
293
|
-
# Only show root executions
|
|
206
|
+
# Only show root executions - children are nested under parents
|
|
294
207
|
scope = scope.where(parent_execution_id: nil)
|
|
295
208
|
|
|
296
|
-
# Eager load children for
|
|
209
|
+
# Eager load children for grouping
|
|
297
210
|
scope = scope.includes(:child_executions)
|
|
298
211
|
|
|
299
212
|
scope
|
|
300
213
|
end
|
|
301
|
-
|
|
302
|
-
# Applies execution type filter (all, agents, workflows, or specific workflow type)
|
|
303
|
-
#
|
|
304
|
-
# @param scope [ActiveRecord::Relation] The current scope
|
|
305
|
-
# @return [ActiveRecord::Relation] Filtered scope
|
|
306
|
-
def apply_execution_type_filter(scope)
|
|
307
|
-
return scope unless Execution.column_names.include?("workflow_type")
|
|
308
|
-
|
|
309
|
-
execution_type = params[:execution_type]
|
|
310
|
-
case execution_type
|
|
311
|
-
when "agents"
|
|
312
|
-
# Only show executions where workflow_type is null/empty (regular agents)
|
|
313
|
-
scope.where(workflow_type: [nil, ""])
|
|
314
|
-
when "workflows"
|
|
315
|
-
# Only show executions with a workflow_type (any workflow)
|
|
316
|
-
scope.where.not(workflow_type: [nil, ""])
|
|
317
|
-
when "pipeline", "parallel", "router"
|
|
318
|
-
# Show specific workflow type
|
|
319
|
-
scope.where(workflow_type: execution_type)
|
|
320
|
-
else
|
|
321
|
-
scope
|
|
322
|
-
end
|
|
323
|
-
end
|
|
324
214
|
end
|
|
325
215
|
end
|
|
326
216
|
end
|
|
@@ -10,11 +10,26 @@ module RubyLLM
|
|
|
10
10
|
# @see TenantBudget For budget configuration model
|
|
11
11
|
# @api private
|
|
12
12
|
class TenantsController < ApplicationController
|
|
13
|
-
|
|
13
|
+
TENANT_SORTABLE_COLUMNS = %w[name enforcement daily_limit monthly_limit].freeze
|
|
14
|
+
DEFAULT_TENANT_SORT_COLUMN = "name"
|
|
15
|
+
DEFAULT_TENANT_SORT_DIRECTION = "asc"
|
|
16
|
+
|
|
17
|
+
# Lists all tenant budgets with optional search and sorting
|
|
14
18
|
#
|
|
15
19
|
# @return [void]
|
|
16
20
|
def index
|
|
17
|
-
@
|
|
21
|
+
@sort_params = parse_tenant_sort_params
|
|
22
|
+
scope = TenantBudget.all
|
|
23
|
+
|
|
24
|
+
if params[:q].present?
|
|
25
|
+
@search_query = params[:q].to_s.strip
|
|
26
|
+
scope = scope.where(
|
|
27
|
+
"tenant_id LIKE :q OR name LIKE :q",
|
|
28
|
+
q: "%#{TenantBudget.sanitize_sql_like(@search_query)}%"
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
@tenants = scope.order(@sort_params[:column] => @sort_params[:direction].to_sym)
|
|
18
33
|
end
|
|
19
34
|
|
|
20
35
|
# Shows a single tenant's budget details
|
|
@@ -95,6 +110,19 @@ module RubyLLM
|
|
|
95
110
|
}
|
|
96
111
|
end
|
|
97
112
|
|
|
113
|
+
# Parses and validates sort parameters for tenants list
|
|
114
|
+
#
|
|
115
|
+
# @return [Hash] Contains :column and :direction keys
|
|
116
|
+
def parse_tenant_sort_params
|
|
117
|
+
column = params[:sort].to_s
|
|
118
|
+
direction = params[:direction].to_s.downcase
|
|
119
|
+
|
|
120
|
+
{
|
|
121
|
+
column: TENANT_SORTABLE_COLUMNS.include?(column) ? column : DEFAULT_TENANT_SORT_COLUMN,
|
|
122
|
+
direction: %w[asc desc].include?(direction) ? direction : DEFAULT_TENANT_SORT_DIRECTION
|
|
123
|
+
}
|
|
124
|
+
end
|
|
125
|
+
|
|
98
126
|
# Calculates percentage used
|
|
99
127
|
#
|
|
100
128
|
# @param current [Numeric] Current usage
|
|
@@ -19,13 +19,10 @@ module RubyLLM
|
|
|
19
19
|
"dashboard/index" => "Dashboard",
|
|
20
20
|
"agents/index" => "Agent-DSL",
|
|
21
21
|
"agents/show" => "Agent-DSL",
|
|
22
|
-
"workflows/index" => "Workflows",
|
|
23
|
-
"workflows/show" => "Workflows",
|
|
24
22
|
"executions/index" => "Execution-Tracking",
|
|
25
23
|
"executions/show" => "Execution-Tracking",
|
|
26
24
|
"tenants/index" => "Multi-Tenancy",
|
|
27
|
-
"system_config/show" => "Configuration"
|
|
28
|
-
"api_configurations/show" => "Configuration"
|
|
25
|
+
"system_config/show" => "Configuration"
|
|
29
26
|
}.freeze
|
|
30
27
|
|
|
31
28
|
# Returns the documentation URL for the current page or a specific page key
|
|
@@ -59,27 +56,11 @@ module RubyLLM
|
|
|
59
56
|
|
|
60
57
|
# Returns the URL for "All Tenants" (clears tenant filter)
|
|
61
58
|
#
|
|
62
|
-
#
|
|
63
|
-
# 1. Query param routes - removes tenant_id from query params
|
|
64
|
-
# 2. Path-based tenant routes - navigates to equivalent global route
|
|
59
|
+
# Removes tenant_id from query params to show unfiltered results.
|
|
65
60
|
#
|
|
66
61
|
# @return [String] URL without tenant filtering
|
|
67
62
|
def all_tenants_url
|
|
68
|
-
|
|
69
|
-
tenant_route_mappings = {
|
|
70
|
-
"tenant" => ruby_llm_agents.api_configuration_path,
|
|
71
|
-
"edit_tenant" => ruby_llm_agents.edit_api_configuration_path
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
# Check if current action has a global equivalent
|
|
75
|
-
if tenant_route_mappings.key?(action_name)
|
|
76
|
-
base_path = tenant_route_mappings[action_name]
|
|
77
|
-
query = request.query_parameters.except("tenant_id")
|
|
78
|
-
query.any? ? "#{base_path}?#{query.to_query}" : base_path
|
|
79
|
-
else
|
|
80
|
-
# For query param routes, just remove tenant_id
|
|
81
|
-
url_for(request.query_parameters.except("tenant_id"))
|
|
82
|
-
end
|
|
63
|
+
url_for(request.query_parameters.except("tenant_id"))
|
|
83
64
|
end
|
|
84
65
|
|
|
85
66
|
# Formats large numbers with human-readable suffixes (K, M, B)
|
|
@@ -121,7 +102,7 @@ module RubyLLM
|
|
|
121
102
|
# @return [ActiveSupport::SafeBuffer] HTML badge element
|
|
122
103
|
def render_enabled_badge(enabled)
|
|
123
104
|
if enabled
|
|
124
|
-
'<span class="inline-flex items-center px-2 py-1 rounded text-xs font-medium bg-green-100 dark:bg-green-
|
|
105
|
+
'<span class="inline-flex items-center px-2 py-1 rounded text-xs font-medium bg-green-100 dark:bg-green-500/20 text-green-700 dark:text-green-300">Enabled</span>'.html_safe
|
|
125
106
|
else
|
|
126
107
|
'<span class="inline-flex items-center px-2 py-1 rounded text-xs font-medium bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400">Disabled</span>'.html_safe
|
|
127
108
|
end
|
|
@@ -133,7 +114,7 @@ module RubyLLM
|
|
|
133
114
|
# @return [ActiveSupport::SafeBuffer] HTML badge element
|
|
134
115
|
def render_configured_badge(configured)
|
|
135
116
|
if configured
|
|
136
|
-
'<span class="inline-flex items-center px-2 py-1 rounded text-xs font-medium bg-green-100 dark:bg-green-
|
|
117
|
+
'<span class="inline-flex items-center px-2 py-1 rounded text-xs font-medium bg-green-100 dark:bg-green-500/20 text-green-700 dark:text-green-300">Configured</span>'.html_safe
|
|
137
118
|
else
|
|
138
119
|
'<span class="inline-flex items-center px-2 py-1 rounded text-xs font-medium bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400">Not configured</span>'.html_safe
|
|
139
120
|
end
|
|
@@ -254,7 +235,7 @@ module RubyLLM
|
|
|
254
235
|
end
|
|
255
236
|
|
|
256
237
|
if is_improvement
|
|
257
|
-
content_tag(:span, class: "inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium text-green-700 dark:text-green-300 bg-green-100 dark:bg-green-
|
|
238
|
+
content_tag(:span, class: "inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium text-green-700 dark:text-green-300 bg-green-100 dark:bg-green-500/20 rounded-full") do
|
|
258
239
|
safe_join([
|
|
259
240
|
content_tag(:svg, class: "w-3 h-3", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24") do
|
|
260
241
|
content_tag(:path, nil, "stroke-linecap": "round", "stroke-linejoin": "round", "stroke-width": "2", d: "M5 10l7-7m0 0l7 7m-7-7v18")
|
|
@@ -263,7 +244,7 @@ module RubyLLM
|
|
|
263
244
|
])
|
|
264
245
|
end
|
|
265
246
|
elsif is_regression
|
|
266
|
-
content_tag(:span, class: "inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium text-red-700 dark:text-red-300 bg-red-100 dark:bg-red-
|
|
247
|
+
content_tag(:span, class: "inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium text-red-700 dark:text-red-300 bg-red-100 dark:bg-red-500/20 rounded-full") do
|
|
267
248
|
safe_join([
|
|
268
249
|
content_tag(:svg, class: "w-3 h-3", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24") do
|
|
269
250
|
content_tag(:path, nil, "stroke-linecap": "round", "stroke-linejoin": "round", "stroke-width": "2", d: "M19 14l-7 7m0 0l-7-7m7 7V3")
|
|
@@ -306,31 +287,16 @@ module RubyLLM
|
|
|
306
287
|
|
|
307
288
|
# Returns human-readable display name for time range
|
|
308
289
|
#
|
|
309
|
-
# @param range [String] Range parameter (today, 7d, 30d
|
|
290
|
+
# @param range [String] Range parameter (today, 7d, 30d)
|
|
310
291
|
# @return [String] Human-readable range name
|
|
311
292
|
# @example
|
|
312
|
-
# range_display_name("7d") #=> "
|
|
313
|
-
# range_display_name("2024-01-01_2024-01-15") #=> "Jan 1 - Jan 15"
|
|
293
|
+
# range_display_name("7d") #=> "7 Days"
|
|
314
294
|
def range_display_name(range)
|
|
315
295
|
case range
|
|
316
296
|
when "today" then "Today"
|
|
317
|
-
when "7d" then "
|
|
318
|
-
when "30d" then "
|
|
319
|
-
|
|
320
|
-
when "90d" then "Last 90 Days"
|
|
321
|
-
else
|
|
322
|
-
if range&.include?("_")
|
|
323
|
-
from_str, to_str = range.split("_")
|
|
324
|
-
from_date = Date.parse(from_str) rescue nil
|
|
325
|
-
to_date = Date.parse(to_str) rescue nil
|
|
326
|
-
if from_date && to_date
|
|
327
|
-
"#{from_date.strftime('%b %-d')} - #{to_date.strftime('%b %-d')}"
|
|
328
|
-
else
|
|
329
|
-
"Custom Range"
|
|
330
|
-
end
|
|
331
|
-
else
|
|
332
|
-
"Today"
|
|
333
|
-
end
|
|
297
|
+
when "7d" then "7 Days"
|
|
298
|
+
when "30d" then "30 Days"
|
|
299
|
+
else "Today"
|
|
334
300
|
end
|
|
335
301
|
end
|
|
336
302
|
|
|
@@ -391,7 +357,7 @@ module RubyLLM
|
|
|
391
357
|
# @return [ActiveSupport::SafeBuffer] HTML summary banner
|
|
392
358
|
def comparison_summary_badge(improvements_count, regressions_count, v2_label)
|
|
393
359
|
if improvements_count >= 3 && regressions_count == 0
|
|
394
|
-
content_tag(:span, class: "inline-flex items-center gap-1 px-3 py-1 text-sm font-medium text-green-700 dark:text-green-300 bg-green-100 dark:bg-green-
|
|
360
|
+
content_tag(:span, class: "inline-flex items-center gap-1 px-3 py-1 text-sm font-medium text-green-700 dark:text-green-300 bg-green-100 dark:bg-green-500/20 rounded-lg") do
|
|
395
361
|
safe_join([
|
|
396
362
|
content_tag(:svg, class: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24") do
|
|
397
363
|
content_tag(:path, nil, "stroke-linecap": "round", "stroke-linejoin": "round", "stroke-width": "2", d: "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z")
|
|
@@ -400,7 +366,7 @@ module RubyLLM
|
|
|
400
366
|
])
|
|
401
367
|
end
|
|
402
368
|
elsif regressions_count >= 3 && improvements_count == 0
|
|
403
|
-
content_tag(:span, class: "inline-flex items-center gap-1 px-3 py-1 text-sm font-medium text-red-700 dark:text-red-300 bg-red-100 dark:bg-red-
|
|
369
|
+
content_tag(:span, class: "inline-flex items-center gap-1 px-3 py-1 text-sm font-medium text-red-700 dark:text-red-300 bg-red-100 dark:bg-red-500/20 rounded-lg") do
|
|
404
370
|
safe_join([
|
|
405
371
|
content_tag(:svg, class: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24") do
|
|
406
372
|
content_tag(:path, nil, "stroke-linecap": "round", "stroke-linejoin": "round", "stroke-width": "2", d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z")
|
|
@@ -409,7 +375,7 @@ module RubyLLM
|
|
|
409
375
|
])
|
|
410
376
|
end
|
|
411
377
|
elsif improvements_count > 0 || regressions_count > 0
|
|
412
|
-
content_tag(:span, class: "inline-flex items-center gap-1 px-3 py-1 text-sm font-medium text-amber-700 dark:text-amber-300 bg-amber-100 dark:bg-amber-
|
|
378
|
+
content_tag(:span, class: "inline-flex items-center gap-1 px-3 py-1 text-sm font-medium text-amber-700 dark:text-amber-300 bg-amber-100 dark:bg-amber-500/20 rounded-lg") do
|
|
413
379
|
safe_join([
|
|
414
380
|
content_tag(:svg, class: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24") do
|
|
415
381
|
content_tag(:path, nil, "stroke-linecap": "round", "stroke-linejoin": "round", "stroke-width": "2", d: "M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4")
|
|
@@ -552,14 +518,14 @@ module RubyLLM
|
|
|
552
518
|
|
|
553
519
|
escaped_value = ERB::Util.html_escape(token[:value])
|
|
554
520
|
if is_key
|
|
555
|
-
result << %(<span class="text-purple-600">#{escaped_value}</span>)
|
|
521
|
+
result << %(<span class="text-purple-600 dark:text-purple-400">#{escaped_value}</span>)
|
|
556
522
|
else
|
|
557
|
-
result << %(<span class="text-green-600">#{escaped_value}</span>)
|
|
523
|
+
result << %(<span class="text-green-600 dark:text-green-400">#{escaped_value}</span>)
|
|
558
524
|
end
|
|
559
525
|
when :number
|
|
560
|
-
result << %(<span class="text-blue-600">#{token[:value]}</span>)
|
|
526
|
+
result << %(<span class="text-blue-600 dark:text-blue-400">#{token[:value]}</span>)
|
|
561
527
|
when :boolean
|
|
562
|
-
result << %(<span class="text-amber-600">#{token[:value]}</span>)
|
|
528
|
+
result << %(<span class="text-amber-600 dark:text-amber-400">#{token[:value]}</span>)
|
|
563
529
|
when :null
|
|
564
530
|
result << %(<span class="text-gray-400">#{token[:value]}</span>)
|
|
565
531
|
else
|