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
@@ -1,13 +1,13 @@
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">Agents</h1>
4
- <%= render "ruby_llm/agents/shared/doc_link" %>
5
- </div>
6
- <p class="text-gray-500 dark:text-gray-400 mt-1">All available agents with execution statistics</p>
7
- </div>
8
-
9
1
  <%
10
2
  audio_count = @agents_by_type[:speaker].size + @agents_by_type[:transcriber].size
3
+ sc = @sort_params[:column]
4
+ sd = @sort_params[:direction]
5
+ sort_url = ->(col) {
6
+ dir = (col == sc && sd == "asc") ? "desc" : "asc"
7
+ url_for(request.query_parameters.merge(sort: col, direction: dir))
8
+ }
9
+ sort_active = ->(col) { col == sc ? "text-gray-600 dark:text-gray-300" : "" }
10
+ sort_arrow = ->(col) { col == sc ? (sd == "asc" ? " ↑" : " ↓") : "" }
11
11
  %>
12
12
  <div x-data="{
13
13
  activeSubTab: <%= params[:subtab].present? ? "'#{params[:subtab]}'" : 'null' %>,
@@ -15,9 +15,6 @@
15
15
  all: <%= @agent_count %>,
16
16
  agent: <%= @agents_by_type[:agent].size %>,
17
17
  embedder: <%= @agents_by_type[:embedder].size %>,
18
- moderator: <%= @agents_by_type[:moderator].size %>,
19
- speaker: <%= @agents_by_type[:speaker].size %>,
20
- transcriber: <%= @agents_by_type[:transcriber].size %>,
21
18
  audio: <%= audio_count %>,
22
19
  image_generator: <%= @agents_by_type[:image_generator].size %>,
23
20
  deleted: <%= @deleted_count %>
@@ -37,292 +34,126 @@
37
34
  history.replaceState({}, '', url);
38
35
  }
39
36
  }">
40
- <!-- Agent Sub-tabs -->
41
- <div class="mb-4">
42
- <div class="flex flex-wrap gap-2">
37
+ <!-- ── agents ──────────────────────────── -->
38
+ <div class="flex items-center gap-3 mb-4">
39
+ <span class="text-[10px] font-medium text-gray-400 dark:text-gray-600 uppercase tracking-widest font-mono">agents</span>
40
+ <div class="font-mono text-xs text-gray-400 dark:text-gray-500 flex items-center gap-1.5">
41
+ <span x-text="currentCount"></span>
42
+ <%= render "ruby_llm/agents/shared/doc_link" %>
43
+ </div>
44
+ <div class="flex-1 border-t border-gray-200 dark:border-gray-800"></div>
45
+ <div class="flex font-mono text-xs gap-0.5">
43
46
  <button type="button" @click="activeSubTab = null; updateUrl()"
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 (<%= @agent_count %>)
47
- </button>
47
+ :class="activeSubTab === null ? 'text-gray-900 dark:text-gray-100' : 'text-gray-400 dark:text-gray-500 hover:text-gray-700 dark:hover:text-gray-300'"
48
+ class="px-2 py-0.5">all</button>
48
49
  <button type="button" @click="activeSubTab = 'agent'; updateUrl()"
49
- :class="activeSubTab === 'agent' ? 'bg-blue-600 text-white' : 'bg-blue-100 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 hover:bg-blue-200 dark:hover:bg-blue-800'"
50
- class="px-3 py-1.5 rounded-md text-sm font-medium transition-colors">
51
- Agents (<%= @agents_by_type[:agent].size %>)
52
- </button>
50
+ :class="activeSubTab === 'agent' ? 'text-gray-900 dark:text-gray-100' : 'text-gray-400 dark:text-gray-500 hover:text-gray-700 dark:hover:text-gray-300'"
51
+ class="px-2 py-0.5">agents</button>
53
52
  <button type="button" @click="activeSubTab = 'embedder'; updateUrl()"
54
- :class="activeSubTab === 'embedder' ? 'bg-purple-600 text-white' : 'bg-purple-100 dark:bg-purple-900/50 text-purple-700 dark:text-purple-300 hover:bg-purple-200 dark:hover:bg-purple-800'"
55
- class="px-3 py-1.5 rounded-md text-sm font-medium transition-colors">
56
- Embedders (<%= @agents_by_type[:embedder].size %>)
57
- </button>
58
- <button type="button" @click="activeSubTab = 'moderator'; updateUrl()"
59
- :class="activeSubTab === 'moderator' ? 'bg-orange-600 text-white' : 'bg-orange-100 dark:bg-orange-900/50 text-orange-700 dark:text-orange-300 hover:bg-orange-200 dark:hover:bg-orange-800'"
60
- class="px-3 py-1.5 rounded-md text-sm font-medium transition-colors">
61
- Moderators (<%= @agents_by_type[:moderator].size %>)
62
- </button>
53
+ :class="activeSubTab === 'embedder' ? 'text-gray-900 dark:text-gray-100' : 'text-gray-400 dark:text-gray-500 hover:text-gray-700 dark:hover:text-gray-300'"
54
+ class="px-2 py-0.5">embedders</button>
63
55
  <% if audio_count > 0 %>
64
56
  <button type="button" @click="activeSubTab = 'audio'; updateUrl()"
65
- :class="activeSubTab === 'audio' ? 'bg-pink-600 text-white' : 'bg-pink-100 dark:bg-pink-900/50 text-pink-700 dark:text-pink-300 hover:bg-pink-200 dark:hover:bg-pink-800'"
66
- class="px-3 py-1.5 rounded-md text-sm font-medium transition-colors">
67
- Audio (<%= audio_count %>)
68
- </button>
57
+ :class="activeSubTab === 'audio' ? 'text-gray-900 dark:text-gray-100' : 'text-gray-400 dark:text-gray-500 hover:text-gray-700 dark:hover:text-gray-300'"
58
+ class="px-2 py-0.5">audio</button>
69
59
  <% end %>
70
60
  <% if @agents_by_type[:image_generator].size > 0 %>
71
61
  <button type="button" @click="activeSubTab = 'image_generator'; updateUrl()"
72
- :class="activeSubTab === 'image_generator' ? 'bg-teal-600 text-white' : 'bg-teal-100 dark:bg-teal-900/50 text-teal-700 dark:text-teal-300 hover:bg-teal-200 dark:hover:bg-teal-800'"
73
- class="px-3 py-1.5 rounded-md text-sm font-medium transition-colors">
74
- Image Generators (<%= @agents_by_type[:image_generator].size %>)
75
- </button>
62
+ :class="activeSubTab === 'image_generator' ? 'text-gray-900 dark:text-gray-100' : 'text-gray-400 dark:text-gray-500 hover:text-gray-700 dark:hover:text-gray-300'"
63
+ class="px-2 py-0.5">image</button>
76
64
  <% end %>
77
65
  <% if @deleted_count > 0 %>
78
66
  <button type="button" @click="activeSubTab = 'deleted'; updateUrl()"
79
- :class="activeSubTab === 'deleted' ? 'bg-gray-600 text-white' : 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-600'"
80
- class="px-3 py-1.5 rounded-md text-sm font-medium transition-colors">
81
- Deleted (<%= @deleted_count %>)
82
- </button>
67
+ :class="activeSubTab === 'deleted' ? 'text-gray-900 dark:text-gray-100' : 'text-gray-400 dark:text-gray-500 hover:text-gray-700 dark:hover:text-gray-300'"
68
+ class="px-2 py-0.5">deleted</button>
83
69
  <% end %>
84
70
  </div>
85
71
  </div>
86
72
 
87
- <!-- Agents Table -->
88
73
  <% if @agents.empty? && @deleted_agents.empty? %>
89
74
  <%= render "ruby_llm/agents/agents/empty_state", type: "agents" %>
90
75
  <% else %>
91
- <div class="bg-white dark:bg-gray-800 rounded-lg shadow overflow-hidden">
92
- <div class="overflow-x-auto">
93
- <table class="w-full divide-y divide-gray-200 dark:divide-gray-700">
94
- <thead class="bg-gray-50 dark:bg-gray-900">
95
- <tr>
96
- <%= render "ruby_llm/agents/agents/sortable_header",
97
- column: "name", label: "Name",
98
- current_sort: @sort_params[:column], current_direction: @sort_params[:direction] %>
99
-
100
- <%= render "ruby_llm/agents/agents/sortable_header",
101
- column: "agent_type", label: "Type",
102
- current_sort: @sort_params[:column], current_direction: @sort_params[:direction] %>
103
-
104
- <th scope="col" class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
105
- Status
106
- </th>
107
-
108
- <%= render "ruby_llm/agents/agents/sortable_header",
109
- column: "model", label: "Model",
110
- current_sort: @sort_params[:column], current_direction: @sort_params[:direction],
111
- th_class: "hidden lg:table-cell" %>
112
-
113
- <%= render "ruby_llm/agents/agents/sortable_header",
114
- column: "execution_count", label: "Executions",
115
- current_sort: @sort_params[:column], current_direction: @sort_params[:direction] %>
116
-
117
- <%= render "ruby_llm/agents/agents/sortable_header",
118
- column: "total_cost", label: "Cost",
119
- current_sort: @sort_params[:column], current_direction: @sort_params[:direction],
120
- th_class: "hidden md:table-cell" %>
121
-
122
- <%= render "ruby_llm/agents/agents/sortable_header",
123
- column: "success_rate", label: "Success",
124
- current_sort: @sort_params[:column], current_direction: @sort_params[:direction] %>
125
-
126
- <%= render "ruby_llm/agents/agents/sortable_header",
127
- column: "last_executed", label: "Last Run",
128
- current_sort: @sort_params[:column], current_direction: @sort_params[:direction] %>
129
- </tr>
130
- </thead>
131
-
132
- <tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-100/50 dark:divide-gray-700/50">
133
- <% @agents.each_with_index do |agent, index| %>
134
- <%
135
- # Determine if this agent should be shown based on sub-tab (exclude deleted tab)
136
- show_condition = if agent[:agent_type] == 'speaker' || agent[:agent_type] == 'transcriber'
137
- "activeSubTab !== 'deleted' && (activeSubTab === null || activeSubTab === 'audio' || activeSubTab === '#{agent[:agent_type]}')"
138
- else
139
- "activeSubTab !== 'deleted' && (activeSubTab === null || activeSubTab === '#{agent[:agent_type]}')"
140
- end
141
- row_bg = index.even? ? '' : 'bg-gray-50/50 dark:bg-gray-900/30'
142
- success_rate = agent[:success_rate] || 0
143
- %>
144
- <tr
145
- x-show="<%= show_condition %>"
146
- class="<%= row_bg %> hover:bg-blue-50/50 dark:hover:bg-blue-900/20 transition-colors cursor-pointer"
147
- onclick="window.location='<%= ruby_llm_agents.agent_path(ERB::Util.url_encode(agent[:name]), subtab: params[:subtab]) %>'"
148
- >
149
- <!-- Name -->
150
- <td class="px-4 py-3">
151
- <div class="flex items-center gap-2">
152
- <span class="text-sm font-medium text-gray-900 dark:text-gray-100">
153
- <%= agent[:name].split('::').last.gsub(/Agent$/, '') %>
154
- </span>
155
- <span class="hidden lg:inline text-xs text-gray-400 dark:text-gray-500">v<%= agent[:version] %></span>
156
- </div>
157
- <% if agent[:description].present? %>
158
- <p class="text-xs text-gray-500 dark:text-gray-400 truncate max-w-64 mt-0.5" title="<%= agent[:description] %>">
159
- <%= agent[:description] %>
160
- </p>
161
- <% end %>
162
- </td>
163
-
164
- <!-- Type -->
165
- <td class="px-4 py-3 whitespace-nowrap">
166
- <span class="text-sm text-gray-600 dark:text-gray-400">
167
- <%= agent[:agent_type]&.titleize || 'Agent' %>
168
- </span>
169
- </td>
170
-
171
- <!-- Status -->
172
- <td class="px-4 py-3 whitespace-nowrap">
173
- <% if agent[:active] %>
174
- <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 dark:bg-green-900/50 text-green-800 dark:text-green-300">
175
- Active
176
- </span>
177
- <% else %>
178
- <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400">
179
- Deleted
180
- </span>
181
- <% end %>
182
- </td>
183
-
184
- <!-- Model -->
185
- <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400 hidden lg:table-cell">
186
- <span title="<%= agent[:model] %>">
187
- <%= agent[:model]&.split("/")&.last || agent[:model] || "-" %>
188
- </span>
189
- </td>
190
-
191
- <!-- Executions -->
192
- <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700 dark:text-gray-300 font-mono">
193
- <%= number_with_delimiter(agent[:execution_count] || 0) %>
194
- </td>
195
-
196
- <!-- Cost -->
197
- <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700 dark:text-gray-300 font-mono hidden md:table-cell">
198
- $<%= number_with_precision(agent[:total_cost] || 0, precision: 4) %>
199
- </td>
200
-
201
- <!-- Success Rate -->
202
- <td class="px-4 py-3 whitespace-nowrap">
203
- <div class="flex items-center gap-1.5">
204
- <% if success_rate >= 95 %>
205
- <span class="w-2 h-2 rounded-full bg-green-500"></span>
206
- <span class="text-sm text-green-600 dark:text-green-400"><%= success_rate %>%</span>
207
- <% elsif success_rate >= 80 %>
208
- <span class="w-2 h-2 rounded-full bg-yellow-500"></span>
209
- <span class="text-sm text-yellow-600 dark:text-yellow-400"><%= success_rate %>%</span>
210
- <% else %>
211
- <span class="w-2 h-2 rounded-full bg-red-500"></span>
212
- <span class="text-sm text-red-600 dark:text-red-400"><%= success_rate %>%</span>
213
- <% end %>
214
- </div>
215
- </td>
76
+ <!-- Column headers -->
77
+ <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">
78
+ <%= link_to "name#{sort_arrow.call('name')}".html_safe, sort_url.call("name"),
79
+ class: "flex-[2] min-w-0 hover:text-gray-600 dark:hover:text-gray-300 #{sort_active.call('name')}" %>
80
+ <%= link_to "type#{sort_arrow.call('agent_type')}".html_safe, sort_url.call("agent_type"),
81
+ class: "flex-1 min-w-0 hidden sm:block hover:text-gray-600 dark:hover:text-gray-300 #{sort_active.call('agent_type')}" %>
82
+ <span class="w-1.5 flex-shrink-0"></span>
83
+ <%= link_to "model#{sort_arrow.call('model')}".html_safe, sort_url.call("model"),
84
+ class: "flex-[2] min-w-0 hidden lg:block hover:text-gray-600 dark:hover:text-gray-300 #{sort_active.call('model')}" %>
85
+ <%= link_to "runs#{sort_arrow.call('execution_count')}".html_safe, sort_url.call("execution_count"),
86
+ class: "w-14 flex-shrink-0 text-right hover:text-gray-600 dark:hover:text-gray-300 #{sort_active.call('execution_count')}" %>
87
+ <%= link_to "cost#{sort_arrow.call('total_cost')}".html_safe, sort_url.call("total_cost"),
88
+ class: "w-16 flex-shrink-0 text-right hidden md:block hover:text-gray-600 dark:hover:text-gray-300 #{sort_active.call('total_cost')}" %>
89
+ <%= link_to "ok%#{sort_arrow.call('success_rate')}".html_safe, sort_url.call("success_rate"),
90
+ class: "w-12 flex-shrink-0 text-right hover:text-gray-600 dark:hover:text-gray-300 #{sort_active.call('success_rate')}" %>
91
+ <%= link_to "last run#{sort_arrow.call('last_executed')}".html_safe, sort_url.call("last_executed"),
92
+ class: "w-24 flex-shrink-0 text-right hover:text-gray-600 dark:hover:text-gray-300 #{sort_active.call('last_executed')}" %>
93
+ </div>
216
94
 
217
- <!-- Last Executed -->
218
- <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
219
- <% if agent[:last_executed] %>
220
- <%= time_ago_in_words(agent[:last_executed]) %> ago
221
- <% else %>
222
- <span class="text-gray-400 dark:text-gray-500">Never</span>
223
- <% end %>
224
- </td>
225
- </tr>
95
+ <!-- Data rows -->
96
+ <div class="font-mono text-xs space-y-px">
97
+ <% @agents.each do |agent| %>
98
+ <%
99
+ show_condition = if agent[:agent_type] == 'speaker' || agent[:agent_type] == 'transcriber'
100
+ "activeSubTab !== 'deleted' && (activeSubTab === null || activeSubTab === 'audio' || activeSubTab === '#{agent[:agent_type]}')"
101
+ else
102
+ "activeSubTab !== 'deleted' && (activeSubTab === null || activeSubTab === '#{agent[:agent_type]}')"
103
+ end
104
+ success_rate = agent[:success_rate] || 0
105
+ dot_color = agent[:active] ? (success_rate >= 95 ? 'bg-green-500' : success_rate >= 80 ? 'bg-yellow-500' : 'bg-red-500') : 'bg-gray-400'
106
+ %>
107
+ <div x-show="<%= show_condition %>"
108
+ 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"
109
+ onclick="window.location='<%= ruby_llm_agents.agent_path(ERB::Util.url_encode(agent[:name]), subtab: params[:subtab]) %>'"
110
+ title="<%= agent[:description] %>">
111
+ <span class="flex-[2] min-w-0 truncate text-gray-900 dark:text-gray-200">
112
+ <%= agent[:name].split('::').last.gsub(/Agent$/, '') %><span class="hidden lg:inline text-gray-400 dark:text-gray-600 ml-1 text-[10px]">v<%= agent[:version] %></span>
113
+ </span>
114
+ <span class="flex-1 min-w-0 truncate text-gray-500 dark:text-gray-400 hidden sm:inline"><%= agent[:agent_type]&.to_s || 'agent' %></span>
115
+ <span class="w-1.5 h-1.5 rounded-full flex-shrink-0 <%= dot_color %>"></span>
116
+ <span class="flex-[2] min-w-0 truncate text-gray-400 dark:text-gray-600 hidden lg:inline"><%= agent[:model]&.split("/")&.last || "—" %></span>
117
+ <span class="w-14 flex-shrink-0 text-right text-gray-500 dark:text-gray-400"><%= number_with_delimiter(agent[:execution_count] || 0) %><span class="text-gray-400 dark:text-gray-600">r</span></span>
118
+ <span class="w-16 flex-shrink-0 text-right text-gray-500 dark:text-gray-400 hidden md:inline">$<%= number_with_precision(agent[:total_cost] || 0, precision: 2) %></span>
119
+ <span class="w-12 flex-shrink-0 text-right <%= success_rate >= 95 ? 'text-green-500' : success_rate >= 80 ? 'text-yellow-500' : 'text-red-500' %>"><%= success_rate.round %>%</span>
120
+ <span class="w-24 flex-shrink-0 text-gray-400 dark:text-gray-600 text-right whitespace-nowrap">
121
+ <% if agent[:last_executed] %>
122
+ <%= time_ago_in_words(agent[:last_executed]) %>
123
+ <% else %>
124
+
226
125
  <% end %>
126
+ </span>
127
+ </div>
128
+ <% end %>
227
129
 
228
- <!-- Deleted Agents -->
229
- <% @deleted_agents.each_with_index do |agent, index| %>
230
- <%
231
- row_bg = index.even? ? '' : 'bg-gray-50/50 dark:bg-gray-900/30'
232
- success_rate = agent[:success_rate] || 0
233
- %>
234
- <tr
235
- x-show="activeSubTab === 'deleted'"
236
- class="<%= row_bg %> hover:bg-blue-50/50 dark:hover:bg-blue-900/20 transition-colors cursor-pointer"
237
- onclick="window.location='<%= ruby_llm_agents.agent_path(ERB::Util.url_encode(agent[:name]), subtab: params[:subtab]) %>'"
238
- >
239
- <!-- Name -->
240
- <td class="px-4 py-3">
241
- <div class="flex items-center gap-2">
242
- <span class="text-sm font-medium text-gray-900 dark:text-gray-100">
243
- <%= agent[:name].split('::').last.gsub(/Agent$/, '') %>
244
- </span>
245
- <span class="hidden lg:inline text-xs text-gray-400 dark:text-gray-500">v<%= agent[:version] %></span>
246
- </div>
247
- <% if agent[:description].present? %>
248
- <p class="text-xs text-gray-500 dark:text-gray-400 truncate max-w-48 mt-0.5" title="<%= agent[:description] %>">
249
- <%= agent[:description] %>
250
- </p>
251
- <% end %>
252
- </td>
253
-
254
- <!-- Type -->
255
- <td class="px-4 py-3 whitespace-nowrap">
256
- <span class="text-sm text-gray-600 dark:text-gray-400">
257
- <%= agent[:agent_type]&.titleize || 'Agent' %>
258
- </span>
259
- </td>
260
-
261
- <!-- Status -->
262
- <td class="px-4 py-3 whitespace-nowrap">
263
- <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400">
264
- Deleted
265
- </span>
266
- </td>
267
-
268
- <!-- Model -->
269
- <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400 hidden lg:table-cell">
270
- <span title="<%= agent[:model] %>">
271
- <%= agent[:model]&.split("/")&.last || agent[:model] || "-" %>
272
- </span>
273
- </td>
274
-
275
- <!-- Executions -->
276
- <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700 dark:text-gray-300 font-mono">
277
- <%= number_with_delimiter(agent[:execution_count] || 0) %>
278
- </td>
279
-
280
- <!-- Cost -->
281
- <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700 dark:text-gray-300 font-mono hidden md:table-cell">
282
- $<%= number_with_precision(agent[:total_cost] || 0, precision: 4) %>
283
- </td>
284
-
285
- <!-- Success Rate -->
286
- <td class="px-4 py-3 whitespace-nowrap">
287
- <div class="flex items-center gap-1.5">
288
- <% if success_rate >= 95 %>
289
- <span class="w-2 h-2 rounded-full bg-green-500"></span>
290
- <span class="text-sm text-green-600 dark:text-green-400"><%= success_rate %>%</span>
291
- <% elsif success_rate >= 80 %>
292
- <span class="w-2 h-2 rounded-full bg-yellow-500"></span>
293
- <span class="text-sm text-yellow-600 dark:text-yellow-400"><%= success_rate %>%</span>
294
- <% else %>
295
- <span class="w-2 h-2 rounded-full bg-red-500"></span>
296
- <span class="text-sm text-red-600 dark:text-red-400"><%= success_rate %>%</span>
297
- <% end %>
298
- </div>
299
- </td>
300
-
301
- <!-- Last Executed -->
302
- <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
303
- <% if agent[:last_executed] %>
304
- <%= time_ago_in_words(agent[:last_executed]) %> ago
305
- <% else %>
306
- <span class="text-gray-400 dark:text-gray-500">Never</span>
307
- <% end %>
308
- </td>
309
- </tr>
130
+ <!-- Deleted agents -->
131
+ <% @deleted_agents.each do |agent| %>
132
+ <% success_rate = agent[:success_rate] || 0 %>
133
+ <div x-show="activeSubTab === 'deleted'"
134
+ 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"
135
+ onclick="window.location='<%= ruby_llm_agents.agent_path(ERB::Util.url_encode(agent[:name]), subtab: params[:subtab]) %>'"
136
+ title="<%= agent[:description] %>">
137
+ <span class="flex-[2] min-w-0 truncate text-gray-500 dark:text-gray-400 line-through"><%= agent[:name].split('::').last.gsub(/Agent$/, '') %></span>
138
+ <span class="flex-1 min-w-0 truncate text-gray-400 dark:text-gray-600 hidden sm:inline"><%= agent[:agent_type]&.to_s || 'agent' %></span>
139
+ <span class="w-1.5 h-1.5 rounded-full flex-shrink-0 bg-gray-400 dark:bg-gray-600"></span>
140
+ <span class="flex-[2] min-w-0 truncate text-gray-400 dark:text-gray-600 hidden lg:inline"><%= agent[:model]&.split("/")&.last || "—" %></span>
141
+ <span class="w-14 flex-shrink-0 text-right text-gray-400 dark:text-gray-600"><%= number_with_delimiter(agent[:execution_count] || 0) %><span class="text-gray-400 dark:text-gray-600">r</span></span>
142
+ <span class="w-16 flex-shrink-0 text-right text-gray-400 dark:text-gray-600 hidden md:inline">$<%= number_with_precision(agent[:total_cost] || 0, precision: 2) %></span>
143
+ <span class="w-12 flex-shrink-0 text-right text-gray-400 dark:text-gray-600"><%= success_rate.round %>%</span>
144
+ <span class="w-24 flex-shrink-0 text-gray-400 dark:text-gray-600 text-right whitespace-nowrap">
145
+ <% if agent[:last_executed] %>
146
+ <%= time_ago_in_words(agent[:last_executed]) %>
147
+ <% else %>
148
+
310
149
  <% end %>
150
+ </span>
151
+ </div>
152
+ <% end %>
311
153
 
312
- <!-- Empty State -->
313
- <tr x-show="currentCount === 0" x-cloak>
314
- <td colspan="8" class="px-6 py-12 text-center">
315
- <div class="text-gray-400 dark:text-gray-500">
316
- <svg class="mx-auto h-12 w-12 mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
317
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" 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" />
318
- </svg>
319
- <p class="text-lg font-medium text-gray-500 dark:text-gray-400">No agents in this category</p>
320
- <p class="text-sm mt-1">Try selecting a different filter</p>
321
- </div>
322
- </td>
323
- </tr>
324
- </tbody>
325
- </table>
154
+ <!-- Empty filter state -->
155
+ <div x-show="currentCount === 0" x-cloak class="py-4 px-2 text-gray-400 dark:text-gray-600">
156
+ no agents in this category
326
157
  </div>
327
158
  </div>
328
159
  <% end %>