ruby_llm-agents 0.3.1 → 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/README.md +88 -0
- 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/scopes.rb +10 -0
- data/app/models/ruby_llm/agents/execution.rb +26 -58
- data/app/views/layouts/rubyllm/agents/application.html.erb +103 -352
- data/app/views/rubyllm/agents/agents/_agent.html.erb +87 -0
- data/app/views/rubyllm/agents/agents/index.html.erb +2 -71
- data/app/views/rubyllm/agents/agents/show.html.erb +349 -416
- data/app/views/rubyllm/agents/dashboard/_action_center.html.erb +7 -7
- 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/_execution_item.html.erb +54 -39
- 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 -151
- data/app/views/rubyllm/agents/executions/show.html.erb +256 -93
- 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/generators/ruby_llm_agents/templates/add_tool_calls_migration.rb.tt +28 -0
- data/lib/generators/ruby_llm_agents/templates/migration.rb.tt +7 -0
- data/lib/generators/ruby_llm_agents/upgrade_generator.rb +13 -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 +19 -619
- data/lib/ruby_llm/agents/instrumentation.rb +36 -3
- data/lib/ruby_llm/agents/result.rb +235 -0
- data/lib/ruby_llm/agents/version.rb +1 -1
- data/lib/ruby_llm/agents.rb +1 -0
- metadata +15 -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
|
@@ -1,23 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
fill="none"
|
|
7
|
-
stroke="currentColor"
|
|
8
|
-
viewBox="0 0 24 24"
|
|
9
|
-
>
|
|
10
|
-
<path
|
|
11
|
-
stroke-linecap="round"
|
|
12
|
-
stroke-linejoin="round"
|
|
13
|
-
stroke-width="2"
|
|
14
|
-
d="M10 19l-7-7m0 0l7-7m-7 7h18"
|
|
15
|
-
/>
|
|
16
|
-
</svg>
|
|
17
|
-
Back to Agents
|
|
18
|
-
</span>
|
|
19
|
-
<% end %>
|
|
20
|
-
</div>
|
|
1
|
+
<%= render "rubyllm/agents/shared/breadcrumbs", items: [
|
|
2
|
+
{ label: "Dashboard", path: ruby_llm_agents.root_path },
|
|
3
|
+
{ label: "Agents", path: ruby_llm_agents.agents_path },
|
|
4
|
+
{ label: @agent_type.gsub(/Agent$/, '') }
|
|
5
|
+
] %>
|
|
21
6
|
|
|
22
7
|
<!-- Header -->
|
|
23
8
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6 mb-6">
|
|
@@ -49,7 +34,9 @@
|
|
|
49
34
|
<% end %>
|
|
50
35
|
|
|
51
36
|
<% if @config %>
|
|
52
|
-
<span class="text-sm text-gray-500 dark:text-gray-400">
|
|
37
|
+
<span class="text-sm text-gray-500 dark:text-gray-400">
|
|
38
|
+
v<%= @config[:version] %>
|
|
39
|
+
</span>
|
|
53
40
|
<% end %>
|
|
54
41
|
</div>
|
|
55
42
|
|
|
@@ -61,8 +48,29 @@
|
|
|
61
48
|
<% end %>
|
|
62
49
|
</div>
|
|
63
50
|
|
|
64
|
-
<div class="text-right
|
|
65
|
-
<p
|
|
51
|
+
<div class="text-right">
|
|
52
|
+
<p class="text-sm text-gray-500 dark:text-gray-400">
|
|
53
|
+
<%= number_with_delimiter(@stats[:count]) %> total executions
|
|
54
|
+
</p>
|
|
55
|
+
|
|
56
|
+
<div class="flex items-center justify-end gap-3 mt-1">
|
|
57
|
+
<% status_colors = {
|
|
58
|
+
"success" => "bg-green-500",
|
|
59
|
+
"error" => "bg-red-500",
|
|
60
|
+
"timeout" => "bg-yellow-500",
|
|
61
|
+
"running" => "bg-blue-500"
|
|
62
|
+
} %>
|
|
63
|
+
|
|
64
|
+
<% @status_distribution.each do |status, count| %>
|
|
65
|
+
<div class="flex items-center gap-1">
|
|
66
|
+
<span class="w-2 h-2 rounded-full <%= status_colors[status] || 'bg-gray-400' %> <%= status == 'running' ? 'animate-pulse' : '' %>"></span>
|
|
67
|
+
|
|
68
|
+
<span class="text-xs text-gray-600 dark:text-gray-400">
|
|
69
|
+
<%= number_with_delimiter(count) %>
|
|
70
|
+
</span>
|
|
71
|
+
</div>
|
|
72
|
+
<% end %>
|
|
73
|
+
</div>
|
|
66
74
|
</div>
|
|
67
75
|
</div>
|
|
68
76
|
</div>
|
|
@@ -71,8 +79,18 @@
|
|
|
71
79
|
<% if @config && @circuit_breaker_status.present? %>
|
|
72
80
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-4 mb-6">
|
|
73
81
|
<div class="flex items-center justify-between mb-3">
|
|
74
|
-
<h3
|
|
75
|
-
|
|
82
|
+
<h3
|
|
83
|
+
class="
|
|
84
|
+
text-sm font-medium text-gray-700 dark:text-gray-300 uppercase
|
|
85
|
+
tracking-wider
|
|
86
|
+
"
|
|
87
|
+
>
|
|
88
|
+
Circuit Breaker Status
|
|
89
|
+
</h3>
|
|
90
|
+
|
|
91
|
+
<span class="text-xs text-gray-400 dark:text-gray-500">
|
|
92
|
+
Auto-refreshes every 30s
|
|
93
|
+
</span>
|
|
76
94
|
</div>
|
|
77
95
|
|
|
78
96
|
<div class="flex flex-wrap gap-3">
|
|
@@ -81,11 +99,21 @@
|
|
|
81
99
|
<% if status[:open] %>
|
|
82
100
|
<!-- Open/Tripped indicator -->
|
|
83
101
|
<span class="relative flex h-3 w-3">
|
|
84
|
-
<span
|
|
102
|
+
<span
|
|
103
|
+
class="
|
|
104
|
+
animate-ping absolute inline-flex h-full w-full
|
|
105
|
+
rounded-full bg-red-400 opacity-75
|
|
106
|
+
"
|
|
107
|
+
></span>
|
|
108
|
+
|
|
85
109
|
<span class="relative inline-flex rounded-full h-3 w-3 bg-red-500"></span>
|
|
86
110
|
</span>
|
|
87
|
-
<span class="text-sm font-medium text-red-700 dark:text-red-300"
|
|
88
|
-
|
|
111
|
+
<span class="text-sm font-medium text-red-700 dark:text-red-300">
|
|
112
|
+
<%= model_id %>
|
|
113
|
+
</span>
|
|
114
|
+
<span class="text-xs text-red-500 dark:text-red-400 ml-1">
|
|
115
|
+
OPEN
|
|
116
|
+
</span>
|
|
89
117
|
<% if status[:cooldown_remaining] %>
|
|
90
118
|
<span class="text-xs text-red-400 dark:text-red-500 ml-1">
|
|
91
119
|
(resets in <%= status[:cooldown_remaining] %>s)
|
|
@@ -94,11 +122,16 @@
|
|
|
94
122
|
<% else %>
|
|
95
123
|
<!-- Closed/Healthy indicator -->
|
|
96
124
|
<span class="inline-flex rounded-full h-3 w-3 bg-green-500"></span>
|
|
97
|
-
<span class="text-sm font-medium text-green-700 dark:text-green-300"
|
|
98
|
-
|
|
125
|
+
<span class="text-sm font-medium text-green-700 dark:text-green-300">
|
|
126
|
+
<%= model_id %>
|
|
127
|
+
</span>
|
|
128
|
+
<span class="text-xs text-green-500 dark:text-green-400 ml-1">
|
|
129
|
+
OK
|
|
130
|
+
</span>
|
|
99
131
|
<% if status[:failure_count] && status[:failure_count] > 0 %>
|
|
100
132
|
<span class="text-xs text-yellow-500 dark:text-yellow-400 ml-1">
|
|
101
|
-
(<%= status[:failure_count] %>/<%= status[:threshold] %>
|
|
133
|
+
(<%= status[:failure_count] %>/<%= status[:threshold] %>
|
|
134
|
+
failures)
|
|
102
135
|
</span>
|
|
103
136
|
<% end %>
|
|
104
137
|
<% end %>
|
|
@@ -108,8 +141,8 @@
|
|
|
108
141
|
|
|
109
142
|
<% if @circuit_breaker_status.values.any? { |s| s[:open] } %>
|
|
110
143
|
<p class="mt-3 text-xs text-gray-500 dark:text-gray-400">
|
|
111
|
-
Open circuit breakers will skip the model and try fallbacks (if
|
|
112
|
-
They automatically reset after the cooldown period.
|
|
144
|
+
Open circuit breakers will skip the model and try fallbacks (if
|
|
145
|
+
configured). They automatically reset after the cooldown period.
|
|
113
146
|
</p>
|
|
114
147
|
<% end %>
|
|
115
148
|
</div>
|
|
@@ -118,6 +151,7 @@
|
|
|
118
151
|
<!-- Stats Grid -->
|
|
119
152
|
<% success_rate = @stats[:success_rate] || 0 %>
|
|
120
153
|
<% success_rate_color = success_rate >= 95 ? 'text-green-600' : success_rate >= 80 ? 'text-yellow-600' : 'text-red-600' %>
|
|
154
|
+
|
|
121
155
|
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4 mb-6">
|
|
122
156
|
<%= render "rubyllm/agents/shared/stat_card",
|
|
123
157
|
title: "Executions",
|
|
@@ -164,77 +198,60 @@
|
|
|
164
198
|
<!-- Charts Section -->
|
|
165
199
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
|
166
200
|
<!-- Executions Over Time -->
|
|
167
|
-
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6
|
|
201
|
+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6 overflow-hidden">
|
|
168
202
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
|
|
169
203
|
Executions (30 days)
|
|
170
204
|
</h3>
|
|
171
|
-
|
|
172
|
-
<div id="executions-chart" style="height: 250px;">
|
|
173
|
-
<% success_data = @trend_data.map { |d| [d[:date].strftime("%b %d"), d[:count] - (d[:error_count] || 0)] }.to_h
|
|
174
|
-
failed_data = @trend_data.map { |d| [d[:date].strftime("%b %d"), d[:error_count] || 0] }.to_h %>
|
|
175
|
-
|
|
176
|
-
<%= area_chart [
|
|
177
|
-
{ name: "Success", data: success_data },
|
|
178
|
-
{ name: "Failed", data: failed_data }
|
|
179
|
-
], colors: ["#10B981", "#EF4444"], stacked: true, library: {
|
|
180
|
-
yAxis: { min: 0 },
|
|
181
|
-
legend: { align: "center", verticalAlign: "bottom" },
|
|
182
|
-
plotOptions: { area: { stacking: "normal" } }
|
|
183
|
-
} %>
|
|
184
|
-
</div>
|
|
205
|
+
<div id="executions-chart" style="height: 220px;"></div>
|
|
185
206
|
</div>
|
|
186
207
|
|
|
187
208
|
<!-- Cost Over Time -->
|
|
188
|
-
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6
|
|
189
|
-
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
<%= line_chart cost_data, colors: ["#10B981"], prefix: "$", library: {
|
|
195
|
-
yAxis: { min: 0 },
|
|
196
|
-
legend: { enabled: false }
|
|
197
|
-
} %>
|
|
198
|
-
</div>
|
|
209
|
+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6 overflow-hidden">
|
|
210
|
+
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
|
|
211
|
+
Cost (30 days)
|
|
212
|
+
</h3>
|
|
213
|
+
<div id="cost-chart" style="height: 220px;"></div>
|
|
199
214
|
</div>
|
|
200
215
|
</div>
|
|
201
216
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
style="background-color: <%= status_colors[status] || '#6B7280' %>"
|
|
220
|
-
></span>
|
|
221
|
-
|
|
222
|
-
<span class="text-sm text-gray-700 dark:text-gray-300 capitalize"><%= status %></span>
|
|
217
|
+
<script>
|
|
218
|
+
(function() {
|
|
219
|
+
const trendData = <%= raw @trend_data.to_json %>;
|
|
220
|
+
const categories = trendData.map(d => d.date.split('-').slice(1).join('/'));
|
|
221
|
+
|
|
222
|
+
// Executions chart
|
|
223
|
+
Highcharts.chart('executions-chart', {
|
|
224
|
+
chart: { type: 'areaspline', backgroundColor: 'transparent' },
|
|
225
|
+
xAxis: { categories: categories, labels: { style: { color: '#9CA3AF' } } },
|
|
226
|
+
yAxis: { min: 0, title: { text: null }, labels: { style: { color: '#9CA3AF' } } },
|
|
227
|
+
legend: { enabled: false },
|
|
228
|
+
plotOptions: { areaspline: { fillOpacity: 0.2, marker: { enabled: false } } },
|
|
229
|
+
series: [
|
|
230
|
+
{ name: 'Success', color: '#10B981', data: trendData.map(d => d.count - (d.error_count || 0)) },
|
|
231
|
+
{ name: 'Failed', color: '#EF4444', data: trendData.map(d => d.error_count || 0) }
|
|
232
|
+
]
|
|
233
|
+
});
|
|
223
234
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
235
|
+
// Cost chart
|
|
236
|
+
Highcharts.chart('cost-chart', {
|
|
237
|
+
chart: { type: 'spline', backgroundColor: 'transparent' },
|
|
238
|
+
xAxis: { categories: categories, labels: { style: { color: '#9CA3AF' } } },
|
|
239
|
+
yAxis: { min: 0, title: { text: null }, labels: { style: { color: '#9CA3AF' }, format: '${value}' } },
|
|
240
|
+
legend: { enabled: false },
|
|
241
|
+
plotOptions: { spline: { marker: { enabled: false } } },
|
|
242
|
+
tooltip: { valuePrefix: '$' },
|
|
243
|
+
series: [{ name: 'Cost', color: '#10B981', data: trendData.map(d => parseFloat(d.total_cost) || 0) }]
|
|
244
|
+
});
|
|
245
|
+
})();
|
|
246
|
+
</script>
|
|
232
247
|
|
|
233
248
|
<!-- Finish Reason Distribution -->
|
|
234
249
|
<% if @finish_reason_distribution.present? && @finish_reason_distribution.any? %>
|
|
235
250
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-4 mb-6">
|
|
236
251
|
<div class="flex items-center justify-between">
|
|
237
|
-
<p class="text-sm text-gray-500 dark:text-gray-400 uppercase">
|
|
252
|
+
<p class="text-sm text-gray-500 dark:text-gray-400 uppercase">
|
|
253
|
+
Finish Reasons
|
|
254
|
+
</p>
|
|
238
255
|
|
|
239
256
|
<div class="flex flex-wrap gap-4">
|
|
240
257
|
<% finish_colors = {
|
|
@@ -252,7 +269,9 @@
|
|
|
252
269
|
style="background-color: <%= finish_colors[reason] || '#6B7280' %>"
|
|
253
270
|
></span>
|
|
254
271
|
|
|
255
|
-
<span class="text-sm text-gray-700 dark:text-gray-300"
|
|
272
|
+
<span class="text-sm text-gray-700 dark:text-gray-300">
|
|
273
|
+
<%= reason || 'unknown' %>
|
|
274
|
+
</span>
|
|
256
275
|
|
|
257
276
|
<span class="text-sm font-medium text-gray-900 dark:text-gray-100 ml-1">
|
|
258
277
|
(<%= number_with_delimiter(count) %>)
|
|
@@ -271,31 +290,48 @@
|
|
|
271
290
|
<% if @config %>
|
|
272
291
|
<!-- Configuration -->
|
|
273
292
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6 mb-6">
|
|
274
|
-
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
|
|
293
|
+
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
|
|
294
|
+
Configuration
|
|
295
|
+
</h3>
|
|
275
296
|
|
|
276
297
|
<!-- Basic Configuration -->
|
|
277
|
-
<p class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-3">
|
|
298
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-3">
|
|
299
|
+
Basic
|
|
300
|
+
</p>
|
|
301
|
+
|
|
278
302
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
|
|
279
303
|
<div>
|
|
280
304
|
<p class="text-sm text-gray-500 dark:text-gray-400">Model</p>
|
|
281
|
-
|
|
305
|
+
|
|
306
|
+
<p class="font-medium text-gray-900 dark:text-gray-100">
|
|
307
|
+
<%= @config[:model] %>
|
|
308
|
+
</p>
|
|
282
309
|
</div>
|
|
283
310
|
|
|
284
311
|
<div>
|
|
285
312
|
<p class="text-sm text-gray-500 dark:text-gray-400">Temperature</p>
|
|
286
|
-
|
|
313
|
+
|
|
314
|
+
<p class="font-medium text-gray-900 dark:text-gray-100">
|
|
315
|
+
<%= @config[:temperature] %>
|
|
316
|
+
</p>
|
|
287
317
|
</div>
|
|
288
318
|
|
|
289
319
|
<div>
|
|
290
320
|
<p class="text-sm text-gray-500 dark:text-gray-400">Timeout</p>
|
|
291
|
-
|
|
321
|
+
|
|
322
|
+
<p class="font-medium text-gray-900 dark:text-gray-100">
|
|
323
|
+
<%= @config[:timeout] %> seconds
|
|
324
|
+
</p>
|
|
292
325
|
</div>
|
|
293
326
|
|
|
294
327
|
<div>
|
|
295
328
|
<p class="text-sm text-gray-500 dark:text-gray-400">Cache</p>
|
|
329
|
+
|
|
296
330
|
<p class="font-medium text-gray-900 dark:text-gray-100">
|
|
297
331
|
<% if @config[:cache_enabled] %>
|
|
298
|
-
Enabled (
|
|
332
|
+
Enabled (
|
|
333
|
+
<%= @config[:cache_ttl].inspect %>
|
|
334
|
+
)
|
|
299
335
|
<% else %>
|
|
300
336
|
<span class="text-gray-400 dark:text-gray-500">Disabled</span>
|
|
301
337
|
<% end %>
|
|
@@ -304,16 +340,22 @@
|
|
|
304
340
|
</div>
|
|
305
341
|
|
|
306
342
|
<!-- Reliability Configuration -->
|
|
307
|
-
<%
|
|
308
|
-
retries_config = @config[:retries] || {}
|
|
343
|
+
<% retries_config = @config[:retries] || {}
|
|
309
344
|
has_retries = (retries_config[:max] || 0) > 0
|
|
310
345
|
has_fallbacks = @config[:fallback_models].present? && @config[:fallback_models].any?
|
|
311
346
|
has_total_timeout = @config[:total_timeout].present?
|
|
312
347
|
has_circuit_breaker = @config[:circuit_breaker].present?
|
|
313
|
-
has_any_reliability = has_retries || has_fallbacks || has_total_timeout || has_circuit_breaker
|
|
314
|
-
|
|
348
|
+
has_any_reliability = has_retries || has_fallbacks || has_total_timeout || has_circuit_breaker %>
|
|
349
|
+
|
|
315
350
|
<div class="border-t border-gray-100 dark:border-gray-700 pt-4 mb-6">
|
|
316
|
-
<p
|
|
351
|
+
<p
|
|
352
|
+
class="
|
|
353
|
+
text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wider
|
|
354
|
+
mb-3
|
|
355
|
+
"
|
|
356
|
+
>
|
|
357
|
+
Reliability
|
|
358
|
+
</p>
|
|
317
359
|
|
|
318
360
|
<% if has_any_reliability %>
|
|
319
361
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
@@ -321,26 +363,48 @@
|
|
|
321
363
|
<div class="flex items-start gap-3 p-3 rounded-lg <%= has_retries ? 'bg-green-50 dark:bg-green-900/20' : 'bg-gray-50 dark:bg-gray-700/50' %>">
|
|
322
364
|
<div class="flex-shrink-0 mt-0.5">
|
|
323
365
|
<% if has_retries %>
|
|
324
|
-
<svg
|
|
325
|
-
|
|
366
|
+
<svg
|
|
367
|
+
class="w-5 h-5 text-green-500"
|
|
368
|
+
fill="currentColor"
|
|
369
|
+
viewBox="0 0 20 20"
|
|
370
|
+
>
|
|
371
|
+
<path
|
|
372
|
+
fill-rule="evenodd"
|
|
373
|
+
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
|
374
|
+
clip-rule="evenodd"
|
|
375
|
+
/>
|
|
326
376
|
</svg>
|
|
327
377
|
<% else %>
|
|
328
|
-
<svg
|
|
329
|
-
|
|
378
|
+
<svg
|
|
379
|
+
class="w-5 h-5 text-gray-400"
|
|
380
|
+
fill="currentColor"
|
|
381
|
+
viewBox="0 0 20 20"
|
|
382
|
+
>
|
|
383
|
+
<path
|
|
384
|
+
fill-rule="evenodd"
|
|
385
|
+
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
|
|
386
|
+
clip-rule="evenodd"
|
|
387
|
+
/>
|
|
330
388
|
</svg>
|
|
331
389
|
<% end %>
|
|
332
390
|
</div>
|
|
391
|
+
|
|
333
392
|
<div>
|
|
334
|
-
<p class="text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
393
|
+
<p class="text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
394
|
+
Retries
|
|
395
|
+
</p>
|
|
396
|
+
|
|
335
397
|
<% if has_retries %>
|
|
336
398
|
<p class="text-xs text-gray-600 dark:text-gray-400 mt-1">
|
|
337
|
-
Max: <%= retries_config[:max] %> ·
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
399
|
+
Max: <%= retries_config[:max] %> · Backoff:
|
|
400
|
+
<%= retries_config[:backoff] %> · Base:
|
|
401
|
+
<%= retries_config[:base] %>s · Max delay:
|
|
402
|
+
<%= retries_config[:max_delay] %>s
|
|
341
403
|
</p>
|
|
342
404
|
<% else %>
|
|
343
|
-
<p class="text-xs text-gray-400 dark:text-gray-500 mt-1">
|
|
405
|
+
<p class="text-xs text-gray-400 dark:text-gray-500 mt-1">
|
|
406
|
+
Not configured
|
|
407
|
+
</p>
|
|
344
408
|
<% end %>
|
|
345
409
|
</div>
|
|
346
410
|
</div>
|
|
@@ -349,23 +413,45 @@
|
|
|
349
413
|
<div class="flex items-start gap-3 p-3 rounded-lg <%= has_fallbacks ? 'bg-green-50 dark:bg-green-900/20' : 'bg-gray-50 dark:bg-gray-700/50' %>">
|
|
350
414
|
<div class="flex-shrink-0 mt-0.5">
|
|
351
415
|
<% if has_fallbacks %>
|
|
352
|
-
<svg
|
|
353
|
-
|
|
416
|
+
<svg
|
|
417
|
+
class="w-5 h-5 text-green-500"
|
|
418
|
+
fill="currentColor"
|
|
419
|
+
viewBox="0 0 20 20"
|
|
420
|
+
>
|
|
421
|
+
<path
|
|
422
|
+
fill-rule="evenodd"
|
|
423
|
+
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
|
424
|
+
clip-rule="evenodd"
|
|
425
|
+
/>
|
|
354
426
|
</svg>
|
|
355
427
|
<% else %>
|
|
356
|
-
<svg
|
|
357
|
-
|
|
428
|
+
<svg
|
|
429
|
+
class="w-5 h-5 text-gray-400"
|
|
430
|
+
fill="currentColor"
|
|
431
|
+
viewBox="0 0 20 20"
|
|
432
|
+
>
|
|
433
|
+
<path
|
|
434
|
+
fill-rule="evenodd"
|
|
435
|
+
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
|
|
436
|
+
clip-rule="evenodd"
|
|
437
|
+
/>
|
|
358
438
|
</svg>
|
|
359
439
|
<% end %>
|
|
360
440
|
</div>
|
|
441
|
+
|
|
361
442
|
<div>
|
|
362
|
-
<p class="text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
443
|
+
<p class="text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
444
|
+
Fallback Models
|
|
445
|
+
</p>
|
|
446
|
+
|
|
363
447
|
<% if has_fallbacks %>
|
|
364
448
|
<p class="text-xs text-gray-600 dark:text-gray-400 mt-1">
|
|
365
449
|
<%= @config[:fallback_models].join(" → ") %>
|
|
366
450
|
</p>
|
|
367
451
|
<% else %>
|
|
368
|
-
<p class="text-xs text-gray-400 dark:text-gray-500 mt-1">
|
|
452
|
+
<p class="text-xs text-gray-400 dark:text-gray-500 mt-1">
|
|
453
|
+
Not configured
|
|
454
|
+
</p>
|
|
369
455
|
<% end %>
|
|
370
456
|
</div>
|
|
371
457
|
</div>
|
|
@@ -374,23 +460,45 @@
|
|
|
374
460
|
<div class="flex items-start gap-3 p-3 rounded-lg <%= has_total_timeout ? 'bg-green-50 dark:bg-green-900/20' : 'bg-gray-50 dark:bg-gray-700/50' %>">
|
|
375
461
|
<div class="flex-shrink-0 mt-0.5">
|
|
376
462
|
<% if has_total_timeout %>
|
|
377
|
-
<svg
|
|
378
|
-
|
|
463
|
+
<svg
|
|
464
|
+
class="w-5 h-5 text-green-500"
|
|
465
|
+
fill="currentColor"
|
|
466
|
+
viewBox="0 0 20 20"
|
|
467
|
+
>
|
|
468
|
+
<path
|
|
469
|
+
fill-rule="evenodd"
|
|
470
|
+
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
|
471
|
+
clip-rule="evenodd"
|
|
472
|
+
/>
|
|
379
473
|
</svg>
|
|
380
474
|
<% else %>
|
|
381
|
-
<svg
|
|
382
|
-
|
|
475
|
+
<svg
|
|
476
|
+
class="w-5 h-5 text-gray-400"
|
|
477
|
+
fill="currentColor"
|
|
478
|
+
viewBox="0 0 20 20"
|
|
479
|
+
>
|
|
480
|
+
<path
|
|
481
|
+
fill-rule="evenodd"
|
|
482
|
+
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
|
|
483
|
+
clip-rule="evenodd"
|
|
484
|
+
/>
|
|
383
485
|
</svg>
|
|
384
486
|
<% end %>
|
|
385
487
|
</div>
|
|
488
|
+
|
|
386
489
|
<div>
|
|
387
|
-
<p class="text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
490
|
+
<p class="text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
491
|
+
Total Timeout
|
|
492
|
+
</p>
|
|
493
|
+
|
|
388
494
|
<% if has_total_timeout %>
|
|
389
495
|
<p class="text-xs text-gray-600 dark:text-gray-400 mt-1">
|
|
390
496
|
<%= @config[:total_timeout] %> seconds across all attempts
|
|
391
497
|
</p>
|
|
392
498
|
<% else %>
|
|
393
|
-
<p class="text-xs text-gray-400 dark:text-gray-500 mt-1">
|
|
499
|
+
<p class="text-xs text-gray-400 dark:text-gray-500 mt-1">
|
|
500
|
+
Not configured
|
|
501
|
+
</p>
|
|
394
502
|
<% end %>
|
|
395
503
|
</div>
|
|
396
504
|
</div>
|
|
@@ -399,43 +507,79 @@
|
|
|
399
507
|
<div class="flex items-start gap-3 p-3 rounded-lg <%= has_circuit_breaker ? 'bg-green-50 dark:bg-green-900/20' : 'bg-gray-50 dark:bg-gray-700/50' %>">
|
|
400
508
|
<div class="flex-shrink-0 mt-0.5">
|
|
401
509
|
<% if has_circuit_breaker %>
|
|
402
|
-
<svg
|
|
403
|
-
|
|
510
|
+
<svg
|
|
511
|
+
class="w-5 h-5 text-green-500"
|
|
512
|
+
fill="currentColor"
|
|
513
|
+
viewBox="0 0 20 20"
|
|
514
|
+
>
|
|
515
|
+
<path
|
|
516
|
+
fill-rule="evenodd"
|
|
517
|
+
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
|
518
|
+
clip-rule="evenodd"
|
|
519
|
+
/>
|
|
404
520
|
</svg>
|
|
405
521
|
<% else %>
|
|
406
|
-
<svg
|
|
407
|
-
|
|
522
|
+
<svg
|
|
523
|
+
class="w-5 h-5 text-gray-400"
|
|
524
|
+
fill="currentColor"
|
|
525
|
+
viewBox="0 0 20 20"
|
|
526
|
+
>
|
|
527
|
+
<path
|
|
528
|
+
fill-rule="evenodd"
|
|
529
|
+
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
|
|
530
|
+
clip-rule="evenodd"
|
|
531
|
+
/>
|
|
408
532
|
</svg>
|
|
409
533
|
<% end %>
|
|
410
534
|
</div>
|
|
535
|
+
|
|
411
536
|
<div>
|
|
412
|
-
<p class="text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
537
|
+
<p class="text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
538
|
+
Circuit Breaker
|
|
539
|
+
</p>
|
|
540
|
+
|
|
413
541
|
<% if has_circuit_breaker %>
|
|
414
542
|
<% cb = @config[:circuit_breaker] %>
|
|
415
543
|
<p class="text-xs text-gray-600 dark:text-gray-400 mt-1">
|
|
416
|
-
Opens after <%= cb[:errors] %> errors within
|
|
417
|
-
Cooldown: <%= cb[:cooldown] %>s
|
|
544
|
+
Opens after <%= cb[:errors] %> errors within
|
|
545
|
+
<%= cb[:within] %>s · Cooldown: <%= cb[:cooldown] %>s
|
|
418
546
|
</p>
|
|
419
547
|
<% else %>
|
|
420
|
-
<p class="text-xs text-gray-400 dark:text-gray-500 mt-1">
|
|
548
|
+
<p class="text-xs text-gray-400 dark:text-gray-500 mt-1">
|
|
549
|
+
Not configured
|
|
550
|
+
</p>
|
|
421
551
|
<% end %>
|
|
422
552
|
</div>
|
|
423
553
|
</div>
|
|
424
554
|
</div>
|
|
425
555
|
<% else %>
|
|
426
|
-
<p class="text-sm text-gray-400 dark:text-gray-500">
|
|
556
|
+
<p class="text-sm text-gray-400 dark:text-gray-500">
|
|
557
|
+
No reliability features configured
|
|
558
|
+
</p>
|
|
427
559
|
<% end %>
|
|
428
560
|
</div>
|
|
429
561
|
|
|
430
562
|
<!-- Parameters -->
|
|
431
563
|
<% if @config[:params].present? && @config[:params].any? %>
|
|
432
564
|
<div class="border-t border-gray-100 dark:border-gray-700 pt-4">
|
|
433
|
-
<p
|
|
565
|
+
<p
|
|
566
|
+
class="
|
|
567
|
+
text-xs text-gray-500 dark:text-gray-400 uppercase
|
|
568
|
+
tracking-wider mb-3
|
|
569
|
+
"
|
|
570
|
+
>
|
|
571
|
+
Parameters
|
|
572
|
+
</p>
|
|
434
573
|
|
|
435
574
|
<div class="space-y-2">
|
|
436
575
|
<% @config[:params].each do |name, opts| %>
|
|
437
576
|
<div class="flex items-center text-sm">
|
|
438
|
-
<code
|
|
577
|
+
<code
|
|
578
|
+
class="
|
|
579
|
+
bg-gray-100 dark:bg-gray-700 dark:text-gray-200 px-2
|
|
580
|
+
py-0.5 rounded font-mono
|
|
581
|
+
"
|
|
582
|
+
>
|
|
439
583
|
<%= name %>
|
|
440
584
|
</code>
|
|
441
585
|
|
|
@@ -448,7 +592,9 @@
|
|
|
448
592
|
default: <%= opts[:default].inspect %>
|
|
449
593
|
</span>
|
|
450
594
|
<% else %>
|
|
451
|
-
<span class="ml-2 text-xs text-gray-400 dark:text-gray-500">
|
|
595
|
+
<span class="ml-2 text-xs text-gray-400 dark:text-gray-500">
|
|
596
|
+
optional
|
|
597
|
+
</span>
|
|
452
598
|
<% end %>
|
|
453
599
|
</div>
|
|
454
600
|
<% end %>
|
|
@@ -460,7 +606,9 @@
|
|
|
460
606
|
|
|
461
607
|
<!-- Executions -->
|
|
462
608
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
|
463
|
-
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
|
|
609
|
+
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
|
|
610
|
+
Executions
|
|
611
|
+
</h3>
|
|
464
612
|
|
|
465
613
|
<%= turbo_frame_tag "executions_table" do %>
|
|
466
614
|
<%
|
|
@@ -469,217 +617,84 @@
|
|
|
469
617
|
selected_versions = params[:versions].present? ? (params[:versions].is_a?(Array) ? params[:versions] : params[:versions].split(",")) : []
|
|
470
618
|
selected_models = params[:models].present? ? (params[:models].is_a?(Array) ? params[:models] : params[:models].split(",")) : []
|
|
471
619
|
selected_temperatures = params[:temperatures].present? ? (params[:temperatures].is_a?(Array) ? params[:temperatures] : params[:temperatures].split(",")).map(&:to_s) : []
|
|
620
|
+
|
|
621
|
+
status_options = [
|
|
622
|
+
{ value: "success", label: "Success", color: "bg-green-500" },
|
|
623
|
+
{ value: "error", label: "Error", color: "bg-red-500" },
|
|
624
|
+
{ value: "running", label: "Running", color: "bg-blue-500" },
|
|
625
|
+
{ value: "timeout", label: "Timeout", color: "bg-yellow-500" }
|
|
626
|
+
]
|
|
627
|
+
version_options = @versions.map { |v| { value: v.to_s, label: "v#{v}" } }
|
|
628
|
+
model_options = @models.map { |m| { value: m, label: m } }
|
|
629
|
+
temperature_options = @temperatures.map { |t| { value: t.to_s, label: t.to_s } }
|
|
630
|
+
days_options = [
|
|
631
|
+
{ value: "", label: "All Time" },
|
|
632
|
+
{ value: "1", label: "Today" },
|
|
633
|
+
{ value: "7", label: "Last 7 Days" },
|
|
634
|
+
{ value: "30", label: "Last 30 Days" }
|
|
635
|
+
]
|
|
472
636
|
%>
|
|
473
637
|
|
|
474
|
-
<%= form_with url: ruby_llm_agents.agent_path(@agent_type), method: :get, data: { turbo_frame: "executions_table" }
|
|
638
|
+
<%= form_with url: ruby_llm_agents.agent_path(@agent_type), method: :get, data: { turbo_frame: "executions_table" } do |f| %>
|
|
475
639
|
<div class="flex flex-wrap items-center gap-3 mb-4 pb-4 border-b border-gray-100 dark:border-gray-700">
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
end
|
|
487
|
-
else
|
|
488
|
-
status_color = 'bg-gray-400'
|
|
489
|
-
end %>
|
|
490
|
-
<span class="w-2 h-2 rounded-full <%= status_color %>"></span>
|
|
491
|
-
<span class="dropdown-label text-gray-700 dark:text-gray-200">
|
|
492
|
-
<% if selected_statuses.empty? %>
|
|
493
|
-
All Statuses
|
|
494
|
-
<% elsif selected_statuses.length == 1 %>
|
|
495
|
-
<%= selected_statuses.first.capitalize %>
|
|
496
|
-
<% else %>
|
|
497
|
-
<%= selected_statuses.length %> Statuses
|
|
498
|
-
<% end %>
|
|
499
|
-
</span>
|
|
500
|
-
<svg class="w-4 h-4 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
501
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
|
502
|
-
</svg>
|
|
503
|
-
</button>
|
|
504
|
-
<div class="dropdown-menu hidden absolute z-10 mt-1 w-44 bg-white dark:bg-gray-700 rounded-lg shadow-lg border border-gray-200 dark:border-gray-600 py-1">
|
|
505
|
-
<div class="px-3 py-2 border-b border-gray-100 dark:border-gray-600">
|
|
506
|
-
<label class="flex items-center gap-2 cursor-pointer">
|
|
507
|
-
<input type="checkbox" class="select-all-checkbox rounded border-gray-300 dark:border-gray-500 text-blue-600 focus:ring-blue-500 dark:bg-gray-600" onchange="toggleAllOptions(this, 'statuses')" <%= selected_statuses.empty? ? 'checked' : '' %>>
|
|
508
|
-
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">All Statuses</span>
|
|
509
|
-
</label>
|
|
510
|
-
</div>
|
|
511
|
-
<label class="flex items-center gap-2 px-3 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-600 cursor-pointer">
|
|
512
|
-
<input type="checkbox" name="statuses[]" value="success" class="filter-checkbox rounded border-gray-300 dark:border-gray-500 text-blue-600 focus:ring-blue-500 dark:bg-gray-600" onchange="updateMultiSelect('statuses')" <%= selected_statuses.include?('success') ? 'checked' : '' %>>
|
|
513
|
-
<span class="w-2 h-2 rounded-full bg-green-500"></span>
|
|
514
|
-
Success
|
|
515
|
-
</label>
|
|
516
|
-
<label class="flex items-center gap-2 px-3 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-600 cursor-pointer">
|
|
517
|
-
<input type="checkbox" name="statuses[]" value="error" class="filter-checkbox rounded border-gray-300 dark:border-gray-500 text-blue-600 focus:ring-blue-500 dark:bg-gray-600" onchange="updateMultiSelect('statuses')" <%= selected_statuses.include?('error') ? 'checked' : '' %>>
|
|
518
|
-
<span class="w-2 h-2 rounded-full bg-red-500"></span>
|
|
519
|
-
Error
|
|
520
|
-
</label>
|
|
521
|
-
<label class="flex items-center gap-2 px-3 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-600 cursor-pointer">
|
|
522
|
-
<input type="checkbox" name="statuses[]" value="running" class="filter-checkbox rounded border-gray-300 dark:border-gray-500 text-blue-600 focus:ring-blue-500 dark:bg-gray-600" onchange="updateMultiSelect('statuses')" <%= selected_statuses.include?('running') ? 'checked' : '' %>>
|
|
523
|
-
<span class="w-2 h-2 rounded-full bg-blue-500 animate-pulse"></span>
|
|
524
|
-
Running
|
|
525
|
-
</label>
|
|
526
|
-
<label class="flex items-center gap-2 px-3 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-600 cursor-pointer">
|
|
527
|
-
<input type="checkbox" name="statuses[]" value="timeout" class="filter-checkbox rounded border-gray-300 dark:border-gray-500 text-blue-600 focus:ring-blue-500 dark:bg-gray-600" onchange="updateMultiSelect('statuses')" <%= selected_statuses.include?('timeout') ? 'checked' : '' %>>
|
|
528
|
-
<span class="w-2 h-2 rounded-full bg-yellow-500"></span>
|
|
529
|
-
Timeout
|
|
530
|
-
</label>
|
|
531
|
-
</div>
|
|
532
|
-
</div>
|
|
533
|
-
|
|
534
|
-
<!-- Version Filter (Multi-select) -->
|
|
640
|
+
<%# Status Filter (Multi-select) %>
|
|
641
|
+
<%= render "rubyllm/agents/shared/filter_dropdown",
|
|
642
|
+
name: "statuses[]",
|
|
643
|
+
filter_id: "statuses",
|
|
644
|
+
label: "Status",
|
|
645
|
+
all_label: "All Statuses",
|
|
646
|
+
options: status_options,
|
|
647
|
+
selected: selected_statuses %>
|
|
648
|
+
|
|
649
|
+
<%# Version Filter (Multi-select) %>
|
|
535
650
|
<% if @versions.any? %>
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
<% elsif selected_versions.length == 1 %>
|
|
545
|
-
v<%= selected_versions.first %>
|
|
546
|
-
<% else %>
|
|
547
|
-
<%= selected_versions.length %> Versions
|
|
548
|
-
<% end %>
|
|
549
|
-
</span>
|
|
550
|
-
<svg class="w-4 h-4 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
551
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
|
552
|
-
</svg>
|
|
553
|
-
</button>
|
|
554
|
-
<div class="dropdown-menu hidden absolute z-10 mt-1 w-44 bg-white dark:bg-gray-700 rounded-lg shadow-lg border border-gray-200 dark:border-gray-600 py-1 max-h-64 overflow-y-auto">
|
|
555
|
-
<div class="px-3 py-2 border-b border-gray-100 dark:border-gray-600">
|
|
556
|
-
<label class="flex items-center gap-2 cursor-pointer">
|
|
557
|
-
<input type="checkbox" class="select-all-checkbox rounded border-gray-300 dark:border-gray-500 text-blue-600 focus:ring-blue-500 dark:bg-gray-600" onchange="toggleAllOptions(this, 'versions')" <%= selected_versions.empty? ? 'checked' : '' %>>
|
|
558
|
-
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">All Versions</span>
|
|
559
|
-
</label>
|
|
560
|
-
</div>
|
|
561
|
-
<% @versions.each do |version| %>
|
|
562
|
-
<label class="flex items-center gap-2 px-3 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-600 cursor-pointer">
|
|
563
|
-
<input type="checkbox" name="versions[]" value="<%= version %>" class="filter-checkbox rounded border-gray-300 dark:border-gray-500 text-blue-600 focus:ring-blue-500 dark:bg-gray-600" onchange="updateMultiSelect('versions')" <%= selected_versions.include?(version.to_s) ? 'checked' : '' %>>
|
|
564
|
-
v<%= version %>
|
|
565
|
-
</label>
|
|
566
|
-
<% end %>
|
|
567
|
-
</div>
|
|
568
|
-
</div>
|
|
651
|
+
<%= render "rubyllm/agents/shared/filter_dropdown",
|
|
652
|
+
name: "versions[]",
|
|
653
|
+
filter_id: "versions",
|
|
654
|
+
label: "Version",
|
|
655
|
+
all_label: "All Versions",
|
|
656
|
+
options: version_options,
|
|
657
|
+
selected: selected_versions,
|
|
658
|
+
icon: "M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" %>
|
|
569
659
|
<% end %>
|
|
570
660
|
|
|
571
|
-
|
|
661
|
+
<%# Model Filter (Multi-select) %>
|
|
572
662
|
<% if @models.length > 1 %>
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
<% elsif selected_models.length == 1 %>
|
|
582
|
-
<%= selected_models.first %>
|
|
583
|
-
<% else %>
|
|
584
|
-
<%= selected_models.length %> Models
|
|
585
|
-
<% end %>
|
|
586
|
-
</span>
|
|
587
|
-
<svg class="w-4 h-4 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
588
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
|
589
|
-
</svg>
|
|
590
|
-
</button>
|
|
591
|
-
<div class="dropdown-menu hidden absolute z-10 mt-1 w-56 bg-white dark:bg-gray-700 rounded-lg shadow-lg border border-gray-200 dark:border-gray-600 py-1 max-h-64 overflow-y-auto">
|
|
592
|
-
<div class="px-3 py-2 border-b border-gray-100 dark:border-gray-600">
|
|
593
|
-
<label class="flex items-center gap-2 cursor-pointer">
|
|
594
|
-
<input type="checkbox" class="select-all-checkbox rounded border-gray-300 dark:border-gray-500 text-blue-600 focus:ring-blue-500 dark:bg-gray-600" onchange="toggleAllOptions(this, 'models')" <%= selected_models.empty? ? 'checked' : '' %>>
|
|
595
|
-
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">All Models</span>
|
|
596
|
-
</label>
|
|
597
|
-
</div>
|
|
598
|
-
<% @models.each do |model| %>
|
|
599
|
-
<label class="flex items-center gap-2 px-3 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-600 cursor-pointer">
|
|
600
|
-
<input type="checkbox" name="models[]" value="<%= model %>" class="filter-checkbox rounded border-gray-300 dark:border-gray-500 text-blue-600 focus:ring-blue-500 dark:bg-gray-600" onchange="updateMultiSelect('models')" <%= selected_models.include?(model) ? 'checked' : '' %>>
|
|
601
|
-
<%= model %>
|
|
602
|
-
</label>
|
|
603
|
-
<% end %>
|
|
604
|
-
</div>
|
|
605
|
-
</div>
|
|
663
|
+
<%= render "rubyllm/agents/shared/filter_dropdown",
|
|
664
|
+
name: "models[]",
|
|
665
|
+
filter_id: "models",
|
|
666
|
+
label: "Model",
|
|
667
|
+
all_label: "All Models",
|
|
668
|
+
options: model_options,
|
|
669
|
+
selected: selected_models,
|
|
670
|
+
icon: "M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" %>
|
|
606
671
|
<% end %>
|
|
607
672
|
|
|
608
|
-
|
|
673
|
+
<%# Temperature Filter (Multi-select) %>
|
|
609
674
|
<% if @temperatures.length > 1 %>
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
<% elsif selected_temperatures.length == 1 %>
|
|
619
|
-
<%= selected_temperatures.first %>
|
|
620
|
-
<% else %>
|
|
621
|
-
<%= selected_temperatures.length %> Temps
|
|
622
|
-
<% end %>
|
|
623
|
-
</span>
|
|
624
|
-
<svg class="w-4 h-4 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
625
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
|
626
|
-
</svg>
|
|
627
|
-
</button>
|
|
628
|
-
<div class="dropdown-menu hidden absolute z-10 mt-1 w-36 bg-white dark:bg-gray-700 rounded-lg shadow-lg border border-gray-200 dark:border-gray-600 py-1 max-h-64 overflow-y-auto">
|
|
629
|
-
<div class="px-3 py-2 border-b border-gray-100 dark:border-gray-600">
|
|
630
|
-
<label class="flex items-center gap-2 cursor-pointer">
|
|
631
|
-
<input type="checkbox" class="select-all-checkbox rounded border-gray-300 dark:border-gray-500 text-blue-600 focus:ring-blue-500 dark:bg-gray-600" onchange="toggleAllOptions(this, 'temperatures')" <%= selected_temperatures.empty? ? 'checked' : '' %>>
|
|
632
|
-
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">All Temps</span>
|
|
633
|
-
</label>
|
|
634
|
-
</div>
|
|
635
|
-
<% @temperatures.each do |temp| %>
|
|
636
|
-
<label class="flex items-center gap-2 px-3 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-600 cursor-pointer">
|
|
637
|
-
<input type="checkbox" name="temperatures[]" value="<%= temp %>" class="filter-checkbox rounded border-gray-300 dark:border-gray-500 text-blue-600 focus:ring-blue-500 dark:bg-gray-600" onchange="updateMultiSelect('temperatures')" <%= selected_temperatures.include?(temp.to_s) ? 'checked' : '' %>>
|
|
638
|
-
<%= temp %>
|
|
639
|
-
</label>
|
|
640
|
-
<% end %>
|
|
641
|
-
</div>
|
|
642
|
-
</div>
|
|
675
|
+
<%= render "rubyllm/agents/shared/filter_dropdown",
|
|
676
|
+
name: "temperatures[]",
|
|
677
|
+
filter_id: "temperatures",
|
|
678
|
+
label: "Temp",
|
|
679
|
+
all_label: "All Temps",
|
|
680
|
+
options: temperature_options,
|
|
681
|
+
selected: selected_temperatures,
|
|
682
|
+
icon: "M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" %>
|
|
643
683
|
<% end %>
|
|
644
684
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
<% case params[:days]
|
|
653
|
-
when '1' then %>Today<%
|
|
654
|
-
when '7' then %>Last 7 Days<%
|
|
655
|
-
when '30' then %>Last 30 Days<%
|
|
656
|
-
else %>All Time<%
|
|
657
|
-
end %>
|
|
658
|
-
</span>
|
|
659
|
-
<svg class="w-4 h-4 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
660
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
|
661
|
-
</svg>
|
|
662
|
-
</button>
|
|
663
|
-
<div class="dropdown-menu hidden absolute z-10 mt-1 w-40 bg-white dark:bg-gray-700 rounded-lg shadow-lg border border-gray-200 dark:border-gray-600 py-1">
|
|
664
|
-
<a href="#" onclick="selectSingleFilter('days', '', 'All Time'); return false;" class="flex items-center gap-2 px-3 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-600 <%= params[:days].blank? ? 'bg-blue-50 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300' : '' %>">
|
|
665
|
-
All Time
|
|
666
|
-
</a>
|
|
667
|
-
<a href="#" onclick="selectSingleFilter('days', '1', 'Today'); return false;" class="flex items-center gap-2 px-3 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-600 <%= params[:days] == '1' ? 'bg-blue-50 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300' : '' %>">
|
|
668
|
-
Today
|
|
669
|
-
</a>
|
|
670
|
-
<a href="#" onclick="selectSingleFilter('days', '7', 'Last 7 Days'); return false;" class="flex items-center gap-2 px-3 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-600 <%= params[:days] == '7' ? 'bg-blue-50 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300' : '' %>">
|
|
671
|
-
Last 7 Days
|
|
672
|
-
</a>
|
|
673
|
-
<a href="#" onclick="selectSingleFilter('days', '30', 'Last 30 Days'); return false;" class="flex items-center gap-2 px-3 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-600 <%= params[:days] == '30' ? 'bg-blue-50 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300' : '' %>">
|
|
674
|
-
Last 30 Days
|
|
675
|
-
</a>
|
|
676
|
-
</div>
|
|
677
|
-
<%= f.hidden_field :days, value: params[:days], id: "filter_days" %>
|
|
678
|
-
</div>
|
|
685
|
+
<%# Time Range Filter (Single-select) %>
|
|
686
|
+
<%= render "rubyllm/agents/shared/select_dropdown",
|
|
687
|
+
name: "days",
|
|
688
|
+
filter_id: "days",
|
|
689
|
+
options: days_options,
|
|
690
|
+
selected: params[:days].to_s,
|
|
691
|
+
icon: "M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" %>
|
|
679
692
|
|
|
680
|
-
|
|
693
|
+
<%# Clear Filters %>
|
|
681
694
|
<% if has_filters %>
|
|
682
|
-
<%= link_to ruby_llm_agents.agent_path(@agent_type),
|
|
695
|
+
<%= link_to ruby_llm_agents.agent_path(@agent_type),
|
|
696
|
+
data: { turbo_frame: "executions_table" },
|
|
697
|
+
class: "flex items-center gap-1 px-3 py-2 text-sm text-red-500 dark:text-red-400 hover:text-red-600 dark:hover:text-red-300 hover:bg-red-50 dark:hover:bg-red-900/30 rounded-lg transition-colors" do %>
|
|
683
698
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
684
699
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
|
685
700
|
</svg>
|
|
@@ -687,7 +702,7 @@
|
|
|
687
702
|
<% end %>
|
|
688
703
|
<% end %>
|
|
689
704
|
|
|
690
|
-
|
|
705
|
+
<%# Stats Summary (right aligned) %>
|
|
691
706
|
<div class="ml-auto flex items-center gap-4 text-sm text-gray-500 dark:text-gray-400">
|
|
692
707
|
<span><%= number_with_delimiter(@filter_stats[:total_count]) %> executions</span>
|
|
693
708
|
<span class="text-gray-300 dark:text-gray-600">|</span>
|
|
@@ -701,85 +716,3 @@
|
|
|
701
716
|
<%= render "rubyllm/agents/shared/executions_table", executions: @executions, pagination: @pagination %>
|
|
702
717
|
<% end %>
|
|
703
718
|
</div>
|
|
704
|
-
|
|
705
|
-
<script>
|
|
706
|
-
function toggleDropdown(button) {
|
|
707
|
-
document.querySelectorAll('.filter-dropdown .dropdown-menu').forEach(menu => {
|
|
708
|
-
if (menu !== button.nextElementSibling) {
|
|
709
|
-
menu.classList.add('hidden');
|
|
710
|
-
}
|
|
711
|
-
});
|
|
712
|
-
button.nextElementSibling.classList.toggle('hidden');
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
function selectSingleFilter(name, value, label) {
|
|
716
|
-
document.getElementById('filter_' + name).value = value;
|
|
717
|
-
const dropdown = document.querySelector(`[data-filter="${name}"]`);
|
|
718
|
-
dropdown.querySelector('.dropdown-label').textContent = label;
|
|
719
|
-
const button = dropdown.querySelector('button');
|
|
720
|
-
if (value) {
|
|
721
|
-
button.classList.add('ring-2', 'ring-blue-500', 'ring-offset-1');
|
|
722
|
-
} else {
|
|
723
|
-
button.classList.remove('ring-2', 'ring-blue-500', 'ring-offset-1');
|
|
724
|
-
}
|
|
725
|
-
dropdown.querySelector('.dropdown-menu').classList.add('hidden');
|
|
726
|
-
document.getElementById('agent-filters-form').requestSubmit();
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
function toggleAllOptions(checkbox, filterName) {
|
|
730
|
-
const dropdown = document.querySelector(`[data-filter="${filterName}"]`);
|
|
731
|
-
const checkboxes = dropdown.querySelectorAll('.filter-checkbox');
|
|
732
|
-
|
|
733
|
-
if (checkbox.checked) {
|
|
734
|
-
checkboxes.forEach(cb => cb.checked = false);
|
|
735
|
-
updateMultiSelect(filterName);
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
function updateMultiSelect(filterName) {
|
|
740
|
-
const dropdown = document.querySelector(`[data-filter="${filterName}"]`);
|
|
741
|
-
const checkboxes = dropdown.querySelectorAll('.filter-checkbox:checked');
|
|
742
|
-
const selectAllCheckbox = dropdown.querySelector('.select-all-checkbox');
|
|
743
|
-
const button = dropdown.querySelector('button');
|
|
744
|
-
const label = dropdown.querySelector('.dropdown-label');
|
|
745
|
-
|
|
746
|
-
const count = checkboxes.length;
|
|
747
|
-
|
|
748
|
-
selectAllCheckbox.checked = (count === 0);
|
|
749
|
-
|
|
750
|
-
const labelMap = {
|
|
751
|
-
'statuses': { all: 'All Statuses', plural: 'Statuses' },
|
|
752
|
-
'versions': { all: 'All Versions', plural: 'Versions' },
|
|
753
|
-
'models': { all: 'All Models', plural: 'Models' },
|
|
754
|
-
'temperatures': { all: 'All Temps', plural: 'Temps' }
|
|
755
|
-
};
|
|
756
|
-
|
|
757
|
-
if (count === 0) {
|
|
758
|
-
label.textContent = labelMap[filterName]?.all || 'All';
|
|
759
|
-
button.classList.remove('ring-2', 'ring-blue-500', 'ring-offset-1');
|
|
760
|
-
} else if (count === 1) {
|
|
761
|
-
const value = checkboxes[0].value;
|
|
762
|
-
if (filterName === 'statuses') {
|
|
763
|
-
label.textContent = value.charAt(0).toUpperCase() + value.slice(1);
|
|
764
|
-
} else if (filterName === 'versions') {
|
|
765
|
-
label.textContent = 'v' + value;
|
|
766
|
-
} else {
|
|
767
|
-
label.textContent = value;
|
|
768
|
-
}
|
|
769
|
-
button.classList.add('ring-2', 'ring-blue-500', 'ring-offset-1');
|
|
770
|
-
} else {
|
|
771
|
-
label.textContent = `${count} ${labelMap[filterName]?.plural || 'Items'}`;
|
|
772
|
-
button.classList.add('ring-2', 'ring-blue-500', 'ring-offset-1');
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
document.getElementById('agent-filters-form').requestSubmit();
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
document.addEventListener('click', function(e) {
|
|
779
|
-
if (!e.target.closest('.filter-dropdown')) {
|
|
780
|
-
document.querySelectorAll('.filter-dropdown .dropdown-menu').forEach(menu => {
|
|
781
|
-
menu.classList.add('hidden');
|
|
782
|
-
});
|
|
783
|
-
}
|
|
784
|
-
});
|
|
785
|
-
</script>
|