ruby_llm-agents 0.2.4 → 0.3.1
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 +413 -0
- data/app/channels/ruby_llm/agents/executions_channel.rb +24 -1
- data/app/controllers/concerns/ruby_llm/agents/filterable.rb +81 -0
- data/app/controllers/concerns/ruby_llm/agents/paginatable.rb +51 -0
- data/app/controllers/ruby_llm/agents/agents_controller.rb +228 -59
- data/app/controllers/ruby_llm/agents/dashboard_controller.rb +167 -12
- data/app/controllers/ruby_llm/agents/executions_controller.rb +189 -31
- data/app/controllers/ruby_llm/agents/settings_controller.rb +20 -0
- data/app/helpers/ruby_llm/agents/application_helper.rb +307 -7
- data/app/models/ruby_llm/agents/execution/analytics.rb +224 -20
- data/app/models/ruby_llm/agents/execution/metrics.rb +41 -25
- data/app/models/ruby_llm/agents/execution/scopes.rb +234 -14
- data/app/models/ruby_llm/agents/execution.rb +259 -16
- data/app/services/ruby_llm/agents/agent_registry.rb +49 -12
- data/app/views/layouts/rubyllm/agents/application.html.erb +351 -85
- data/app/views/rubyllm/agents/agents/_version_comparison.html.erb +186 -0
- data/app/views/rubyllm/agents/agents/show.html.erb +233 -10
- data/app/views/rubyllm/agents/dashboard/_action_center.html.erb +62 -0
- data/app/views/rubyllm/agents/dashboard/_alerts_feed.html.erb +62 -0
- data/app/views/rubyllm/agents/dashboard/_breaker_strip.html.erb +47 -0
- data/app/views/rubyllm/agents/dashboard/_budgets_bar.html.erb +165 -0
- data/app/views/rubyllm/agents/dashboard/_now_strip.html.erb +10 -0
- data/app/views/rubyllm/agents/dashboard/_now_strip_values.html.erb +71 -0
- data/app/views/rubyllm/agents/dashboard/index.html.erb +215 -109
- data/app/views/rubyllm/agents/executions/_filters.html.erb +152 -155
- data/app/views/rubyllm/agents/executions/_list.html.erb +103 -12
- data/app/views/rubyllm/agents/executions/dry_run.html.erb +149 -0
- data/app/views/rubyllm/agents/executions/index.html.erb +17 -72
- data/app/views/rubyllm/agents/executions/index.turbo_stream.erb +16 -2
- data/app/views/rubyllm/agents/executions/show.html.erb +693 -14
- data/app/views/rubyllm/agents/settings/show.html.erb +369 -0
- data/app/views/rubyllm/agents/shared/_filter_dropdown.html.erb +121 -0
- data/app/views/rubyllm/agents/shared/_select_dropdown.html.erb +85 -0
- data/config/routes.rb +7 -0
- data/lib/generators/ruby_llm_agents/templates/add_attempts_migration.rb.tt +27 -0
- data/lib/generators/ruby_llm_agents/templates/add_caching_migration.rb.tt +23 -0
- data/lib/generators/ruby_llm_agents/templates/add_finish_reason_migration.rb.tt +19 -0
- data/lib/generators/ruby_llm_agents/templates/add_routing_migration.rb.tt +19 -0
- data/lib/generators/ruby_llm_agents/templates/add_streaming_migration.rb.tt +8 -0
- data/lib/generators/ruby_llm_agents/templates/add_tracing_migration.rb.tt +34 -0
- data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +66 -4
- data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +53 -6
- data/lib/generators/ruby_llm_agents/templates/initializer.rb.tt +143 -8
- data/lib/generators/ruby_llm_agents/templates/migration.rb.tt +38 -1
- data/lib/generators/ruby_llm_agents/upgrade_generator.rb +78 -0
- data/lib/ruby_llm/agents/alert_manager.rb +207 -0
- data/lib/ruby_llm/agents/attempt_tracker.rb +295 -0
- data/lib/ruby_llm/agents/base.rb +597 -112
- data/lib/ruby_llm/agents/budget_tracker.rb +360 -0
- data/lib/ruby_llm/agents/circuit_breaker.rb +197 -0
- data/lib/ruby_llm/agents/configuration.rb +279 -1
- data/lib/ruby_llm/agents/engine.rb +58 -6
- data/lib/ruby_llm/agents/execution_logger_job.rb +17 -6
- data/lib/ruby_llm/agents/inflections.rb +13 -2
- data/lib/ruby_llm/agents/instrumentation.rb +538 -87
- data/lib/ruby_llm/agents/redactor.rb +130 -0
- data/lib/ruby_llm/agents/reliability.rb +185 -0
- data/lib/ruby_llm/agents/version.rb +3 -1
- data/lib/ruby_llm/agents.rb +52 -0
- metadata +41 -2
- data/app/controllers/ruby_llm/agents/application_controller.rb +0 -37
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
<%# Budget bars for daily and monthly spending %>
|
|
2
|
+
<% if budget_status[:enabled] %>
|
|
3
|
+
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 p-4 mb-6">
|
|
4
|
+
<div class="flex items-center justify-between mb-3">
|
|
5
|
+
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-200">Budget Usage</h3>
|
|
6
|
+
<span class="text-xs text-gray-500 dark:text-gray-400">
|
|
7
|
+
<%= budget_status[:enforcement] == :hard ? "Hard Enforcement" : "Soft Enforcement" %>
|
|
8
|
+
</span>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
12
|
+
<%# Daily Budget %>
|
|
13
|
+
<% if budget_status[:global_daily] %>
|
|
14
|
+
<% daily = budget_status[:global_daily] %>
|
|
15
|
+
<% percentage = daily[:percentage_used] %>
|
|
16
|
+
<% bar_color = percentage >= 100 ? "bg-red-500" : (percentage >= 80 ? "bg-yellow-500" : "bg-green-500") %>
|
|
17
|
+
<div>
|
|
18
|
+
<div class="flex justify-between items-center mb-1">
|
|
19
|
+
<span class="text-xs font-medium text-gray-600 dark:text-gray-300">Daily</span>
|
|
20
|
+
<span class="text-xs text-gray-500 dark:text-gray-400">
|
|
21
|
+
$<%= number_with_precision(daily[:current], precision: 2) %> / $<%= number_with_precision(daily[:limit], precision: 2) %>
|
|
22
|
+
</span>
|
|
23
|
+
</div>
|
|
24
|
+
<div class="relative w-full h-2 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
|
|
25
|
+
<div class="absolute h-full <%= bar_color %> rounded-full transition-all duration-300" style="width: <%= [percentage, 100].min %>%"></div>
|
|
26
|
+
<%# Soft cap marker at 80% %>
|
|
27
|
+
<div class="absolute h-full w-0.5 bg-yellow-600 dark:bg-yellow-400" style="left: 80%"></div>
|
|
28
|
+
</div>
|
|
29
|
+
<div class="flex justify-between items-center mt-1">
|
|
30
|
+
<span class="text-xs <%= percentage >= 100 ? 'text-red-600 dark:text-red-400 font-medium' : 'text-gray-400 dark:text-gray-500' %>">
|
|
31
|
+
<%= percentage.round(1) %>% used
|
|
32
|
+
</span>
|
|
33
|
+
<span class="text-xs text-gray-400 dark:text-gray-500">
|
|
34
|
+
$<%= number_with_precision(daily[:remaining], precision: 2) %> remaining
|
|
35
|
+
</span>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
<% end %>
|
|
39
|
+
|
|
40
|
+
<%# Monthly Budget %>
|
|
41
|
+
<% if budget_status[:global_monthly] %>
|
|
42
|
+
<% monthly = budget_status[:global_monthly] %>
|
|
43
|
+
<% percentage = monthly[:percentage_used] %>
|
|
44
|
+
<% bar_color = percentage >= 100 ? "bg-red-500" : (percentage >= 80 ? "bg-yellow-500" : "bg-green-500") %>
|
|
45
|
+
<div>
|
|
46
|
+
<div class="flex justify-between items-center mb-1">
|
|
47
|
+
<span class="text-xs font-medium text-gray-600 dark:text-gray-300">Monthly</span>
|
|
48
|
+
<span class="text-xs text-gray-500 dark:text-gray-400">
|
|
49
|
+
$<%= number_with_precision(monthly[:current], precision: 2) %> / $<%= number_with_precision(monthly[:limit], precision: 2) %>
|
|
50
|
+
</span>
|
|
51
|
+
</div>
|
|
52
|
+
<div class="relative w-full h-2 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
|
|
53
|
+
<div class="absolute h-full <%= bar_color %> rounded-full transition-all duration-300" style="width: <%= [percentage, 100].min %>%"></div>
|
|
54
|
+
<%# Soft cap marker at 80% %>
|
|
55
|
+
<div class="absolute h-full w-0.5 bg-yellow-600 dark:bg-yellow-400" style="left: 80%"></div>
|
|
56
|
+
</div>
|
|
57
|
+
<div class="flex justify-between items-center mt-1">
|
|
58
|
+
<span class="text-xs <%= percentage >= 100 ? 'text-red-600 dark:text-red-400 font-medium' : 'text-gray-400 dark:text-gray-500' %>">
|
|
59
|
+
<%= percentage.round(1) %>% used
|
|
60
|
+
</span>
|
|
61
|
+
<span class="text-xs text-gray-400 dark:text-gray-500">
|
|
62
|
+
$<%= number_with_precision(monthly[:remaining], precision: 2) %> remaining
|
|
63
|
+
</span>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
<% end %>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<% unless budget_status[:global_daily] || budget_status[:global_monthly] %>
|
|
70
|
+
<p class="text-sm text-gray-500 dark:text-gray-400 text-center py-2">
|
|
71
|
+
No budget limits configured
|
|
72
|
+
</p>
|
|
73
|
+
<% end %>
|
|
74
|
+
|
|
75
|
+
<%# Forecast Section %>
|
|
76
|
+
<% if budget_status[:forecast] %>
|
|
77
|
+
<div class="mt-4 pt-4 border-t border-gray-100 dark:border-gray-700">
|
|
78
|
+
<div class="flex items-center gap-2 mb-3">
|
|
79
|
+
<svg class="w-4 h-4 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
80
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"/>
|
|
81
|
+
</svg>
|
|
82
|
+
<h4 class="text-sm font-semibold text-gray-700 dark:text-gray-200">Forecast</h4>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
86
|
+
<%# Daily Forecast %>
|
|
87
|
+
<% if budget_status[:forecast][:daily] %>
|
|
88
|
+
<% daily_forecast = budget_status[:forecast][:daily] %>
|
|
89
|
+
<div class="bg-gray-50 dark:bg-gray-700/50 rounded-lg p-3">
|
|
90
|
+
<div class="flex items-center justify-between mb-2">
|
|
91
|
+
<span class="text-xs font-medium text-gray-600 dark:text-gray-300">Daily Projection</span>
|
|
92
|
+
<% if daily_forecast[:on_track] %>
|
|
93
|
+
<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-900/50 rounded-full">
|
|
94
|
+
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
95
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
|
96
|
+
</svg>
|
|
97
|
+
On Track
|
|
98
|
+
</span>
|
|
99
|
+
<% else %>
|
|
100
|
+
<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-900/50 rounded-full">
|
|
101
|
+
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
102
|
+
<path 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"/>
|
|
103
|
+
</svg>
|
|
104
|
+
Over Budget
|
|
105
|
+
</span>
|
|
106
|
+
<% end %>
|
|
107
|
+
</div>
|
|
108
|
+
<div class="flex items-baseline gap-2">
|
|
109
|
+
<span class="text-lg font-bold text-gray-900 dark:text-gray-100">
|
|
110
|
+
$<%= number_with_precision(daily_forecast[:projected], precision: 2) %>
|
|
111
|
+
</span>
|
|
112
|
+
<span class="text-xs text-gray-500 dark:text-gray-400">
|
|
113
|
+
/ $<%= number_with_precision(daily_forecast[:limit], precision: 2) %> limit
|
|
114
|
+
</span>
|
|
115
|
+
</div>
|
|
116
|
+
<div class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
|
117
|
+
<span>$<%= number_with_precision(daily_forecast[:rate_per_hour], precision: 4) %>/hr</span>
|
|
118
|
+
<span class="mx-2">•</span>
|
|
119
|
+
<span><%= daily_forecast[:hours_remaining] %> hrs remaining</span>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
<% end %>
|
|
123
|
+
|
|
124
|
+
<%# Monthly Forecast %>
|
|
125
|
+
<% if budget_status[:forecast][:monthly] %>
|
|
126
|
+
<% monthly_forecast = budget_status[:forecast][:monthly] %>
|
|
127
|
+
<div class="bg-gray-50 dark:bg-gray-700/50 rounded-lg p-3">
|
|
128
|
+
<div class="flex items-center justify-between mb-2">
|
|
129
|
+
<span class="text-xs font-medium text-gray-600 dark:text-gray-300">Monthly Projection</span>
|
|
130
|
+
<% if monthly_forecast[:on_track] %>
|
|
131
|
+
<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-900/50 rounded-full">
|
|
132
|
+
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
133
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
|
134
|
+
</svg>
|
|
135
|
+
On Track
|
|
136
|
+
</span>
|
|
137
|
+
<% else %>
|
|
138
|
+
<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-900/50 rounded-full">
|
|
139
|
+
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
140
|
+
<path 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"/>
|
|
141
|
+
</svg>
|
|
142
|
+
Over Budget
|
|
143
|
+
</span>
|
|
144
|
+
<% end %>
|
|
145
|
+
</div>
|
|
146
|
+
<div class="flex items-baseline gap-2">
|
|
147
|
+
<span class="text-lg font-bold text-gray-900 dark:text-gray-100">
|
|
148
|
+
$<%= number_with_precision(monthly_forecast[:projected], precision: 2) %>
|
|
149
|
+
</span>
|
|
150
|
+
<span class="text-xs text-gray-500 dark:text-gray-400">
|
|
151
|
+
/ $<%= number_with_precision(monthly_forecast[:limit], precision: 2) %> limit
|
|
152
|
+
</span>
|
|
153
|
+
</div>
|
|
154
|
+
<div class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
|
155
|
+
<span>$<%= number_with_precision(monthly_forecast[:rate_per_day], precision: 2) %>/day</span>
|
|
156
|
+
<span class="mx-2">•</span>
|
|
157
|
+
<span><%= monthly_forecast[:days_remaining] %> days remaining</span>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
<% end %>
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
<% end %>
|
|
164
|
+
</div>
|
|
165
|
+
<% end %>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<div class="mb-6">
|
|
2
|
+
<div class="flex items-center mb-3">
|
|
3
|
+
<span class="w-2 h-2 bg-green-500 rounded-full mr-2 animate-pulse"></span>
|
|
4
|
+
<h3 class="text-sm font-medium text-gray-600 dark:text-gray-400">Live</h3>
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
<div id="now-strip-values">
|
|
8
|
+
<%= render partial: "rubyllm/agents/dashboard/now_strip_values", locals: { now_strip: now_strip } %>
|
|
9
|
+
</div>
|
|
10
|
+
</div>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-3">
|
|
2
|
+
<!-- Running -->
|
|
3
|
+
<div class="flex items-center bg-white dark:bg-gray-800 rounded-lg px-4 py-3 shadow-sm border border-gray-100 dark:border-gray-700">
|
|
4
|
+
<div class="flex-shrink-0 w-8 h-8 bg-blue-100 dark:bg-blue-900 rounded-full flex items-center justify-center mr-3">
|
|
5
|
+
<span class="w-2.5 h-2.5 bg-blue-500 rounded-full animate-pulse"></span>
|
|
6
|
+
</div>
|
|
7
|
+
<div>
|
|
8
|
+
<p id="now-running" class="text-lg font-bold text-gray-900 dark:text-gray-100"><%= now_strip[:running] %></p>
|
|
9
|
+
<p class="text-xs text-gray-500 dark:text-gray-400">Running</p>
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<!-- Success Today -->
|
|
14
|
+
<div class="flex items-center bg-white dark:bg-gray-800 rounded-lg px-4 py-3 shadow-sm border border-gray-100 dark:border-gray-700">
|
|
15
|
+
<div class="flex-shrink-0 w-8 h-8 bg-green-100 dark:bg-green-900 rounded-full flex items-center justify-center mr-3">
|
|
16
|
+
<span class="w-2.5 h-2.5 bg-green-500 rounded-full"></span>
|
|
17
|
+
</div>
|
|
18
|
+
<div>
|
|
19
|
+
<p id="now-success" class="text-lg font-bold text-green-600 dark:text-green-400"><%= now_strip[:success_today] %></p>
|
|
20
|
+
<p class="text-xs text-gray-500 dark:text-gray-400">Success</p>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<!-- Errors Today -->
|
|
25
|
+
<div class="flex items-center bg-white dark:bg-gray-800 rounded-lg px-4 py-3 shadow-sm border border-gray-100 dark:border-gray-700">
|
|
26
|
+
<div class="flex-shrink-0 w-8 h-8 <%= now_strip[:errors_today] > 0 ? 'bg-red-100 dark:bg-red-900' : 'bg-gray-100 dark:bg-gray-700' %> rounded-full flex items-center justify-center mr-3">
|
|
27
|
+
<span class="w-2.5 h-2.5 <%= now_strip[:errors_today] > 0 ? 'bg-red-500' : 'bg-gray-400' %> rounded-full"></span>
|
|
28
|
+
</div>
|
|
29
|
+
<div>
|
|
30
|
+
<p id="now-errors" class="text-lg font-bold <%= now_strip[:errors_today] > 0 ? 'text-red-600 dark:text-red-400' : 'text-gray-900 dark:text-gray-100' %>"><%= now_strip[:errors_today] %></p>
|
|
31
|
+
<p class="text-xs text-gray-500 dark:text-gray-400">Errors</p>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<!-- Timeouts Today -->
|
|
36
|
+
<div class="flex items-center bg-white dark:bg-gray-800 rounded-lg px-4 py-3 shadow-sm border border-gray-100 dark:border-gray-700">
|
|
37
|
+
<div class="flex-shrink-0 w-8 h-8 <%= now_strip[:timeouts_today] > 0 ? 'bg-yellow-100 dark:bg-yellow-900' : 'bg-gray-100 dark:bg-gray-700' %> rounded-full flex items-center justify-center mr-3">
|
|
38
|
+
<span class="w-2.5 h-2.5 <%= now_strip[:timeouts_today] > 0 ? 'bg-yellow-500' : 'bg-gray-400' %> rounded-full"></span>
|
|
39
|
+
</div>
|
|
40
|
+
<div>
|
|
41
|
+
<p id="now-timeouts" class="text-lg font-bold <%= now_strip[:timeouts_today] > 0 ? 'text-yellow-600 dark:text-yellow-400' : 'text-gray-900 dark:text-gray-100' %>"><%= now_strip[:timeouts_today] %></p>
|
|
42
|
+
<p class="text-xs text-gray-500 dark:text-gray-400">Timeouts</p>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<!-- Cost Today -->
|
|
47
|
+
<div class="flex items-center bg-white dark:bg-gray-800 rounded-lg px-4 py-3 shadow-sm border border-gray-100 dark:border-gray-700">
|
|
48
|
+
<div class="flex-shrink-0 w-8 h-8 bg-amber-100 dark:bg-amber-900 rounded-full flex items-center justify-center mr-3">
|
|
49
|
+
<svg class="w-4 h-4 text-amber-600 dark:text-amber-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
50
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
51
|
+
</svg>
|
|
52
|
+
</div>
|
|
53
|
+
<div>
|
|
54
|
+
<p id="now-cost-today" class="text-lg font-bold text-gray-900 dark:text-gray-100">$<%= number_with_precision(now_strip[:cost_today], precision: 4) %></p>
|
|
55
|
+
<p class="text-xs text-gray-500 dark:text-gray-400">Cost Today</p>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<!-- Success Rate -->
|
|
60
|
+
<div class="flex items-center bg-white dark:bg-gray-800 rounded-lg px-4 py-3 shadow-sm border border-gray-100 dark:border-gray-700">
|
|
61
|
+
<div class="flex-shrink-0 w-8 h-8 bg-green-100 dark:bg-green-900 rounded-full flex items-center justify-center mr-3">
|
|
62
|
+
<svg class="w-4 h-4 text-green-600 dark:text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
63
|
+
<path 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"/>
|
|
64
|
+
</svg>
|
|
65
|
+
</div>
|
|
66
|
+
<div>
|
|
67
|
+
<p id="now-success-rate" class="text-lg font-bold text-gray-900 dark:text-gray-100"><%= now_strip[:success_rate] %>%</p>
|
|
68
|
+
<p class="text-xs text-gray-500 dark:text-gray-400">Success Rate</p>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
@@ -1,121 +1,227 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
<div class="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
|
4
|
-
<%= render "rubyllm/agents/shared/stat_card",
|
|
5
|
-
title: "Executions",
|
|
6
|
-
value: number_with_delimiter(@stats[:total_executions]),
|
|
7
|
-
icon: "M13 10V3L4 14h7v7l9-11h-7z",
|
|
8
|
-
icon_color: "text-blue-500" %>
|
|
9
|
-
|
|
10
|
-
<%= render "rubyllm/agents/shared/stat_card",
|
|
11
|
-
title: "Failed",
|
|
12
|
-
value: number_with_delimiter(@stats[:failed]),
|
|
13
|
-
icon: "M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z",
|
|
14
|
-
icon_color: "text-red-500",
|
|
15
|
-
value_color: "text-red-600" %>
|
|
16
|
-
|
|
17
|
-
<%= render "rubyllm/agents/shared/stat_card",
|
|
18
|
-
title: "Total Cost",
|
|
19
|
-
value: "$#{number_with_precision(@stats[:total_cost], precision: 4)}",
|
|
20
|
-
icon: "M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z",
|
|
21
|
-
icon_color: "text-amber-500" %>
|
|
22
|
-
|
|
23
|
-
<%= render "rubyllm/agents/shared/stat_card",
|
|
24
|
-
title: "Total Tokens",
|
|
25
|
-
value: number_with_delimiter(@stats[:total_tokens]),
|
|
26
|
-
icon: "M7 20l4-16m2 16l4-16M6 9h14M4 15h14",
|
|
27
|
-
icon_color: "text-indigo-500" %>
|
|
28
|
-
</div>
|
|
1
|
+
<!-- Action Center (only when critical alerts exist) -->
|
|
2
|
+
<%= render partial: "rubyllm/agents/dashboard/action_center", locals: { critical_alerts: @critical_alerts } %>
|
|
29
3
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
<div class="px-6 py-4 border-b border-gray-100 dark:border-gray-700">
|
|
33
|
-
<div class="flex justify-between items-center">
|
|
34
|
-
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">Today's Activity</h3>
|
|
35
|
-
<span class="text-xs text-gray-400 dark:text-gray-500">Hourly executions</span>
|
|
36
|
-
</div>
|
|
37
|
-
</div>
|
|
4
|
+
<!-- Now Strip -->
|
|
5
|
+
<%= render partial: "rubyllm/agents/dashboard/now_strip", locals: { now_strip: @now_strip } %>
|
|
38
6
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
7
|
+
<!-- Today's Activity Chart -->
|
|
8
|
+
<div
|
|
9
|
+
class="
|
|
10
|
+
bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100
|
|
11
|
+
dark:border-gray-700 mb-6
|
|
12
|
+
"
|
|
13
|
+
>
|
|
14
|
+
<div class="px-6 py-4 border-b border-gray-100 dark:border-gray-700">
|
|
15
|
+
<div class="flex justify-between items-center">
|
|
16
|
+
<div class="flex items-center space-x-2">
|
|
17
|
+
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">
|
|
18
|
+
Activity
|
|
19
|
+
</h3>
|
|
20
|
+
|
|
21
|
+
<span class="flex items-center text-xs text-green-600 dark:text-green-400">
|
|
22
|
+
<span class="w-1.5 h-1.5 bg-green-500 rounded-full mr-1 animate-pulse"></span>
|
|
23
|
+
Live
|
|
24
|
+
</span>
|
|
53
25
|
</div>
|
|
26
|
+
|
|
27
|
+
<span class="text-xs text-gray-400 dark:text-gray-500">
|
|
28
|
+
Updates every 5s
|
|
29
|
+
</span>
|
|
54
30
|
</div>
|
|
55
31
|
</div>
|
|
56
32
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
success
|
|
73
|
-
</span>
|
|
74
|
-
|
|
75
|
-
<span class="flex items-center">
|
|
76
|
-
<span class="w-2 h-2 bg-red-500 rounded-full mr-1"></span>
|
|
77
|
-
error
|
|
78
|
-
</span>
|
|
79
|
-
|
|
80
|
-
<span class="flex items-center">
|
|
81
|
-
<span class="w-2 h-2 bg-yellow-500 rounded-full mr-1"></span>
|
|
82
|
-
timeout
|
|
83
|
-
</span>
|
|
84
|
-
</div>
|
|
85
|
-
</div>
|
|
33
|
+
<div
|
|
34
|
+
id="hourly-chart"
|
|
35
|
+
class="p-6 pb-3 pt-8"
|
|
36
|
+
style="height: 400px;"
|
|
37
|
+
data-chart-url="<%= ruby_llm_agents.chart_data_path %>"
|
|
38
|
+
>
|
|
39
|
+
<div id="activity-chart" style="width: 100%; height: 100%;"></div>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<script>
|
|
43
|
+
(function() {
|
|
44
|
+
const chartUrl = '<%= ruby_llm_agents.chart_data_path %>';
|
|
45
|
+
let activityChart = null;
|
|
46
|
+
const hourMs = 3600000;
|
|
47
|
+
const dayMs = 24 * hourMs;
|
|
86
48
|
|
|
87
|
-
|
|
49
|
+
// Convert server data to datetime points
|
|
50
|
+
function convertToDatetimePoints(data) {
|
|
51
|
+
const now = Date.now();
|
|
52
|
+
const numPoints = data.length;
|
|
53
|
+
return data.map((val, i) => {
|
|
54
|
+
// Each point is 1 hour apart, last point at current hour
|
|
55
|
+
return [now - (numPoints - 1 - i) * hourMs, val];
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function initLiveChart() {
|
|
60
|
+
fetch(chartUrl)
|
|
61
|
+
.then(res => res.json())
|
|
62
|
+
.then(data => {
|
|
63
|
+
const now = Date.now();
|
|
64
|
+
const successData = convertToDatetimePoints(data.series[0].data);
|
|
65
|
+
const failedData = convertToDatetimePoints(data.series[1].data);
|
|
66
|
+
|
|
67
|
+
activityChart = Highcharts.chart('activity-chart', {
|
|
68
|
+
chart: {
|
|
69
|
+
type: 'spline',
|
|
70
|
+
backgroundColor: 'transparent',
|
|
71
|
+
animation: { duration: 300 }
|
|
72
|
+
},
|
|
73
|
+
title: { text: null },
|
|
74
|
+
xAxis: {
|
|
75
|
+
type: 'datetime',
|
|
76
|
+
tickPixelInterval: 150,
|
|
77
|
+
min: now - dayMs,
|
|
78
|
+
max: now,
|
|
79
|
+
labels: { style: { color: '#9CA3AF' } },
|
|
80
|
+
lineColor: 'rgba(156, 163, 175, 0.2)'
|
|
81
|
+
},
|
|
82
|
+
yAxis: {
|
|
83
|
+
title: { text: null },
|
|
84
|
+
min: 0,
|
|
85
|
+
allowDecimals: false,
|
|
86
|
+
labels: { style: { color: '#9CA3AF' } },
|
|
87
|
+
gridLineColor: 'rgba(156, 163, 175, 0.2)'
|
|
88
|
+
},
|
|
89
|
+
legend: {
|
|
90
|
+
align: 'center',
|
|
91
|
+
verticalAlign: 'bottom',
|
|
92
|
+
itemStyle: { color: '#9CA3AF' }
|
|
93
|
+
},
|
|
94
|
+
credits: { enabled: false },
|
|
95
|
+
tooltip: {
|
|
96
|
+
backgroundColor: 'rgba(17, 24, 39, 0.9)',
|
|
97
|
+
borderColor: 'rgba(75, 85, 99, 0.5)',
|
|
98
|
+
style: { color: '#F3F4F6' },
|
|
99
|
+
headerFormat: '<b>{series.name}</b><br/>',
|
|
100
|
+
pointFormat: '{point.x:%H:%M} - {point.y}'
|
|
101
|
+
},
|
|
102
|
+
plotOptions: {
|
|
103
|
+
spline: {
|
|
104
|
+
lineWidth: 2,
|
|
105
|
+
marker: {
|
|
106
|
+
enabled: true,
|
|
107
|
+
radius: 4,
|
|
108
|
+
symbol: 'circle'
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
series: [{
|
|
113
|
+
name: 'Success',
|
|
114
|
+
color: '#10B981',
|
|
115
|
+
data: successData
|
|
116
|
+
}, {
|
|
117
|
+
name: 'Failed',
|
|
118
|
+
color: '#EF4444',
|
|
119
|
+
data: failedData
|
|
120
|
+
}]
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Update chart every 5 seconds with fresh data
|
|
124
|
+
setInterval(function() {
|
|
125
|
+
fetch(chartUrl)
|
|
126
|
+
.then(res => res.json())
|
|
127
|
+
.then(newData => {
|
|
128
|
+
const now = Date.now();
|
|
129
|
+
const newSuccessData = convertToDatetimePoints(newData.series[0].data);
|
|
130
|
+
const newFailedData = convertToDatetimePoints(newData.series[1].data);
|
|
131
|
+
|
|
132
|
+
// Update x-axis range
|
|
133
|
+
activityChart.xAxis[0].setExtremes(now - dayMs, now, false);
|
|
134
|
+
|
|
135
|
+
// Replace series data
|
|
136
|
+
activityChart.series[0].setData(newSuccessData, false);
|
|
137
|
+
activityChart.series[1].setData(newFailedData, true);
|
|
138
|
+
});
|
|
139
|
+
}, 5000);
|
|
140
|
+
})
|
|
141
|
+
.catch(err => console.log('[RubyLLM::Agents] Chart init error:', err));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (document.readyState === 'loading') {
|
|
145
|
+
document.addEventListener('DOMContentLoaded', initLiveChart);
|
|
146
|
+
} else {
|
|
147
|
+
initLiveChart();
|
|
148
|
+
}
|
|
149
|
+
})();
|
|
150
|
+
</script>
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
<!-- Live Activity Feed -->
|
|
154
|
+
<div
|
|
155
|
+
class="
|
|
156
|
+
bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100
|
|
157
|
+
dark:border-gray-700
|
|
158
|
+
"
|
|
159
|
+
>
|
|
160
|
+
<div class="px-6 py-4 border-b border-gray-100 dark:border-gray-700">
|
|
161
|
+
<div class="flex justify-between items-center">
|
|
162
|
+
<div class="flex items-center space-x-3">
|
|
163
|
+
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">
|
|
164
|
+
Live Activity
|
|
165
|
+
</h3>
|
|
166
|
+
|
|
167
|
+
<div
|
|
168
|
+
class="
|
|
169
|
+
flex items-center space-x-3 text-xs text-gray-400
|
|
170
|
+
dark:text-gray-500
|
|
171
|
+
"
|
|
172
|
+
>
|
|
173
|
+
<span class="flex items-center">
|
|
174
|
+
<span class="w-2 h-2 bg-blue-500 rounded-full mr-1 animate-pulse"></span>
|
|
175
|
+
running
|
|
176
|
+
</span>
|
|
177
|
+
|
|
178
|
+
<span class="flex items-center">
|
|
179
|
+
<span class="w-2 h-2 bg-green-500 rounded-full mr-1"></span>
|
|
180
|
+
success
|
|
181
|
+
</span>
|
|
182
|
+
|
|
183
|
+
<span class="flex items-center">
|
|
184
|
+
<span class="w-2 h-2 bg-red-500 rounded-full mr-1"></span>
|
|
185
|
+
error
|
|
186
|
+
</span>
|
|
187
|
+
|
|
188
|
+
<span class="flex items-center">
|
|
189
|
+
<span class="w-2 h-2 bg-yellow-500 rounded-full mr-1"></span>
|
|
190
|
+
timeout
|
|
191
|
+
</span>
|
|
192
|
+
</div>
|
|
88
193
|
</div>
|
|
194
|
+
|
|
195
|
+
<%= link_to "View All", ruby_llm_agents.executions_path, data: { turbo: false }, class: "text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 text-sm font-medium" %>
|
|
89
196
|
</div>
|
|
197
|
+
</div>
|
|
90
198
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
>
|
|
96
|
-
<% if @recent_executions.any? %>
|
|
97
|
-
<% @recent_executions.each do |execution| %>
|
|
98
|
-
<%= render partial: "rubyllm/agents/dashboard/execution_item", locals: { execution: execution } %>
|
|
99
|
-
<% end %>
|
|
100
|
-
<% else %>
|
|
101
|
-
<div id="empty-state" class="py-10 text-center text-gray-500 dark:text-gray-400">
|
|
102
|
-
<svg
|
|
103
|
-
class="w-12 h-12 mx-auto mb-3 text-gray-300 dark:text-gray-600"
|
|
104
|
-
fill="none"
|
|
105
|
-
stroke="currentColor"
|
|
106
|
-
viewBox="0 0 24 24"
|
|
107
|
-
>
|
|
108
|
-
<path
|
|
109
|
-
stroke-linecap="round"
|
|
110
|
-
stroke-linejoin="round"
|
|
111
|
-
stroke-width="1.5"
|
|
112
|
-
d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"
|
|
113
|
-
/>
|
|
114
|
-
</svg>
|
|
115
|
-
|
|
116
|
-
<p>No executions yet. Create an agent and make a call!</p>
|
|
117
|
-
</div>
|
|
199
|
+
<div id="activity-feed" class="px-6 py-2">
|
|
200
|
+
<% if @recent_executions.any? %>
|
|
201
|
+
<% @recent_executions.each do |execution| %>
|
|
202
|
+
<%= render partial: "rubyllm/agents/dashboard/execution_item", locals: { execution: execution } %>
|
|
118
203
|
<% end %>
|
|
119
|
-
|
|
204
|
+
<% else %>
|
|
205
|
+
<div
|
|
206
|
+
id="empty-state"
|
|
207
|
+
class="py-10 text-center text-gray-500 dark:text-gray-400"
|
|
208
|
+
>
|
|
209
|
+
<svg
|
|
210
|
+
class="w-12 h-12 mx-auto mb-3 text-gray-300 dark:text-gray-600"
|
|
211
|
+
fill="none"
|
|
212
|
+
stroke="currentColor"
|
|
213
|
+
viewBox="0 0 24 24"
|
|
214
|
+
>
|
|
215
|
+
<path
|
|
216
|
+
stroke-linecap="round"
|
|
217
|
+
stroke-linejoin="round"
|
|
218
|
+
stroke-width="1.5"
|
|
219
|
+
d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"
|
|
220
|
+
/>
|
|
221
|
+
</svg>
|
|
222
|
+
|
|
223
|
+
<p>No executions yet. Create an agent and make a call!</p>
|
|
224
|
+
</div>
|
|
225
|
+
<% end %>
|
|
120
226
|
</div>
|
|
121
|
-
|
|
227
|
+
</div>
|