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,61 +1,38 @@
1
1
  <% if critical_alerts.any? %>
2
- <div id="action-center" class="mb-6 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-xl p-4">
3
- <div class="flex items-center mb-3">
4
- <svg class="w-5 h-5 text-red-600 dark:text-red-400 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
5
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
6
- </svg>
7
- <h3 class="font-semibold text-red-800 dark:text-red-200">Action Required</h3>
2
+ <div id="action-center" class="mb-6">
3
+ <div class="flex items-center gap-3 mb-3">
4
+ <span class="text-[10px] font-medium text-red-500 dark:text-red-400 uppercase tracking-widest font-mono">action required</span>
5
+ <span class="font-mono text-xs text-red-400 dark:text-red-500"><%= critical_alerts.size %></span>
6
+ <div class="flex-1 border-t border-gray-200 dark:border-gray-800"></div>
8
7
  </div>
9
8
 
10
- <div class="space-y-2">
9
+ <div class="font-mono text-xs space-y-1">
11
10
  <% critical_alerts.each do |alert| %>
12
- <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 sm:gap-0 bg-white dark:bg-gray-800 rounded-lg px-4 py-3 shadow-sm">
13
- <% case alert[:type] %>
14
- <% when :breaker %>
15
- <div class="flex items-center">
16
- <span class="w-2 h-2 bg-orange-500 rounded-full mr-3 flex-shrink-0"></span>
17
- <div>
18
- <p class="text-sm font-medium text-gray-900 dark:text-gray-100">
19
- Circuit breaker open: <%= alert[:data][:agent_type].gsub(/Agent$/, '') %>
20
- </p>
21
- <p class="text-xs text-gray-500 dark:text-gray-400">
22
- <%= alert[:data][:model_id] %> &middot; <%= alert[:data][:failure_count] %>/<%= alert[:data][:threshold] %> failures
23
- </p>
24
- </div>
25
- </div>
26
- <span class="text-xs text-orange-600 dark:text-orange-400 font-medium ml-5 sm:ml-0">
27
- <%= alert[:data][:cooldown_remaining] %>s remaining
28
- </span>
11
+ <% case alert[:type] %>
12
+ <% when :breaker %>
13
+ <div class="flex items-center gap-3 py-1.5 px-2 -mx-2 rounded hover:bg-gray-100 dark:hover:bg-gray-800/50">
14
+ <span class="w-1.5 h-1.5 bg-orange-500 rounded-full flex-shrink-0"></span>
15
+ <span class="text-gray-900 dark:text-gray-100">circuit breaker open: <span class="text-gray-500 dark:text-gray-400"><%= alert[:data][:agent_type].gsub(/Agent$/, '') %></span></span>
16
+ <span class="text-gray-400 dark:text-gray-600"><%= alert[:data][:model_id] %> &middot; <%= alert[:data][:failure_count] %>/<%= alert[:data][:threshold] %> failures</span>
17
+ <span class="ml-auto text-orange-600 dark:text-orange-400"><%= alert[:data][:cooldown_remaining] %>s remaining</span>
18
+ </div>
29
19
 
30
- <% when :budget_breach %>
31
- <div class="flex items-center">
32
- <span class="w-2 h-2 bg-red-500 rounded-full mr-3 flex-shrink-0"></span>
33
- <div>
34
- <p class="text-sm font-medium text-gray-900 dark:text-gray-100">
35
- <%= alert[:data][:period].to_s.capitalize %> budget exceeded
36
- </p>
37
- <p class="text-xs text-gray-500 dark:text-gray-400">
38
- $<%= number_with_precision(alert[:data][:current], precision: 2) %> / $<%= number_with_precision(alert[:data][:limit], precision: 2) %>
39
- </p>
40
- </div>
41
- </div>
42
- <%= link_to "Adjust", ruby_llm_agents.system_config_path, class: "text-xs text-red-600 dark:text-red-400 hover:underline font-medium ml-5 sm:ml-0" %>
20
+ <% when :budget_breach %>
21
+ <div class="flex items-center gap-3 py-1.5 px-2 -mx-2 rounded hover:bg-gray-100 dark:hover:bg-gray-800/50">
22
+ <span class="w-1.5 h-1.5 bg-red-500 rounded-full flex-shrink-0"></span>
23
+ <span class="text-gray-900 dark:text-gray-100"><%= alert[:data][:period].to_s.capitalize %> budget exceeded</span>
24
+ <span class="text-gray-400 dark:text-gray-600">$<%= number_with_precision(alert[:data][:current], precision: 2) %> / $<%= number_with_precision(alert[:data][:limit], precision: 2) %></span>
25
+ <%= link_to "adjust", ruby_llm_agents.system_config_path, class: "ml-auto text-red-600 dark:text-red-400 hover:underline" %>
26
+ </div>
43
27
 
44
- <% when :error_spike %>
45
- <div class="flex items-center">
46
- <span class="w-2 h-2 bg-red-500 rounded-full mr-3 flex-shrink-0 animate-pulse"></span>
47
- <div>
48
- <p class="text-sm font-medium text-gray-900 dark:text-gray-100">
49
- Error spike detected
50
- </p>
51
- <p class="text-xs text-gray-500 dark:text-gray-400">
52
- <%= alert[:data][:count] %> errors in last 15 minutes
53
- </p>
54
- </div>
55
- </div>
56
- <%= link_to "View failures", ruby_llm_agents.executions_path(status: "error"), class: "text-xs text-red-600 dark:text-red-400 hover:underline font-medium ml-5 sm:ml-0" %>
57
- <% end %>
58
- </div>
28
+ <% when :error_spike %>
29
+ <div class="flex items-center gap-3 py-1.5 px-2 -mx-2 rounded hover:bg-gray-100 dark:hover:bg-gray-800/50">
30
+ <span class="w-1.5 h-1.5 bg-red-500 rounded-full flex-shrink-0 animate-pulse"></span>
31
+ <span class="text-gray-900 dark:text-gray-100">error spike detected</span>
32
+ <span class="text-gray-400 dark:text-gray-600"><%= alert[:data][:count] %> errors in last 15 minutes</span>
33
+ <%= link_to "view failures", ruby_llm_agents.executions_path(status: "error"), class: "ml-auto text-red-600 dark:text-red-400 hover:underline" %>
34
+ </div>
35
+ <% end %>
59
36
  <% end %>
60
37
  </div>
61
38
  </div>
@@ -2,114 +2,88 @@
2
2
  <%# @param tenant_budget [Hash] Budget data from controller %>
3
3
 
4
4
  <% if tenant_budget.present? %>
5
- <div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 mb-6">
6
- <div class="px-6 py-3 border-b border-gray-100 dark:border-gray-700">
7
- <div class="flex items-center justify-between">
8
- <div class="flex items-center gap-2">
9
- <svg class="w-5 h-5 text-blue-600 dark:text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
10
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
11
- </svg>
12
- <h3 class="text-base font-semibold text-gray-900 dark:text-gray-100">
13
- Tenant Budget: <%= tenant_budget[:tenant_id] %>
14
- </h3>
15
- </div>
16
- <%# Enforcement badge %>
17
- <% enforcement = tenant_budget[:enforcement].to_s %>
18
- <% badge_class = case enforcement
19
- when "hard" then "bg-red-100 dark:bg-red-900/50 text-red-700 dark:text-red-300"
20
- when "soft" then "bg-yellow-100 dark:bg-yellow-900/50 text-yellow-700 dark:text-yellow-300"
21
- else "bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400"
22
- end %>
23
- <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium <%= badge_class %>">
24
- <%= enforcement.capitalize %> Enforcement
25
- </span>
26
- </div>
5
+ <%
6
+ enforcement = tenant_budget[:enforcement].to_s
7
+ enforcement_badge = case enforcement
8
+ when "hard" then "badge-error"
9
+ when "soft" then "badge-timeout"
10
+ else ""
11
+ end
12
+ %>
13
+ <div class="mb-6">
14
+ <div class="flex items-center gap-3 mb-3">
15
+ <span class="text-[10px] font-medium text-gray-400 dark:text-gray-600 uppercase tracking-widest font-mono">tenant budget: <%= tenant_budget[:tenant_id] %></span>
16
+ <% if enforcement_badge.present? %>
17
+ <span class="badge <%= enforcement_badge %> badge-sm"><%= enforcement %></span>
18
+ <% end %>
19
+ <div class="flex-1 border-t border-gray-200 dark:border-gray-800"></div>
27
20
  </div>
28
21
 
29
- <div class="p-6">
30
- <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
31
- <%# Daily Budget %>
32
- <div>
33
- <div class="flex items-center justify-between mb-2">
34
- <span class="text-sm font-medium text-gray-700 dark:text-gray-300">Daily Budget</span>
35
- <span class="text-sm text-gray-500 dark:text-gray-400">
36
- $<%= number_with_precision(tenant_budget[:daily_spend], precision: 2) %>
37
- <% if tenant_budget[:daily_limit] %>
38
- / $<%= number_with_precision(tenant_budget[:daily_limit], precision: 2) %>
39
- <% else %>
40
- (no limit)
41
- <% end %>
42
- </span>
22
+ <div class="font-mono text-xs space-y-2">
23
+ <%# Daily Budget %>
24
+ <% if tenant_budget[:daily_limit] %>
25
+ <%
26
+ daily_pct = [tenant_budget[:daily_percentage], 100].min
27
+ bar_color = if daily_pct >= 100 then "bg-red-500"
28
+ elsif daily_pct >= 80 then "bg-yellow-500"
29
+ else "bg-green-500"
30
+ end
31
+ %>
32
+ <div class="flex items-center gap-3">
33
+ <span class="w-28 flex-shrink-0 text-gray-500 dark:text-gray-400">daily</span>
34
+ <span class="w-44 flex-shrink-0 text-gray-800 dark:text-gray-200">$<%= number_with_precision(tenant_budget[:daily_spend], precision: 2) %> <span class="text-gray-400 dark:text-gray-600">/ $<%= number_with_precision(tenant_budget[:daily_limit], precision: 2) %></span></span>
35
+ <span class="w-12 flex-shrink-0 text-right text-gray-400 dark:text-gray-600"><%= tenant_budget[:daily_percentage] %>%</span>
36
+ <div class="flex-1 bg-gray-200 dark:bg-gray-800 rounded-full h-1">
37
+ <div class="<%= bar_color %> h-1 rounded-full transition-all" style="width: <%= daily_pct %>%"></div>
43
38
  </div>
44
- <% if tenant_budget[:daily_limit] %>
45
- <% daily_pct = [tenant_budget[:daily_percentage], 100].min %>
46
- <% bar_color = if daily_pct >= 100
47
- "bg-red-500"
48
- elsif daily_pct >= 80
49
- "bg-yellow-500"
50
- else
51
- "bg-green-500"
52
- end %>
53
- <div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2.5">
54
- <div class="<%= bar_color %> h-2.5 rounded-full transition-all duration-300" style="width: <%= daily_pct %>%"></div>
55
- </div>
56
- <p class="text-xs text-gray-500 dark:text-gray-400 mt-1"><%= tenant_budget[:daily_percentage] %>% used</p>
57
- <% else %>
58
- <div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2.5">
59
- <div class="bg-gray-400 h-2.5 rounded-full" style="width: 0%"></div>
60
- </div>
61
- <p class="text-xs text-gray-500 dark:text-gray-400 mt-1">No daily limit configured</p>
62
- <% end %>
63
39
  </div>
64
-
65
- <%# Monthly Budget %>
66
- <div>
67
- <div class="flex items-center justify-between mb-2">
68
- <span class="text-sm font-medium text-gray-700 dark:text-gray-300">Monthly Budget</span>
69
- <span class="text-sm text-gray-500 dark:text-gray-400">
70
- $<%= number_with_precision(tenant_budget[:monthly_spend], precision: 2) %>
71
- <% if tenant_budget[:monthly_limit] %>
72
- / $<%= number_with_precision(tenant_budget[:monthly_limit], precision: 2) %>
73
- <% else %>
74
- (no limit)
75
- <% end %>
76
- </span>
77
- </div>
78
- <% if tenant_budget[:monthly_limit] %>
79
- <% monthly_pct = [tenant_budget[:monthly_percentage], 100].min %>
80
- <% bar_color = if monthly_pct >= 100
81
- "bg-red-500"
82
- elsif monthly_pct >= 80
83
- "bg-yellow-500"
84
- else
85
- "bg-blue-500"
86
- end %>
87
- <div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2.5">
88
- <div class="<%= bar_color %> h-2.5 rounded-full transition-all duration-300" style="width: <%= monthly_pct %>%"></div>
89
- </div>
90
- <p class="text-xs text-gray-500 dark:text-gray-400 mt-1"><%= tenant_budget[:monthly_percentage] %>% used</p>
91
- <% else %>
92
- <div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2.5">
93
- <div class="bg-gray-400 h-2.5 rounded-full" style="width: 0%"></div>
94
- </div>
95
- <p class="text-xs text-gray-500 dark:text-gray-400 mt-1">No monthly limit configured</p>
96
- <% end %>
40
+ <% else %>
41
+ <div class="flex items-center gap-3">
42
+ <span class="w-28 flex-shrink-0 text-gray-500 dark:text-gray-400">daily</span>
43
+ <span class="text-gray-800 dark:text-gray-200">$<%= number_with_precision(tenant_budget[:daily_spend], precision: 2) %></span>
44
+ <span class="text-gray-400 dark:text-gray-600">(no limit)</span>
97
45
  </div>
98
- </div>
46
+ <% end %>
99
47
 
100
- <%# Per-agent limits if configured %>
101
- <% if tenant_budget[:per_agent_daily].present? %>
102
- <div class="mt-4 pt-4 border-t border-gray-100 dark:border-gray-700">
103
- <p class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide mb-2">Per-Agent Daily Limits</p>
104
- <div class="flex flex-wrap gap-2">
105
- <% tenant_budget[:per_agent_daily].each do |agent, limit| %>
106
- <span class="inline-flex items-center px-2 py-1 rounded text-xs bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300">
107
- <%= agent.to_s.gsub(/Agent$/, "") %>: $<%= number_with_precision(limit, precision: 2) %>
108
- </span>
109
- <% end %>
48
+ <%# Monthly Budget %>
49
+ <% if tenant_budget[:monthly_limit] %>
50
+ <%
51
+ monthly_pct = [tenant_budget[:monthly_percentage], 100].min
52
+ bar_color = if monthly_pct >= 100 then "bg-red-500"
53
+ elsif monthly_pct >= 80 then "bg-yellow-500"
54
+ else "bg-blue-500"
55
+ end
56
+ %>
57
+ <div class="flex items-center gap-3">
58
+ <span class="w-28 flex-shrink-0 text-gray-500 dark:text-gray-400">monthly</span>
59
+ <span class="w-44 flex-shrink-0 text-gray-800 dark:text-gray-200">$<%= number_with_precision(tenant_budget[:monthly_spend], precision: 2) %> <span class="text-gray-400 dark:text-gray-600">/ $<%= number_with_precision(tenant_budget[:monthly_limit], precision: 2) %></span></span>
60
+ <span class="w-12 flex-shrink-0 text-right text-gray-400 dark:text-gray-600"><%= tenant_budget[:monthly_percentage] %>%</span>
61
+ <div class="flex-1 bg-gray-200 dark:bg-gray-800 rounded-full h-1">
62
+ <div class="<%= bar_color %> h-1 rounded-full transition-all" style="width: <%= monthly_pct %>%"></div>
110
63
  </div>
111
64
  </div>
65
+ <% else %>
66
+ <div class="flex items-center gap-3">
67
+ <span class="w-28 flex-shrink-0 text-gray-500 dark:text-gray-400">monthly</span>
68
+ <span class="text-gray-800 dark:text-gray-200">$<%= number_with_precision(tenant_budget[:monthly_spend], precision: 2) %></span>
69
+ <span class="text-gray-400 dark:text-gray-600">(no limit)</span>
70
+ </div>
112
71
  <% end %>
113
72
  </div>
73
+
74
+ <%# Per-agent limits if configured %>
75
+ <% if tenant_budget[:per_agent_daily].present? %>
76
+ <div class="mt-3 font-mono text-xs">
77
+ <span class="text-gray-400 dark:text-gray-600">per-agent daily:</span>
78
+ <div class="mt-1 space-y-0.5 pl-2">
79
+ <% tenant_budget[:per_agent_daily].each do |agent, limit| %>
80
+ <div class="flex items-center gap-2">
81
+ <span class="text-gray-900 dark:text-gray-200"><%= agent.to_s.gsub(/Agent$/, "") %></span>
82
+ <span class="text-gray-400 dark:text-gray-600">$<%= number_with_precision(limit, precision: 2) %></span>
83
+ </div>
84
+ <% end %>
85
+ </div>
86
+ </div>
87
+ <% end %>
114
88
  </div>
115
89
  <% end %>