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.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +132 -1263
  3. data/app/controllers/concerns/ruby_llm/agents/filterable.rb +5 -1
  4. data/app/controllers/concerns/ruby_llm/agents/paginatable.rb +2 -1
  5. data/app/controllers/ruby_llm/agents/agents_controller.rb +21 -2
  6. data/app/controllers/ruby_llm/agents/dashboard_controller.rb +137 -9
  7. data/app/controllers/ruby_llm/agents/executions_controller.rb +83 -5
  8. data/app/models/ruby_llm/agents/execution/analytics.rb +103 -12
  9. data/app/models/ruby_llm/agents/execution/scopes.rb +25 -0
  10. data/app/models/ruby_llm/agents/execution/workflow.rb +299 -0
  11. data/app/models/ruby_llm/agents/execution.rb +28 -59
  12. data/app/models/ruby_llm/agents/tenant_budget.rb +165 -0
  13. data/app/services/ruby_llm/agents/agent_registry.rb +118 -7
  14. data/app/views/layouts/ruby_llm/agents/application.html.erb +430 -0
  15. data/app/views/ruby_llm/agents/agents/_empty_state.html.erb +23 -0
  16. data/app/views/ruby_llm/agents/agents/_workflow.html.erb +125 -0
  17. data/app/views/ruby_llm/agents/agents/index.html.erb +93 -0
  18. data/app/views/ruby_llm/agents/agents/show.html.erb +775 -0
  19. data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +112 -0
  20. data/app/views/ruby_llm/agents/dashboard/_budgets_bar.html.erb +75 -0
  21. data/app/views/{rubyllm → ruby_llm}/agents/dashboard/_execution_item.html.erb +7 -4
  22. data/app/views/ruby_llm/agents/dashboard/_now_strip.html.erb +84 -0
  23. data/app/views/ruby_llm/agents/dashboard/_tenant_budget.html.erb +115 -0
  24. data/app/views/ruby_llm/agents/dashboard/_top_errors.html.erb +49 -0
  25. data/app/views/ruby_llm/agents/dashboard/index.html.erb +155 -0
  26. data/app/views/{rubyllm → ruby_llm}/agents/executions/_execution.html.erb +1 -1
  27. data/app/views/{rubyllm → ruby_llm}/agents/executions/_filters.html.erb +39 -11
  28. data/app/views/{rubyllm → ruby_llm}/agents/executions/_list.html.erb +19 -9
  29. data/app/views/ruby_llm/agents/executions/_workflow_summary.html.erb +101 -0
  30. data/app/views/ruby_llm/agents/executions/index.html.erb +88 -0
  31. data/app/views/{rubyllm → ruby_llm}/agents/executions/show.html.erb +260 -200
  32. data/app/views/{rubyllm → ruby_llm}/agents/settings/show.html.erb +1 -1
  33. data/app/views/ruby_llm/agents/shared/_breadcrumbs.html.erb +48 -0
  34. data/app/views/ruby_llm/agents/shared/_executions_table.html.erb +251 -0
  35. data/app/views/{rubyllm → ruby_llm}/agents/shared/_filter_dropdown.html.erb +1 -1
  36. data/app/views/ruby_llm/agents/shared/_nav_link.html.erb +27 -0
  37. data/app/views/{rubyllm → ruby_llm}/agents/shared/_select_dropdown.html.erb +1 -1
  38. data/app/views/{rubyllm → ruby_llm}/agents/shared/_status_badge.html.erb +1 -1
  39. data/app/views/{rubyllm → ruby_llm}/agents/shared/_status_dot.html.erb +1 -1
  40. data/app/views/ruby_llm/agents/shared/_tenant_filter.html.erb +26 -0
  41. data/app/views/ruby_llm/agents/shared/_workflow_type_badge.html.erb +61 -0
  42. data/config/routes.rb +2 -0
  43. data/lib/generators/ruby_llm_agents/multi_tenancy_generator.rb +97 -0
  44. data/lib/generators/ruby_llm_agents/templates/add_attempts_migration.rb.tt +3 -3
  45. data/lib/generators/ruby_llm_agents/templates/add_tenant_to_executions_migration.rb.tt +23 -0
  46. data/lib/generators/ruby_llm_agents/templates/add_tool_calls_migration.rb.tt +2 -2
  47. data/lib/generators/ruby_llm_agents/templates/add_workflow_migration.rb.tt +38 -0
  48. data/lib/generators/ruby_llm_agents/templates/create_tenant_budgets_migration.rb.tt +45 -0
  49. data/lib/generators/ruby_llm_agents/templates/migration.rb.tt +17 -5
  50. data/lib/generators/ruby_llm_agents/upgrade_generator.rb +13 -0
  51. data/lib/ruby_llm/agents/alert_manager.rb +20 -16
  52. data/lib/ruby_llm/agents/base/caching.rb +40 -0
  53. data/lib/ruby_llm/agents/base/cost_calculation.rb +105 -0
  54. data/lib/ruby_llm/agents/base/dsl.rb +261 -0
  55. data/lib/ruby_llm/agents/base/execution.rb +258 -0
  56. data/lib/ruby_llm/agents/base/reliability_execution.rb +136 -0
  57. data/lib/ruby_llm/agents/base/response_building.rb +86 -0
  58. data/lib/ruby_llm/agents/base/tool_tracking.rb +57 -0
  59. data/lib/ruby_llm/agents/base.rb +37 -801
  60. data/lib/ruby_llm/agents/budget_tracker.rb +250 -139
  61. data/lib/ruby_llm/agents/cache_helper.rb +98 -0
  62. data/lib/ruby_llm/agents/circuit_breaker.rb +48 -30
  63. data/lib/ruby_llm/agents/configuration.rb +40 -1
  64. data/lib/ruby_llm/agents/engine.rb +65 -1
  65. data/lib/ruby_llm/agents/inflections.rb +14 -0
  66. data/lib/ruby_llm/agents/instrumentation.rb +66 -0
  67. data/lib/ruby_llm/agents/reliability.rb +8 -2
  68. data/lib/ruby_llm/agents/version.rb +1 -1
  69. data/lib/ruby_llm/agents/workflow/instrumentation.rb +254 -0
  70. data/lib/ruby_llm/agents/workflow/parallel.rb +282 -0
  71. data/lib/ruby_llm/agents/workflow/pipeline.rb +306 -0
  72. data/lib/ruby_llm/agents/workflow/result.rb +390 -0
  73. data/lib/ruby_llm/agents/workflow/router.rb +429 -0
  74. data/lib/ruby_llm/agents/workflow.rb +232 -0
  75. data/lib/ruby_llm/agents.rb +1 -0
  76. metadata +57 -75
  77. data/app/channels/ruby_llm/agents/executions_channel.rb +0 -46
  78. data/app/javascript/ruby_llm/agents/controllers/filter_controller.js +0 -56
  79. data/app/javascript/ruby_llm/agents/controllers/index.js +0 -12
  80. data/app/javascript/ruby_llm/agents/controllers/refresh_controller.js +0 -83
  81. data/app/views/layouts/rubyllm/agents/application.html.erb +0 -626
  82. data/app/views/rubyllm/agents/agents/index.html.erb +0 -20
  83. data/app/views/rubyllm/agents/agents/show.html.erb +0 -772
  84. data/app/views/rubyllm/agents/dashboard/_budgets_bar.html.erb +0 -165
  85. data/app/views/rubyllm/agents/dashboard/_now_strip.html.erb +0 -10
  86. data/app/views/rubyllm/agents/dashboard/_now_strip_values.html.erb +0 -71
  87. data/app/views/rubyllm/agents/dashboard/index.html.erb +0 -197
  88. data/app/views/rubyllm/agents/executions/index.html.erb +0 -28
  89. data/app/views/rubyllm/agents/executions/index.turbo_stream.erb +0 -18
  90. data/app/views/rubyllm/agents/shared/_executions_table.html.erb +0 -193
  91. /data/app/views/{rubyllm → ruby_llm}/agents/agents/_agent.html.erb +0 -0
  92. /data/app/views/{rubyllm → ruby_llm}/agents/agents/_version_comparison.html.erb +0 -0
  93. /data/app/views/{rubyllm → ruby_llm}/agents/dashboard/_action_center.html.erb +0 -0
  94. /data/app/views/{rubyllm → ruby_llm}/agents/dashboard/_alerts_feed.html.erb +0 -0
  95. /data/app/views/{rubyllm → ruby_llm}/agents/dashboard/_breaker_strip.html.erb +0 -0
  96. /data/app/views/{rubyllm → ruby_llm}/agents/executions/dry_run.html.erb +0 -0
  97. /data/app/views/{rubyllm → ruby_llm}/agents/shared/_stat_card.html.erb +0 -0
@@ -0,0 +1,125 @@
1
+ <%
2
+ workflow_colors = {
3
+ "pipeline" => { border: "border-l-indigo-500", icon_bg: "bg-indigo-100 dark:bg-indigo-900/50", icon_text: "text-indigo-600 dark:text-indigo-300" },
4
+ "parallel" => { border: "border-l-cyan-500", icon_bg: "bg-cyan-100 dark:bg-cyan-900/50", icon_text: "text-cyan-600 dark:text-cyan-300" },
5
+ "router" => { border: "border-l-amber-500", icon_bg: "bg-amber-100 dark:bg-amber-900/50", icon_text: "text-amber-600 dark:text-amber-300" }
6
+ }
7
+ colors = workflow_colors[workflow[:workflow_type]] || { border: "border-l-gray-400", icon_bg: "bg-gray-100", icon_text: "text-gray-600" }
8
+
9
+ child_label = case workflow[:workflow_type]
10
+ when "pipeline" then "steps"
11
+ when "parallel" then "branches"
12
+ when "router" then "routes"
13
+ else "children"
14
+ end
15
+ %>
16
+
17
+ <div x-data="{ expanded: false }" class="block bg-white dark:bg-gray-800 rounded-lg shadow hover:shadow-md transition-shadow border-l-4 <%= colors[:border] %>">
18
+ <%= link_to ruby_llm_agents.agent_path(workflow[:name]), class: "block p-4 sm:p-5" do %>
19
+ <!-- Header Row -->
20
+ <div class="flex items-center justify-between gap-2">
21
+ <div class="flex items-center gap-2 min-w-0">
22
+ <%= render "ruby_llm/agents/shared/workflow_type_badge", workflow_type: workflow[:workflow_type], size: :sm %>
23
+ <h3 class="font-semibold text-gray-900 dark:text-gray-100 truncate">
24
+ <%= workflow[:name].gsub(/Workflow$/, '').gsub(/Pipeline$|Parallel$|Router$/, '') %>
25
+ </h3>
26
+ <span class="hidden sm:inline text-sm text-gray-500 dark:text-gray-400">v<%= workflow[:version] %></span>
27
+ <% if workflow[:active] %>
28
+ <span class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-green-100 dark:bg-green-900/50 text-green-800 dark:text-green-300">Active</span>
29
+ <% else %>
30
+ <span class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400">Deleted</span>
31
+ <% end %>
32
+ </div>
33
+ <span class="hidden sm:block text-sm text-gray-500 dark:text-gray-400">
34
+ <%= workflow[:workflow_children]&.size || 0 %> <%= child_label %>
35
+ </span>
36
+ </div>
37
+
38
+ <!-- Stats Row -->
39
+ <div class="mt-2 sm:mt-3 sm:border-t sm:border-gray-100 sm:dark:border-gray-700 sm:pt-3">
40
+ <% success_rate = workflow[:success_rate] || 0 %>
41
+ <!-- Mobile: compact inline -->
42
+ <div class="sm:hidden text-xs text-gray-500 dark:text-gray-400">
43
+ <%= number_with_delimiter(workflow[:execution_count]) %> runs
44
+ <span class="mx-1 text-gray-300 dark:text-gray-600">-</span>
45
+ $<%= number_with_precision(workflow[:total_cost] || 0, precision: 2) %>
46
+ <span class="mx-1 text-gray-300 dark:text-gray-600">-</span>
47
+ <span class="<%= success_rate >= 95 ? 'text-green-600 dark:text-green-400' : success_rate >= 80 ? 'text-yellow-600 dark:text-yellow-400' : 'text-red-600 dark:text-red-400' %>">
48
+ <%= success_rate %>%
49
+ </span>
50
+ </div>
51
+
52
+ <!-- Desktop: full stats with icons -->
53
+ <div class="hidden sm:flex items-center justify-between text-sm">
54
+ <div class="flex items-center space-x-6">
55
+ <div class="flex items-center text-gray-600 dark:text-gray-300">
56
+ <svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
57
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
58
+ </svg>
59
+ <span><%= number_with_delimiter(workflow[:execution_count]) %> executions</span>
60
+ </div>
61
+ <div class="flex items-center text-gray-600 dark:text-gray-300">
62
+ <svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
63
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="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" />
64
+ </svg>
65
+ <span>$<%= number_with_precision(workflow[:total_cost] || 0, precision: 4) %></span>
66
+ </div>
67
+ <div class="flex items-center">
68
+ <% if success_rate >= 95 %>
69
+ <span class="w-2 h-2 rounded-full bg-green-500 mr-1.5"></span>
70
+ <span class="text-green-600 dark:text-green-400"><%= success_rate %>%</span>
71
+ <% elsif success_rate >= 80 %>
72
+ <span class="w-2 h-2 rounded-full bg-yellow-500 mr-1.5"></span>
73
+ <span class="text-yellow-600 dark:text-yellow-400"><%= success_rate %>%</span>
74
+ <% else %>
75
+ <span class="w-2 h-2 rounded-full bg-red-500 mr-1.5"></span>
76
+ <span class="text-red-600 dark:text-red-400"><%= success_rate %>%</span>
77
+ <% end %>
78
+ </div>
79
+ </div>
80
+ <div class="text-gray-400 dark:text-gray-500">
81
+ <% if workflow[:last_executed] %>
82
+ <%= time_ago_in_words(workflow[:last_executed]) %> ago
83
+ <% else %>
84
+ Never executed
85
+ <% end %>
86
+ </div>
87
+ </div>
88
+ </div>
89
+ <% end %>
90
+
91
+ <!-- Expandable Child Agents Section -->
92
+ <% if workflow[:workflow_children].present? && workflow[:workflow_children].any? %>
93
+ <div class="border-t border-gray-100 dark:border-gray-700 px-4 sm:px-5 py-2">
94
+ <button type="button" @click.prevent="expanded = !expanded" class="w-full flex items-center justify-between text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300">
95
+ <span class="font-medium">
96
+ <%= child_label.capitalize %> (<%= workflow[:workflow_children].size %>)
97
+ </span>
98
+ <svg class="w-4 h-4 transition-transform duration-200" :class="{ 'rotate-180': expanded }" fill="none" stroke="currentColor" viewBox="0 0 24 24">
99
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
100
+ </svg>
101
+ </button>
102
+
103
+ <div x-show="expanded" x-cloak x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0 -translate-y-1" x-transition:enter-end="opacity-100 translate-y-0" class="mt-2 space-y-1">
104
+ <% workflow[:workflow_children].each_with_index do |child, index| %>
105
+ <div class="flex items-center gap-2 text-sm py-1.5 px-2 rounded bg-gray-50 dark:bg-gray-900/50">
106
+ <span class="w-5 h-5 flex items-center justify-center rounded-full <%= colors[:icon_bg] %> text-xs <%= colors[:icon_text] %>">
107
+ <%= index + 1 %>
108
+ </span>
109
+ <span class="font-medium text-gray-700 dark:text-gray-300"><%= child[:name] %></span>
110
+ <span class="text-gray-400 dark:text-gray-500">-></span>
111
+ <span class="text-gray-600 dark:text-gray-400 font-mono text-xs"><%= child[:agent] %></span>
112
+ <% if child[:optional] %>
113
+ <span class="text-xs text-gray-400 dark:text-gray-500 italic">(optional)</span>
114
+ <% end %>
115
+ <% if child[:description].present? %>
116
+ <span class="text-xs text-gray-400 dark:text-gray-500 truncate max-w-xs" title="<%= child[:description] %>">
117
+ - <%= child[:description].truncate(30) %>
118
+ </span>
119
+ <% end %>
120
+ </div>
121
+ <% end %>
122
+ </div>
123
+ </div>
124
+ <% end %>
125
+ </div>
@@ -0,0 +1,93 @@
1
+ <div class="mb-6">
2
+ <h1 class="text-2xl font-bold text-gray-900 dark:text-gray-100">Agents & Workflows</h1>
3
+ <p class="text-gray-500 dark:text-gray-400 mt-1">All available agents and workflows with execution statistics</p>
4
+ </div>
5
+
6
+ <div x-data="{ activeTab: 'agents', activeSubTab: null }">
7
+ <!-- Tab Navigation -->
8
+ <div class="border-b border-gray-200 dark:border-gray-700 mb-6">
9
+ <nav class="-mb-px flex space-x-6" aria-label="Tabs">
10
+ <!-- Agents Tab -->
11
+ <button type="button" @click="activeTab = 'agents'; activeSubTab = null"
12
+ :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 hover:border-gray-300'"
13
+ class="whitespace-nowrap py-3 px-1 border-b-2 font-medium text-sm flex items-center gap-2 transition-colors">
14
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
15
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
16
+ </svg>
17
+ Agents
18
+ <span class="inline-flex items-center justify-center px-2 py-0.5 rounded-full text-xs font-medium"
19
+ :class="activeTab === 'agents' ? 'bg-blue-100 dark:bg-blue-900/50 text-blue-600 dark:text-blue-300' : 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400'">
20
+ <%= @agent_count %>
21
+ </span>
22
+ </button>
23
+
24
+ <!-- Workflows Tab -->
25
+ <button type="button" @click="activeTab = 'workflows'; activeSubTab = null"
26
+ :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 hover:border-gray-300'"
27
+ class="whitespace-nowrap py-3 px-1 border-b-2 font-medium text-sm flex items-center gap-2 transition-colors">
28
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
29
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="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"/>
30
+ </svg>
31
+ Workflows
32
+ <span class="inline-flex items-center justify-center px-2 py-0.5 rounded-full text-xs font-medium"
33
+ :class="activeTab === 'workflows' ? 'bg-blue-100 dark:bg-blue-900/50 text-blue-600 dark:text-blue-300' : 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400'">
34
+ <%= @workflow_count %>
35
+ </span>
36
+ </button>
37
+ </nav>
38
+ </div>
39
+
40
+ <!-- Workflow Sub-tabs -->
41
+ <div x-show="activeTab === 'workflows'" x-cloak class="mb-4">
42
+ <div class="flex flex-wrap gap-2">
43
+ <button type="button" @click="activeSubTab = null"
44
+ :class="activeSubTab === null ? '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'"
45
+ class="px-3 py-1.5 rounded-md text-sm font-medium transition-colors">
46
+ All (<%= @workflow_count %>)
47
+ </button>
48
+ <button type="button" @click="activeSubTab = 'pipeline'"
49
+ :class="activeSubTab === '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'"
50
+ class="px-3 py-1.5 rounded-md text-sm font-medium transition-colors inline-flex items-center gap-1.5">
51
+ <span aria-hidden="true">-></span> Pipeline (<%= @workflows_by_type[:pipeline].size %>)
52
+ </button>
53
+ <button type="button" @click="activeSubTab = 'parallel'"
54
+ :class="activeSubTab === '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'"
55
+ class="px-3 py-1.5 rounded-md text-sm font-medium transition-colors inline-flex items-center gap-1.5">
56
+ <span aria-hidden="true">//</span> Parallel (<%= @workflows_by_type[:parallel].size %>)
57
+ </button>
58
+ <button type="button" @click="activeSubTab = 'router'"
59
+ :class="activeSubTab === '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'"
60
+ class="px-3 py-1.5 rounded-md text-sm font-medium transition-colors inline-flex items-center gap-1.5">
61
+ <span aria-hidden="true">&lt;&gt;</span> Router (<%= @workflows_by_type[:router].size %>)
62
+ </button>
63
+ </div>
64
+ </div>
65
+
66
+ <!-- Agents Content -->
67
+ <div x-show="activeTab === 'agents'">
68
+ <% if @agents.empty? %>
69
+ <%= render "ruby_llm/agents/agents/empty_state", type: "agents" %>
70
+ <% else %>
71
+ <div class="space-y-3 sm:space-y-4">
72
+ <% @agents.each do |agent| %>
73
+ <%= render partial: "ruby_llm/agents/agents/agent", locals: { agent: agent } %>
74
+ <% end %>
75
+ </div>
76
+ <% end %>
77
+ </div>
78
+
79
+ <!-- Workflows Content -->
80
+ <div x-show="activeTab === 'workflows'" x-cloak>
81
+ <% if @workflows.empty? %>
82
+ <%= render "ruby_llm/agents/agents/empty_state", type: "workflows" %>
83
+ <% else %>
84
+ <div class="space-y-3 sm:space-y-4">
85
+ <% @workflows.each do |workflow| %>
86
+ <div x-show="activeSubTab === null || activeSubTab === '<%= workflow[:workflow_type] %>'">
87
+ <%= render partial: "ruby_llm/agents/agents/workflow", locals: { workflow: workflow } %>
88
+ </div>
89
+ <% end %>
90
+ </div>
91
+ <% end %>
92
+ </div>
93
+ </div>