ruby_llm-agents 0.3.4 → 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 +80 -16
- data/app/controllers/ruby_llm/agents/executions_controller.rb +83 -5
- data/app/models/ruby_llm/agents/execution/analytics.rb +17 -27
- 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 +9 -1
- 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/{rubyllm → ruby_llm}/agents/application.html.erb +91 -29
- 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/{rubyllm → ruby_llm}/agents/agents/show.html.erb +77 -20
- data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +112 -0
- data/app/views/{rubyllm → ruby_llm}/agents/dashboard/_execution_item.html.erb +7 -4
- data/app/views/ruby_llm/agents/dashboard/_tenant_budget.html.erb +115 -0
- data/app/views/{rubyllm → ruby_llm}/agents/dashboard/index.html.erb +9 -6
- 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 +137 -126
- data/app/views/{rubyllm → ruby_llm}/agents/shared/_breadcrumbs.html.erb +2 -2
- 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/{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/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 +4 -7
- data/lib/ruby_llm/agents/base/cost_calculation.rb +5 -3
- data/lib/ruby_llm/agents/base/execution.rb +61 -9
- data/lib/ruby_llm/agents/base/reliability_execution.rb +14 -9
- data/lib/ruby_llm/agents/base.rb +26 -0
- 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 +50 -60
- data/app/views/rubyllm/agents/agents/index.html.erb +0 -20
- data/app/views/rubyllm/agents/dashboard/_agent_comparison.html.erb +0 -46
- 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/dashboard/_budgets_bar.html.erb +0 -0
- /data/app/views/{rubyllm → ruby_llm}/agents/dashboard/_now_strip.html.erb +0 -0
- /data/app/views/{rubyllm → ruby_llm}/agents/dashboard/_top_errors.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/settings/show.html.erb +0 -0
- /data/app/views/{rubyllm → ruby_llm}/agents/shared/_nav_link.html.erb +0 -0
- /data/app/views/{rubyllm → ruby_llm}/agents/shared/_stat_card.html.erb +0 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 h-full" x-data="{ activeTab: 'agents' }">
|
|
2
|
+
<div class="px-4 py-3 border-b border-gray-100 dark:border-gray-700">
|
|
3
|
+
<div class="flex items-center justify-between">
|
|
4
|
+
<h3 class="text-base font-semibold text-gray-900 dark:text-gray-100">Performance</h3>
|
|
5
|
+
<!-- Tab Buttons -->
|
|
6
|
+
<div class="flex space-x-1 bg-gray-100 dark:bg-gray-700 rounded-lg p-0.5">
|
|
7
|
+
<button type="button" @click="activeTab = 'agents'"
|
|
8
|
+
:class="activeTab === 'agents' ? 'bg-white dark:bg-gray-600 shadow-sm' : 'hover:bg-gray-50 dark:hover:bg-gray-600'"
|
|
9
|
+
class="px-3 py-1 text-xs font-medium rounded-md transition-colors text-gray-700 dark:text-gray-200">
|
|
10
|
+
Agents
|
|
11
|
+
</button>
|
|
12
|
+
<button type="button" @click="activeTab = 'workflows'"
|
|
13
|
+
:class="activeTab === 'workflows' ? 'bg-white dark:bg-gray-600 shadow-sm' : 'hover:bg-gray-50 dark:hover:bg-gray-600'"
|
|
14
|
+
class="px-3 py-1 text-xs font-medium rounded-md transition-colors text-gray-700 dark:text-gray-200">
|
|
15
|
+
Workflows
|
|
16
|
+
</button>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<!-- Agents Tab -->
|
|
22
|
+
<div x-show="activeTab === 'agents'">
|
|
23
|
+
<% if agent_stats&.any? %>
|
|
24
|
+
<div class="overflow-x-auto">
|
|
25
|
+
<table class="w-full text-sm">
|
|
26
|
+
<thead>
|
|
27
|
+
<tr class="text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
28
|
+
<th class="px-4 py-2">Agent</th>
|
|
29
|
+
<th class="px-4 py-2 text-right">Runs</th>
|
|
30
|
+
<th class="px-4 py-2 text-right">Cost</th>
|
|
31
|
+
<th class="px-4 py-2 text-right">Success</th>
|
|
32
|
+
</tr>
|
|
33
|
+
</thead>
|
|
34
|
+
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
|
|
35
|
+
<% agent_stats.first(5).each do |agent| %>
|
|
36
|
+
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50">
|
|
37
|
+
<td class="px-4 py-2">
|
|
38
|
+
<span class="text-sm font-medium text-gray-900 dark:text-gray-100 truncate block max-w-[120px]" title="<%= agent[:agent_type] %>">
|
|
39
|
+
<%= agent[:agent_type].to_s.demodulize %>
|
|
40
|
+
</span>
|
|
41
|
+
</td>
|
|
42
|
+
<td class="px-4 py-2 text-right text-gray-600 dark:text-gray-300">
|
|
43
|
+
<%= number_with_delimiter(agent[:executions]) %>
|
|
44
|
+
</td>
|
|
45
|
+
<td class="px-4 py-2 text-right text-gray-600 dark:text-gray-300">
|
|
46
|
+
$<%= number_with_precision(agent[:total_cost], precision: 2) %>
|
|
47
|
+
</td>
|
|
48
|
+
<td class="px-4 py-2 text-right">
|
|
49
|
+
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium <%= agent[:success_rate] >= 95 ? 'bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-300' : agent[:success_rate] >= 80 ? 'bg-yellow-100 dark:bg-yellow-900 text-yellow-700 dark:text-yellow-300' : 'bg-red-100 dark:bg-red-900 text-red-700 dark:text-red-300' %>">
|
|
50
|
+
<%= agent[:success_rate].round %>%
|
|
51
|
+
</span>
|
|
52
|
+
</td>
|
|
53
|
+
</tr>
|
|
54
|
+
<% end %>
|
|
55
|
+
</tbody>
|
|
56
|
+
</table>
|
|
57
|
+
</div>
|
|
58
|
+
<% else %>
|
|
59
|
+
<div class="px-4 py-8 text-center text-gray-500 dark:text-gray-400">
|
|
60
|
+
<p class="text-sm">No agent data yet</p>
|
|
61
|
+
</div>
|
|
62
|
+
<% end %>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<!-- Workflows Tab -->
|
|
66
|
+
<div x-show="activeTab === 'workflows'" x-cloak>
|
|
67
|
+
<% if @workflow_stats&.any? %>
|
|
68
|
+
<div class="overflow-x-auto">
|
|
69
|
+
<table class="w-full text-sm">
|
|
70
|
+
<thead>
|
|
71
|
+
<tr class="text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
72
|
+
<th class="px-4 py-2">Workflow</th>
|
|
73
|
+
<th class="px-4 py-2">Type</th>
|
|
74
|
+
<th class="px-4 py-2 text-right">Runs</th>
|
|
75
|
+
<th class="px-4 py-2 text-right">Cost</th>
|
|
76
|
+
<th class="px-4 py-2 text-right">Success</th>
|
|
77
|
+
</tr>
|
|
78
|
+
</thead>
|
|
79
|
+
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
|
|
80
|
+
<% @workflow_stats.first(5).each do |workflow| %>
|
|
81
|
+
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50">
|
|
82
|
+
<td class="px-4 py-2">
|
|
83
|
+
<span class="text-sm font-medium text-gray-900 dark:text-gray-100 truncate block max-w-[100px]" title="<%= workflow[:agent_type] %>">
|
|
84
|
+
<%= workflow[:agent_type].to_s.demodulize.gsub(/Workflow$|Pipeline$|Parallel$|Router$/, '') %>
|
|
85
|
+
</span>
|
|
86
|
+
</td>
|
|
87
|
+
<td class="px-4 py-2">
|
|
88
|
+
<%= render "ruby_llm/agents/shared/workflow_type_badge", workflow_type: workflow[:workflow_type], size: :xs, show_label: false %>
|
|
89
|
+
</td>
|
|
90
|
+
<td class="px-4 py-2 text-right text-gray-600 dark:text-gray-300">
|
|
91
|
+
<%= number_with_delimiter(workflow[:executions]) %>
|
|
92
|
+
</td>
|
|
93
|
+
<td class="px-4 py-2 text-right text-gray-600 dark:text-gray-300">
|
|
94
|
+
$<%= number_with_precision(workflow[:total_cost], precision: 2) %>
|
|
95
|
+
</td>
|
|
96
|
+
<td class="px-4 py-2 text-right">
|
|
97
|
+
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium <%= workflow[:success_rate] >= 95 ? 'bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-300' : workflow[:success_rate] >= 80 ? 'bg-yellow-100 dark:bg-yellow-900 text-yellow-700 dark:text-yellow-300' : 'bg-red-100 dark:bg-red-900 text-red-700 dark:text-red-300' %>">
|
|
98
|
+
<%= workflow[:success_rate].round %>%
|
|
99
|
+
</span>
|
|
100
|
+
</td>
|
|
101
|
+
</tr>
|
|
102
|
+
<% end %>
|
|
103
|
+
</tbody>
|
|
104
|
+
</table>
|
|
105
|
+
</div>
|
|
106
|
+
<% else %>
|
|
107
|
+
<div class="px-4 py-8 text-center text-gray-500 dark:text-gray-400">
|
|
108
|
+
<p class="text-sm">No workflow data yet</p>
|
|
109
|
+
</div>
|
|
110
|
+
<% end %>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
<div id="execution-<%= execution.id %>" class="py-2.5 hover:bg-gray-50 dark:hover:bg-gray-700/50 -mx-2 px-2 rounded-lg transition-colors">
|
|
2
|
-
<%= link_to ruby_llm_agents.execution_path(execution),
|
|
2
|
+
<%= link_to ruby_llm_agents.execution_path(execution), class: "block" do %>
|
|
3
3
|
<!-- Row 1: Status dot + Agent + Badges + Timestamp -->
|
|
4
4
|
<div class="flex items-center justify-between gap-2">
|
|
5
5
|
<div class="flex items-center gap-2 min-w-0">
|
|
6
|
-
<%= render "
|
|
6
|
+
<%= render "ruby_llm/agents/shared/status_dot", status: execution.status %>
|
|
7
|
+
<% if execution.respond_to?(:workflow_type) && execution.workflow_type.present? %>
|
|
8
|
+
<%= render "ruby_llm/agents/shared/workflow_type_badge", workflow_type: execution.workflow_type, size: :xs, show_label: false %>
|
|
9
|
+
<% end %>
|
|
7
10
|
<span class="font-medium text-sm text-gray-900 dark:text-gray-100 truncate">
|
|
8
|
-
<%= execution.agent_type.gsub(/Agent$/, '') %>
|
|
11
|
+
<%= execution.agent_type.gsub(/Agent$|Workflow$|Pipeline$|Parallel$|Router$/, '') %>
|
|
9
12
|
</span>
|
|
10
13
|
<span class="hidden sm:inline text-xs text-gray-400 dark:text-gray-500">v<%= execution.agent_version %></span>
|
|
11
14
|
<% unless execution.status_running? %>
|
|
@@ -45,7 +48,7 @@
|
|
|
45
48
|
$<%= number_with_precision(execution.total_cost || 0, precision: 4) %>
|
|
46
49
|
<span class="mx-1.5 text-gray-300 dark:text-gray-600">·</span>
|
|
47
50
|
<%= number_to_human_short(execution.duration_ms || 0) %>ms
|
|
48
|
-
<% if execution.attempts_count && execution.attempts_count > 1 %>
|
|
51
|
+
<% if execution.respond_to?(:attempts_count) && execution.attempts_count && execution.attempts_count > 1 %>
|
|
49
52
|
<span class="mx-1.5 text-gray-300 dark:text-gray-600">·</span>
|
|
50
53
|
<span class="text-amber-600 dark:text-amber-400"><%= execution.attempts_count %> retries</span>
|
|
51
54
|
<% end %>
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
<%# Tenant Budget Widget - shows budget limits and usage for current tenant %>
|
|
2
|
+
<%# @param tenant_budget [Hash] Budget data from controller %>
|
|
3
|
+
|
|
4
|
+
<% if tenant_budget.present? %>
|
|
5
|
+
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 mb-6">
|
|
6
|
+
<div class="px-6 py-3 border-b border-gray-100 dark:border-gray-700">
|
|
7
|
+
<div class="flex items-center justify-between">
|
|
8
|
+
<div class="flex items-center gap-2">
|
|
9
|
+
<svg class="w-5 h-5 text-blue-600 dark:text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
10
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
|
|
11
|
+
</svg>
|
|
12
|
+
<h3 class="text-base font-semibold text-gray-900 dark:text-gray-100">
|
|
13
|
+
Tenant Budget: <%= tenant_budget[:tenant_id] %>
|
|
14
|
+
</h3>
|
|
15
|
+
</div>
|
|
16
|
+
<%# Enforcement badge %>
|
|
17
|
+
<% enforcement = tenant_budget[:enforcement].to_s %>
|
|
18
|
+
<% badge_class = case enforcement
|
|
19
|
+
when "hard" then "bg-red-100 dark:bg-red-900/50 text-red-700 dark:text-red-300"
|
|
20
|
+
when "soft" then "bg-yellow-100 dark:bg-yellow-900/50 text-yellow-700 dark:text-yellow-300"
|
|
21
|
+
else "bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400"
|
|
22
|
+
end %>
|
|
23
|
+
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium <%= badge_class %>">
|
|
24
|
+
<%= enforcement.capitalize %> Enforcement
|
|
25
|
+
</span>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<div class="p-6">
|
|
30
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
31
|
+
<%# Daily Budget %>
|
|
32
|
+
<div>
|
|
33
|
+
<div class="flex items-center justify-between mb-2">
|
|
34
|
+
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">Daily Budget</span>
|
|
35
|
+
<span class="text-sm text-gray-500 dark:text-gray-400">
|
|
36
|
+
$<%= number_with_precision(tenant_budget[:daily_spend], precision: 2) %>
|
|
37
|
+
<% if tenant_budget[:daily_limit] %>
|
|
38
|
+
/ $<%= number_with_precision(tenant_budget[:daily_limit], precision: 2) %>
|
|
39
|
+
<% else %>
|
|
40
|
+
(no limit)
|
|
41
|
+
<% end %>
|
|
42
|
+
</span>
|
|
43
|
+
</div>
|
|
44
|
+
<% if tenant_budget[:daily_limit] %>
|
|
45
|
+
<% daily_pct = [tenant_budget[:daily_percentage], 100].min %>
|
|
46
|
+
<% bar_color = if daily_pct >= 100
|
|
47
|
+
"bg-red-500"
|
|
48
|
+
elsif daily_pct >= 80
|
|
49
|
+
"bg-yellow-500"
|
|
50
|
+
else
|
|
51
|
+
"bg-green-500"
|
|
52
|
+
end %>
|
|
53
|
+
<div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2.5">
|
|
54
|
+
<div class="<%= bar_color %> h-2.5 rounded-full transition-all duration-300" style="width: <%= daily_pct %>%"></div>
|
|
55
|
+
</div>
|
|
56
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1"><%= tenant_budget[:daily_percentage] %>% used</p>
|
|
57
|
+
<% else %>
|
|
58
|
+
<div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2.5">
|
|
59
|
+
<div class="bg-gray-400 h-2.5 rounded-full" style="width: 0%"></div>
|
|
60
|
+
</div>
|
|
61
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">No daily limit configured</p>
|
|
62
|
+
<% end %>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<%# Monthly Budget %>
|
|
66
|
+
<div>
|
|
67
|
+
<div class="flex items-center justify-between mb-2">
|
|
68
|
+
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">Monthly Budget</span>
|
|
69
|
+
<span class="text-sm text-gray-500 dark:text-gray-400">
|
|
70
|
+
$<%= number_with_precision(tenant_budget[:monthly_spend], precision: 2) %>
|
|
71
|
+
<% if tenant_budget[:monthly_limit] %>
|
|
72
|
+
/ $<%= number_with_precision(tenant_budget[:monthly_limit], precision: 2) %>
|
|
73
|
+
<% else %>
|
|
74
|
+
(no limit)
|
|
75
|
+
<% end %>
|
|
76
|
+
</span>
|
|
77
|
+
</div>
|
|
78
|
+
<% if tenant_budget[:monthly_limit] %>
|
|
79
|
+
<% monthly_pct = [tenant_budget[:monthly_percentage], 100].min %>
|
|
80
|
+
<% bar_color = if monthly_pct >= 100
|
|
81
|
+
"bg-red-500"
|
|
82
|
+
elsif monthly_pct >= 80
|
|
83
|
+
"bg-yellow-500"
|
|
84
|
+
else
|
|
85
|
+
"bg-blue-500"
|
|
86
|
+
end %>
|
|
87
|
+
<div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2.5">
|
|
88
|
+
<div class="<%= bar_color %> h-2.5 rounded-full transition-all duration-300" style="width: <%= monthly_pct %>%"></div>
|
|
89
|
+
</div>
|
|
90
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1"><%= tenant_budget[:monthly_percentage] %>% used</p>
|
|
91
|
+
<% else %>
|
|
92
|
+
<div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2.5">
|
|
93
|
+
<div class="bg-gray-400 h-2.5 rounded-full" style="width: 0%"></div>
|
|
94
|
+
</div>
|
|
95
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">No monthly limit configured</p>
|
|
96
|
+
<% end %>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<%# Per-agent limits if configured %>
|
|
101
|
+
<% if tenant_budget[:per_agent_daily].present? %>
|
|
102
|
+
<div class="mt-4 pt-4 border-t border-gray-100 dark:border-gray-700">
|
|
103
|
+
<p class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide mb-2">Per-Agent Daily Limits</p>
|
|
104
|
+
<div class="flex flex-wrap gap-2">
|
|
105
|
+
<% tenant_budget[:per_agent_daily].each do |agent, limit| %>
|
|
106
|
+
<span class="inline-flex items-center px-2 py-1 rounded text-xs bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300">
|
|
107
|
+
<%= agent.to_s.gsub(/Agent$/, "") %>: $<%= number_with_precision(limit, precision: 2) %>
|
|
108
|
+
</span>
|
|
109
|
+
<% end %>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
<% end %>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
<% end %>
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
<!-- Action Center (only when critical alerts exist) -->
|
|
2
|
-
<%= render partial: "
|
|
2
|
+
<%= render partial: "ruby_llm/agents/dashboard/action_center", locals: { critical_alerts: @critical_alerts } %>
|
|
3
3
|
|
|
4
4
|
<!-- Now Strip -->
|
|
5
|
-
<%= render partial: "
|
|
5
|
+
<%= render partial: "ruby_llm/agents/dashboard/now_strip", locals: { now_strip: @now_strip } %>
|
|
6
|
+
|
|
7
|
+
<!-- Tenant Budget (when viewing specific tenant) -->
|
|
8
|
+
<%= render partial: "ruby_llm/agents/dashboard/tenant_budget", locals: { tenant_budget: @tenant_budget } %>
|
|
6
9
|
|
|
7
10
|
<!-- Activity Chart -->
|
|
8
11
|
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 mb-6">
|
|
@@ -129,13 +132,13 @@
|
|
|
129
132
|
<div class="px-6 py-3 border-b border-gray-100 dark:border-gray-700">
|
|
130
133
|
<div class="flex justify-between items-center">
|
|
131
134
|
<h3 class="text-base font-semibold text-gray-900 dark:text-gray-100">Recent Activity</h3>
|
|
132
|
-
<%= link_to "View All", ruby_llm_agents.executions_path,
|
|
135
|
+
<%= link_to "View All", ruby_llm_agents.executions_path, class: "text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 text-sm font-medium" %>
|
|
133
136
|
</div>
|
|
134
137
|
</div>
|
|
135
138
|
<div id="activity-feed" class="px-6 py-2">
|
|
136
139
|
<% if @recent_executions.any? %>
|
|
137
140
|
<% @recent_executions.first(5).each do |execution| %>
|
|
138
|
-
<%= render partial: "
|
|
141
|
+
<%= render partial: "ruby_llm/agents/dashboard/execution_item", locals: { execution: execution } %>
|
|
139
142
|
<% end %>
|
|
140
143
|
<% else %>
|
|
141
144
|
<div class="py-8 text-center text-gray-500 dark:text-gray-400">
|
|
@@ -147,6 +150,6 @@
|
|
|
147
150
|
|
|
148
151
|
<!-- Agent Comparison + Top Errors -->
|
|
149
152
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
150
|
-
<%= render partial: "
|
|
151
|
-
<%= render partial: "
|
|
153
|
+
<%= render partial: "ruby_llm/agents/dashboard/agent_comparison", locals: { agent_stats: @agent_stats } %>
|
|
154
|
+
<%= render partial: "ruby_llm/agents/dashboard/top_errors", locals: { top_errors: @top_errors } %>
|
|
152
155
|
</div>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<%= link_to ruby_llm_agents.execution_path(execution),
|
|
1
|
+
<%= link_to ruby_llm_agents.execution_path(execution), class: "block px-6 py-4 hover:bg-gray-50 transition-colors" do %>
|
|
2
2
|
<div class="flex items-center justify-between">
|
|
3
3
|
<div class="flex items-center space-x-4">
|
|
4
4
|
<!-- Status Badge -->
|
|
@@ -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 %>
|