ruby_llm-agents 1.3.3 → 2.0.0

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 (192) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +101 -334
  3. data/app/controllers/concerns/ruby_llm/agents/sortable.rb +0 -1
  4. data/app/controllers/ruby_llm/agents/agents_controller.rb +5 -56
  5. data/app/controllers/ruby_llm/agents/dashboard_controller.rb +22 -106
  6. data/app/controllers/ruby_llm/agents/executions_controller.rb +4 -114
  7. data/app/controllers/ruby_llm/agents/tenants_controller.rb +30 -2
  8. data/app/helpers/ruby_llm/agents/application_helper.rb +19 -53
  9. data/app/models/ruby_llm/agents/execution/analytics.rb +13 -54
  10. data/app/models/ruby_llm/agents/execution/scopes.rb +61 -14
  11. data/app/models/ruby_llm/agents/execution.rb +46 -10
  12. data/app/models/ruby_llm/agents/execution_detail.rb +18 -0
  13. data/app/models/ruby_llm/agents/tenant/budgetable.rb +132 -24
  14. data/app/models/ruby_llm/agents/tenant/incrementable.rb +117 -0
  15. data/app/models/ruby_llm/agents/tenant/resettable.rb +128 -0
  16. data/app/models/ruby_llm/agents/tenant/trackable.rb +46 -12
  17. data/app/models/ruby_llm/agents/tenant.rb +2 -3
  18. data/app/models/ruby_llm/agents/tenant_budget.rb +6 -3
  19. data/app/services/ruby_llm/agents/agent_registry.rb +6 -112
  20. data/app/views/layouts/ruby_llm/agents/application.html.erb +87 -252
  21. data/app/views/ruby_llm/agents/agents/_config_agent.html.erb +71 -218
  22. data/app/views/ruby_llm/agents/agents/_config_embedder.html.erb +20 -63
  23. data/app/views/ruby_llm/agents/agents/_config_image_generator.html.erb +44 -131
  24. data/app/views/ruby_llm/agents/agents/_config_moderator.html.erb +16 -57
  25. data/app/views/ruby_llm/agents/agents/_config_speaker.html.erb +39 -104
  26. data/app/views/ruby_llm/agents/agents/_config_transcriber.html.erb +29 -82
  27. data/app/views/ruby_llm/agents/agents/_empty_state.html.erb +4 -14
  28. data/app/views/ruby_llm/agents/agents/index.html.erb +105 -274
  29. data/app/views/ruby_llm/agents/agents/show.html.erb +248 -378
  30. data/app/views/ruby_llm/agents/dashboard/_action_center.html.erb +29 -52
  31. data/app/views/ruby_llm/agents/dashboard/_tenant_budget.html.erb +73 -99
  32. data/app/views/ruby_llm/agents/dashboard/index.html.erb +228 -433
  33. data/app/views/ruby_llm/agents/executions/_execution.html.erb +1 -1
  34. data/app/views/ruby_llm/agents/executions/_filters.html.erb +4 -25
  35. data/app/views/ruby_llm/agents/executions/_list.html.erb +111 -152
  36. data/app/views/ruby_llm/agents/executions/index.html.erb +5 -7
  37. data/app/views/ruby_llm/agents/executions/show.html.erb +528 -989
  38. data/app/views/ruby_llm/agents/shared/_agent_type_badge.html.erb +5 -21
  39. data/app/views/ruby_llm/agents/shared/_executions_table.html.erb +70 -191
  40. data/app/views/ruby_llm/agents/shared/_filter_dropdown.html.erb +16 -44
  41. data/app/views/ruby_llm/agents/shared/_select_dropdown.html.erb +12 -41
  42. data/app/views/ruby_llm/agents/shared/_status_badge.html.erb +11 -65
  43. data/app/views/ruby_llm/agents/shared/_tenant_filter.html.erb +6 -5
  44. data/app/views/ruby_llm/agents/system_config/show.html.erb +240 -351
  45. data/app/views/ruby_llm/agents/tenants/_form.html.erb +67 -77
  46. data/app/views/ruby_llm/agents/tenants/edit.html.erb +7 -9
  47. data/app/views/ruby_llm/agents/tenants/index.html.erb +100 -122
  48. data/app/views/ruby_llm/agents/tenants/show.html.erb +146 -336
  49. data/config/routes.rb +0 -13
  50. data/lib/generators/ruby_llm_agents/install_generator.rb +9 -14
  51. data/lib/generators/ruby_llm_agents/migrate_structure_generator.rb +2 -12
  52. data/lib/generators/ruby_llm_agents/restructure_generator.rb +0 -2
  53. data/lib/generators/ruby_llm_agents/templates/add_usage_counters_to_tenants_migration.rb.tt +37 -0
  54. data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +1 -2
  55. data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +1 -1
  56. data/lib/generators/ruby_llm_agents/templates/application_image_pipeline.rb.tt +0 -1
  57. data/lib/generators/ruby_llm_agents/templates/create_execution_details_migration.rb.tt +27 -0
  58. data/lib/generators/ruby_llm_agents/templates/create_tenants_migration.rb.tt +25 -0
  59. data/lib/generators/ruby_llm_agents/templates/image_pipeline.rb.tt +0 -1
  60. data/lib/generators/ruby_llm_agents/templates/initializer.rb.tt +9 -12
  61. data/lib/generators/ruby_llm_agents/templates/migration.rb.tt +40 -71
  62. data/lib/generators/ruby_llm_agents/templates/remove_agent_version_migration.rb.tt +13 -0
  63. data/lib/generators/ruby_llm_agents/templates/remove_workflow_columns_migration.rb.tt +19 -0
  64. data/lib/generators/ruby_llm_agents/templates/skills/AGENTS.md.tt +2 -4
  65. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_PIPELINES.md.tt +0 -1
  66. data/lib/generators/ruby_llm_agents/templates/split_execution_details_migration.rb.tt +232 -0
  67. data/lib/generators/ruby_llm_agents/upgrade_generator.rb +58 -262
  68. data/lib/ruby_llm/agents/audio/speaker.rb +0 -1
  69. data/lib/ruby_llm/agents/audio/transcriber.rb +0 -1
  70. data/lib/ruby_llm/agents/base_agent.rb +52 -6
  71. data/lib/ruby_llm/agents/core/base/callbacks.rb +142 -0
  72. data/lib/ruby_llm/agents/core/base.rb +23 -55
  73. data/lib/ruby_llm/agents/core/configuration.rb +58 -117
  74. data/lib/ruby_llm/agents/core/errors.rb +0 -58
  75. data/lib/ruby_llm/agents/core/instrumentation.rb +157 -110
  76. data/lib/ruby_llm/agents/core/llm_tenant.rb +8 -7
  77. data/lib/ruby_llm/agents/core/version.rb +1 -1
  78. data/lib/ruby_llm/agents/dsl/base.rb +157 -17
  79. data/lib/ruby_llm/agents/dsl/caching.rb +33 -2
  80. data/lib/ruby_llm/agents/dsl/reliability.rb +148 -0
  81. data/lib/ruby_llm/agents/dsl.rb +1 -2
  82. data/lib/ruby_llm/agents/image/analyzer/execution.rb +1 -2
  83. data/lib/ruby_llm/agents/image/background_remover/execution.rb +1 -2
  84. data/lib/ruby_llm/agents/image/concerns/image_operation_dsl.rb +1 -13
  85. data/lib/ruby_llm/agents/image/concerns/image_operation_execution.rb +2 -2
  86. data/lib/ruby_llm/agents/image/editor/dsl.rb +0 -14
  87. data/lib/ruby_llm/agents/image/editor/execution.rb +1 -10
  88. data/lib/ruby_llm/agents/image/editor.rb +0 -1
  89. data/lib/ruby_llm/agents/image/generator.rb +0 -21
  90. data/lib/ruby_llm/agents/image/pipeline/dsl.rb +0 -13
  91. data/lib/ruby_llm/agents/image/pipeline/execution.rb +0 -1
  92. data/lib/ruby_llm/agents/image/transformer/dsl.rb +0 -13
  93. data/lib/ruby_llm/agents/image/transformer/execution.rb +1 -10
  94. data/lib/ruby_llm/agents/image/transformer.rb +0 -1
  95. data/lib/ruby_llm/agents/image/upscaler/execution.rb +1 -2
  96. data/lib/ruby_llm/agents/image/variator/execution.rb +1 -2
  97. data/lib/ruby_llm/agents/infrastructure/alert_manager.rb +78 -173
  98. data/lib/ruby_llm/agents/infrastructure/attempt_tracker.rb +1 -0
  99. data/lib/ruby_llm/agents/infrastructure/budget/budget_query.rb +66 -2
  100. data/lib/ruby_llm/agents/infrastructure/budget/spend_recorder.rb +0 -12
  101. data/lib/ruby_llm/agents/infrastructure/circuit_breaker.rb +10 -13
  102. data/lib/ruby_llm/agents/infrastructure/reliability.rb +37 -2
  103. data/lib/ruby_llm/agents/pipeline/context.rb +0 -1
  104. data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +28 -4
  105. data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +3 -10
  106. data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +88 -55
  107. data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +5 -41
  108. data/lib/ruby_llm/agents/rails/engine.rb +6 -6
  109. data/lib/ruby_llm/agents/results/base.rb +1 -49
  110. data/lib/ruby_llm/agents/text/embedder.rb +0 -1
  111. data/lib/ruby_llm/agents.rb +1 -9
  112. data/lib/tasks/ruby_llm_agents.rake +34 -0
  113. metadata +12 -81
  114. data/app/controllers/ruby_llm/agents/api_configurations_controller.rb +0 -214
  115. data/app/controllers/ruby_llm/agents/workflows_controller.rb +0 -544
  116. data/app/mailers/ruby_llm/agents/alert_mailer.rb +0 -84
  117. data/app/mailers/ruby_llm/agents/application_mailer.rb +0 -28
  118. data/app/models/ruby_llm/agents/api_configuration.rb +0 -386
  119. data/app/models/ruby_llm/agents/execution/workflow.rb +0 -170
  120. data/app/models/ruby_llm/agents/tenant/configurable.rb +0 -135
  121. data/app/views/ruby_llm/agents/agents/_agent.html.erb +0 -98
  122. data/app/views/ruby_llm/agents/agents/_version_comparison.html.erb +0 -186
  123. data/app/views/ruby_llm/agents/agents/_workflow.html.erb +0 -126
  124. data/app/views/ruby_llm/agents/alert_mailer/alert_notification.html.erb +0 -107
  125. data/app/views/ruby_llm/agents/alert_mailer/alert_notification.text.erb +0 -18
  126. data/app/views/ruby_llm/agents/api_configurations/_api_key_field.html.erb +0 -34
  127. data/app/views/ruby_llm/agents/api_configurations/_form.html.erb +0 -288
  128. data/app/views/ruby_llm/agents/api_configurations/edit.html.erb +0 -95
  129. data/app/views/ruby_llm/agents/api_configurations/edit_tenant.html.erb +0 -97
  130. data/app/views/ruby_llm/agents/api_configurations/show.html.erb +0 -214
  131. data/app/views/ruby_llm/agents/api_configurations/tenant.html.erb +0 -179
  132. data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +0 -73
  133. data/app/views/ruby_llm/agents/dashboard/_alerts_feed.html.erb +0 -62
  134. data/app/views/ruby_llm/agents/dashboard/_breaker_strip.html.erb +0 -47
  135. data/app/views/ruby_llm/agents/dashboard/_budgets_bar.html.erb +0 -75
  136. data/app/views/ruby_llm/agents/dashboard/_model_comparison.html.erb +0 -56
  137. data/app/views/ruby_llm/agents/dashboard/_model_cost_breakdown.html.erb +0 -115
  138. data/app/views/ruby_llm/agents/dashboard/_now_strip.html.erb +0 -59
  139. data/app/views/ruby_llm/agents/dashboard/_top_errors.html.erb +0 -60
  140. data/app/views/ruby_llm/agents/executions/_workflow_summary.html.erb +0 -86
  141. data/app/views/ruby_llm/agents/executions/dry_run.html.erb +0 -149
  142. data/app/views/ruby_llm/agents/shared/_breadcrumbs.html.erb +0 -48
  143. data/app/views/ruby_llm/agents/shared/_nav_link.html.erb +0 -27
  144. data/app/views/ruby_llm/agents/shared/_stat_card.html.erb +0 -14
  145. data/app/views/ruby_llm/agents/shared/_workflow_type_badge.html.erb +0 -35
  146. data/app/views/ruby_llm/agents/workflows/_empty_state.html.erb +0 -22
  147. data/app/views/ruby_llm/agents/workflows/_step_performance.html.erb +0 -228
  148. data/app/views/ruby_llm/agents/workflows/_structure_dsl.html.erb +0 -539
  149. data/app/views/ruby_llm/agents/workflows/_structure_parallel.html.erb +0 -76
  150. data/app/views/ruby_llm/agents/workflows/_structure_pipeline.html.erb +0 -74
  151. data/app/views/ruby_llm/agents/workflows/_structure_router.html.erb +0 -108
  152. data/app/views/ruby_llm/agents/workflows/_workflow_diagram.html.erb +0 -920
  153. data/app/views/ruby_llm/agents/workflows/index.html.erb +0 -179
  154. data/app/views/ruby_llm/agents/workflows/show.html.erb +0 -467
  155. data/lib/generators/ruby_llm_agents/api_configuration_generator.rb +0 -100
  156. data/lib/generators/ruby_llm_agents/templates/add_workflow_migration.rb.tt +0 -38
  157. data/lib/generators/ruby_llm_agents/templates/application_workflow.rb.tt +0 -48
  158. data/lib/generators/ruby_llm_agents/templates/create_api_configurations_migration.rb.tt +0 -90
  159. data/lib/generators/ruby_llm_agents/templates/skills/WORKFLOWS.md.tt +0 -551
  160. data/lib/ruby_llm/agents/core/base/moderation_dsl.rb +0 -181
  161. data/lib/ruby_llm/agents/core/base/moderation_execution.rb +0 -274
  162. data/lib/ruby_llm/agents/core/resolved_config.rb +0 -348
  163. data/lib/ruby_llm/agents/image/generator/content_policy.rb +0 -95
  164. data/lib/ruby_llm/agents/infrastructure/redactor.rb +0 -130
  165. data/lib/ruby_llm/agents/results/moderation_result.rb +0 -158
  166. data/lib/ruby_llm/agents/text/moderator.rb +0 -237
  167. data/lib/ruby_llm/agents/workflow/approval.rb +0 -205
  168. data/lib/ruby_llm/agents/workflow/approval_store.rb +0 -179
  169. data/lib/ruby_llm/agents/workflow/async.rb +0 -220
  170. data/lib/ruby_llm/agents/workflow/async_executor.rb +0 -156
  171. data/lib/ruby_llm/agents/workflow/dsl/executor.rb +0 -467
  172. data/lib/ruby_llm/agents/workflow/dsl/input_schema.rb +0 -244
  173. data/lib/ruby_llm/agents/workflow/dsl/iteration_executor.rb +0 -289
  174. data/lib/ruby_llm/agents/workflow/dsl/parallel_group.rb +0 -107
  175. data/lib/ruby_llm/agents/workflow/dsl/route_builder.rb +0 -150
  176. data/lib/ruby_llm/agents/workflow/dsl/schedule_helpers.rb +0 -187
  177. data/lib/ruby_llm/agents/workflow/dsl/step_config.rb +0 -352
  178. data/lib/ruby_llm/agents/workflow/dsl/step_executor.rb +0 -415
  179. data/lib/ruby_llm/agents/workflow/dsl/wait_config.rb +0 -257
  180. data/lib/ruby_llm/agents/workflow/dsl/wait_executor.rb +0 -317
  181. data/lib/ruby_llm/agents/workflow/dsl.rb +0 -576
  182. data/lib/ruby_llm/agents/workflow/instrumentation.rb +0 -249
  183. data/lib/ruby_llm/agents/workflow/notifiers/base.rb +0 -117
  184. data/lib/ruby_llm/agents/workflow/notifiers/email.rb +0 -117
  185. data/lib/ruby_llm/agents/workflow/notifiers/slack.rb +0 -180
  186. data/lib/ruby_llm/agents/workflow/notifiers/webhook.rb +0 -121
  187. data/lib/ruby_llm/agents/workflow/notifiers.rb +0 -70
  188. data/lib/ruby_llm/agents/workflow/orchestrator.rb +0 -416
  189. data/lib/ruby_llm/agents/workflow/result.rb +0 -592
  190. data/lib/ruby_llm/agents/workflow/thread_pool.rb +0 -185
  191. data/lib/ruby_llm/agents/workflow/throttle_manager.rb +0 -206
  192. data/lib/ruby_llm/agents/workflow/wait_result.rb +0 -213
@@ -12,7 +12,7 @@
12
12
  <%= execution.agent_type.gsub(/Agent$/, '') %>
13
13
  </p>
14
14
  <p class="text-sm text-gray-500">
15
- v<%= execution.agent_version %> · <%= execution.model_id %>
15
+ <%= execution.model_id %>
16
16
  </p>
17
17
  </div>
18
18
  </div>
@@ -3,25 +3,16 @@
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_execution_type = params[:execution_type].presence
7
6
 
8
- has_filters = selected_agents.any? || selected_statuses.any? || params[:days].present? || selected_models.any? || params[:tenant_id].present? || selected_execution_type.present?
7
+ has_filters = selected_agents.any? || selected_statuses.any? || params[:days].present? || selected_models.any? || params[:tenant_id].present?
9
8
  active_filter_count = [
10
9
  selected_agents.any? ? 1 : 0,
11
10
  selected_statuses.any? ? 1 : 0,
12
11
  params[:days].present? ? 1 : 0,
13
12
  selected_models.any? ? 1 : 0,
14
- params[:tenant_id].present? ? 1 : 0,
15
- selected_execution_type.present? ? 1 : 0
13
+ params[:tenant_id].present? ? 1 : 0
16
14
  ].sum
17
15
 
18
- # Execution type options (replaces the old tabs)
19
- execution_type_options = [
20
- { value: "", label: "All Executions" },
21
- { value: "agents", label: "Agents Only" },
22
- { value: "workflows", label: "Workflows" }
23
- ]
24
-
25
16
  status_options = [
26
17
  { value: "success", label: "Success", color: "bg-green-500" },
27
18
  { value: "error", label: "Error", color: "bg-red-500" },
@@ -44,7 +35,7 @@
44
35
  { value: m, label: short_name }
45
36
  end || []
46
37
  %>
47
- <div id="filters-container" class="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-3 mb-6"
38
+ <div id="filters-container" class="mb-6"
48
39
  x-data="{ mobileOpen: false }">
49
40
  <%= form_with url: ruby_llm_agents.executions_path, method: :get,
50
41
  local: true,
@@ -79,18 +70,6 @@
79
70
 
80
71
  <%# Responsive flex: column on mobile, row on desktop %>
81
72
  <div class="flex flex-col md:flex-row md:items-center gap-2">
82
- <%# Execution Type Filter (replaces tabs) %>
83
- <div class="md:w-auto">
84
- <%= render "ruby_llm/agents/shared/select_dropdown",
85
- name: "execution_type",
86
- filter_id: "execution_type",
87
- options: execution_type_options,
88
- selected: selected_execution_type,
89
- icon: "M4 6h16M4 10h16M4 14h16M4 18h16",
90
- width: "w-44",
91
- full_width: true %>
92
- </div>
93
-
94
73
  <%# Status Filter %>
95
74
  <div class="md:w-auto">
96
75
  <%= render "ruby_llm/agents/shared/filter_dropdown",
@@ -194,7 +173,7 @@
194
173
  <span class="md:hidden">Clear</span>
195
174
  <% end %>
196
175
  <% end %>
197
- <%= 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, tenant_id: params[:tenant_id].presence, execution_type: selected_execution_type),
176
+ <%= 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, tenant_id: params[:tenant_id].presence),
198
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-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",
199
178
  title: "Export CSV" do %>
200
179
  <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -1,185 +1,144 @@
1
- <div id="executions-list" class="bg-white dark:bg-gray-800 rounded-lg shadow overflow-hidden">
1
+ <%# Show tenant column when multi-tenancy is enabled and no specific tenant is selected %>
2
+ <% show_tenant_column = tenant_filter_enabled? && current_tenant_id.blank? %>
3
+
4
+ <%
5
+ # Inline sort helper
6
+ sort_column = @sort_params[:column]
7
+ sort_direction = @sort_params[:direction]
8
+
9
+ sort_link = ->(column, label, align: "left", extra_class: "") {
10
+ is_active = column == sort_column
11
+ next_dir = is_active && sort_direction == "asc" ? "desc" : "asc"
12
+ url = url_for(request.query_parameters.merge(sort: column, direction: next_dir, page: 1))
13
+
14
+ arrow = if is_active && sort_direction == "asc"
15
+ raw('<svg class="w-2.5 h-2.5 inline" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15l7-7 7 7"/></svg>')
16
+ elsif is_active
17
+ raw('<svg class="w-2.5 h-2.5 inline" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>')
18
+ else
19
+ ""
20
+ end
21
+
22
+ active_class = is_active ? "text-gray-700 dark:text-gray-300" : ""
23
+
24
+ raw(%(<a href="#{url}" class="group inline-flex items-center gap-0.5 hover:text-gray-700 dark:hover:text-gray-300 #{active_class} #{extra_class}"><span>#{label}</span><span class="#{is_active ? 'opacity-100' : 'opacity-0 group-hover:opacity-50'} transition-opacity">#{arrow}</span></a>))
25
+ }
26
+ %>
27
+
28
+ <div id="executions-list">
2
29
  <% if executions.empty? %>
3
- <div class="px-6 py-12 text-center text-gray-500 dark:text-gray-400">
4
- <p class="text-lg">No executions found</p>
5
- <p class="text-sm mt-1">Try adjusting your filters</p>
6
- </div>
30
+ <div class="font-mono text-xs text-gray-400 dark:text-gray-600 py-4 px-2">no executions found</div>
7
31
  <% else %>
8
- <div class="overflow-x-auto">
9
- <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
10
- <thead class="bg-gray-50 dark:bg-gray-900">
11
- <tr>
12
- <%= render "ruby_llm/agents/shared/sortable_header",
13
- column: "agent_type", label: "Agent",
14
- current_sort: @sort_params[:column], current_direction: @sort_params[:direction] %>
15
-
16
- <%= render "ruby_llm/agents/shared/sortable_header",
17
- column: "status", label: "Status",
18
- current_sort: @sort_params[:column], current_direction: @sort_params[:direction] %>
19
-
20
- <%= render "ruby_llm/agents/shared/sortable_header",
21
- column: "model_id", label: "Model",
22
- current_sort: @sort_params[:column], current_direction: @sort_params[:direction] %>
23
-
24
- <%= render "ruby_llm/agents/shared/sortable_header",
25
- column: "agent_version", label: "Version",
26
- current_sort: @sort_params[:column], current_direction: @sort_params[:direction] %>
27
-
28
- <%= render "ruby_llm/agents/shared/sortable_header",
29
- column: "total_tokens", label: "Tokens",
30
- current_sort: @sort_params[:column], current_direction: @sort_params[:direction] %>
31
-
32
- <%= render "ruby_llm/agents/shared/sortable_header",
33
- column: "total_cost", label: "Cost",
34
- current_sort: @sort_params[:column], current_direction: @sort_params[:direction] %>
35
-
36
- <%= render "ruby_llm/agents/shared/sortable_header",
37
- column: "duration_ms", label: "Duration",
38
- current_sort: @sort_params[:column], current_direction: @sort_params[:direction] %>
39
-
40
- <%= render "ruby_llm/agents/shared/sortable_header",
41
- column: "created_at", label: "Time",
42
- current_sort: @sort_params[:column], current_direction: @sort_params[:direction] %>
43
- </tr>
44
- </thead>
45
-
46
- <tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-100/50 dark:divide-gray-700/50">
47
- <% executions.each_with_index do |execution, index| %>
48
- <% attempts_count = (execution.respond_to?(:attempts_count) && execution.attempts_count) || (execution.respond_to?(:attempts) && execution.attempts&.size) || 1 %>
49
- <% row_bg = index.even? ? '' : 'bg-gray-50/50 dark:bg-gray-900/30' %>
50
- <tr
51
- class="<%= row_bg %> hover:bg-blue-50/50 dark:hover:bg-blue-900/20 transition-colors cursor-pointer"
52
- id="execution-row-<%= execution.id %>"
53
- onclick="window.location='<%= ruby_llm_agents.execution_path(execution) %>'"
54
- >
55
- <td class="px-4 py-3 whitespace-nowrap">
56
- <div class="flex items-center gap-2">
57
- <% if execution.respond_to?(:workflow_type) && execution.workflow_type.present? %>
58
- <%= render "ruby_llm/agents/shared/workflow_type_badge", workflow_type: execution.workflow_type, size: :xs, show_label: false %>
59
- <% end %>
60
- <span class="text-sm font-medium text-gray-900 dark:text-gray-100">
61
- <%= execution.agent_type.gsub(/Agent$|Workflow$|Pipeline$|Parallel$|Router$/, '') %>
62
- </span>
63
- </div>
64
- <% if execution.respond_to?(:parent_execution_id) && execution.parent_execution_id.present? %>
65
- <span class="text-xs text-gray-400 dark:text-gray-500 ml-5 block">
66
- child of #<%= execution.parent_execution_id %>
67
- </span>
68
- <% end %>
69
- </td>
70
-
71
- <td class="px-4 py-3 whitespace-nowrap">
72
- <div>
73
- <%= render "ruby_llm/agents/shared/status_badge", status: execution.status, attempts_count: attempts_count %>
74
- </div>
75
- <% if execution.status_error? && execution.error_class.present? %>
76
- <span class="text-xs text-red-500 dark:text-red-400 block mt-0.5">
77
- <%= execution.error_class.split("::").last %>
78
- </span>
79
- <% end %>
80
- </td>
81
-
82
- <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
83
- <span title="<%= execution.model_id %>">
84
- <%= execution.model_id&.split("/")&.last || execution.model_id || "-" %>
85
- </span>
86
- </td>
87
-
88
- <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
89
- v<%= execution.agent_version %>
90
- </td>
91
-
92
- <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700 dark:text-gray-300 font-mono">
93
- <%= number_to_human_short(execution.total_tokens || 0) %>
94
- </td>
95
-
96
- <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700 dark:text-gray-300 font-mono">
97
- <%= number_to_human_short(execution.total_cost || 0, prefix: "$", precision: 2) %>
98
- </td>
99
-
100
- <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700 dark:text-gray-300 font-mono">
101
- <%= number_with_delimiter(execution.duration_ms || 0) %>ms
102
- </td>
32
+ <!-- Column headers -->
33
+ <div class="flex items-center gap-3 px-2 -mx-2 font-mono text-[10px] text-gray-400 dark:text-gray-600 uppercase tracking-wider mb-1">
34
+ <span class="flex-1 min-w-0"><%= sort_link.call("agent_type", "agent") %></span>
35
+ <span class="w-20 flex-shrink-0"><%= sort_link.call("status", "status") %></span>
36
+ <% if show_tenant_column %>
37
+ <span class="flex-1 min-w-0 hidden sm:block">tenant</span>
38
+ <% end %>
39
+ <span class="flex-1 min-w-0 hidden lg:block"><%= sort_link.call("model_id", "model") %></span>
40
+ <span class="w-16 flex-shrink-0 text-right"><%= sort_link.call("duration_ms", "duration", extra_class: "justify-end w-full") %></span>
41
+ <span class="w-16 flex-shrink-0 text-right hidden md:block"><%= sort_link.call("total_tokens", "tokens", extra_class: "justify-end w-full") %></span>
42
+ <span class="w-16 flex-shrink-0 text-right hidden md:block"><%= sort_link.call("total_cost", "cost", extra_class: "justify-end w-full") %></span>
43
+ <span class="w-24 flex-shrink-0 text-right"><%= sort_link.call("created_at", "time", extra_class: "justify-end w-full") %></span>
44
+ </div>
103
45
 
104
- <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
105
- <%= time_ago_in_words(execution.created_at) %> ago
106
- </td>
107
- </tr>
46
+ <!-- Rows -->
47
+ <div class="font-mono text-xs space-y-px">
48
+ <% executions.each do |execution| %>
49
+ <% attempts_count = (execution.respond_to?(:attempts_count) && execution.attempts_count) || (execution.respond_to?(:attempts) && execution.attempts&.size) || 1 %>
50
+ <%
51
+ status_badge_class = case execution.status.to_s
52
+ when "running" then "badge-running"
53
+ when "success" then "badge-success"
54
+ when "error" then "badge-error"
55
+ when "timeout" then "badge-timeout"
56
+ else "badge-timeout"
57
+ end
58
+ %>
59
+ <div class="group flex items-center gap-3 py-1.5 px-2 -mx-2 rounded hover:bg-gray-100 dark:hover:bg-gray-800/50 cursor-pointer"
60
+ onclick="window.location='<%= ruby_llm_agents.execution_path(execution) %>'">
61
+ <span class="flex-1 min-w-0 truncate">
62
+ <%= link_to execution.agent_type.gsub(/Agent$/, ""),
63
+ ruby_llm_agents.agent_path(execution.agent_type),
64
+ class: "text-gray-900 dark:text-gray-200 hover:underline",
65
+ onclick: "event.stopPropagation()" %>
66
+ </span>
67
+ <span class="w-20 flex-shrink-0">
68
+ <span class="badge badge-sm <%= status_badge_class %>"><%= execution.status %></span>
69
+ </span>
70
+ <% if show_tenant_column %>
71
+ <span class="flex-1 min-w-0 truncate text-gray-400 dark:text-gray-600 hidden sm:inline"><%= execution.tenant_id.present? ? truncate(execution.tenant_id, length: 30) : "—" %></span>
108
72
  <% end %>
109
- </tbody>
110
- </table>
73
+ <span class="flex-1 min-w-0 truncate text-gray-400 dark:text-gray-600 hidden lg:inline" title="<%= execution.model_id %>"><%= execution.model_id %></span>
74
+ <span class="w-16 flex-shrink-0 text-right text-gray-500 dark:text-gray-400">
75
+ <% if execution.status_running? %>
76
+ <span class="text-blue-500 animate-pulse">...</span>
77
+ <% else %>
78
+ <%= execution.duration_ms ? format_duration_ms(execution.duration_ms) : "—" %>
79
+ <% end %>
80
+ </span>
81
+ <span class="w-16 flex-shrink-0 text-right text-gray-500 dark:text-gray-400 hidden md:inline"><%= number_with_delimiter(execution.total_tokens || 0) %></span>
82
+ <span class="w-16 flex-shrink-0 text-right text-gray-500 dark:text-gray-400 hidden md:inline">$<%= number_with_precision(execution.total_cost || 0, precision: 4) %></span>
83
+ <span class="w-24 flex-shrink-0 text-gray-400 dark:text-gray-600 text-right whitespace-nowrap"><%= time_ago_in_words(execution.created_at) %></span>
84
+ </div>
85
+ <% if execution.status_error? && execution.error_class.present? %>
86
+ <div class="flex items-center gap-1 pl-5 py-0.5 text-red-400 dark:text-red-500/70 text-xs font-mono">
87
+ <span class="text-gray-300 dark:text-gray-700">└</span>
88
+ <span class="truncate"><%= execution.error_class.split("::").last %><%= execution.error_message.present? ? ": #{truncate(execution.error_message, length: 100)}" : "" %></span>
89
+ </div>
90
+ <% end %>
91
+ <% end %>
111
92
  </div>
93
+
94
+ <%# Pagination %>
112
95
  <% if pagination && pagination[:total_pages] > 1 %>
113
- <% current_page = pagination[:current_page]
96
+ <%
97
+ current_page = pagination[:current_page]
114
98
  total_pages = pagination[:total_pages]
115
99
  total_count = pagination[:total_count]
116
100
  per_page = pagination[:per_page]
117
-
118
101
  from_record = ((current_page - 1) * per_page) + 1
119
- to_record = [current_page * per_page, total_count].min %>
120
- <div class="px-4 py-4 flex items-center justify-between border-t border-gray-100 dark:border-gray-700">
121
- <p class="text-sm text-gray-500 dark:text-gray-400">
122
- Showing <%= from_record %>-<%= to_record %> of
123
- <%= number_with_delimiter(total_count) %> executions
124
- </p>
125
-
126
- <nav class="flex items-center space-x-1">
102
+ to_record = [current_page * per_page, total_count].min
103
+ %>
104
+ <div class="flex items-center justify-between font-mono text-xs mt-4 pt-4 border-t border-gray-200 dark:border-gray-800">
105
+ <span class="text-gray-400 dark:text-gray-600"><%= from_record %>-<%= to_record %> of <%= number_with_delimiter(total_count) %></span>
106
+ <nav class="flex items-center gap-1">
127
107
  <% if current_page > 1 %>
128
- <%= link_to "Previous", url_for(request.query_parameters.merge(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" %>
108
+ <%= link_to "prev", url_for(request.query_parameters.merge(page: current_page - 1)),
109
+ class: "px-2 py-0.5 text-gray-400 dark:text-gray-500 hover:text-gray-700 dark:hover:text-gray-300" %>
129
110
  <% else %>
130
- <span
131
- class="
132
- px-3 py-1.5 text-sm font-medium text-gray-400 dark:text-gray-500 bg-gray-100 dark:bg-gray-800
133
- border border-gray-200 dark:border-gray-700 rounded-md cursor-not-allowed
134
- "
135
- >
136
- Previous
137
- </span>
111
+ <span class="px-2 py-0.5 text-gray-300 dark:text-gray-700">prev</span>
138
112
  <% end %>
139
113
 
140
- <% window = 2
141
- left_edge = 1
142
- right_edge = 1
143
-
114
+ <%
115
+ window = 2
144
116
  pages_to_show = []
145
117
  (1..total_pages).each do |page|
146
- if page <= left_edge ||
147
- page > total_pages - right_edge ||
148
- (page >= current_page - window && page <= current_page + window)
118
+ if page <= 1 || page >= total_pages || (page >= current_page - window && page <= current_page + window)
149
119
  pages_to_show << page
150
120
  elsif pages_to_show.last != :gap
151
121
  pages_to_show << :gap
152
122
  end
153
- end %>
123
+ end
124
+ %>
154
125
 
155
126
  <% pages_to_show.each do |page| %>
156
127
  <% if page == :gap %>
157
- <span class="px-2 py-1.5 text-sm text-gray-500 dark:text-gray-400">...</span>
128
+ <span class="px-1 text-gray-400 dark:text-gray-600">...</span>
158
129
  <% elsif page == current_page %>
159
- <span
160
- class="
161
- px-3 py-1.5 text-sm font-medium text-white bg-blue-600
162
- border border-blue-600 rounded-md
163
- "
164
- >
165
- <%= page %>
166
- </span>
130
+ <span class="px-2 py-0.5 text-gray-900 dark:text-gray-100"><%= page %></span>
167
131
  <% else %>
168
- <%= link_to page, url_for(request.query_parameters.merge(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" %>
132
+ <%= link_to page, url_for(request.query_parameters.merge(page: page)),
133
+ class: "px-2 py-0.5 text-gray-400 dark:text-gray-500 hover:text-gray-700 dark:hover:text-gray-300" %>
169
134
  <% end %>
170
135
  <% end %>
171
136
 
172
137
  <% if current_page < total_pages %>
173
- <%= link_to "Next", url_for(request.query_parameters.merge(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" %>
138
+ <%= link_to "next", url_for(request.query_parameters.merge(page: current_page + 1)),
139
+ class: "px-2 py-0.5 text-gray-400 dark:text-gray-500 hover:text-gray-700 dark:hover:text-gray-300" %>
174
140
  <% else %>
175
- <span
176
- class="
177
- px-3 py-1.5 text-sm font-medium text-gray-400 dark:text-gray-500 bg-gray-100 dark:bg-gray-800
178
- border border-gray-200 dark:border-gray-700 rounded-md cursor-not-allowed
179
- "
180
- >
181
- Next
182
- </span>
141
+ <span class="px-2 py-0.5 text-gray-300 dark:text-gray-700">next</span>
183
142
  <% end %>
184
143
  </nav>
185
144
  </div>
@@ -1,12 +1,10 @@
1
- <div class="mb-6">
2
- <div class="flex items-center gap-2">
3
- <h1 class="text-2xl font-bold text-gray-900 dark:text-gray-100">Executions</h1>
4
- <%= render "ruby_llm/agents/shared/doc_link" %>
5
- </div>
6
- <p class="text-gray-500 dark:text-gray-400 mt-1">All agent and workflow execution history</p>
1
+ <div class="flex items-center gap-3 mb-6">
2
+ <span class="text-[10px] font-medium text-gray-400 dark:text-gray-600 uppercase tracking-widest font-mono">executions</span>
3
+ <%= render "ruby_llm/agents/shared/doc_link" %>
4
+ <div class="flex-1 border-t border-gray-200 dark:border-gray-800"></div>
7
5
  </div>
8
6
 
9
7
  <div id="executions_content">
10
- <%= render partial: "ruby_llm/agents/executions/filters", locals: { agent_types: @agent_types, model_ids: @model_ids, workflow_types: @workflow_types, filter_stats: @filter_stats } %>
8
+ <%= render partial: "ruby_llm/agents/executions/filters", locals: { agent_types: @agent_types, model_ids: @model_ids, filter_stats: @filter_stats } %>
11
9
  <%= render partial: "ruby_llm/agents/executions/list", locals: { executions: @executions, pagination: @pagination, filter_stats: @filter_stats } %>
12
10
  </div>