ruby_llm-agents 0.3.3 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/controllers/ruby_llm/agents/dashboard_controller.rb +68 -4
- data/app/models/ruby_llm/agents/execution/analytics.rb +114 -13
- data/app/models/ruby_llm/agents/execution.rb +19 -58
- data/app/views/layouts/rubyllm/agents/application.html.erb +92 -350
- data/app/views/rubyllm/agents/agents/show.html.erb +331 -385
- data/app/views/rubyllm/agents/dashboard/_agent_comparison.html.erb +46 -0
- data/app/views/rubyllm/agents/dashboard/_budgets_bar.html.erb +0 -90
- data/app/views/rubyllm/agents/dashboard/_now_strip.html.erb +79 -5
- data/app/views/rubyllm/agents/dashboard/_top_errors.html.erb +49 -0
- data/app/views/rubyllm/agents/dashboard/index.html.erb +76 -121
- data/app/views/rubyllm/agents/executions/show.html.erb +134 -85
- data/app/views/rubyllm/agents/settings/show.html.erb +1 -1
- data/app/views/rubyllm/agents/shared/_breadcrumbs.html.erb +48 -0
- data/app/views/rubyllm/agents/shared/_nav_link.html.erb +27 -0
- data/config/routes.rb +2 -0
- data/lib/ruby_llm/agents/base/caching.rb +43 -0
- data/lib/ruby_llm/agents/base/cost_calculation.rb +103 -0
- data/lib/ruby_llm/agents/base/dsl.rb +261 -0
- data/lib/ruby_llm/agents/base/execution.rb +206 -0
- data/lib/ruby_llm/agents/base/reliability_execution.rb +131 -0
- data/lib/ruby_llm/agents/base/response_building.rb +86 -0
- data/lib/ruby_llm/agents/base/tool_tracking.rb +57 -0
- data/lib/ruby_llm/agents/base.rb +15 -805
- data/lib/ruby_llm/agents/version.rb +1 -1
- metadata +12 -20
- data/app/channels/ruby_llm/agents/executions_channel.rb +0 -46
- data/app/javascript/ruby_llm/agents/controllers/filter_controller.js +0 -56
- data/app/javascript/ruby_llm/agents/controllers/index.js +0 -12
- data/app/javascript/ruby_llm/agents/controllers/refresh_controller.js +0 -83
- data/app/views/rubyllm/agents/dashboard/_now_strip_values.html.erb +0 -71
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 h-full">
|
|
2
|
+
<div class="px-4 py-3 border-b border-gray-100 dark:border-gray-700">
|
|
3
|
+
<h3 class="text-base font-semibold text-gray-900 dark:text-gray-100">Agents</h3>
|
|
4
|
+
</div>
|
|
5
|
+
|
|
6
|
+
<% if agent_stats.any? %>
|
|
7
|
+
<div class="overflow-x-auto">
|
|
8
|
+
<table class="w-full text-sm">
|
|
9
|
+
<thead>
|
|
10
|
+
<tr class="text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
11
|
+
<th class="px-4 py-2">Agent</th>
|
|
12
|
+
<th class="px-4 py-2 text-right">Runs</th>
|
|
13
|
+
<th class="px-4 py-2 text-right">Cost</th>
|
|
14
|
+
<th class="px-4 py-2 text-right">Success</th>
|
|
15
|
+
</tr>
|
|
16
|
+
</thead>
|
|
17
|
+
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
|
|
18
|
+
<% agent_stats.first(5).each do |agent| %>
|
|
19
|
+
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50">
|
|
20
|
+
<td class="px-4 py-2">
|
|
21
|
+
<span class="text-sm font-medium text-gray-900 dark:text-gray-100 truncate block max-w-[120px]" title="<%= agent[:agent_type] %>">
|
|
22
|
+
<%= agent[:agent_type].to_s.demodulize %>
|
|
23
|
+
</span>
|
|
24
|
+
</td>
|
|
25
|
+
<td class="px-4 py-2 text-right text-gray-600 dark:text-gray-300">
|
|
26
|
+
<%= number_with_delimiter(agent[:executions]) %>
|
|
27
|
+
</td>
|
|
28
|
+
<td class="px-4 py-2 text-right text-gray-600 dark:text-gray-300">
|
|
29
|
+
$<%= number_with_precision(agent[:total_cost], precision: 2) %>
|
|
30
|
+
</td>
|
|
31
|
+
<td class="px-4 py-2 text-right">
|
|
32
|
+
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium <%= agent[:success_rate] >= 95 ? 'bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-300' : agent[:success_rate] >= 80 ? 'bg-yellow-100 dark:bg-yellow-900 text-yellow-700 dark:text-yellow-300' : 'bg-red-100 dark:bg-red-900 text-red-700 dark:text-red-300' %>">
|
|
33
|
+
<%= agent[:success_rate].round %>%
|
|
34
|
+
</span>
|
|
35
|
+
</td>
|
|
36
|
+
</tr>
|
|
37
|
+
<% end %>
|
|
38
|
+
</tbody>
|
|
39
|
+
</table>
|
|
40
|
+
</div>
|
|
41
|
+
<% else %>
|
|
42
|
+
<div class="px-4 py-8 text-center text-gray-500 dark:text-gray-400">
|
|
43
|
+
<p class="text-sm">No agent data yet</p>
|
|
44
|
+
</div>
|
|
45
|
+
<% end %>
|
|
46
|
+
</div>
|
|
@@ -71,95 +71,5 @@
|
|
|
71
71
|
No budget limits configured
|
|
72
72
|
</p>
|
|
73
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
74
|
</div>
|
|
165
75
|
<% end %>
|
|
@@ -1,10 +1,84 @@
|
|
|
1
1
|
<div class="mb-6">
|
|
2
|
-
<div class="flex items-center mb-3">
|
|
3
|
-
<
|
|
4
|
-
|
|
2
|
+
<div class="flex items-center justify-end mb-3">
|
|
3
|
+
<div class="flex space-x-1 bg-gray-100 dark:bg-gray-700 rounded-lg p-1">
|
|
4
|
+
<%= link_to "Today", ruby_llm_agents.root_path(range: "today"),
|
|
5
|
+
class: "px-3 py-1 text-xs font-medium rounded-md transition-colors #{@selected_range == 'today' ? 'bg-white dark:bg-gray-600 text-gray-900 dark:text-white shadow-sm' : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white'}" %>
|
|
6
|
+
<%= link_to "7 Days", ruby_llm_agents.root_path(range: "7d"),
|
|
7
|
+
class: "px-3 py-1 text-xs font-medium rounded-md transition-colors #{@selected_range == '7d' ? 'bg-white dark:bg-gray-600 text-gray-900 dark:text-white shadow-sm' : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white'}" %>
|
|
8
|
+
<%= link_to "30 Days", ruby_llm_agents.root_path(range: "30d"),
|
|
9
|
+
class: "px-3 py-1 text-xs font-medium rounded-md transition-colors #{@selected_range == '30d' ? 'bg-white dark:bg-gray-600 text-gray-900 dark:text-white shadow-sm' : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white'}" %>
|
|
10
|
+
</div>
|
|
5
11
|
</div>
|
|
6
12
|
|
|
7
|
-
<div
|
|
8
|
-
|
|
13
|
+
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-3">
|
|
14
|
+
<!-- Running -->
|
|
15
|
+
<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">
|
|
16
|
+
<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">
|
|
17
|
+
<span class="w-2.5 h-2.5 bg-blue-500 rounded-full animate-pulse"></span>
|
|
18
|
+
</div>
|
|
19
|
+
<div>
|
|
20
|
+
<p class="text-lg font-bold text-gray-900 dark:text-gray-100"><%= now_strip[:running] %></p>
|
|
21
|
+
<p class="text-xs text-gray-500 dark:text-gray-400">Running</p>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<!-- Success -->
|
|
26
|
+
<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">
|
|
27
|
+
<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">
|
|
28
|
+
<span class="w-2.5 h-2.5 bg-green-500 rounded-full"></span>
|
|
29
|
+
</div>
|
|
30
|
+
<div>
|
|
31
|
+
<p class="text-lg font-bold text-green-600 dark:text-green-400"><%= now_strip[:success_today] %></p>
|
|
32
|
+
<p class="text-xs text-gray-500 dark:text-gray-400">Success</p>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<!-- Errors -->
|
|
37
|
+
<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">
|
|
38
|
+
<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">
|
|
39
|
+
<span class="w-2.5 h-2.5 <%= now_strip[:errors_today] > 0 ? 'bg-red-500' : 'bg-gray-400' %> rounded-full"></span>
|
|
40
|
+
</div>
|
|
41
|
+
<div>
|
|
42
|
+
<p 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>
|
|
43
|
+
<p class="text-xs text-gray-500 dark:text-gray-400">Errors</p>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<!-- Timeouts -->
|
|
48
|
+
<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">
|
|
49
|
+
<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">
|
|
50
|
+
<span class="w-2.5 h-2.5 <%= now_strip[:timeouts_today] > 0 ? 'bg-yellow-500' : 'bg-gray-400' %> rounded-full"></span>
|
|
51
|
+
</div>
|
|
52
|
+
<div>
|
|
53
|
+
<p 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>
|
|
54
|
+
<p class="text-xs text-gray-500 dark:text-gray-400">Timeouts</p>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<!-- Cost -->
|
|
59
|
+
<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">
|
|
60
|
+
<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">
|
|
61
|
+
<svg class="w-4 h-4 text-amber-600 dark:text-amber-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
62
|
+
<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"/>
|
|
63
|
+
</svg>
|
|
64
|
+
</div>
|
|
65
|
+
<div>
|
|
66
|
+
<p class="text-lg font-bold text-gray-900 dark:text-gray-100">$<%= number_with_precision(now_strip[:cost_today], precision: 4) %></p>
|
|
67
|
+
<p class="text-xs text-gray-500 dark:text-gray-400">Cost</p>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<!-- Success Rate -->
|
|
72
|
+
<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">
|
|
73
|
+
<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">
|
|
74
|
+
<svg class="w-4 h-4 text-green-600 dark:text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
75
|
+
<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"/>
|
|
76
|
+
</svg>
|
|
77
|
+
</div>
|
|
78
|
+
<div>
|
|
79
|
+
<p class="text-lg font-bold text-gray-900 dark:text-gray-100"><%= now_strip[:success_rate] %>%</p>
|
|
80
|
+
<p class="text-xs text-gray-500 dark:text-gray-400">Success Rate</p>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
9
83
|
</div>
|
|
10
84
|
</div>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 h-full">
|
|
2
|
+
<div class="px-4 py-3 border-b border-gray-100 dark:border-gray-700">
|
|
3
|
+
<h3 class="text-base font-semibold text-gray-900 dark:text-gray-100">Top Errors</h3>
|
|
4
|
+
</div>
|
|
5
|
+
|
|
6
|
+
<% if top_errors.any? %>
|
|
7
|
+
<div class="overflow-x-auto">
|
|
8
|
+
<table class="w-full text-sm">
|
|
9
|
+
<thead>
|
|
10
|
+
<tr class="text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
11
|
+
<th class="px-4 py-2">Error</th>
|
|
12
|
+
<th class="px-4 py-2 text-right">Count</th>
|
|
13
|
+
<th class="px-4 py-2 text-right">Last Seen</th>
|
|
14
|
+
</tr>
|
|
15
|
+
</thead>
|
|
16
|
+
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
|
|
17
|
+
<% top_errors.first(5).each do |error| %>
|
|
18
|
+
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50">
|
|
19
|
+
<td class="px-4 py-2">
|
|
20
|
+
<span class="text-sm font-medium text-gray-900 dark:text-gray-100 truncate block max-w-[180px]" title="<%= error[:error_class] %>">
|
|
21
|
+
<%= error[:error_class].to_s.split("::").last %>
|
|
22
|
+
</span>
|
|
23
|
+
</td>
|
|
24
|
+
<td class="px-4 py-2 text-right">
|
|
25
|
+
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-red-100 dark:bg-red-900 text-red-700 dark:text-red-300">
|
|
26
|
+
<%= number_with_delimiter(error[:count]) %>
|
|
27
|
+
</span>
|
|
28
|
+
</td>
|
|
29
|
+
<td class="px-4 py-2 text-right text-xs text-gray-500 dark:text-gray-400">
|
|
30
|
+
<% if error[:last_seen] %>
|
|
31
|
+
<%= time_ago_in_words(error[:last_seen]) %>
|
|
32
|
+
<% else %>
|
|
33
|
+
-
|
|
34
|
+
<% end %>
|
|
35
|
+
</td>
|
|
36
|
+
</tr>
|
|
37
|
+
<% end %>
|
|
38
|
+
</tbody>
|
|
39
|
+
</table>
|
|
40
|
+
</div>
|
|
41
|
+
<% else %>
|
|
42
|
+
<div class="px-4 py-8 text-center text-green-500 dark:text-green-400">
|
|
43
|
+
<svg class="w-8 h-8 mx-auto mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
44
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
45
|
+
</svg>
|
|
46
|
+
<p class="text-sm">No errors</p>
|
|
47
|
+
</div>
|
|
48
|
+
<% end %>
|
|
49
|
+
</div>
|
|
@@ -4,194 +4,149 @@
|
|
|
4
4
|
<!-- Now Strip -->
|
|
5
5
|
<%= render partial: "rubyllm/agents/dashboard/now_strip", locals: { now_strip: @now_strip } %>
|
|
6
6
|
|
|
7
|
-
<!--
|
|
8
|
-
<div
|
|
9
|
-
class="
|
|
10
|
-
|
|
11
|
-
|
|
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>
|
|
25
|
-
</div>
|
|
26
|
-
|
|
27
|
-
<span class="text-xs text-gray-400 dark:text-gray-500">
|
|
28
|
-
Updates every 5s
|
|
29
|
-
</span>
|
|
7
|
+
<!-- Activity Chart -->
|
|
8
|
+
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 mb-6">
|
|
9
|
+
<div class="px-6 py-3 border-b border-gray-100 dark:border-gray-700">
|
|
10
|
+
<div class="flex items-center justify-between">
|
|
11
|
+
<h3 class="text-base font-semibold text-gray-900 dark:text-gray-100">Activity</h3>
|
|
12
|
+
<span id="chart-totals" class="text-sm text-gray-500 dark:text-gray-400"></span>
|
|
30
13
|
</div>
|
|
31
14
|
</div>
|
|
32
15
|
|
|
33
|
-
<div
|
|
34
|
-
id="hourly-chart"
|
|
35
|
-
class="p-6 pb-3 pt-8 h-[280px] sm:h-[400px]"
|
|
36
|
-
data-chart-url="<%= ruby_llm_agents.chart_data_path %>"
|
|
37
|
-
>
|
|
16
|
+
<div id="activity-chart-container" class="p-4 h-[320px] sm:h-[380px]">
|
|
38
17
|
<div id="activity-chart" style="width: 100%; height: 100%;"></div>
|
|
39
18
|
</div>
|
|
40
19
|
|
|
41
20
|
<script>
|
|
42
21
|
(function() {
|
|
43
|
-
const
|
|
44
|
-
|
|
22
|
+
const range = '<%= @selected_range %>';
|
|
23
|
+
const chartUrl = '<%= ruby_llm_agents.chart_data_path %>?range=' + range;
|
|
45
24
|
const hourMs = 3600000;
|
|
46
25
|
const dayMs = 24 * hourMs;
|
|
47
26
|
|
|
48
|
-
|
|
49
|
-
function convertToDatetimePoints(data) {
|
|
27
|
+
function convertToDatetimePoints(data, range) {
|
|
50
28
|
const now = Date.now();
|
|
51
29
|
const numPoints = data.length;
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
30
|
+
if (range === 'today') {
|
|
31
|
+
return data.map((val, i) => [now - (numPoints - 1 - i) * hourMs, val]);
|
|
32
|
+
} else {
|
|
33
|
+
return data.map((val, i) => [now - (numPoints - 1 - i) * dayMs, val]);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function formatNumber(num) {
|
|
38
|
+
return num.toLocaleString();
|
|
56
39
|
}
|
|
57
40
|
|
|
58
|
-
function
|
|
41
|
+
function initChart() {
|
|
59
42
|
fetch(chartUrl)
|
|
60
43
|
.then(res => res.json())
|
|
61
44
|
.then(data => {
|
|
62
45
|
const now = Date.now();
|
|
63
|
-
const successData = convertToDatetimePoints(data.series[0].data);
|
|
64
|
-
const failedData = convertToDatetimePoints(data.series[1].data);
|
|
46
|
+
const successData = convertToDatetimePoints(data.series[0].data, range);
|
|
47
|
+
const failedData = convertToDatetimePoints(data.series[1].data, range);
|
|
48
|
+
const costData = convertToDatetimePoints(data.series[2].data, range);
|
|
65
49
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
50
|
+
// Update totals in header
|
|
51
|
+
const totals = data.totals;
|
|
52
|
+
const totalExecs = totals.success + totals.failed;
|
|
53
|
+
document.getElementById('chart-totals').textContent =
|
|
54
|
+
formatNumber(totalExecs) + ' executions • $' + totals.cost.toFixed(2);
|
|
55
|
+
|
|
56
|
+
const timeRange = range === 'today' ? dayMs : (range === '7d' ? 7 * dayMs : 30 * dayMs);
|
|
57
|
+
const dateFormat = range === 'today' ? '%H:%M' : '%b %d';
|
|
58
|
+
|
|
59
|
+
Highcharts.chart('activity-chart', {
|
|
60
|
+
chart: { type: 'areaspline', backgroundColor: 'transparent' },
|
|
72
61
|
title: { text: null },
|
|
73
62
|
xAxis: {
|
|
74
63
|
type: 'datetime',
|
|
75
|
-
|
|
76
|
-
min: now - dayMs,
|
|
64
|
+
min: now - timeRange,
|
|
77
65
|
max: now,
|
|
78
|
-
labels: { style: { color: '#9CA3AF' } },
|
|
66
|
+
labels: { style: { color: '#9CA3AF' }, format: '{value:' + dateFormat + '}' },
|
|
79
67
|
lineColor: 'rgba(156, 163, 175, 0.2)'
|
|
80
68
|
},
|
|
81
|
-
yAxis: {
|
|
69
|
+
yAxis: [{
|
|
82
70
|
title: { text: null },
|
|
83
71
|
min: 0,
|
|
84
72
|
allowDecimals: false,
|
|
85
73
|
labels: { style: { color: '#9CA3AF' } },
|
|
86
74
|
gridLineColor: 'rgba(156, 163, 175, 0.2)'
|
|
87
|
-
},
|
|
75
|
+
}, {
|
|
76
|
+
title: { text: null },
|
|
77
|
+
min: 0,
|
|
78
|
+
labels: { style: { color: '#F59E0B' }, format: '${value}' },
|
|
79
|
+
opposite: true,
|
|
80
|
+
gridLineWidth: 0
|
|
81
|
+
}],
|
|
88
82
|
legend: {
|
|
83
|
+
enabled: true,
|
|
89
84
|
align: 'center',
|
|
90
85
|
verticalAlign: 'bottom',
|
|
91
|
-
itemStyle: { color: '#9CA3AF' }
|
|
86
|
+
itemStyle: { color: '#9CA3AF', fontWeight: 'normal' },
|
|
87
|
+
itemHoverStyle: { color: '#D1D5DB' }
|
|
92
88
|
},
|
|
93
89
|
credits: { enabled: false },
|
|
94
90
|
tooltip: {
|
|
95
|
-
|
|
91
|
+
shared: true,
|
|
92
|
+
backgroundColor: 'rgba(17, 24, 39, 0.95)',
|
|
96
93
|
borderColor: 'rgba(75, 85, 99, 0.5)',
|
|
97
94
|
style: { color: '#F3F4F6' },
|
|
98
|
-
|
|
99
|
-
pointFormat: '{point.x:%H:%M} - {point.y}'
|
|
95
|
+
xDateFormat: range === 'today' ? '%H:%M' : '%b %d'
|
|
100
96
|
},
|
|
101
97
|
plotOptions: {
|
|
98
|
+
areaspline: {
|
|
99
|
+
fillOpacity: 0.15,
|
|
100
|
+
lineWidth: 2,
|
|
101
|
+
marker: { enabled: false, states: { hover: { enabled: true, radius: 4 } } }
|
|
102
|
+
},
|
|
102
103
|
spline: {
|
|
103
104
|
lineWidth: 2,
|
|
104
|
-
marker: {
|
|
105
|
-
enabled: true,
|
|
106
|
-
radius: 4,
|
|
107
|
-
symbol: 'circle'
|
|
108
|
-
}
|
|
105
|
+
marker: { enabled: false, states: { hover: { enabled: true, radius: 4 } } }
|
|
109
106
|
}
|
|
110
107
|
},
|
|
111
|
-
series: [
|
|
112
|
-
name: 'Success',
|
|
113
|
-
color: '#
|
|
114
|
-
data:
|
|
115
|
-
|
|
116
|
-
name: 'Failed',
|
|
117
|
-
color: '#EF4444',
|
|
118
|
-
data: failedData
|
|
119
|
-
}]
|
|
108
|
+
series: [
|
|
109
|
+
{ name: 'Success', type: 'areaspline', color: '#10B981', data: successData, yAxis: 0 },
|
|
110
|
+
{ name: 'Failed', type: 'areaspline', color: '#EF4444', data: failedData, yAxis: 0 },
|
|
111
|
+
{ name: 'Cost', type: 'spline', color: '#F59E0B', data: costData, yAxis: 1, tooltip: { valuePrefix: '$' } }
|
|
112
|
+
]
|
|
120
113
|
});
|
|
121
|
-
|
|
122
|
-
// Update chart every 5 seconds with fresh data
|
|
123
|
-
setInterval(function() {
|
|
124
|
-
fetch(chartUrl)
|
|
125
|
-
.then(res => res.json())
|
|
126
|
-
.then(newData => {
|
|
127
|
-
const now = Date.now();
|
|
128
|
-
const newSuccessData = convertToDatetimePoints(newData.series[0].data);
|
|
129
|
-
const newFailedData = convertToDatetimePoints(newData.series[1].data);
|
|
130
|
-
|
|
131
|
-
// Update x-axis range
|
|
132
|
-
activityChart.xAxis[0].setExtremes(now - dayMs, now, false);
|
|
133
|
-
|
|
134
|
-
// Replace series data
|
|
135
|
-
activityChart.series[0].setData(newSuccessData, false);
|
|
136
|
-
activityChart.series[1].setData(newFailedData, true);
|
|
137
|
-
});
|
|
138
|
-
}, 5000);
|
|
139
114
|
})
|
|
140
|
-
.catch(err => console.log('[RubyLLM::Agents] Chart
|
|
115
|
+
.catch(err => console.log('[RubyLLM::Agents] Chart error:', err));
|
|
141
116
|
}
|
|
142
117
|
|
|
143
118
|
if (document.readyState === 'loading') {
|
|
144
|
-
document.addEventListener('DOMContentLoaded',
|
|
119
|
+
document.addEventListener('DOMContentLoaded', initChart);
|
|
145
120
|
} else {
|
|
146
|
-
|
|
121
|
+
initChart();
|
|
147
122
|
}
|
|
148
123
|
})();
|
|
149
124
|
</script>
|
|
150
125
|
</div>
|
|
151
126
|
|
|
152
127
|
<!-- Live Activity Feed -->
|
|
153
|
-
<div
|
|
154
|
-
class="
|
|
155
|
-
bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100
|
|
156
|
-
dark:border-gray-700
|
|
157
|
-
"
|
|
158
|
-
>
|
|
159
|
-
<div class="px-6 py-4 border-b border-gray-100 dark:border-gray-700">
|
|
128
|
+
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 mb-6">
|
|
129
|
+
<div class="px-6 py-3 border-b border-gray-100 dark:border-gray-700">
|
|
160
130
|
<div class="flex justify-between items-center">
|
|
161
|
-
<h3 class="text-
|
|
162
|
-
Live Activity
|
|
163
|
-
</h3>
|
|
164
|
-
|
|
131
|
+
<h3 class="text-base font-semibold text-gray-900 dark:text-gray-100">Recent Activity</h3>
|
|
165
132
|
<%= 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" %>
|
|
166
133
|
</div>
|
|
167
134
|
</div>
|
|
168
|
-
|
|
169
135
|
<div id="activity-feed" class="px-6 py-2">
|
|
170
136
|
<% if @recent_executions.any? %>
|
|
171
|
-
<% @recent_executions.each do |execution| %>
|
|
137
|
+
<% @recent_executions.first(5).each do |execution| %>
|
|
172
138
|
<%= render partial: "rubyllm/agents/dashboard/execution_item", locals: { execution: execution } %>
|
|
173
139
|
<% end %>
|
|
174
140
|
<% else %>
|
|
175
|
-
<div
|
|
176
|
-
|
|
177
|
-
class="py-10 text-center text-gray-500 dark:text-gray-400"
|
|
178
|
-
>
|
|
179
|
-
<svg
|
|
180
|
-
class="w-12 h-12 mx-auto mb-3 text-gray-300 dark:text-gray-600"
|
|
181
|
-
fill="none"
|
|
182
|
-
stroke="currentColor"
|
|
183
|
-
viewBox="0 0 24 24"
|
|
184
|
-
>
|
|
185
|
-
<path
|
|
186
|
-
stroke-linecap="round"
|
|
187
|
-
stroke-linejoin="round"
|
|
188
|
-
stroke-width="1.5"
|
|
189
|
-
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"
|
|
190
|
-
/>
|
|
191
|
-
</svg>
|
|
192
|
-
|
|
193
|
-
<p>No executions yet. Create an agent and make a call!</p>
|
|
141
|
+
<div class="py-8 text-center text-gray-500 dark:text-gray-400">
|
|
142
|
+
<p class="text-sm">No executions yet</p>
|
|
194
143
|
</div>
|
|
195
144
|
<% end %>
|
|
196
145
|
</div>
|
|
197
146
|
</div>
|
|
147
|
+
|
|
148
|
+
<!-- Agent Comparison + Top Errors -->
|
|
149
|
+
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
150
|
+
<%= render partial: "rubyllm/agents/dashboard/agent_comparison", locals: { agent_stats: @agent_stats } %>
|
|
151
|
+
<%= render partial: "rubyllm/agents/dashboard/top_errors", locals: { top_errors: @top_errors } %>
|
|
152
|
+
</div>
|