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
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
selected_agents = params[:agent_types].present? ? (params[:agent_types].is_a?(Array) ? params[:agent_types] : params[:agent_types].split(",")) : []
|
|
4
4
|
selected_statuses = params[:statuses].present? ? (params[:statuses].is_a?(Array) ? params[:statuses] : params[:statuses].split(",")) : []
|
|
5
5
|
selected_models = params[:model_ids].present? ? (params[:model_ids].is_a?(Array) ? params[:model_ids] : params[:model_ids].split(",")) : []
|
|
6
|
+
selected_workflows = params[:workflow_types].present? ? (params[:workflow_types].is_a?(Array) ? params[:workflow_types] : params[:workflow_types].split(",")) : []
|
|
6
7
|
|
|
7
|
-
has_filters = selected_agents.any? || selected_statuses.any? || params[:days].present? || selected_models.any?
|
|
8
|
+
has_filters = selected_agents.any? || selected_statuses.any? || params[:days].present? || selected_models.any? || selected_workflows.any?
|
|
8
9
|
active_filter_count = [
|
|
9
10
|
selected_agents.any? ? 1 : 0,
|
|
10
11
|
selected_statuses.any? ? 1 : 0,
|
|
11
12
|
params[:days].present? ? 1 : 0,
|
|
12
|
-
selected_models.any? ? 1 : 0
|
|
13
|
+
selected_models.any? ? 1 : 0,
|
|
14
|
+
selected_workflows.any? ? 1 : 0
|
|
13
15
|
].sum
|
|
14
16
|
|
|
15
17
|
status_options = [
|
|
@@ -33,11 +35,23 @@
|
|
|
33
35
|
short_name = m.split("/").last.split(":").first # Handle "provider/model:version" format
|
|
34
36
|
{ value: m, label: short_name }
|
|
35
37
|
end || []
|
|
38
|
+
|
|
39
|
+
# Build workflow type options with icons
|
|
40
|
+
workflow_type_labels = {
|
|
41
|
+
"pipeline" => { label: "Pipeline", icon: "→" },
|
|
42
|
+
"parallel" => { label: "Parallel", icon: "⫴" },
|
|
43
|
+
"router" => { label: "Router", icon: "⑂" }
|
|
44
|
+
}
|
|
45
|
+
workflow_options = [{ value: "single", label: "Single Agent" }]
|
|
46
|
+
workflow_options += (local_assigns[:workflow_types] || []).map do |wt|
|
|
47
|
+
info = workflow_type_labels[wt] || { label: wt.titleize, icon: "" }
|
|
48
|
+
{ value: wt, label: "#{info[:icon]} #{info[:label]}".strip }
|
|
49
|
+
end
|
|
36
50
|
%>
|
|
37
51
|
<div id="filters-container" class="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-4 mb-6"
|
|
38
52
|
x-data="{ mobileOpen: false }">
|
|
39
53
|
<%= form_with url: ruby_llm_agents.executions_path, method: :get,
|
|
40
|
-
|
|
54
|
+
local: true,
|
|
41
55
|
id: "filters-form" do |f| %>
|
|
42
56
|
|
|
43
57
|
<%# Mobile: Toggle button (only shows on small screens) %>
|
|
@@ -71,7 +85,7 @@
|
|
|
71
85
|
<div class="flex flex-col md:flex-row md:items-center gap-3">
|
|
72
86
|
<%# Status Filter %>
|
|
73
87
|
<div class="md:w-auto">
|
|
74
|
-
<%= render "
|
|
88
|
+
<%= render "ruby_llm/agents/shared/filter_dropdown",
|
|
75
89
|
name: "statuses[]",
|
|
76
90
|
filter_id: "statuses",
|
|
77
91
|
label: "Status",
|
|
@@ -84,7 +98,7 @@
|
|
|
84
98
|
|
|
85
99
|
<%# Time Range Filter %>
|
|
86
100
|
<div class="md:w-auto">
|
|
87
|
-
<%= render "
|
|
101
|
+
<%= render "ruby_llm/agents/shared/select_dropdown",
|
|
88
102
|
name: "days",
|
|
89
103
|
filter_id: "days",
|
|
90
104
|
options: time_options,
|
|
@@ -97,7 +111,7 @@
|
|
|
97
111
|
<%# Model Filter %>
|
|
98
112
|
<% if model_options.any? %>
|
|
99
113
|
<div class="md:w-auto">
|
|
100
|
-
<%= render "
|
|
114
|
+
<%= render "ruby_llm/agents/shared/filter_dropdown",
|
|
101
115
|
name: "model_ids[]",
|
|
102
116
|
filter_id: "model_ids",
|
|
103
117
|
label: "Model",
|
|
@@ -112,7 +126,7 @@
|
|
|
112
126
|
<%# Agent Types Filter %>
|
|
113
127
|
<% if agent_types.any? %>
|
|
114
128
|
<div class="md:w-auto">
|
|
115
|
-
<%= render "
|
|
129
|
+
<%= render "ruby_llm/agents/shared/filter_dropdown",
|
|
116
130
|
name: "agent_types[]",
|
|
117
131
|
filter_id: "agent_types",
|
|
118
132
|
label: "Agents",
|
|
@@ -125,6 +139,22 @@
|
|
|
125
139
|
</div>
|
|
126
140
|
<% end %>
|
|
127
141
|
|
|
142
|
+
<%# Workflow Type Filter %>
|
|
143
|
+
<% if workflow_options.length > 1 %>
|
|
144
|
+
<div class="md:w-auto">
|
|
145
|
+
<%= render "ruby_llm/agents/shared/filter_dropdown",
|
|
146
|
+
name: "workflow_types[]",
|
|
147
|
+
filter_id: "workflow_types",
|
|
148
|
+
label: "Type",
|
|
149
|
+
all_label: "All Types",
|
|
150
|
+
options: workflow_options,
|
|
151
|
+
selected: selected_workflows,
|
|
152
|
+
icon: "M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zm0 8a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zm12 0a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z",
|
|
153
|
+
width: "w-44",
|
|
154
|
+
full_width: true %>
|
|
155
|
+
</div>
|
|
156
|
+
<% end %>
|
|
157
|
+
|
|
128
158
|
<%# Spacer (desktop only) %>
|
|
129
159
|
<div class="hidden md:block flex-1"></div>
|
|
130
160
|
|
|
@@ -144,7 +174,6 @@
|
|
|
144
174
|
<div class="flex items-center gap-2 md:gap-1">
|
|
145
175
|
<% if has_filters %>
|
|
146
176
|
<%= link_to ruby_llm_agents.executions_path,
|
|
147
|
-
data: { turbo_stream: true, turbo_action: "advance" },
|
|
148
177
|
class: "flex-1 md:flex-initial flex items-center justify-center gap-2 px-3 py-2 md:p-2 text-sm md:text-base text-red-600 md:text-gray-400 dark:text-red-400 md:dark:text-gray-400 bg-red-50 md:bg-transparent dark:bg-red-900/20 md:dark:bg-transparent hover:text-red-500 hover:bg-red-100 md:hover:bg-red-50 dark:hover:bg-red-900/30 md:dark:hover:bg-red-900/20 rounded-lg transition-colors",
|
|
149
178
|
title: "Clear filters" do %>
|
|
150
179
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
@@ -153,10 +182,9 @@
|
|
|
153
182
|
<span class="md:hidden">Clear</span>
|
|
154
183
|
<% end %>
|
|
155
184
|
<% end %>
|
|
156
|
-
<%= link_to ruby_llm_agents.export_executions_path(agent_types: selected_agents.presence, statuses: selected_statuses.presence, days: params[:days].presence, model_ids: selected_models.presence),
|
|
185
|
+
<%= link_to ruby_llm_agents.export_executions_path(agent_types: selected_agents.presence, statuses: selected_statuses.presence, days: params[:days].presence, model_ids: selected_models.presence, workflow_types: selected_workflows.presence),
|
|
157
186
|
class: "flex-1 md:flex-initial flex items-center justify-center gap-2 px-3 py-2 md:p-2 text-sm md:text-base text-gray-600 md:text-gray-400 dark:text-gray-300 md:dark:text-gray-400 bg-gray-50 md:bg-transparent dark:bg-gray-700 md:dark:bg-transparent hover:text-gray-600 md:hover:text-gray-600 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-600 md:dark:hover:bg-gray-700 rounded-lg transition-colors",
|
|
158
|
-
title: "Export CSV"
|
|
159
|
-
data: { turbo: false } do %>
|
|
187
|
+
title: "Export CSV" do %>
|
|
160
188
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
161
189
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
|
162
190
|
</svg>
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
|
|
95
95
|
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-100 dark:divide-gray-700">
|
|
96
96
|
<% executions.each do |execution| %>
|
|
97
|
-
<% has_attempts = execution.attempts.present? && execution.attempts.size > 0 %>
|
|
97
|
+
<% has_attempts = execution.respond_to?(:attempts) && execution.attempts.present? && execution.attempts.size > 0 %>
|
|
98
98
|
<tr
|
|
99
99
|
class="hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
|
100
100
|
id="execution-row-<%= execution.id %>"
|
|
@@ -118,13 +118,23 @@
|
|
|
118
118
|
</td>
|
|
119
119
|
|
|
120
120
|
<td class="px-4 py-3 whitespace-nowrap cursor-pointer" onclick="window.location='<%= ruby_llm_agents.execution_path(execution) %>'">
|
|
121
|
-
<
|
|
122
|
-
|
|
123
|
-
|
|
121
|
+
<div class="flex items-center gap-2">
|
|
122
|
+
<% if execution.respond_to?(:workflow_type) && execution.workflow_type.present? %>
|
|
123
|
+
<%= render "ruby_llm/agents/shared/workflow_type_badge", workflow_type: execution.workflow_type, size: :xs, show_label: false %>
|
|
124
|
+
<% end %>
|
|
125
|
+
<span class="text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
126
|
+
<%= execution.agent_type.gsub(/Agent$|Workflow$|Pipeline$|Parallel$|Router$/, '') %>
|
|
127
|
+
</span>
|
|
128
|
+
</div>
|
|
129
|
+
<% if execution.respond_to?(:parent_execution_id) && execution.parent_execution_id.present? %>
|
|
130
|
+
<span class="text-xs text-gray-400 dark:text-gray-500 ml-5 block">
|
|
131
|
+
child of #<%= execution.parent_execution_id %>
|
|
132
|
+
</span>
|
|
133
|
+
<% end %>
|
|
124
134
|
</td>
|
|
125
135
|
|
|
126
136
|
<td class="px-4 py-3 whitespace-nowrap cursor-pointer" onclick="window.location='<%= ruby_llm_agents.execution_path(execution) %>'">
|
|
127
|
-
<%= render "
|
|
137
|
+
<%= render "ruby_llm/agents/shared/status_badge", status: execution.status %>
|
|
128
138
|
</td>
|
|
129
139
|
|
|
130
140
|
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400 cursor-pointer" onclick="window.location='<%= ruby_llm_agents.execution_path(execution) %>'">
|
|
@@ -133,7 +143,7 @@
|
|
|
133
143
|
|
|
134
144
|
<!-- Attempts Count -->
|
|
135
145
|
<td class="px-4 py-3 whitespace-nowrap text-sm text-center cursor-pointer" onclick="window.location='<%= ruby_llm_agents.execution_path(execution) %>'">
|
|
136
|
-
<% attempts_count = execution.attempts_count || (execution.attempts&.size || 1
|
|
146
|
+
<% attempts_count = (execution.respond_to?(:attempts_count) && execution.attempts_count) || (execution.respond_to?(:attempts) && execution.attempts&.size) || 1 %>
|
|
137
147
|
<% if attempts_count > 1 %>
|
|
138
148
|
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200">
|
|
139
149
|
<%= attempts_count %> attempts
|
|
@@ -257,7 +267,7 @@
|
|
|
257
267
|
|
|
258
268
|
<nav class="flex items-center space-x-1">
|
|
259
269
|
<% if current_page > 1 %>
|
|
260
|
-
<%= link_to "Previous", url_for(page: current_page - 1),
|
|
270
|
+
<%= link_to "Previous", url_for(page: current_page - 1), class: "px-3 py-1.5 text-sm font-medium text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-md hover:bg-gray-50 dark:hover:bg-gray-700" %>
|
|
261
271
|
<% else %>
|
|
262
272
|
<span
|
|
263
273
|
class="
|
|
@@ -297,12 +307,12 @@
|
|
|
297
307
|
<%= page %>
|
|
298
308
|
</span>
|
|
299
309
|
<% else %>
|
|
300
|
-
<%= link_to page, url_for(page: page),
|
|
310
|
+
<%= link_to page, url_for(page: page), class: "px-3 py-1.5 text-sm font-medium text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-md hover:bg-gray-50 dark:hover:bg-gray-700" %>
|
|
301
311
|
<% end %>
|
|
302
312
|
<% end %>
|
|
303
313
|
|
|
304
314
|
<% if current_page < total_pages %>
|
|
305
|
-
<%= link_to "Next", url_for(page: current_page + 1),
|
|
315
|
+
<%= link_to "Next", url_for(page: current_page + 1), class: "px-3 py-1.5 text-sm font-medium text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-md hover:bg-gray-50 dark:hover:bg-gray-700" %>
|
|
306
316
|
<% else %>
|
|
307
317
|
<span
|
|
308
318
|
class="
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
<%# Simplified Workflow Summary - Clean table approach %>
|
|
2
|
+
<%# @param execution [RubyLLM::Agents::Execution] The workflow execution to display %>
|
|
3
|
+
|
|
4
|
+
<% if execution.root_workflow? %>
|
|
5
|
+
<% stats = execution.workflow_aggregate_stats %>
|
|
6
|
+
<% steps = execution.workflow_steps.to_a %>
|
|
7
|
+
|
|
8
|
+
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden mb-6">
|
|
9
|
+
<!-- Header -->
|
|
10
|
+
<div class="px-4 py-3 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
|
|
11
|
+
<div class="flex items-center gap-2">
|
|
12
|
+
<% case execution.workflow_type
|
|
13
|
+
when "pipeline" %>
|
|
14
|
+
<span class="text-indigo-600 dark:text-indigo-400">→</span>
|
|
15
|
+
<span class="font-medium text-gray-900 dark:text-gray-100">Pipeline</span>
|
|
16
|
+
<span class="text-gray-500 dark:text-gray-400">· <%= stats[:steps_count] %> steps</span>
|
|
17
|
+
<% when "parallel" %>
|
|
18
|
+
<span class="text-cyan-600 dark:text-cyan-400">⫴</span>
|
|
19
|
+
<span class="font-medium text-gray-900 dark:text-gray-100">Parallel</span>
|
|
20
|
+
<span class="text-gray-500 dark:text-gray-400">· <%= stats[:steps_count] %> branches</span>
|
|
21
|
+
<% when "router" %>
|
|
22
|
+
<span class="text-amber-600 dark:text-amber-400">⑂</span>
|
|
23
|
+
<span class="font-medium text-gray-900 dark:text-gray-100">Router</span>
|
|
24
|
+
<span class="text-gray-500 dark:text-gray-400">→ <%= execution.routed_to %></span>
|
|
25
|
+
<% end %>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<% overall_status = execution.workflow_overall_status %>
|
|
29
|
+
<%= render "ruby_llm/agents/shared/status_badge", status: overall_status.to_s, size: :sm %>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<!-- Table -->
|
|
33
|
+
<div class="overflow-x-auto">
|
|
34
|
+
<table class="min-w-full text-sm">
|
|
35
|
+
<thead class="bg-gray-50 dark:bg-gray-900/50">
|
|
36
|
+
<tr>
|
|
37
|
+
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">
|
|
38
|
+
<%= execution.pipeline_workflow? ? "Step" : execution.parallel_workflow? ? "Branch" : "Route" %>
|
|
39
|
+
</th>
|
|
40
|
+
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Status</th>
|
|
41
|
+
<th class="px-4 py-2 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Duration</th>
|
|
42
|
+
<th class="px-4 py-2 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Tokens</th>
|
|
43
|
+
<th class="px-4 py-2 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Cost</th>
|
|
44
|
+
</tr>
|
|
45
|
+
</thead>
|
|
46
|
+
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
|
|
47
|
+
<% steps.each_with_index do |step, index| %>
|
|
48
|
+
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800/50">
|
|
49
|
+
<td class="px-4 py-2">
|
|
50
|
+
<%= link_to ruby_llm_agents.execution_path(step.id), class: "text-blue-600 dark:text-blue-400 hover:underline" do %>
|
|
51
|
+
<% if execution.pipeline_workflow? %>
|
|
52
|
+
<span class="text-gray-400 dark:text-gray-500"><%= index + 1 %>.</span>
|
|
53
|
+
<% end %>
|
|
54
|
+
<%= step.workflow_step || step.agent_type.gsub(/Agent$/, "") %>
|
|
55
|
+
<% end %>
|
|
56
|
+
</td>
|
|
57
|
+
<td class="px-4 py-2">
|
|
58
|
+
<% case step.status
|
|
59
|
+
when "success" %>
|
|
60
|
+
<span class="text-green-600 dark:text-green-400">✓</span>
|
|
61
|
+
<% when "error" %>
|
|
62
|
+
<span class="text-red-600 dark:text-red-400">✗</span>
|
|
63
|
+
<% when "timeout" %>
|
|
64
|
+
<span class="text-orange-600 dark:text-orange-400">⏱</span>
|
|
65
|
+
<% when "running" %>
|
|
66
|
+
<span class="text-blue-600 dark:text-blue-400 animate-pulse">●</span>
|
|
67
|
+
<% else %>
|
|
68
|
+
<span class="text-gray-400">○</span>
|
|
69
|
+
<% end %>
|
|
70
|
+
</td>
|
|
71
|
+
<td class="px-4 py-2 text-right text-gray-600 dark:text-gray-300 tabular-nums">
|
|
72
|
+
<%= step.duration_ms ? "#{number_with_delimiter(step.duration_ms)}ms" : "-" %>
|
|
73
|
+
</td>
|
|
74
|
+
<td class="px-4 py-2 text-right text-gray-600 dark:text-gray-300 tabular-nums">
|
|
75
|
+
<%= number_with_delimiter(step.total_tokens || 0) %>
|
|
76
|
+
</td>
|
|
77
|
+
<td class="px-4 py-2 text-right text-gray-600 dark:text-gray-300 tabular-nums">
|
|
78
|
+
$<%= number_with_precision(step.total_cost || 0, precision: 4) %>
|
|
79
|
+
</td>
|
|
80
|
+
</tr>
|
|
81
|
+
<% end %>
|
|
82
|
+
</tbody>
|
|
83
|
+
<tfoot class="bg-gray-50 dark:bg-gray-900/50 font-medium">
|
|
84
|
+
<tr>
|
|
85
|
+
<td class="px-4 py-2 text-gray-700 dark:text-gray-300">Total</td>
|
|
86
|
+
<td class="px-4 py-2"></td>
|
|
87
|
+
<td class="px-4 py-2 text-right text-gray-700 dark:text-gray-300 tabular-nums">
|
|
88
|
+
<%= stats[:wall_clock_ms] ? "#{number_with_delimiter(stats[:wall_clock_ms])}ms" : "-" %>
|
|
89
|
+
</td>
|
|
90
|
+
<td class="px-4 py-2 text-right text-gray-700 dark:text-gray-300 tabular-nums">
|
|
91
|
+
<%= number_with_delimiter(stats[:total_tokens]) %>
|
|
92
|
+
</td>
|
|
93
|
+
<td class="px-4 py-2 text-right text-gray-700 dark:text-gray-300 tabular-nums">
|
|
94
|
+
$<%= number_with_precision(stats[:total_cost], precision: 4) %>
|
|
95
|
+
</td>
|
|
96
|
+
</tr>
|
|
97
|
+
</tfoot>
|
|
98
|
+
</table>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
<% end %>
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
<h2 class="text-2xl font-bold text-gray-900 dark:text-gray-100 mb-6">Executions</h2>
|
|
2
|
+
|
|
3
|
+
<div id="executions_content" x-data="{ activeTab: '<%= params[:execution_type] || 'all' %>', activeWorkflowType: '<%= params[:workflow_type_tab] %>' }">
|
|
4
|
+
<!-- Top-Level Execution Type Tabs -->
|
|
5
|
+
<div class="border-b border-gray-200 dark:border-gray-700 mb-4">
|
|
6
|
+
<nav class="-mb-px flex space-x-6" aria-label="Execution Types">
|
|
7
|
+
<button type="button" @click="activeTab = 'all'; activeWorkflowType = ''; submitFilters()"
|
|
8
|
+
:class="activeTab === 'all' ? 'border-blue-500 text-blue-600 dark:text-blue-400' : 'border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300'"
|
|
9
|
+
class="whitespace-nowrap py-3 px-1 border-b-2 font-medium text-sm transition-colors">
|
|
10
|
+
All
|
|
11
|
+
</button>
|
|
12
|
+
<button type="button" @click="activeTab = 'agents'; activeWorkflowType = ''; submitFilters()"
|
|
13
|
+
:class="activeTab === 'agents' ? 'border-blue-500 text-blue-600 dark:text-blue-400' : 'border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300'"
|
|
14
|
+
class="whitespace-nowrap py-3 px-1 border-b-2 font-medium text-sm transition-colors">
|
|
15
|
+
Agents Only
|
|
16
|
+
</button>
|
|
17
|
+
<button type="button" @click="activeTab = 'workflows'; activeWorkflowType = ''; submitFilters()"
|
|
18
|
+
:class="activeTab === 'workflows' ? 'border-blue-500 text-blue-600 dark:text-blue-400' : 'border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300'"
|
|
19
|
+
class="whitespace-nowrap py-3 px-1 border-b-2 font-medium text-sm transition-colors">
|
|
20
|
+
Workflows
|
|
21
|
+
</button>
|
|
22
|
+
</nav>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<!-- Workflow Sub-tabs -->
|
|
26
|
+
<div x-show="activeTab === 'workflows'" x-cloak class="mb-4">
|
|
27
|
+
<div class="flex flex-wrap gap-2">
|
|
28
|
+
<button type="button" @click="activeWorkflowType = ''; submitFilters()"
|
|
29
|
+
:class="activeWorkflowType === '' ? 'bg-gray-900 dark:bg-gray-100 text-white dark:text-gray-900' : 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600'"
|
|
30
|
+
class="px-3 py-1.5 rounded-md text-sm font-medium transition-colors">
|
|
31
|
+
All Workflows
|
|
32
|
+
</button>
|
|
33
|
+
<button type="button" @click="activeWorkflowType = 'pipeline'; submitFilters()"
|
|
34
|
+
:class="activeWorkflowType === 'pipeline' ? 'bg-indigo-600 text-white' : 'bg-indigo-100 dark:bg-indigo-900/50 text-indigo-700 dark:text-indigo-300 hover:bg-indigo-200 dark:hover:bg-indigo-800'"
|
|
35
|
+
class="px-3 py-1.5 rounded-md text-sm font-medium transition-colors inline-flex items-center gap-1.5">
|
|
36
|
+
<span aria-hidden="true">-></span> Pipeline
|
|
37
|
+
</button>
|
|
38
|
+
<button type="button" @click="activeWorkflowType = 'parallel'; submitFilters()"
|
|
39
|
+
:class="activeWorkflowType === 'parallel' ? 'bg-cyan-600 text-white' : 'bg-cyan-100 dark:bg-cyan-900/50 text-cyan-700 dark:text-cyan-300 hover:bg-cyan-200 dark:hover:bg-cyan-800'"
|
|
40
|
+
class="px-3 py-1.5 rounded-md text-sm font-medium transition-colors inline-flex items-center gap-1.5">
|
|
41
|
+
<span aria-hidden="true">//</span> Parallel
|
|
42
|
+
</button>
|
|
43
|
+
<button type="button" @click="activeWorkflowType = 'router'; submitFilters()"
|
|
44
|
+
:class="activeWorkflowType === 'router' ? 'bg-amber-600 text-white' : 'bg-amber-100 dark:bg-amber-900/50 text-amber-700 dark:text-amber-300 hover:bg-amber-200 dark:hover:bg-amber-800'"
|
|
45
|
+
class="px-3 py-1.5 rounded-md text-sm font-medium transition-colors inline-flex items-center gap-1.5">
|
|
46
|
+
<span aria-hidden="true"><></span> Router
|
|
47
|
+
</button>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<!-- Hidden inputs for tab state -->
|
|
52
|
+
<input type="hidden" id="execution-type-filter" name="execution_type" x-model="activeTab" form="filters-form">
|
|
53
|
+
<input type="hidden" id="workflow-type-tab-filter" name="workflow_type_tab" x-model="activeWorkflowType" form="filters-form">
|
|
54
|
+
|
|
55
|
+
<%= render partial: "ruby_llm/agents/executions/filters", locals: { agent_types: @agent_types, model_ids: @model_ids, workflow_types: @workflow_types, filter_stats: @filter_stats } %>
|
|
56
|
+
<%= render partial: "ruby_llm/agents/executions/list", locals: { executions: @executions, pagination: @pagination, filter_stats: @filter_stats } %>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<script>
|
|
60
|
+
// Toggle attempts expansion (used by executions list)
|
|
61
|
+
function toggleAttempts(executionId) {
|
|
62
|
+
const attemptsRow = document.getElementById('attempts-row-' + executionId);
|
|
63
|
+
const expandBtn = document.querySelector(`button[data-execution-id="${executionId}"]`);
|
|
64
|
+
|
|
65
|
+
if (attemptsRow) {
|
|
66
|
+
const isHidden = attemptsRow.classList.contains('hidden');
|
|
67
|
+
attemptsRow.classList.toggle('hidden');
|
|
68
|
+
|
|
69
|
+
// Rotate the chevron
|
|
70
|
+
if (expandBtn) {
|
|
71
|
+
const svg = expandBtn.querySelector('svg');
|
|
72
|
+
if (svg) {
|
|
73
|
+
svg.classList.toggle('rotate-90', isHidden);
|
|
74
|
+
}
|
|
75
|
+
expandBtn.setAttribute('aria-expanded', isHidden ? 'true' : 'false');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Submit filters when tabs change
|
|
81
|
+
function submitFilters() {
|
|
82
|
+
// Delay to allow Alpine to update x-model values
|
|
83
|
+
setTimeout(function() {
|
|
84
|
+
const form = document.getElementById('filters-form');
|
|
85
|
+
if (form) form.requestSubmit();
|
|
86
|
+
}, 50);
|
|
87
|
+
}
|
|
88
|
+
</script>
|