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
@@ -14,7 +14,7 @@
14
14
  {
15
15
  icon: "robot",
16
16
  label: "Agent",
17
- bg: "bg-blue-100 dark:bg-blue-900/50",
17
+ bg: "bg-blue-100 dark:bg-blue-500/20",
18
18
  text: "text-blue-700 dark:text-blue-300",
19
19
  icon_char: "🤖"
20
20
  }
@@ -22,7 +22,7 @@
22
22
  {
23
23
  icon: "chart",
24
24
  label: "Embedder",
25
- bg: "bg-purple-100 dark:bg-purple-900/50",
25
+ bg: "bg-purple-100 dark:bg-purple-500/20",
26
26
  text: "text-purple-700 dark:text-purple-300",
27
27
  icon_char: "📊"
28
28
  }
@@ -30,7 +30,7 @@
30
30
  {
31
31
  icon: "mic",
32
32
  label: "Transcriber",
33
- bg: "bg-green-100 dark:bg-green-900/50",
33
+ bg: "bg-green-100 dark:bg-green-500/20",
34
34
  text: "text-green-700 dark:text-green-300",
35
35
  icon_char: "🎤"
36
36
  }
@@ -38,7 +38,7 @@
38
38
  {
39
39
  icon: "speaker",
40
40
  label: "Speaker",
41
- bg: "bg-orange-100 dark:bg-orange-900/50",
41
+ bg: "bg-orange-100 dark:bg-orange-500/20",
42
42
  text: "text-orange-700 dark:text-orange-300",
43
43
  icon_char: "🔊"
44
44
  }
@@ -46,26 +46,10 @@
46
46
  {
47
47
  icon: "palette",
48
48
  label: "Image Gen",
49
- bg: "bg-pink-100 dark:bg-pink-900/50",
49
+ bg: "bg-pink-100 dark:bg-pink-500/20",
50
50
  text: "text-pink-700 dark:text-pink-300",
51
51
  icon_char: "🎨"
52
52
  }
53
- when "moderator"
54
- {
55
- icon: "shield",
56
- label: "Moderator",
57
- bg: "bg-red-100 dark:bg-red-900/50",
58
- text: "text-red-700 dark:text-red-300",
59
- icon_char: "🛡️"
60
- }
61
- when "workflow"
62
- {
63
- icon: "gear",
64
- label: "Workflow",
65
- bg: "bg-indigo-100 dark:bg-indigo-900/50",
66
- text: "text-indigo-700 dark:text-indigo-300",
67
- icon_char: "⚙️"
68
- }
69
53
  else
70
54
  {
71
55
  icon: "question",
@@ -1,182 +1,66 @@
1
1
  <%# Show tenant column when multi-tenancy is enabled and no specific tenant is selected %>
2
2
  <% show_tenant_column = tenant_filter_enabled? && current_tenant_id.blank? %>
3
+ <% show_agent_column = !local_assigns.fetch(:hide_agent_column, false) %>
3
4
 
4
5
  <% if executions.empty? %>
5
- <p class="text-gray-500 dark:text-gray-400 text-center py-8">No executions found.</p>
6
+ <div class="font-mono text-xs text-gray-400 dark:text-gray-600 py-4 px-2">no executions found</div>
6
7
  <% else %>
7
- <div class="overflow-x-auto">
8
- <table class="min-w-full text-sm">
9
- <thead class="bg-gray-50 dark:bg-gray-900/50">
10
- <tr>
11
- <th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Status</th>
12
- <th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Agent</th>
13
- <% if show_tenant_column %>
14
- <th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Tenant</th>
15
- <% end %>
16
- <th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Model</th>
17
- <th scope="col" class="px-4 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Duration</th>
18
- <th scope="col" class="px-4 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Tokens</th>
19
- <th scope="col" class="px-4 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Cost</th>
20
- <th scope="col" class="px-4 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Time</th>
21
- </tr>
22
- </thead>
23
- <tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-100 dark:divide-gray-700">
24
- <% executions.each do |execution| %>
25
- <%
26
- is_workflow = execution.workflow_type.present?
27
- children = execution.child_executions.sort_by(&:created_at)
28
- has_children = children.any?
29
- %>
30
-
31
- <%# Parent/Main Row %>
32
- <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors">
33
- <%# Status %>
34
- <td class="px-4 py-3 whitespace-nowrap">
35
- <%= render "ruby_llm/agents/shared/status_badge", status: execution.status, size: :sm %>
36
- </td>
37
-
38
- <%# Agent Name with Workflow Badge %>
39
- <td class="px-4 py-3 whitespace-nowrap">
40
- <div class="flex items-center gap-2">
41
- <%= link_to ruby_llm_agents.execution_path(execution), class: "font-medium text-gray-900 dark:text-gray-100 hover:text-blue-600 dark:hover:text-blue-400" do %>
42
- <%= execution.agent_type.gsub(/Agent$/, "") %>
43
- <% end %>
44
- <% if is_workflow %>
45
- <span class="text-xs text-emerald-600 dark:text-emerald-400">◈</span>
46
- <% end %>
47
- </div>
48
- </td>
49
-
50
- <%# Tenant (only when viewing all tenants) %>
51
- <% if show_tenant_column %>
52
- <td class="px-4 py-3 whitespace-nowrap">
53
- <% if execution.tenant_id.present? %>
54
- <a href="<%= url_for(request.query_parameters.merge(tenant_id: execution.tenant_id)) %>"
55
- class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-blue-50 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 hover:bg-blue-100 dark:hover:bg-blue-900/50">
56
- <%= truncate(execution.tenant_id, length: 15) %>
57
- </a>
58
- <% else %>
59
- <span class="text-gray-400 dark:text-gray-500 text-xs">—</span>
60
- <% end %>
61
- </td>
62
- <% end %>
63
-
64
- <%# Model %>
65
- <td class="px-4 py-3 whitespace-nowrap text-gray-500 dark:text-gray-400 font-mono text-xs">
66
- <%= execution.model_id %>
67
- </td>
68
-
69
- <%# Duration %>
70
- <td class="px-4 py-3 whitespace-nowrap text-right text-gray-900 dark:text-gray-100 tabular-nums">
71
- <%= execution.duration_ms ? "#{number_with_delimiter(execution.duration_ms)}ms" : "-" %>
72
- </td>
73
-
74
- <%# Tokens %>
75
- <td class="px-4 py-3 whitespace-nowrap text-right text-gray-900 dark:text-gray-100 tabular-nums">
76
- <%= number_with_delimiter(execution.total_tokens || 0) %>
77
- </td>
78
-
79
- <%# Cost %>
80
- <td class="px-4 py-3 whitespace-nowrap text-right text-gray-900 dark:text-gray-100 tabular-nums">
81
- $<%= number_with_precision(execution.total_cost || 0, precision: 4) %>
82
- </td>
83
-
84
- <%# Time %>
85
- <td class="px-4 py-3 whitespace-nowrap text-right text-gray-500 dark:text-gray-400">
86
- <%= time_ago_in_words(execution.created_at) %> ago
87
- </td>
88
- </tr>
89
-
90
- <%# Error Row (if applicable) %>
91
- <% if execution.status_error? && execution.error_message.present? %>
92
- <tr class="bg-red-50/50 dark:bg-red-900/20">
93
- <td></td>
94
- <td colspan="<%= show_tenant_column ? 7 : 6 %>" class="px-4 py-2">
95
- <p class="text-xs text-red-600 dark:text-red-400">
96
- <span class="font-medium"><%= execution.error_class %>:</span>
97
- <%= truncate(execution.error_message, length: 120) %>
98
- </p>
99
- </td>
100
- </tr>
101
- <% end %>
102
-
103
- <%# Child Rows (for workflows) %>
104
- <% if has_children %>
105
- <% children.each_with_index do |child, index| %>
106
- <% is_last = index == children.size - 1 %>
107
- <tr class="bg-gray-50/50 dark:bg-gray-900/30 hover:bg-gray-100/50 dark:hover:bg-gray-800/50 transition-colors">
108
- <%# Status with tree line %>
109
- <td class="px-4 py-2 whitespace-nowrap">
110
- <div class="flex items-center">
111
- <span class="text-gray-300 dark:text-gray-600 mr-2 font-mono text-xs"><%= is_last ? "└─" : "├─" %></span>
112
- <% case child.status
113
- when "success" %>
114
- <span class="text-green-600 dark:text-green-400">✓</span>
115
- <% when "error" %>
116
- <span class="text-red-600 dark:text-red-400">✗</span>
117
- <% when "timeout" %>
118
- <span class="text-orange-600 dark:text-orange-400">⏱</span>
119
- <% when "running" %>
120
- <span class="text-blue-600 dark:text-blue-400 animate-pulse">●</span>
121
- <% else %>
122
- <span class="text-gray-400">○</span>
123
- <% end %>
124
- </div>
125
- </td>
126
-
127
- <%# Step Name %>
128
- <td class="px-4 py-2 whitespace-nowrap">
129
- <%= link_to ruby_llm_agents.execution_path(child), class: "text-gray-600 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400" do %>
130
- <span class="text-gray-400 dark:text-gray-500 text-xs"><%= index + 1 %>.</span>
131
- <%= child.workflow_step || child.agent_type.gsub(/Agent$/, "") %>
132
- <% end %>
133
- </td>
134
-
135
- <%# Tenant - empty for child rows %>
136
- <% if show_tenant_column %>
137
- <td class="px-4 py-2"></td>
138
- <% end %>
139
-
140
- <%# Model %>
141
- <td class="px-4 py-2 whitespace-nowrap text-gray-400 dark:text-gray-500 font-mono text-xs">
142
- <%= child.model_id %>
143
- </td>
144
-
145
- <%# Duration %>
146
- <td class="px-4 py-2 whitespace-nowrap text-right text-gray-600 dark:text-gray-300 tabular-nums text-xs">
147
- <%= child.duration_ms ? "#{number_with_delimiter(child.duration_ms)}ms" : "-" %>
148
- </td>
149
-
150
- <%# Tokens %>
151
- <td class="px-4 py-2 whitespace-nowrap text-right text-gray-600 dark:text-gray-300 tabular-nums text-xs">
152
- <%= number_with_delimiter(child.total_tokens || 0) %>
153
- </td>
154
-
155
- <%# Cost %>
156
- <td class="px-4 py-2 whitespace-nowrap text-right text-gray-600 dark:text-gray-300 tabular-nums text-xs">
157
- $<%= number_with_precision(child.total_cost || 0, precision: 4) %>
158
- </td>
159
-
160
- <%# Time - empty for children %>
161
- <td class="px-4 py-2"></td>
162
- </tr>
8
+ <!-- Column headers -->
9
+ <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">
10
+ <span class="w-20 flex-shrink-0">status</span>
11
+ <% if show_agent_column %>
12
+ <span class="flex-1 min-w-0">agent</span>
13
+ <% end %>
14
+ <% if show_tenant_column %>
15
+ <span class="flex-1 min-w-0 hidden sm:block">tenant</span>
16
+ <% end %>
17
+ <span class="flex-1 min-w-0 hidden lg:block">model</span>
18
+ <span class="w-16 flex-shrink-0 text-right">duration</span>
19
+ <span class="w-16 flex-shrink-0 text-right hidden md:block">tokens</span>
20
+ <span class="w-16 flex-shrink-0 text-right hidden md:block">cost</span>
21
+ <span class="w-24 flex-shrink-0 text-right">time</span>
22
+ </div>
163
23
 
164
- <%# Child Error Row %>
165
- <% if child.status_error? && child.error_message.present? %>
166
- <tr class="bg-red-50/30 dark:bg-red-900/10">
167
- <td></td>
168
- <td colspan="<%= show_tenant_column ? 7 : 6 %>" class="px-4 py-1.5 pl-8">
169
- <p class="text-xs text-red-500 dark:text-red-400">
170
- <%= truncate(child.error_message, length: 100) %>
171
- </p>
172
- </td>
173
- </tr>
174
- <% end %>
175
- <% end %>
176
- <% end %>
24
+ <!-- Rows -->
25
+ <div class="font-mono text-xs space-y-px">
26
+ <% executions.each do |execution| %>
27
+ <%
28
+ status_badge_class = case execution.status.to_s
29
+ when "running" then "badge-running"
30
+ when "success" then "badge-success"
31
+ when "error" then "badge-error"
32
+ when "timeout" then "badge-timeout"
33
+ else "badge-timeout"
34
+ end
35
+ %>
36
+ <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"
37
+ onclick="window.location='<%= ruby_llm_agents.execution_path(execution) %>'">
38
+ <span class="w-20 flex-shrink-0"><span class="badge badge-sm <%= status_badge_class %>"><%= execution.status %></span></span>
39
+ <% if show_agent_column %>
40
+ <span class="flex-1 min-w-0 truncate text-gray-900 dark:text-gray-200"><%= execution.agent_type.gsub(/Agent$/, "") %></span>
41
+ <% end %>
42
+ <% if show_tenant_column %>
43
+ <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>
177
44
  <% end %>
178
- </tbody>
179
- </table>
45
+ <span class="flex-1 min-w-0 truncate text-gray-400 dark:text-gray-600 hidden lg:inline"><%= execution.model_id %></span>
46
+ <span class="w-16 flex-shrink-0 text-right text-gray-500 dark:text-gray-400">
47
+ <% if execution.status_running? %>
48
+ <span class="text-blue-500 animate-pulse">...</span>
49
+ <% else %>
50
+ <%= execution.duration_ms ? format_duration_ms(execution.duration_ms) : "—" %>
51
+ <% end %>
52
+ </span>
53
+ <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>
54
+ <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>
55
+ <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>
56
+ </div>
57
+ <% if execution.status_error? && execution.error_message.present? %>
58
+ <div class="flex items-center gap-1 pl-5 py-0.5 text-red-400 dark:text-red-500/70 text-xs font-mono">
59
+ <span class="text-gray-300 dark:text-gray-700">└</span>
60
+ <span class="truncate"><%= execution.error_class %>: <%= truncate(execution.error_message, length: 100) %></span>
61
+ </div>
62
+ <% end %>
63
+ <% end %>
180
64
  </div>
181
65
 
182
66
  <%# Pagination %>
@@ -186,31 +70,24 @@
186
70
  total_pages = pagination[:total_pages]
187
71
  total_count = pagination[:total_count]
188
72
  per_page = pagination[:per_page]
189
-
190
73
  from_record = ((current_page - 1) * per_page) + 1
191
74
  to_record = [current_page * per_page, total_count].min
192
75
  %>
193
- <div class="mt-4 flex items-center justify-between border-t border-gray-100 dark:border-gray-700 pt-4">
194
- <p class="text-sm text-gray-500 dark:text-gray-400">
195
- Showing <%= from_record %>-<%= to_record %> of <%= number_with_delimiter(total_count) %> executions
196
- </p>
197
- <nav class="flex items-center space-x-1">
76
+ <div class="flex items-center justify-between font-mono text-xs mt-4 pt-4 border-t border-gray-200 dark:border-gray-800">
77
+ <span class="text-gray-400 dark:text-gray-600"><%= from_record %>-<%= to_record %> of <%= number_with_delimiter(total_count) %></span>
78
+ <nav class="flex items-center gap-1">
198
79
  <% if current_page > 1 %>
199
- <%= 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" %>
80
+ <%= link_to "prev", url_for(request.query_parameters.merge(page: current_page - 1)),
81
+ class: "px-2 py-0.5 text-gray-400 dark:text-gray-500 hover:text-gray-700 dark:hover:text-gray-300" %>
200
82
  <% else %>
201
- <span class="px-3 py-1.5 text-sm font-medium text-gray-400 dark:text-gray-500 bg-gray-100 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-md cursor-not-allowed">Previous</span>
83
+ <span class="px-2 py-0.5 text-gray-300 dark:text-gray-700">prev</span>
202
84
  <% end %>
203
85
 
204
86
  <%
205
87
  window = 2
206
- left_edge = 1
207
- right_edge = 1
208
-
209
88
  pages_to_show = []
210
89
  (1..total_pages).each do |page|
211
- if page <= left_edge ||
212
- page > total_pages - right_edge ||
213
- (page >= current_page - window && page <= current_page + window)
90
+ if page <= 1 || page >= total_pages || (page >= current_page - window && page <= current_page + window)
214
91
  pages_to_show << page
215
92
  elsif pages_to_show.last != :gap
216
93
  pages_to_show << :gap
@@ -220,18 +97,20 @@
220
97
 
221
98
  <% pages_to_show.each do |page| %>
222
99
  <% if page == :gap %>
223
- <span class="px-2 py-1.5 text-sm text-gray-500 dark:text-gray-400">...</span>
100
+ <span class="px-1 text-gray-400 dark:text-gray-600">...</span>
224
101
  <% elsif page == current_page %>
225
- <span class="px-3 py-1.5 text-sm font-medium text-white bg-blue-600 border border-blue-600 rounded-md"><%= page %></span>
102
+ <span class="px-2 py-0.5 text-gray-900 dark:text-gray-100"><%= page %></span>
226
103
  <% else %>
227
- <%= 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" %>
104
+ <%= link_to page, url_for(request.query_parameters.merge(page: page)),
105
+ class: "px-2 py-0.5 text-gray-400 dark:text-gray-500 hover:text-gray-700 dark:hover:text-gray-300" %>
228
106
  <% end %>
229
107
  <% end %>
230
108
 
231
109
  <% if current_page < total_pages %>
232
- <%= 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" %>
110
+ <%= link_to "next", url_for(request.query_parameters.merge(page: current_page + 1)),
111
+ class: "px-2 py-0.5 text-gray-400 dark:text-gray-500 hover:text-gray-700 dark:hover:text-gray-300" %>
233
112
  <% else %>
234
- <span class="px-3 py-1.5 text-sm font-medium text-gray-400 dark:text-gray-500 bg-gray-100 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-md cursor-not-allowed">Next</span>
113
+ <span class="px-2 py-0.5 text-gray-300 dark:text-gray-700">next</span>
235
114
  <% end %>
236
115
  </nav>
237
116
  </div>
@@ -10,18 +10,12 @@
10
10
  # { value: "success", label: "Success", color: "bg-green-500" },
11
11
  # { value: "error", label: "Error", color: "bg-red-500" }
12
12
  # ],
13
- # selected: ["success"],
14
- # icon: "M9.75 17L9 20l-1...", # optional SVG path
15
- # width: "w-48", # optional, default w-48
16
- # full_width: false # optional, for mobile
13
+ # selected: ["success"]
17
14
 
18
15
  selected = local_assigns[:selected] || []
19
- width = local_assigns[:width] || "w-48"
20
- full_width = local_assigns[:full_width] || false
21
16
  show_all = local_assigns.fetch(:show_all_option, true)
22
17
  all_label = local_assigns[:all_label] || "All"
23
18
 
24
- # Determine current label
25
19
  current_label = if selected.empty?
26
20
  label
27
21
  elsif selected.length == 1
@@ -31,7 +25,6 @@
31
25
  "#{selected.length} #{label}"
32
26
  end
33
27
 
34
- # Get color for single selection (if applicable)
35
28
  current_color = if selected.length == 1
36
29
  opt = options.find { |o| o[:value].to_s == selected.first.to_s }
37
30
  opt&.dig(:color)
@@ -40,41 +33,28 @@
40
33
  has_selection = selected.any?
41
34
  %>
42
35
  <div class="relative" x-data="{ open: false }" @click.outside="open = false" data-filter="<%= filter_id %>">
43
- <%# Trigger button %>
44
36
  <button type="button" @click="open = !open"
45
- class="<%= full_width ? 'w-full md:w-auto justify-between md:justify-start' : '' %> flex items-center gap-2 px-3 py-2 text-sm
46
- bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-lg
47
- hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors
48
- <%= has_selection ? 'border-blue-300 dark:border-blue-600' : '' %>">
49
- <div class="flex items-center gap-2">
50
- <% if current_color.present? %>
51
- <span class="w-2 h-2 rounded-full <%= current_color %> <%= current_color.include?('blue') && selected.first == 'running' ? 'animate-pulse' : '' %>"></span>
52
- <% elsif local_assigns[:icon].present? %>
53
- <svg class="w-4 h-4 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
54
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="<%= icon %>"/>
55
- </svg>
56
- <% elsif !has_selection && options.first&.dig(:color) %>
57
- <span class="w-2 h-2 rounded-full bg-gray-400"></span>
58
- <% end %>
59
- <span class="text-gray-700 dark:text-gray-200"><%= current_label %></span>
60
- </div>
61
- <svg class="w-4 h-4 text-gray-400 dark:text-gray-500 flex-shrink-0 transition-transform" :class="{ 'rotate-180': open }" fill="none" stroke="currentColor" viewBox="0 0 24 24">
37
+ class="flex items-center gap-1.5 px-2 py-1 font-mono text-xs rounded
38
+ hover:bg-gray-100 dark:hover:bg-gray-800/50 transition-colors
39
+ <%= has_selection ? 'text-gray-900 dark:text-gray-100' : 'text-gray-400 dark:text-gray-500' %>">
40
+ <% if current_color.present? %>
41
+ <span class="w-1.5 h-1.5 rounded-full <%= current_color %> <%= current_color.include?('blue') && selected.first == 'running' ? 'animate-pulse' : '' %>"></span>
42
+ <% end %>
43
+ <span><%= current_label %></span>
44
+ <svg class="w-3 h-3 text-gray-400 dark:text-gray-600 transition-transform" :class="{ 'rotate-180': open }" fill="none" stroke="currentColor" viewBox="0 0 24 24">
62
45
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
63
46
  </svg>
64
47
  </button>
65
48
 
66
- <%# Dropdown menu %>
67
49
  <div x-show="open" x-cloak x-transition:enter="transition ease-out duration-100"
68
50
  x-transition:enter-start="opacity-0 scale-95" x-transition:enter-end="opacity-100 scale-100"
69
51
  x-transition:leave="transition ease-in duration-75"
70
52
  x-transition:leave-start="opacity-100 scale-100" x-transition:leave-end="opacity-0 scale-95"
71
- class="absolute z-20 mt-1 <%= full_width ? 'left-0 right-0 md:left-auto md:right-auto md:min-w-max' : 'min-w-max' %>
72
- bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 py-1
73
- max-h-64 overflow-y-auto">
53
+ class="absolute z-20 mt-1 min-w-max bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 py-1 max-h-64 overflow-y-auto">
74
54
 
75
55
  <% if show_all %>
76
- <div class="px-3 py-2 border-b border-gray-100 dark:border-gray-700">
77
- <label class="flex items-center gap-2 cursor-pointer">
56
+ <div class="px-3 py-1.5 border-b border-gray-100 dark:border-gray-700">
57
+ <label class="flex items-center gap-2 cursor-pointer font-mono text-xs">
78
58
  <input type="checkbox"
79
59
  class="rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500 dark:bg-gray-700"
80
60
  <%= selected.empty? ? 'checked' : '' %>
@@ -82,24 +62,20 @@
82
62
  $el.closest('[data-filter]').querySelectorAll('.filter-cb').forEach(c => c.checked = false);
83
63
  $el.closest('form').requestSubmit();
84
64
  ">
85
- <span class="text-sm font-medium text-gray-700 dark:text-gray-200"><%= all_label %></span>
65
+ <span class="text-gray-700 dark:text-gray-200"><%= all_label %></span>
86
66
  </label>
87
67
  </div>
88
68
  <% end %>
89
69
 
90
70
  <% options.each do |option| %>
91
- <div class="flex items-center gap-2 px-3 py-2 text-sm text-gray-700 dark:text-gray-200
92
- hover:bg-gray-50 dark:hover:bg-gray-700">
93
- <%# Checkbox: toggle this item, dropdown stays open %>
71
+ <div class="flex items-center gap-2 px-3 py-1.5 font-mono text-xs text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700">
94
72
  <input type="checkbox"
95
73
  name="<%= name %>"
96
74
  value="<%= option[:value] %>"
97
75
  class="filter-cb rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500 dark:bg-gray-700"
98
76
  <%= selected.include?(option[:value].to_s) ? 'checked' : '' %>
99
77
  @change="$el.closest('form').requestSubmit()">
100
-
101
- <%# Label: click to select ONLY this item AND close dropdown %>
102
- <span class="flex-1 flex items-center gap-2 cursor-pointer"
78
+ <span class="flex-1 flex items-center gap-1.5 cursor-pointer"
103
79
  @click.prevent="
104
80
  $el.closest('[data-filter]').querySelectorAll('.filter-cb').forEach(c => c.checked = false);
105
81
  $el.previousElementSibling.checked = true;
@@ -107,11 +83,7 @@
107
83
  $el.closest('form').requestSubmit();
108
84
  ">
109
85
  <% if option[:color].present? %>
110
- <span class="w-2 h-2 rounded-full <%= option[:color] %> <%= option[:color].include?('blue') && option[:value] == 'running' ? 'animate-pulse' : '' %>"></span>
111
- <% elsif option[:icon].present? %>
112
- <svg class="w-4 h-4 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
113
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="<%= option[:icon] %>"/>
114
- </svg>
86
+ <span class="w-1.5 h-1.5 rounded-full <%= option[:color] %> <%= option[:color].include?('blue') && option[:value] == 'running' ? 'animate-pulse' : '' %>"></span>
115
87
  <% end %>
116
88
  <%= option[:label] %>
117
89
  </span>
@@ -6,58 +6,34 @@
6
6
  # filter_id: "days",
7
7
  # options: [
8
8
  # { value: "", label: "All Time" },
9
- # { value: "1", label: "Today" },
10
- # { value: "7", label: "Last 7 Days" },
11
- # { value: "30", label: "Last 30 Days" }
9
+ # { value: "7", label: "Last 7 Days" }
12
10
  # ],
13
- # selected: "7",
14
- # icon: "M8 7V3m8 4V3...", # optional SVG path
15
- # width: "w-40", # optional, default w-40
16
- # full_width: false # optional, for mobile
11
+ # selected: "7"
17
12
 
18
13
  selected = local_assigns[:selected].to_s
19
- width = local_assigns[:width] || "w-40"
20
- full_width = local_assigns[:full_width] || false
21
-
22
- # Find current option
23
14
  current_option = options.find { |o| o[:value].to_s == selected } || options.first
24
15
  current_label = current_option[:label]
25
-
26
16
  has_selection = selected.present?
27
17
  %>
28
18
  <div class="relative" x-data="{ open: false }" @click.outside="open = false" data-filter="<%= filter_id %>">
29
- <%# Trigger button %>
30
19
  <button type="button" @click="open = !open"
31
- class="<%= full_width ? 'w-full md:w-auto justify-between md:justify-start' : '' %> flex items-center gap-2 px-3 py-2 text-sm
32
- bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-lg
33
- hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors
34
- <%= has_selection ? 'border-blue-300 dark:border-blue-600' : '' %>">
35
- <div class="flex items-center gap-2">
36
- <% if local_assigns[:icon].present? %>
37
- <svg class="w-4 h-4 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
38
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="<%= icon %>"/>
39
- </svg>
40
- <% end %>
41
- <span class="text-gray-700 dark:text-gray-200"><%= current_label %></span>
42
- </div>
43
- <svg class="w-4 h-4 text-gray-400 dark:text-gray-500 flex-shrink-0 transition-transform" :class="{ 'rotate-180': open }" fill="none" stroke="currentColor" viewBox="0 0 24 24">
20
+ class="flex items-center gap-1.5 px-2 py-1 font-mono text-xs rounded
21
+ hover:bg-gray-100 dark:hover:bg-gray-800/50 transition-colors
22
+ <%= has_selection ? 'text-gray-900 dark:text-gray-100' : 'text-gray-400 dark:text-gray-500' %>">
23
+ <span><%= current_label %></span>
24
+ <svg class="w-3 h-3 text-gray-400 dark:text-gray-600 transition-transform" :class="{ 'rotate-180': open }" fill="none" stroke="currentColor" viewBox="0 0 24 24">
44
25
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
45
26
  </svg>
46
27
  </button>
47
28
 
48
- <%# Dropdown menu %>
49
29
  <div x-show="open" x-cloak x-transition:enter="transition ease-out duration-100"
50
30
  x-transition:enter-start="opacity-0 scale-95" x-transition:enter-end="opacity-100 scale-100"
51
31
  x-transition:leave="transition ease-in duration-75"
52
32
  x-transition:leave-start="opacity-100 scale-100" x-transition:leave-end="opacity-0 scale-95"
53
- class="absolute z-20 mt-1 <%= full_width ? 'left-0 right-0 md:left-auto md:right-auto md:min-w-max' : 'min-w-max' %>
54
- bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 py-1">
33
+ class="absolute z-20 mt-1 min-w-max bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 py-1">
55
34
  <% options.each do |option| %>
56
35
  <%
57
36
  is_selected = option[:value].to_s == selected
58
- item_classes = is_selected ?
59
- "bg-blue-50 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300" :
60
- "text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700"
61
37
  %>
62
38
  <button type="button"
63
39
  @click="
@@ -65,17 +41,12 @@
65
41
  open = false;
66
42
  $el.closest('form').requestSubmit();
67
43
  "
68
- class="w-full flex items-center gap-2 px-3 py-2 text-sm text-left <%= item_classes %>">
69
- <% if option[:icon].present? %>
70
- <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
71
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="<%= option[:icon] %>"/>
72
- </svg>
73
- <% end %>
44
+ class="w-full flex items-center gap-2 px-3 py-1.5 font-mono text-xs text-left
45
+ <%= is_selected ? 'text-gray-900 dark:text-gray-100' : 'text-gray-500 dark:text-gray-400' %>
46
+ hover:bg-gray-50 dark:hover:bg-gray-700">
74
47
  <%= option[:label] %>
75
48
  <% if is_selected %>
76
- <svg class="w-4 h-4 ml-auto text-blue-600 dark:text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
77
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
78
- </svg>
49
+ <span class="ml-auto text-gray-400 dark:text-gray-600">&check;</span>
79
50
  <% end %>
80
51
  </button>
81
52
  <% end %>