ruby_llm-agents 0.4.0 → 1.0.0.beta.1

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 (208) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +225 -34
  3. data/app/controllers/ruby_llm/agents/agents_controller.rb +136 -16
  4. data/app/controllers/ruby_llm/agents/api_configurations_controller.rb +214 -0
  5. data/app/controllers/ruby_llm/agents/dashboard_controller.rb +29 -9
  6. data/app/controllers/ruby_llm/agents/{settings_controller.rb → system_config_controller.rb} +3 -3
  7. data/app/controllers/ruby_llm/agents/tenants_controller.rb +109 -0
  8. data/app/controllers/ruby_llm/agents/workflows_controller.rb +355 -0
  9. data/app/helpers/ruby_llm/agents/application_helper.rb +25 -0
  10. data/app/models/ruby_llm/agents/api_configuration.rb +386 -0
  11. data/app/models/ruby_llm/agents/execution.rb +3 -0
  12. data/app/models/ruby_llm/agents/tenant_budget.rb +112 -14
  13. data/app/services/ruby_llm/agents/agent_registry.rb +51 -12
  14. data/app/views/layouts/ruby_llm/agents/application.html.erb +5 -30
  15. data/app/views/ruby_llm/agents/agents/_agent.html.erb +13 -1
  16. data/app/views/ruby_llm/agents/agents/_config_agent.html.erb +235 -0
  17. data/app/views/ruby_llm/agents/agents/_config_embedder.html.erb +70 -0
  18. data/app/views/ruby_llm/agents/agents/_config_image_generator.html.erb +152 -0
  19. data/app/views/ruby_llm/agents/agents/_config_moderator.html.erb +63 -0
  20. data/app/views/ruby_llm/agents/agents/_config_speaker.html.erb +108 -0
  21. data/app/views/ruby_llm/agents/agents/_config_transcriber.html.erb +91 -0
  22. data/app/views/ruby_llm/agents/agents/_workflow.html.erb +1 -1
  23. data/app/views/ruby_llm/agents/agents/index.html.erb +74 -9
  24. data/app/views/ruby_llm/agents/agents/show.html.erb +18 -378
  25. data/app/views/ruby_llm/agents/api_configurations/_api_key_field.html.erb +34 -0
  26. data/app/views/ruby_llm/agents/api_configurations/_form.html.erb +288 -0
  27. data/app/views/ruby_llm/agents/api_configurations/edit.html.erb +95 -0
  28. data/app/views/ruby_llm/agents/api_configurations/edit_tenant.html.erb +97 -0
  29. data/app/views/ruby_llm/agents/api_configurations/show.html.erb +211 -0
  30. data/app/views/ruby_llm/agents/api_configurations/tenant.html.erb +179 -0
  31. data/app/views/ruby_llm/agents/dashboard/_action_center.html.erb +1 -1
  32. data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +269 -15
  33. data/app/views/ruby_llm/agents/executions/show.html.erb +98 -0
  34. data/app/views/ruby_llm/agents/shared/_agent_type_badge.html.erb +93 -0
  35. data/app/views/ruby_llm/agents/{settings → system_config}/show.html.erb +1 -1
  36. data/app/views/ruby_llm/agents/tenants/_form.html.erb +150 -0
  37. data/app/views/ruby_llm/agents/tenants/edit.html.erb +13 -0
  38. data/app/views/ruby_llm/agents/tenants/index.html.erb +129 -0
  39. data/app/views/ruby_llm/agents/tenants/show.html.erb +374 -0
  40. data/app/views/ruby_llm/agents/workflows/_step_performance.html.erb +236 -0
  41. data/app/views/ruby_llm/agents/workflows/_structure_parallel.html.erb +76 -0
  42. data/app/views/ruby_llm/agents/workflows/_structure_pipeline.html.erb +74 -0
  43. data/app/views/ruby_llm/agents/workflows/_structure_router.html.erb +108 -0
  44. data/app/views/ruby_llm/agents/workflows/show.html.erb +442 -0
  45. data/config/routes.rb +13 -1
  46. data/lib/generators/ruby_llm_agents/agent_generator.rb +56 -7
  47. data/lib/generators/ruby_llm_agents/api_configuration_generator.rb +100 -0
  48. data/lib/generators/ruby_llm_agents/background_remover_generator.rb +110 -0
  49. data/lib/generators/ruby_llm_agents/embedder_generator.rb +107 -0
  50. data/lib/generators/ruby_llm_agents/image_analyzer_generator.rb +115 -0
  51. data/lib/generators/ruby_llm_agents/image_editor_generator.rb +108 -0
  52. data/lib/generators/ruby_llm_agents/image_generator_generator.rb +116 -0
  53. data/lib/generators/ruby_llm_agents/image_pipeline_generator.rb +178 -0
  54. data/lib/generators/ruby_llm_agents/image_transformer_generator.rb +109 -0
  55. data/lib/generators/ruby_llm_agents/image_upscaler_generator.rb +103 -0
  56. data/lib/generators/ruby_llm_agents/image_variator_generator.rb +102 -0
  57. data/lib/generators/ruby_llm_agents/install_generator.rb +76 -4
  58. data/lib/generators/ruby_llm_agents/restructure_generator.rb +292 -0
  59. data/lib/generators/ruby_llm_agents/speaker_generator.rb +121 -0
  60. data/lib/generators/ruby_llm_agents/templates/add_execution_type_migration.rb.tt +8 -0
  61. data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +99 -84
  62. data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +42 -40
  63. data/lib/generators/ruby_llm_agents/templates/application_background_remover.rb.tt +26 -0
  64. data/lib/generators/ruby_llm_agents/templates/application_embedder.rb.tt +50 -0
  65. data/lib/generators/ruby_llm_agents/templates/application_image_analyzer.rb.tt +26 -0
  66. data/lib/generators/ruby_llm_agents/templates/application_image_editor.rb.tt +20 -0
  67. data/lib/generators/ruby_llm_agents/templates/application_image_generator.rb.tt +38 -0
  68. data/lib/generators/ruby_llm_agents/templates/application_image_pipeline.rb.tt +139 -0
  69. data/lib/generators/ruby_llm_agents/templates/application_image_transformer.rb.tt +21 -0
  70. data/lib/generators/ruby_llm_agents/templates/application_image_upscaler.rb.tt +20 -0
  71. data/lib/generators/ruby_llm_agents/templates/application_image_variator.rb.tt +20 -0
  72. data/lib/generators/ruby_llm_agents/templates/application_speaker.rb.tt +49 -0
  73. data/lib/generators/ruby_llm_agents/templates/application_transcriber.rb.tt +53 -0
  74. data/lib/generators/ruby_llm_agents/templates/background_remover.rb.tt +44 -0
  75. data/lib/generators/ruby_llm_agents/templates/create_api_configurations_migration.rb.tt +90 -0
  76. data/lib/generators/ruby_llm_agents/templates/embedder.rb.tt +41 -0
  77. data/lib/generators/ruby_llm_agents/templates/image_analyzer.rb.tt +45 -0
  78. data/lib/generators/ruby_llm_agents/templates/image_editor.rb.tt +35 -0
  79. data/lib/generators/ruby_llm_agents/templates/image_generator.rb.tt +47 -0
  80. data/lib/generators/ruby_llm_agents/templates/image_pipeline.rb.tt +50 -0
  81. data/lib/generators/ruby_llm_agents/templates/image_transformer.rb.tt +44 -0
  82. data/lib/generators/ruby_llm_agents/templates/image_upscaler.rb.tt +38 -0
  83. data/lib/generators/ruby_llm_agents/templates/image_variator.rb.tt +33 -0
  84. data/lib/generators/ruby_llm_agents/templates/skills/AGENTS.md.tt +228 -0
  85. data/lib/generators/ruby_llm_agents/templates/skills/BACKGROUND_REMOVERS.md.tt +131 -0
  86. data/lib/generators/ruby_llm_agents/templates/skills/EMBEDDERS.md.tt +255 -0
  87. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_ANALYZERS.md.tt +120 -0
  88. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_EDITORS.md.tt +102 -0
  89. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_GENERATORS.md.tt +282 -0
  90. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_PIPELINES.md.tt +228 -0
  91. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_TRANSFORMERS.md.tt +120 -0
  92. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_UPSCALERS.md.tt +110 -0
  93. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_VARIATORS.md.tt +120 -0
  94. data/lib/generators/ruby_llm_agents/templates/skills/SPEAKERS.md.tt +212 -0
  95. data/lib/generators/ruby_llm_agents/templates/skills/TOOLS.md.tt +227 -0
  96. data/lib/generators/ruby_llm_agents/templates/skills/TRANSCRIBERS.md.tt +251 -0
  97. data/lib/generators/ruby_llm_agents/templates/skills/WORKFLOWS.md.tt +300 -0
  98. data/lib/generators/ruby_llm_agents/templates/speaker.rb.tt +56 -0
  99. data/lib/generators/ruby_llm_agents/templates/transcriber.rb.tt +51 -0
  100. data/lib/generators/ruby_llm_agents/transcriber_generator.rb +107 -0
  101. data/lib/generators/ruby_llm_agents/upgrade_generator.rb +152 -1
  102. data/lib/ruby_llm/agents/audio/speaker.rb +553 -0
  103. data/lib/ruby_llm/agents/audio/transcriber.rb +669 -0
  104. data/lib/ruby_llm/agents/base_agent.rb +675 -0
  105. data/lib/ruby_llm/agents/core/base/moderation_dsl.rb +181 -0
  106. data/lib/ruby_llm/agents/core/base/moderation_execution.rb +274 -0
  107. data/lib/ruby_llm/agents/core/base.rb +135 -0
  108. data/lib/ruby_llm/agents/core/configuration.rb +981 -0
  109. data/lib/ruby_llm/agents/core/errors.rb +150 -0
  110. data/lib/ruby_llm/agents/{instrumentation.rb → core/instrumentation.rb} +93 -4
  111. data/lib/ruby_llm/agents/core/llm_tenant.rb +358 -0
  112. data/lib/ruby_llm/agents/core/resolved_config.rb +348 -0
  113. data/lib/ruby_llm/agents/{version.rb → core/version.rb} +1 -1
  114. data/lib/ruby_llm/agents/dsl/base.rb +110 -0
  115. data/lib/ruby_llm/agents/dsl/caching.rb +142 -0
  116. data/lib/ruby_llm/agents/dsl/reliability.rb +307 -0
  117. data/lib/ruby_llm/agents/dsl.rb +41 -0
  118. data/lib/ruby_llm/agents/image/analyzer/dsl.rb +130 -0
  119. data/lib/ruby_llm/agents/image/analyzer/execution.rb +402 -0
  120. data/lib/ruby_llm/agents/image/analyzer.rb +90 -0
  121. data/lib/ruby_llm/agents/image/background_remover/dsl.rb +154 -0
  122. data/lib/ruby_llm/agents/image/background_remover/execution.rb +240 -0
  123. data/lib/ruby_llm/agents/image/background_remover.rb +89 -0
  124. data/lib/ruby_llm/agents/image/concerns/image_operation_dsl.rb +91 -0
  125. data/lib/ruby_llm/agents/image/concerns/image_operation_execution.rb +165 -0
  126. data/lib/ruby_llm/agents/image/editor/dsl.rb +56 -0
  127. data/lib/ruby_llm/agents/image/editor/execution.rb +207 -0
  128. data/lib/ruby_llm/agents/image/editor.rb +92 -0
  129. data/lib/ruby_llm/agents/image/generator/active_storage_support.rb +127 -0
  130. data/lib/ruby_llm/agents/image/generator/content_policy.rb +95 -0
  131. data/lib/ruby_llm/agents/image/generator/pricing.rb +353 -0
  132. data/lib/ruby_llm/agents/image/generator/templates.rb +124 -0
  133. data/lib/ruby_llm/agents/image/generator.rb +455 -0
  134. data/lib/ruby_llm/agents/image/pipeline/dsl.rb +213 -0
  135. data/lib/ruby_llm/agents/image/pipeline/execution.rb +382 -0
  136. data/lib/ruby_llm/agents/image/pipeline.rb +97 -0
  137. data/lib/ruby_llm/agents/image/transformer/dsl.rb +148 -0
  138. data/lib/ruby_llm/agents/image/transformer/execution.rb +223 -0
  139. data/lib/ruby_llm/agents/image/transformer.rb +95 -0
  140. data/lib/ruby_llm/agents/image/upscaler/dsl.rb +83 -0
  141. data/lib/ruby_llm/agents/image/upscaler/execution.rb +219 -0
  142. data/lib/ruby_llm/agents/image/upscaler.rb +81 -0
  143. data/lib/ruby_llm/agents/image/variator/dsl.rb +62 -0
  144. data/lib/ruby_llm/agents/image/variator/execution.rb +189 -0
  145. data/lib/ruby_llm/agents/image/variator.rb +80 -0
  146. data/lib/ruby_llm/agents/{alert_manager.rb → infrastructure/alert_manager.rb} +17 -22
  147. data/lib/ruby_llm/agents/infrastructure/budget/budget_query.rb +145 -0
  148. data/lib/ruby_llm/agents/infrastructure/budget/config_resolver.rb +149 -0
  149. data/lib/ruby_llm/agents/infrastructure/budget/forecaster.rb +68 -0
  150. data/lib/ruby_llm/agents/infrastructure/budget/spend_recorder.rb +279 -0
  151. data/lib/ruby_llm/agents/infrastructure/budget_tracker.rb +275 -0
  152. data/lib/ruby_llm/agents/{execution_logger_job.rb → infrastructure/execution_logger_job.rb} +17 -1
  153. data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/executor.rb +2 -1
  154. data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/retry_strategy.rb +9 -3
  155. data/lib/ruby_llm/agents/{reliability.rb → infrastructure/reliability.rb} +11 -21
  156. data/lib/ruby_llm/agents/pipeline/builder.rb +215 -0
  157. data/lib/ruby_llm/agents/pipeline/context.rb +255 -0
  158. data/lib/ruby_llm/agents/pipeline/executor.rb +86 -0
  159. data/lib/ruby_llm/agents/pipeline/middleware/base.rb +124 -0
  160. data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +95 -0
  161. data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +171 -0
  162. data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +415 -0
  163. data/lib/ruby_llm/agents/pipeline/middleware/reliability.rb +276 -0
  164. data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +196 -0
  165. data/lib/ruby_llm/agents/pipeline.rb +68 -0
  166. data/lib/ruby_llm/agents/{engine.rb → rails/engine.rb} +79 -10
  167. data/lib/ruby_llm/agents/results/background_removal_result.rb +286 -0
  168. data/lib/ruby_llm/agents/{result.rb → results/base.rb} +73 -1
  169. data/lib/ruby_llm/agents/results/embedding_result.rb +243 -0
  170. data/lib/ruby_llm/agents/results/image_analysis_result.rb +314 -0
  171. data/lib/ruby_llm/agents/results/image_edit_result.rb +250 -0
  172. data/lib/ruby_llm/agents/results/image_generation_result.rb +346 -0
  173. data/lib/ruby_llm/agents/results/image_pipeline_result.rb +399 -0
  174. data/lib/ruby_llm/agents/results/image_transform_result.rb +251 -0
  175. data/lib/ruby_llm/agents/results/image_upscale_result.rb +255 -0
  176. data/lib/ruby_llm/agents/results/image_variation_result.rb +237 -0
  177. data/lib/ruby_llm/agents/results/moderation_result.rb +158 -0
  178. data/lib/ruby_llm/agents/results/speech_result.rb +338 -0
  179. data/lib/ruby_llm/agents/results/transcription_result.rb +408 -0
  180. data/lib/ruby_llm/agents/text/embedder.rb +444 -0
  181. data/lib/ruby_llm/agents/text/moderator.rb +237 -0
  182. data/lib/ruby_llm/agents/workflow/async.rb +220 -0
  183. data/lib/ruby_llm/agents/workflow/async_executor.rb +156 -0
  184. data/lib/ruby_llm/agents/{workflow.rb → workflow/orchestrator.rb} +6 -5
  185. data/lib/ruby_llm/agents/workflow/parallel.rb +34 -17
  186. data/lib/ruby_llm/agents/workflow/thread_pool.rb +185 -0
  187. data/lib/ruby_llm/agents.rb +86 -20
  188. metadata +189 -35
  189. data/lib/ruby_llm/agents/base/caching.rb +0 -40
  190. data/lib/ruby_llm/agents/base/cost_calculation.rb +0 -105
  191. data/lib/ruby_llm/agents/base/dsl.rb +0 -324
  192. data/lib/ruby_llm/agents/base/execution.rb +0 -283
  193. data/lib/ruby_llm/agents/base/reliability_dsl.rb +0 -82
  194. data/lib/ruby_llm/agents/base/reliability_execution.rb +0 -136
  195. data/lib/ruby_llm/agents/base/response_building.rb +0 -86
  196. data/lib/ruby_llm/agents/base/tool_tracking.rb +0 -57
  197. data/lib/ruby_llm/agents/base.rb +0 -209
  198. data/lib/ruby_llm/agents/budget_tracker.rb +0 -471
  199. data/lib/ruby_llm/agents/configuration.rb +0 -357
  200. /data/lib/ruby_llm/agents/{deprecations.rb → core/deprecations.rb} +0 -0
  201. /data/lib/ruby_llm/agents/{inflections.rb → core/inflections.rb} +0 -0
  202. /data/lib/ruby_llm/agents/{attempt_tracker.rb → infrastructure/attempt_tracker.rb} +0 -0
  203. /data/lib/ruby_llm/agents/{cache_helper.rb → infrastructure/cache_helper.rb} +0 -0
  204. /data/lib/ruby_llm/agents/{circuit_breaker.rb → infrastructure/circuit_breaker.rb} +0 -0
  205. /data/lib/ruby_llm/agents/{redactor.rb → infrastructure/redactor.rb} +0 -0
  206. /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/breaker_manager.rb +0 -0
  207. /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/execution_constraints.rb +0 -0
  208. /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/fallback_routing.rb +0 -0
@@ -0,0 +1,150 @@
1
+ <%# Form partial for tenant budget editing %>
2
+ <%= form_with model: tenant, url: tenant_path(tenant), method: :patch, class: "space-y-6" do |f| %>
3
+ <% if tenant.errors.any? %>
4
+ <div class="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4">
5
+ <div class="flex items-center">
6
+ <svg class="w-5 h-5 text-red-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
7
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
8
+ </svg>
9
+ <h3 class="text-sm font-medium text-red-800 dark:text-red-200">
10
+ Please fix the following errors:
11
+ </h3>
12
+ </div>
13
+ <ul class="mt-2 ml-7 list-disc list-inside text-sm text-red-700 dark:text-red-300">
14
+ <% tenant.errors.full_messages.each do |message| %>
15
+ <li><%= message %></li>
16
+ <% end %>
17
+ </ul>
18
+ </div>
19
+ <% end %>
20
+
21
+ <!-- Basic Information -->
22
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
23
+ <h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">Basic Information</h3>
24
+
25
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
26
+ <div>
27
+ <%= f.label :name, "Display Name", class: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" %>
28
+ <%= f.text_field :name,
29
+ class: "w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-gray-100",
30
+ placeholder: "e.g., Acme Corporation" %>
31
+ <p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
32
+ Human-readable name for this tenant. Falls back to tenant ID if not set.
33
+ </p>
34
+ </div>
35
+
36
+ <div>
37
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Tenant ID</label>
38
+ <p class="w-full px-3 py-2 bg-gray-50 dark:bg-gray-700/50 border border-gray-200 dark:border-gray-600 rounded-md text-gray-900 dark:text-gray-100 font-mono text-sm">
39
+ <%= tenant.tenant_id %>
40
+ </p>
41
+ <p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
42
+ System identifier. Cannot be changed.
43
+ </p>
44
+ </div>
45
+ </div>
46
+ </div>
47
+
48
+ <!-- Budget Limits (USD) -->
49
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
50
+ <h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">Budget Limits (USD)</h3>
51
+
52
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
53
+ <div>
54
+ <%= f.label :daily_limit, "Daily Limit", class: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" %>
55
+ <div class="relative">
56
+ <span class="absolute inset-y-0 left-0 pl-3 flex items-center text-gray-500 dark:text-gray-400">$</span>
57
+ <%= f.number_field :daily_limit,
58
+ class: "w-full pl-7 pr-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-gray-100",
59
+ step: "0.01",
60
+ min: "0",
61
+ placeholder: "e.g., 50.00" %>
62
+ </div>
63
+ <p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
64
+ Maximum daily spend in USD. Leave blank for no limit.
65
+ </p>
66
+ </div>
67
+
68
+ <div>
69
+ <%= f.label :monthly_limit, "Monthly Limit", class: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" %>
70
+ <div class="relative">
71
+ <span class="absolute inset-y-0 left-0 pl-3 flex items-center text-gray-500 dark:text-gray-400">$</span>
72
+ <%= f.number_field :monthly_limit,
73
+ class: "w-full pl-7 pr-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-gray-100",
74
+ step: "0.01",
75
+ min: "0",
76
+ placeholder: "e.g., 500.00" %>
77
+ </div>
78
+ <p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
79
+ Maximum monthly spend in USD. Leave blank for no limit.
80
+ </p>
81
+ </div>
82
+ </div>
83
+ </div>
84
+
85
+ <!-- Token Limits -->
86
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
87
+ <h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">Token Limits</h3>
88
+
89
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
90
+ <div>
91
+ <%= f.label :daily_token_limit, "Daily Token Limit", class: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" %>
92
+ <%= f.number_field :daily_token_limit,
93
+ class: "w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-gray-100",
94
+ step: "1",
95
+ min: "0",
96
+ placeholder: "e.g., 1000000" %>
97
+ <p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
98
+ Maximum daily token usage. Leave blank for no limit.
99
+ </p>
100
+ </div>
101
+
102
+ <div>
103
+ <%= f.label :monthly_token_limit, "Monthly Token Limit", class: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" %>
104
+ <%= f.number_field :monthly_token_limit,
105
+ class: "w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-gray-100",
106
+ step: "1",
107
+ min: "0",
108
+ placeholder: "e.g., 10000000" %>
109
+ <p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
110
+ Maximum monthly token usage. Leave blank for no limit.
111
+ </p>
112
+ </div>
113
+ </div>
114
+ </div>
115
+
116
+ <!-- Enforcement -->
117
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
118
+ <h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">Enforcement</h3>
119
+
120
+ <div>
121
+ <%= f.label :enforcement, "Enforcement Mode", class: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" %>
122
+ <%= f.select :enforcement,
123
+ options_for_select([
124
+ ["None - No enforcement", "none"],
125
+ ["Soft - Log warnings but allow requests", "soft"],
126
+ ["Hard - Block requests when limits exceeded", "hard"]
127
+ ], tenant.enforcement || "soft"),
128
+ {},
129
+ class: "w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-gray-100" %>
130
+ <p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
131
+ Controls what happens when budget limits are exceeded.
132
+ </p>
133
+ </div>
134
+
135
+ <div class="mt-4 p-4 bg-gray-50 dark:bg-gray-700/50 rounded-lg">
136
+ <h4 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Enforcement Mode Details</h4>
137
+ <ul class="text-xs text-gray-600 dark:text-gray-400 space-y-1">
138
+ <li><strong>None:</strong> Budget tracking only, no enforcement actions taken.</li>
139
+ <li><strong>Soft:</strong> Logs warnings and triggers alerts when limits are exceeded, but allows requests to proceed.</li>
140
+ <li><strong>Hard:</strong> Blocks agent executions when budget limits are exceeded. Use with caution.</li>
141
+ </ul>
142
+ </div>
143
+ </div>
144
+
145
+ <!-- Actions -->
146
+ <div class="flex items-center justify-end space-x-4">
147
+ <%= link_to "Cancel", tenant_path(tenant), class: "px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors" %>
148
+ <%= f.submit "Save Changes", class: "px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 cursor-pointer transition-colors" %>
149
+ </div>
150
+ <% end %>
@@ -0,0 +1,13 @@
1
+ <%= render "ruby_llm/agents/shared/breadcrumbs", items: [
2
+ { label: "Dashboard", path: ruby_llm_agents.root_path },
3
+ { label: "Tenants", path: ruby_llm_agents.tenants_path },
4
+ { label: @tenant.display_name, path: ruby_llm_agents.tenant_path(@tenant) },
5
+ { label: "Edit" }
6
+ ] %>
7
+
8
+ <div class="mb-6">
9
+ <h1 class="text-2xl font-bold text-gray-900 dark:text-gray-100">Edit Tenant</h1>
10
+ <p class="text-gray-500 dark:text-gray-400 mt-1">Update budget configuration for <%= @tenant.display_name %></p>
11
+ </div>
12
+
13
+ <%= render "form", tenant: @tenant %>
@@ -0,0 +1,129 @@
1
+ <div class="mb-6">
2
+ <h1 class="text-2xl font-bold text-gray-900 dark:text-gray-100">Tenants</h1>
3
+ <p class="text-gray-500 dark:text-gray-400 mt-1">Manage tenant budget configurations</p>
4
+ </div>
5
+
6
+ <% if @tenants.empty? %>
7
+ <div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 p-8 text-center">
8
+ <svg class="w-12 h-12 mx-auto text-gray-400 dark:text-gray-500 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
9
+ <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" />
10
+ </svg>
11
+ <h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-1">No tenants configured</h3>
12
+ <p class="text-gray-500 dark:text-gray-400">Tenant budgets will appear here once created via the API or configuration.</p>
13
+ </div>
14
+ <% else %>
15
+ <div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 overflow-hidden">
16
+ <div class="overflow-x-auto">
17
+ <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
18
+ <thead class="bg-gray-50 dark:bg-gray-900/50">
19
+ <tr>
20
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
21
+ Tenant
22
+ </th>
23
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
24
+ Daily Budget
25
+ </th>
26
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
27
+ Monthly Budget
28
+ </th>
29
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
30
+ Daily Tokens
31
+ </th>
32
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
33
+ Monthly Tokens
34
+ </th>
35
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
36
+ Enforcement
37
+ </th>
38
+ <th scope="col" class="px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
39
+ Actions
40
+ </th>
41
+ </tr>
42
+ </thead>
43
+ <tbody class="divide-y divide-gray-200 dark:divide-gray-700">
44
+ <% @tenants.each do |tenant| %>
45
+ <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors cursor-pointer" data-href="<%= tenant_path(tenant) %>">
46
+ <td class="px-6 py-4 whitespace-nowrap">
47
+ <div class="flex items-center">
48
+ <div class="flex-shrink-0 h-8 w-8 rounded-full bg-blue-100 dark:bg-blue-900/50 flex items-center justify-center">
49
+ <svg class="w-4 h-4 text-blue-600 dark:text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
50
+ <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" />
51
+ </svg>
52
+ </div>
53
+ <div class="ml-3">
54
+ <div class="text-sm font-medium text-gray-900 dark:text-gray-100">
55
+ <%= tenant.display_name %>
56
+ </div>
57
+ <% if tenant.name.present? && tenant.name != tenant.tenant_id %>
58
+ <div class="text-xs text-gray-500 dark:text-gray-400 font-mono">
59
+ <%= tenant.tenant_id %>
60
+ </div>
61
+ <% end %>
62
+ </div>
63
+ </div>
64
+ </td>
65
+ <td class="px-6 py-4 whitespace-nowrap">
66
+ <% if tenant.effective_daily_limit %>
67
+ <span class="text-sm font-medium text-gray-900 dark:text-gray-100">
68
+ $<%= number_with_precision(tenant.effective_daily_limit, precision: 2) %>
69
+ </span>
70
+ <% else %>
71
+ <span class="text-sm text-gray-400 dark:text-gray-500">No limit</span>
72
+ <% end %>
73
+ </td>
74
+ <td class="px-6 py-4 whitespace-nowrap">
75
+ <% if tenant.effective_monthly_limit %>
76
+ <span class="text-sm font-medium text-gray-900 dark:text-gray-100">
77
+ $<%= number_with_precision(tenant.effective_monthly_limit, precision: 2) %>
78
+ </span>
79
+ <% else %>
80
+ <span class="text-sm text-gray-400 dark:text-gray-500">No limit</span>
81
+ <% end %>
82
+ </td>
83
+ <td class="px-6 py-4 whitespace-nowrap">
84
+ <% if tenant.effective_daily_token_limit %>
85
+ <span class="text-sm font-medium text-gray-900 dark:text-gray-100">
86
+ <%= number_with_delimiter(tenant.effective_daily_token_limit) %>
87
+ </span>
88
+ <% else %>
89
+ <span class="text-sm text-gray-400 dark:text-gray-500">No limit</span>
90
+ <% end %>
91
+ </td>
92
+ <td class="px-6 py-4 whitespace-nowrap">
93
+ <% if tenant.effective_monthly_token_limit %>
94
+ <span class="text-sm font-medium text-gray-900 dark:text-gray-100">
95
+ <%= number_with_delimiter(tenant.effective_monthly_token_limit) %>
96
+ </span>
97
+ <% else %>
98
+ <span class="text-sm text-gray-400 dark:text-gray-500">No limit</span>
99
+ <% end %>
100
+ </td>
101
+ <td class="px-6 py-4 whitespace-nowrap">
102
+ <%
103
+ enforcement = tenant.effective_enforcement.to_s
104
+ badge_class = case enforcement
105
+ when "hard" then "bg-red-100 dark:bg-red-900/50 text-red-800 dark:text-red-200"
106
+ when "soft" then "bg-yellow-100 dark:bg-yellow-900/50 text-yellow-800 dark:text-yellow-200"
107
+ else "bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400"
108
+ end
109
+ %>
110
+ <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium <%= badge_class %>">
111
+ <%= enforcement.capitalize %>
112
+ </span>
113
+ </td>
114
+ <td class="px-6 py-4 whitespace-nowrap text-right text-sm">
115
+ <%= link_to tenant_path(tenant), class: "text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300" do %>
116
+ View
117
+ <% end %>
118
+ <span class="text-gray-300 dark:text-gray-600 mx-2">|</span>
119
+ <%= link_to edit_tenant_path(tenant), class: "text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300" do %>
120
+ Edit
121
+ <% end %>
122
+ </td>
123
+ </tr>
124
+ <% end %>
125
+ </tbody>
126
+ </table>
127
+ </div>
128
+ </div>
129
+ <% end %>