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,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>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<div id="execution-detail" data-execution-id="<%= @execution.id %>" data-status="<%= @execution.status %>">
|
|
2
|
-
<%= render "
|
|
2
|
+
<%= render "ruby_llm/agents/shared/breadcrumbs", items: [
|
|
3
3
|
{ label: "Dashboard", path: ruby_llm_agents.root_path },
|
|
4
4
|
{ label: "Executions", path: ruby_llm_agents.executions_path },
|
|
5
5
|
{ label: "##{@execution.id}" }
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<h2 class="text-lg font-bold text-gray-900 dark:text-gray-100 truncate">
|
|
31
31
|
<%= @execution.agent_type.gsub(/Agent$/, '') %>
|
|
32
32
|
</h2>
|
|
33
|
-
<%= render "
|
|
33
|
+
<%= render "ruby_llm/agents/shared/status_badge", status: @execution.status, size: :md %>
|
|
34
34
|
<% if secondary_badges.any? %>
|
|
35
35
|
<div class="relative" x-data="{ showDetails: false }">
|
|
36
36
|
<button
|
|
@@ -74,7 +74,6 @@
|
|
|
74
74
|
<div class="flex items-center gap-3 flex-shrink-0">
|
|
75
75
|
<%= button_to rerun_execution_path(@execution, dry_run: true),
|
|
76
76
|
method: :post,
|
|
77
|
-
data: { turbo: false },
|
|
78
77
|
class: "inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors",
|
|
79
78
|
title: "Preview what would be sent without making an API call" do %>
|
|
80
79
|
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
@@ -115,7 +114,7 @@
|
|
|
115
114
|
<%= @execution.agent_type.gsub(/Agent$/, '') %>
|
|
116
115
|
</h2>
|
|
117
116
|
<div class="flex flex-wrap items-center gap-2 mt-2">
|
|
118
|
-
<%= render "
|
|
117
|
+
<%= render "ruby_llm/agents/shared/status_badge", status: @execution.status, size: :md %>
|
|
119
118
|
<% secondary_badges.each do |badge| %>
|
|
120
119
|
<% badge_classes = case badge[:color]
|
|
121
120
|
when 'cyan' then 'bg-cyan-100 dark:bg-cyan-900/50 text-cyan-800 dark:text-cyan-300'
|
|
@@ -146,7 +145,6 @@
|
|
|
146
145
|
<div class="flex items-center gap-2">
|
|
147
146
|
<%= button_to rerun_execution_path(@execution, dry_run: true),
|
|
148
147
|
method: :post,
|
|
149
|
-
data: { turbo: false },
|
|
150
148
|
class: "flex-1 inline-flex items-center justify-center gap-1.5 px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors",
|
|
151
149
|
title: "Preview what would be sent without making an API call" do %>
|
|
152
150
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
@@ -209,27 +207,124 @@
|
|
|
209
207
|
</div>
|
|
210
208
|
</div>
|
|
211
209
|
|
|
210
|
+
<!-- Workflow Summary Panel (for root workflow executions) -->
|
|
211
|
+
<% if @execution.respond_to?(:root_workflow?) && @execution.root_workflow? %>
|
|
212
|
+
<%= render "ruby_llm/agents/executions/workflow_summary", execution: @execution %>
|
|
213
|
+
<% end %>
|
|
214
|
+
|
|
215
|
+
<!-- Workflow Info (if applicable - for child workflow steps) -->
|
|
216
|
+
<% if (@execution.workflow_type.present? || @execution.workflow_step.present? || @execution.routed_to.present?) && !(@execution.respond_to?(:root_workflow?) && @execution.root_workflow?) %>
|
|
217
|
+
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-4 sm:p-5 mb-6">
|
|
218
|
+
<div class="flex items-center gap-2 mb-4">
|
|
219
|
+
<% case @execution.workflow_type
|
|
220
|
+
when "pipeline" %>
|
|
221
|
+
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md text-sm font-medium bg-indigo-100 dark:bg-indigo-900/50 text-indigo-700 dark:text-indigo-300">
|
|
222
|
+
<span class="text-base">→</span> Pipeline Workflow
|
|
223
|
+
</span>
|
|
224
|
+
<% when "parallel" %>
|
|
225
|
+
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md text-sm font-medium bg-cyan-100 dark:bg-cyan-900/50 text-cyan-700 dark:text-cyan-300">
|
|
226
|
+
<span class="text-base">⫴</span> Parallel Workflow
|
|
227
|
+
</span>
|
|
228
|
+
<% when "router" %>
|
|
229
|
+
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md text-sm font-medium bg-amber-100 dark:bg-amber-900/50 text-amber-700 dark:text-amber-300">
|
|
230
|
+
<span class="text-base">⑂</span> Router Workflow
|
|
231
|
+
</span>
|
|
232
|
+
<% else %>
|
|
233
|
+
<% if @execution.workflow_step.present? %>
|
|
234
|
+
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md text-sm font-medium bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300">
|
|
235
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
236
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 5l7 7-7 7M5 5l7 7-7 7"/>
|
|
237
|
+
</svg>
|
|
238
|
+
Workflow Step
|
|
239
|
+
</span>
|
|
240
|
+
<% end %>
|
|
241
|
+
<% end %>
|
|
242
|
+
<% if @execution.workflow_id.present? %>
|
|
243
|
+
<span class="text-xs text-gray-400 dark:text-gray-500 font-mono" title="Workflow ID: <%= @execution.workflow_id %>">
|
|
244
|
+
<%= @execution.workflow_id.to_s.truncate(12) %>
|
|
245
|
+
</span>
|
|
246
|
+
<% end %>
|
|
247
|
+
</div>
|
|
248
|
+
|
|
249
|
+
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
250
|
+
<% if @execution.workflow_step.present? %>
|
|
251
|
+
<div>
|
|
252
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide">Step Name</p>
|
|
253
|
+
<p class="text-sm font-medium text-gray-900 dark:text-gray-100"><%= @execution.workflow_step %></p>
|
|
254
|
+
</div>
|
|
255
|
+
<% end %>
|
|
256
|
+
|
|
257
|
+
<% if @execution.routed_to.present? %>
|
|
258
|
+
<div>
|
|
259
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide">Routed To</p>
|
|
260
|
+
<p class="text-sm font-medium text-amber-600 dark:text-amber-400"><%= @execution.routed_to %></p>
|
|
261
|
+
</div>
|
|
262
|
+
<% end %>
|
|
263
|
+
|
|
264
|
+
<% if @execution.classification_result.present? %>
|
|
265
|
+
<%
|
|
266
|
+
classification = if @execution.classification_result.is_a?(String)
|
|
267
|
+
begin
|
|
268
|
+
JSON.parse(@execution.classification_result)
|
|
269
|
+
rescue
|
|
270
|
+
{}
|
|
271
|
+
end
|
|
272
|
+
else
|
|
273
|
+
@execution.classification_result || {}
|
|
274
|
+
end
|
|
275
|
+
%>
|
|
276
|
+
<% if classification["method"].present? %>
|
|
277
|
+
<div>
|
|
278
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide">Classification</p>
|
|
279
|
+
<p class="text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
280
|
+
<%= classification["method"] == "llm" ? "LLM" : "Rule-based" %>
|
|
281
|
+
<% if classification["classification_time_ms"].present? %>
|
|
282
|
+
<span class="text-xs text-gray-400 dark:text-gray-500">(<%= classification["classification_time_ms"] %>ms)</span>
|
|
283
|
+
<% end %>
|
|
284
|
+
</p>
|
|
285
|
+
</div>
|
|
286
|
+
<% end %>
|
|
287
|
+
<% if classification["classifier_model"].present? %>
|
|
288
|
+
<div>
|
|
289
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide">Classifier Model</p>
|
|
290
|
+
<p class="text-sm font-medium text-gray-900 dark:text-gray-100 font-mono"><%= classification["classifier_model"] %></p>
|
|
291
|
+
</div>
|
|
292
|
+
<% end %>
|
|
293
|
+
<% end %>
|
|
294
|
+
</div>
|
|
295
|
+
|
|
296
|
+
<% if @execution.parent_execution_id.present? %>
|
|
297
|
+
<div class="mt-4 pt-4 border-t border-gray-100 dark:border-gray-700">
|
|
298
|
+
<span class="text-xs text-gray-500 dark:text-gray-400">Part of workflow:</span>
|
|
299
|
+
<%= link_to "##{@execution.parent_execution_id}",
|
|
300
|
+
ruby_llm_agents.execution_path(@execution.parent_execution_id),
|
|
301
|
+
class: "ml-2 text-blue-600 dark:text-blue-400 hover:underline font-mono text-sm" %>
|
|
302
|
+
</div>
|
|
303
|
+
<% end %>
|
|
304
|
+
</div>
|
|
305
|
+
<% end %>
|
|
306
|
+
|
|
212
307
|
<!-- Stats Grid -->
|
|
213
308
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
|
|
214
|
-
<%= render "
|
|
309
|
+
<%= render "ruby_llm/agents/shared/stat_card",
|
|
215
310
|
title: "Model",
|
|
216
311
|
value: @execution.model_id,
|
|
217
312
|
icon: "M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z",
|
|
218
313
|
icon_color: "text-blue-500" %>
|
|
219
314
|
|
|
220
|
-
<%= render "
|
|
315
|
+
<%= render "ruby_llm/agents/shared/stat_card",
|
|
221
316
|
title: "Duration",
|
|
222
317
|
value: "#{number_to_human_short(@execution.duration_ms || 0)} ms",
|
|
223
318
|
icon: "M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z",
|
|
224
319
|
icon_color: "text-purple-500" %>
|
|
225
320
|
|
|
226
|
-
<%= render "
|
|
321
|
+
<%= render "ruby_llm/agents/shared/stat_card",
|
|
227
322
|
title: "Total Tokens",
|
|
228
323
|
value: number_to_human_short(@execution.total_tokens || 0),
|
|
229
324
|
icon: "M7 20l4-16m2 16l4-16M6 9h14M4 15h14",
|
|
230
325
|
icon_color: "text-indigo-500" %>
|
|
231
326
|
|
|
232
|
-
<%= render "
|
|
327
|
+
<%= render "ruby_llm/agents/shared/stat_card",
|
|
233
328
|
title: "Total Cost",
|
|
234
329
|
value: number_to_human_short(@execution.total_cost || 0, prefix: "$", precision: 2),
|
|
235
330
|
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",
|
|
@@ -297,7 +392,7 @@
|
|
|
297
392
|
<div>
|
|
298
393
|
<h3 class="text-sm font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">Attempts</h3>
|
|
299
394
|
<p class="text-xs text-gray-400 dark:text-gray-500 mt-1">
|
|
300
|
-
<%= @execution.attempts_count
|
|
395
|
+
<%= @execution.respond_to?(:attempts_count) && @execution.attempts_count ? @execution.attempts_count : @execution.attempts.size %> attempt(s)
|
|
301
396
|
<% if @execution.used_fallback? %>
|
|
302
397
|
· <span class="text-amber-500">Used fallback model</span>
|
|
303
398
|
<% end %>
|
|
@@ -306,7 +401,7 @@
|
|
|
306
401
|
<% end %>
|
|
307
402
|
</p>
|
|
308
403
|
</div>
|
|
309
|
-
<% if @execution.
|
|
404
|
+
<% if @execution.used_fallback? %>
|
|
310
405
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-amber-100 dark:bg-amber-900/30 text-amber-800 dark:text-amber-300">
|
|
311
406
|
Fallback: <%= @execution.chosen_model_id %>
|
|
312
407
|
</span>
|
|
@@ -428,46 +523,13 @@
|
|
|
428
523
|
</div>
|
|
429
524
|
<% end %>
|
|
430
525
|
|
|
431
|
-
<!-- Masking Toggle -->
|
|
432
|
-
<div class="bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 rounded-xl p-4 mb-6">
|
|
433
|
-
<div class="flex items-center justify-between">
|
|
434
|
-
<div class="flex items-center gap-2">
|
|
435
|
-
<svg class="w-5 h-5 text-amber-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
436
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/>
|
|
437
|
-
</svg>
|
|
438
|
-
<span class="text-sm font-medium text-amber-800 dark:text-amber-200">Data Masking</span>
|
|
439
|
-
</div>
|
|
440
|
-
<div class="flex items-center gap-3">
|
|
441
|
-
<span id="masking-status" class="text-xs text-amber-600 dark:text-amber-400">
|
|
442
|
-
Sensitive data is hidden
|
|
443
|
-
</span>
|
|
444
|
-
<button
|
|
445
|
-
type="button"
|
|
446
|
-
id="toggle-masking-btn"
|
|
447
|
-
onclick="toggleMasking()"
|
|
448
|
-
class="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md transition-colors bg-amber-100 dark:bg-amber-900/50 text-amber-700 dark:text-amber-300 hover:bg-amber-200 dark:hover:bg-amber-800/50"
|
|
449
|
-
>
|
|
450
|
-
<svg id="eye-icon" class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
451
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|
452
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
|
|
453
|
-
</svg>
|
|
454
|
-
<svg id="eye-off-icon" class="w-4 h-4 hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
455
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"/>
|
|
456
|
-
</svg>
|
|
457
|
-
<span id="toggle-btn-text">Show Original</span>
|
|
458
|
-
</button>
|
|
459
|
-
</div>
|
|
460
|
-
</div>
|
|
461
|
-
</div>
|
|
462
|
-
|
|
463
526
|
<!-- Parameters -->
|
|
464
527
|
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-6 mb-6">
|
|
465
528
|
<div class="flex items-center justify-between mb-4">
|
|
466
529
|
<h3 class="text-sm font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">Parameters</h3>
|
|
467
530
|
<button
|
|
468
531
|
type="button"
|
|
469
|
-
data-copy-json="<%= Base64.strict_encode64(JSON.pretty_generate(
|
|
470
|
-
data-copy-json-original="<%= Base64.strict_encode64(JSON.pretty_generate(@execution.parameters || {})) %>"
|
|
532
|
+
data-copy-json="<%= Base64.strict_encode64(JSON.pretty_generate(@execution.parameters || {})) %>"
|
|
471
533
|
class="copy-json-btn inline-flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md transition-colors"
|
|
472
534
|
>
|
|
473
535
|
<svg class="w-4 h-4 copy-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
@@ -479,8 +541,7 @@
|
|
|
479
541
|
<span>Copy</span>
|
|
480
542
|
</button>
|
|
481
543
|
</div>
|
|
482
|
-
<pre
|
|
483
|
-
<pre id="parameters-original" class="maskable-content hidden bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 rounded-lg p-4 text-sm overflow-x-auto font-mono"><%= highlight_json(@execution.parameters || {}) %></pre>
|
|
544
|
+
<pre class="bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 rounded-lg p-4 text-sm overflow-x-auto font-mono"><%= highlight_json(@execution.parameters || {}) %></pre>
|
|
484
545
|
</div>
|
|
485
546
|
|
|
486
547
|
<!-- Response -->
|
|
@@ -490,8 +551,7 @@
|
|
|
490
551
|
<h3 class="text-sm font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">Response</h3>
|
|
491
552
|
<button
|
|
492
553
|
type="button"
|
|
493
|
-
data-copy-json="<%= Base64.strict_encode64(JSON.pretty_generate(
|
|
494
|
-
data-copy-json-original="<%= Base64.strict_encode64(JSON.pretty_generate(@execution.response)) %>"
|
|
554
|
+
data-copy-json="<%= Base64.strict_encode64(JSON.pretty_generate(@execution.response)) %>"
|
|
495
555
|
class="copy-json-btn inline-flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md transition-colors"
|
|
496
556
|
>
|
|
497
557
|
<svg class="w-4 h-4 copy-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
@@ -503,30 +563,30 @@
|
|
|
503
563
|
<span>Copy</span>
|
|
504
564
|
</button>
|
|
505
565
|
</div>
|
|
506
|
-
<pre
|
|
507
|
-
<pre id="response-original" class="maskable-content hidden bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 rounded-lg p-4 text-sm overflow-x-auto max-h-96 font-mono"><%= highlight_json(@execution.response) %></pre>
|
|
566
|
+
<pre class="bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 rounded-lg p-4 text-sm overflow-x-auto max-h-96 font-mono"><%= highlight_json(@execution.response) %></pre>
|
|
508
567
|
</div>
|
|
509
568
|
<% end %>
|
|
510
569
|
|
|
511
570
|
<!-- Tool Calls -->
|
|
512
|
-
<%
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
571
|
+
<% tool_calls = @execution.tool_calls || [] %>
|
|
572
|
+
<% tool_call_count = tool_calls.size %>
|
|
573
|
+
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-6 mb-6" x-data="{ expanded: <%= tool_call_count <= 3 && tool_call_count > 0 %> }">
|
|
574
|
+
<div class="flex items-center justify-between mb-4">
|
|
575
|
+
<div class="flex items-center gap-2">
|
|
576
|
+
<svg class="w-5 h-5 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
577
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
|
|
578
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|
579
|
+
</svg>
|
|
580
|
+
<h3 class="text-sm font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">Tool Calls</h3>
|
|
581
|
+
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-100 dark:bg-blue-900/50 text-blue-800 dark:text-blue-300">
|
|
582
|
+
<%= tool_call_count %>
|
|
583
|
+
</span>
|
|
584
|
+
</div>
|
|
585
|
+
<% if tool_call_count > 0 %>
|
|
526
586
|
<div class="flex items-center gap-2">
|
|
527
587
|
<button
|
|
528
588
|
type="button"
|
|
529
|
-
data-copy-json="<%= Base64.strict_encode64(JSON.pretty_generate(
|
|
589
|
+
data-copy-json="<%= Base64.strict_encode64(JSON.pretty_generate(tool_calls)) %>"
|
|
530
590
|
class="copy-json-btn inline-flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md transition-colors"
|
|
531
591
|
>
|
|
532
592
|
<svg class="w-4 h-4 copy-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
@@ -543,10 +603,12 @@
|
|
|
543
603
|
</button>
|
|
544
604
|
<% end %>
|
|
545
605
|
</div>
|
|
546
|
-
|
|
606
|
+
<% end %>
|
|
607
|
+
</div>
|
|
547
608
|
|
|
609
|
+
<% if tool_call_count > 0 %>
|
|
548
610
|
<div class="space-y-4" x-show="expanded" <%= tool_call_count > 3 ? 'x-cloak' : '' %>>
|
|
549
|
-
<%
|
|
611
|
+
<% tool_calls.each_with_index do |tool_call, index| %>
|
|
550
612
|
<%
|
|
551
613
|
# Handle both symbol and string keys
|
|
552
614
|
tool_id = tool_call['id'] || tool_call[:id]
|
|
@@ -583,8 +645,10 @@
|
|
|
583
645
|
</div>
|
|
584
646
|
<% end %>
|
|
585
647
|
</div>
|
|
586
|
-
|
|
587
|
-
|
|
648
|
+
<% else %>
|
|
649
|
+
<p class="text-sm text-gray-400 dark:text-gray-500 italic">No tool calls were made during this execution.</p>
|
|
650
|
+
<% end %>
|
|
651
|
+
</div>
|
|
588
652
|
|
|
589
653
|
<!-- Metadata -->
|
|
590
654
|
<% if @execution.metadata.present? && @execution.metadata.any? %>
|
|
@@ -597,8 +661,7 @@
|
|
|
597
661
|
<div class="flex items-center gap-2">
|
|
598
662
|
<button
|
|
599
663
|
type="button"
|
|
600
|
-
data-copy-json="<%= Base64.strict_encode64(JSON.pretty_generate(
|
|
601
|
-
data-copy-json-original="<%= Base64.strict_encode64(JSON.pretty_generate(@execution.metadata)) %>"
|
|
664
|
+
data-copy-json="<%= Base64.strict_encode64(JSON.pretty_generate(@execution.metadata)) %>"
|
|
602
665
|
class="copy-json-btn inline-flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md transition-colors"
|
|
603
666
|
>
|
|
604
667
|
<svg class="w-4 h-4 copy-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
@@ -615,8 +678,7 @@
|
|
|
615
678
|
</div>
|
|
616
679
|
</div>
|
|
617
680
|
<div x-show="expanded" x-cloak class="mt-4">
|
|
618
|
-
<pre
|
|
619
|
-
<pre id="metadata-original" class="maskable-content hidden bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 rounded-lg p-4 text-sm overflow-x-auto font-mono"><%= highlight_json(@execution.metadata) %></pre>
|
|
681
|
+
<pre class="bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 rounded-lg p-4 text-sm overflow-x-auto font-mono"><%= highlight_json(@execution.metadata) %></pre>
|
|
620
682
|
</div>
|
|
621
683
|
</div>
|
|
622
684
|
<% end %>
|
|
@@ -921,61 +983,10 @@
|
|
|
921
983
|
toggle.textContent = isHidden ? 'Collapse' : 'Expand';
|
|
922
984
|
}
|
|
923
985
|
|
|
924
|
-
// Masking state (persisted in localStorage)
|
|
925
|
-
let isMasked = localStorage.getItem('ruby_llm_agents_masking') !== 'false';
|
|
926
|
-
|
|
927
|
-
function toggleMasking() {
|
|
928
|
-
isMasked = !isMasked;
|
|
929
|
-
localStorage.setItem('ruby_llm_agents_masking', isMasked);
|
|
930
|
-
updateMaskingUI();
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
function updateMaskingUI() {
|
|
934
|
-
const status = document.getElementById('masking-status');
|
|
935
|
-
const btnText = document.getElementById('toggle-btn-text');
|
|
936
|
-
const eyeIcon = document.getElementById('eye-icon');
|
|
937
|
-
const eyeOffIcon = document.getElementById('eye-off-icon');
|
|
938
|
-
|
|
939
|
-
// Update all maskable content sections
|
|
940
|
-
document.querySelectorAll('[id$="-masked"]').forEach(function(el) {
|
|
941
|
-
el.classList.toggle('hidden', !isMasked);
|
|
942
|
-
});
|
|
943
|
-
document.querySelectorAll('[id$="-original"]').forEach(function(el) {
|
|
944
|
-
el.classList.toggle('hidden', isMasked);
|
|
945
|
-
});
|
|
946
|
-
|
|
947
|
-
// Update status and button
|
|
948
|
-
if (isMasked) {
|
|
949
|
-
status.textContent = 'Sensitive data is hidden';
|
|
950
|
-
btnText.textContent = 'Show Original';
|
|
951
|
-
eyeIcon.classList.remove('hidden');
|
|
952
|
-
eyeOffIcon.classList.add('hidden');
|
|
953
|
-
} else {
|
|
954
|
-
status.textContent = 'Showing original data';
|
|
955
|
-
btnText.textContent = 'Hide Sensitive';
|
|
956
|
-
eyeIcon.classList.add('hidden');
|
|
957
|
-
eyeOffIcon.classList.remove('hidden');
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
// Update copy buttons to use appropriate data
|
|
961
|
-
document.querySelectorAll('.copy-json-btn').forEach(function(button) {
|
|
962
|
-
const maskedData = button.getAttribute('data-copy-json');
|
|
963
|
-
const originalData = button.getAttribute('data-copy-json-original');
|
|
964
|
-
if (originalData) {
|
|
965
|
-
button.setAttribute('data-active-json', isMasked ? maskedData : originalData);
|
|
966
|
-
}
|
|
967
|
-
});
|
|
968
|
-
}
|
|
969
|
-
|
|
970
986
|
document.addEventListener('DOMContentLoaded', function() {
|
|
971
|
-
// Initialize masking UI on page load
|
|
972
|
-
updateMaskingUI();
|
|
973
|
-
|
|
974
987
|
document.querySelectorAll('.copy-json-btn').forEach(function(button) {
|
|
975
988
|
button.addEventListener('click', function() {
|
|
976
|
-
|
|
977
|
-
const activeData = this.getAttribute('data-active-json') || this.getAttribute('data-copy-json');
|
|
978
|
-
const jsonText = atob(activeData);
|
|
989
|
+
const jsonText = atob(this.getAttribute('data-copy-json'));
|
|
979
990
|
const span = this.querySelector('span');
|
|
980
991
|
const copyIcon = this.querySelector('.copy-icon');
|
|
981
992
|
const checkIcon = this.querySelector('.check-icon');
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
<%
|
|
2
2
|
# Breadcrumb navigation partial
|
|
3
3
|
# Usage:
|
|
4
|
-
# render "
|
|
4
|
+
# render "ruby_llm/agents/shared/breadcrumbs", items: [
|
|
5
5
|
# { label: "Dashboard", path: ruby_llm_agents.root_path },
|
|
6
6
|
# { label: "Executions", path: ruby_llm_agents.executions_path },
|
|
7
7
|
# { label: "#123" } # Current page (no path)
|
|
8
8
|
# ]
|
|
9
9
|
#
|
|
10
10
|
# Or with simple array:
|
|
11
|
-
# render "
|
|
11
|
+
# render "ruby_llm/agents/shared/breadcrumbs", items: [
|
|
12
12
|
# ["Dashboard", ruby_llm_agents.root_path],
|
|
13
13
|
# ["Executions", ruby_llm_agents.executions_path],
|
|
14
14
|
# ["#123"]
|