ruby_llm-agents 0.2.3 → 0.3.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.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +273 -0
  3. data/app/channels/ruby_llm/agents/executions_channel.rb +24 -1
  4. data/app/controllers/concerns/ruby_llm/agents/filterable.rb +81 -0
  5. data/app/controllers/concerns/ruby_llm/agents/paginatable.rb +51 -0
  6. data/app/controllers/ruby_llm/agents/agents_controller.rb +228 -59
  7. data/app/controllers/ruby_llm/agents/dashboard_controller.rb +167 -12
  8. data/app/controllers/ruby_llm/agents/executions_controller.rb +189 -31
  9. data/app/controllers/ruby_llm/agents/settings_controller.rb +20 -0
  10. data/app/helpers/ruby_llm/agents/application_helper.rb +307 -7
  11. data/app/models/ruby_llm/agents/execution/analytics.rb +224 -20
  12. data/app/models/ruby_llm/agents/execution/metrics.rb +41 -25
  13. data/app/models/ruby_llm/agents/execution/scopes.rb +234 -14
  14. data/app/models/ruby_llm/agents/execution.rb +259 -16
  15. data/app/services/ruby_llm/agents/agent_registry.rb +49 -12
  16. data/app/views/layouts/rubyllm/agents/application.html.erb +351 -85
  17. data/app/views/rubyllm/agents/agents/_version_comparison.html.erb +186 -0
  18. data/app/views/rubyllm/agents/agents/show.html.erb +233 -10
  19. data/app/views/rubyllm/agents/dashboard/_action_center.html.erb +62 -0
  20. data/app/views/rubyllm/agents/dashboard/_alerts_feed.html.erb +62 -0
  21. data/app/views/rubyllm/agents/dashboard/_breaker_strip.html.erb +47 -0
  22. data/app/views/rubyllm/agents/dashboard/_budgets_bar.html.erb +165 -0
  23. data/app/views/rubyllm/agents/dashboard/_now_strip.html.erb +10 -0
  24. data/app/views/rubyllm/agents/dashboard/_now_strip_values.html.erb +71 -0
  25. data/app/views/rubyllm/agents/dashboard/index.html.erb +215 -109
  26. data/app/views/rubyllm/agents/executions/_filters.html.erb +152 -155
  27. data/app/views/rubyllm/agents/executions/_list.html.erb +103 -12
  28. data/app/views/rubyllm/agents/executions/dry_run.html.erb +149 -0
  29. data/app/views/rubyllm/agents/executions/index.html.erb +17 -72
  30. data/app/views/rubyllm/agents/executions/index.turbo_stream.erb +16 -2
  31. data/app/views/rubyllm/agents/executions/show.html.erb +693 -14
  32. data/app/views/rubyllm/agents/settings/show.html.erb +369 -0
  33. data/app/views/rubyllm/agents/shared/_filter_dropdown.html.erb +121 -0
  34. data/app/views/rubyllm/agents/shared/_select_dropdown.html.erb +85 -0
  35. data/config/routes.rb +7 -0
  36. data/lib/generators/ruby_llm_agents/templates/add_attempts_migration.rb.tt +27 -0
  37. data/lib/generators/ruby_llm_agents/templates/add_caching_migration.rb.tt +23 -0
  38. data/lib/generators/ruby_llm_agents/templates/add_finish_reason_migration.rb.tt +19 -0
  39. data/lib/generators/ruby_llm_agents/templates/add_routing_migration.rb.tt +19 -0
  40. data/lib/generators/ruby_llm_agents/templates/add_streaming_migration.rb.tt +8 -0
  41. data/lib/generators/ruby_llm_agents/templates/add_tracing_migration.rb.tt +34 -0
  42. data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +66 -4
  43. data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +53 -6
  44. data/lib/generators/ruby_llm_agents/templates/initializer.rb.tt +139 -8
  45. data/lib/generators/ruby_llm_agents/templates/migration.rb.tt +38 -1
  46. data/lib/generators/ruby_llm_agents/upgrade_generator.rb +78 -0
  47. data/lib/ruby_llm/agents/alert_manager.rb +207 -0
  48. data/lib/ruby_llm/agents/attempt_tracker.rb +295 -0
  49. data/lib/ruby_llm/agents/base.rb +580 -112
  50. data/lib/ruby_llm/agents/budget_tracker.rb +360 -0
  51. data/lib/ruby_llm/agents/circuit_breaker.rb +197 -0
  52. data/lib/ruby_llm/agents/configuration.rb +279 -1
  53. data/lib/ruby_llm/agents/engine.rb +59 -6
  54. data/lib/ruby_llm/agents/execution_logger_job.rb +17 -6
  55. data/lib/ruby_llm/agents/inflections.rb +13 -2
  56. data/lib/ruby_llm/agents/instrumentation.rb +538 -87
  57. data/lib/ruby_llm/agents/redactor.rb +130 -0
  58. data/lib/ruby_llm/agents/reliability.rb +185 -0
  59. data/lib/ruby_llm/agents/version.rb +3 -1
  60. data/lib/ruby_llm/agents.rb +52 -0
  61. metadata +41 -2
  62. 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
- <%= turbo_frame_tag "dashboard", data: { connection_target: "dashboardFrame" } do %>
2
- <!-- Stats Cards -->
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
- <!-- Activity Chart -->
31
- <div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 mb-8 pb-24">
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
- <div class="p-6">
40
- <div style="height: 200px;">
41
- <%= area_chart @hourly_activity,
42
- stacked: true,
43
- colors: ["#10B981", "#EF4444"],
44
- library: {
45
- maintainAspectRatio: false,
46
- scales: {
47
- y: { beginAtZero: true, ticks: { stepSize: 1 } }
48
- },
49
- plugins: {
50
- legend: { position: "bottom" }
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
- <!-- Activity Feed -->
58
- <div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700">
59
- <div class="px-6 py-4 border-b border-gray-100 dark:border-gray-700">
60
- <div class="flex justify-between items-center">
61
- <div class="flex items-center space-x-3">
62
- <h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">Activity Feed</h3>
63
-
64
- <div class="flex items-center space-x-3 text-xs text-gray-400 dark:text-gray-500">
65
- <span class="flex items-center">
66
- <span class="w-2 h-2 bg-blue-500 rounded-full mr-1 animate-pulse"></span>
67
- running
68
- </span>
69
-
70
- <span class="flex items-center">
71
- <span class="w-2 h-2 bg-green-500 rounded-full mr-1"></span>
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
- <%= 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" %>
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
- <div
92
- id="activity-feed"
93
- class="px-6 py-2"
94
- data-connection-target="activityFeed"
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
- </div>
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
- <% end %>
227
+ </div>