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