ruby_llm-agents 0.5.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 (190) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +189 -31
  3. data/app/controllers/ruby_llm/agents/agents_controller.rb +136 -16
  4. data/app/controllers/ruby_llm/agents/dashboard_controller.rb +29 -9
  5. data/app/controllers/ruby_llm/agents/workflows_controller.rb +355 -0
  6. data/app/helpers/ruby_llm/agents/application_helper.rb +25 -0
  7. data/app/models/ruby_llm/agents/execution.rb +3 -0
  8. data/app/models/ruby_llm/agents/tenant_budget.rb +58 -15
  9. data/app/services/ruby_llm/agents/agent_registry.rb +51 -12
  10. data/app/views/layouts/ruby_llm/agents/application.html.erb +2 -29
  11. data/app/views/ruby_llm/agents/agents/_agent.html.erb +13 -1
  12. data/app/views/ruby_llm/agents/agents/_config_agent.html.erb +235 -0
  13. data/app/views/ruby_llm/agents/agents/_config_embedder.html.erb +70 -0
  14. data/app/views/ruby_llm/agents/agents/_config_image_generator.html.erb +152 -0
  15. data/app/views/ruby_llm/agents/agents/_config_moderator.html.erb +63 -0
  16. data/app/views/ruby_llm/agents/agents/_config_speaker.html.erb +108 -0
  17. data/app/views/ruby_llm/agents/agents/_config_transcriber.html.erb +91 -0
  18. data/app/views/ruby_llm/agents/agents/_workflow.html.erb +1 -1
  19. data/app/views/ruby_llm/agents/agents/index.html.erb +74 -9
  20. data/app/views/ruby_llm/agents/agents/show.html.erb +18 -378
  21. data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +269 -15
  22. data/app/views/ruby_llm/agents/executions/show.html.erb +16 -0
  23. data/app/views/ruby_llm/agents/shared/_agent_type_badge.html.erb +93 -0
  24. data/app/views/ruby_llm/agents/workflows/_step_performance.html.erb +236 -0
  25. data/app/views/ruby_llm/agents/workflows/_structure_parallel.html.erb +76 -0
  26. data/app/views/ruby_llm/agents/workflows/_structure_pipeline.html.erb +74 -0
  27. data/app/views/ruby_llm/agents/workflows/_structure_router.html.erb +108 -0
  28. data/app/views/ruby_llm/agents/workflows/show.html.erb +442 -0
  29. data/config/routes.rb +1 -0
  30. data/lib/generators/ruby_llm_agents/agent_generator.rb +56 -7
  31. data/lib/generators/ruby_llm_agents/background_remover_generator.rb +110 -0
  32. data/lib/generators/ruby_llm_agents/embedder_generator.rb +107 -0
  33. data/lib/generators/ruby_llm_agents/image_analyzer_generator.rb +115 -0
  34. data/lib/generators/ruby_llm_agents/image_editor_generator.rb +108 -0
  35. data/lib/generators/ruby_llm_agents/image_generator_generator.rb +116 -0
  36. data/lib/generators/ruby_llm_agents/image_pipeline_generator.rb +178 -0
  37. data/lib/generators/ruby_llm_agents/image_transformer_generator.rb +109 -0
  38. data/lib/generators/ruby_llm_agents/image_upscaler_generator.rb +103 -0
  39. data/lib/generators/ruby_llm_agents/image_variator_generator.rb +102 -0
  40. data/lib/generators/ruby_llm_agents/install_generator.rb +76 -4
  41. data/lib/generators/ruby_llm_agents/restructure_generator.rb +292 -0
  42. data/lib/generators/ruby_llm_agents/speaker_generator.rb +121 -0
  43. data/lib/generators/ruby_llm_agents/templates/add_execution_type_migration.rb.tt +8 -0
  44. data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +99 -84
  45. data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +42 -40
  46. data/lib/generators/ruby_llm_agents/templates/application_background_remover.rb.tt +26 -0
  47. data/lib/generators/ruby_llm_agents/templates/application_embedder.rb.tt +50 -0
  48. data/lib/generators/ruby_llm_agents/templates/application_image_analyzer.rb.tt +26 -0
  49. data/lib/generators/ruby_llm_agents/templates/application_image_editor.rb.tt +20 -0
  50. data/lib/generators/ruby_llm_agents/templates/application_image_generator.rb.tt +38 -0
  51. data/lib/generators/ruby_llm_agents/templates/application_image_pipeline.rb.tt +139 -0
  52. data/lib/generators/ruby_llm_agents/templates/application_image_transformer.rb.tt +21 -0
  53. data/lib/generators/ruby_llm_agents/templates/application_image_upscaler.rb.tt +20 -0
  54. data/lib/generators/ruby_llm_agents/templates/application_image_variator.rb.tt +20 -0
  55. data/lib/generators/ruby_llm_agents/templates/application_speaker.rb.tt +49 -0
  56. data/lib/generators/ruby_llm_agents/templates/application_transcriber.rb.tt +53 -0
  57. data/lib/generators/ruby_llm_agents/templates/background_remover.rb.tt +44 -0
  58. data/lib/generators/ruby_llm_agents/templates/embedder.rb.tt +41 -0
  59. data/lib/generators/ruby_llm_agents/templates/image_analyzer.rb.tt +45 -0
  60. data/lib/generators/ruby_llm_agents/templates/image_editor.rb.tt +35 -0
  61. data/lib/generators/ruby_llm_agents/templates/image_generator.rb.tt +47 -0
  62. data/lib/generators/ruby_llm_agents/templates/image_pipeline.rb.tt +50 -0
  63. data/lib/generators/ruby_llm_agents/templates/image_transformer.rb.tt +44 -0
  64. data/lib/generators/ruby_llm_agents/templates/image_upscaler.rb.tt +38 -0
  65. data/lib/generators/ruby_llm_agents/templates/image_variator.rb.tt +33 -0
  66. data/lib/generators/ruby_llm_agents/templates/skills/AGENTS.md.tt +228 -0
  67. data/lib/generators/ruby_llm_agents/templates/skills/BACKGROUND_REMOVERS.md.tt +131 -0
  68. data/lib/generators/ruby_llm_agents/templates/skills/EMBEDDERS.md.tt +255 -0
  69. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_ANALYZERS.md.tt +120 -0
  70. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_EDITORS.md.tt +102 -0
  71. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_GENERATORS.md.tt +282 -0
  72. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_PIPELINES.md.tt +228 -0
  73. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_TRANSFORMERS.md.tt +120 -0
  74. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_UPSCALERS.md.tt +110 -0
  75. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_VARIATORS.md.tt +120 -0
  76. data/lib/generators/ruby_llm_agents/templates/skills/SPEAKERS.md.tt +212 -0
  77. data/lib/generators/ruby_llm_agents/templates/skills/TOOLS.md.tt +227 -0
  78. data/lib/generators/ruby_llm_agents/templates/skills/TRANSCRIBERS.md.tt +251 -0
  79. data/lib/generators/ruby_llm_agents/templates/skills/WORKFLOWS.md.tt +300 -0
  80. data/lib/generators/ruby_llm_agents/templates/speaker.rb.tt +56 -0
  81. data/lib/generators/ruby_llm_agents/templates/transcriber.rb.tt +51 -0
  82. data/lib/generators/ruby_llm_agents/transcriber_generator.rb +107 -0
  83. data/lib/generators/ruby_llm_agents/upgrade_generator.rb +152 -1
  84. data/lib/ruby_llm/agents/audio/speaker.rb +553 -0
  85. data/lib/ruby_llm/agents/audio/transcriber.rb +669 -0
  86. data/lib/ruby_llm/agents/base_agent.rb +675 -0
  87. data/lib/ruby_llm/agents/core/base/moderation_dsl.rb +181 -0
  88. data/lib/ruby_llm/agents/core/base/moderation_execution.rb +274 -0
  89. data/lib/ruby_llm/agents/core/base.rb +135 -0
  90. data/lib/ruby_llm/agents/core/configuration.rb +981 -0
  91. data/lib/ruby_llm/agents/core/errors.rb +150 -0
  92. data/lib/ruby_llm/agents/{instrumentation.rb → core/instrumentation.rb} +22 -1
  93. data/lib/ruby_llm/agents/core/llm_tenant.rb +358 -0
  94. data/lib/ruby_llm/agents/{version.rb → core/version.rb} +1 -1
  95. data/lib/ruby_llm/agents/dsl/base.rb +110 -0
  96. data/lib/ruby_llm/agents/dsl/caching.rb +142 -0
  97. data/lib/ruby_llm/agents/dsl/reliability.rb +307 -0
  98. data/lib/ruby_llm/agents/dsl.rb +41 -0
  99. data/lib/ruby_llm/agents/image/analyzer/dsl.rb +130 -0
  100. data/lib/ruby_llm/agents/image/analyzer/execution.rb +402 -0
  101. data/lib/ruby_llm/agents/image/analyzer.rb +90 -0
  102. data/lib/ruby_llm/agents/image/background_remover/dsl.rb +154 -0
  103. data/lib/ruby_llm/agents/image/background_remover/execution.rb +240 -0
  104. data/lib/ruby_llm/agents/image/background_remover.rb +89 -0
  105. data/lib/ruby_llm/agents/image/concerns/image_operation_dsl.rb +91 -0
  106. data/lib/ruby_llm/agents/image/concerns/image_operation_execution.rb +165 -0
  107. data/lib/ruby_llm/agents/image/editor/dsl.rb +56 -0
  108. data/lib/ruby_llm/agents/image/editor/execution.rb +207 -0
  109. data/lib/ruby_llm/agents/image/editor.rb +92 -0
  110. data/lib/ruby_llm/agents/image/generator/active_storage_support.rb +127 -0
  111. data/lib/ruby_llm/agents/image/generator/content_policy.rb +95 -0
  112. data/lib/ruby_llm/agents/image/generator/pricing.rb +353 -0
  113. data/lib/ruby_llm/agents/image/generator/templates.rb +124 -0
  114. data/lib/ruby_llm/agents/image/generator.rb +455 -0
  115. data/lib/ruby_llm/agents/image/pipeline/dsl.rb +213 -0
  116. data/lib/ruby_llm/agents/image/pipeline/execution.rb +382 -0
  117. data/lib/ruby_llm/agents/image/pipeline.rb +97 -0
  118. data/lib/ruby_llm/agents/image/transformer/dsl.rb +148 -0
  119. data/lib/ruby_llm/agents/image/transformer/execution.rb +223 -0
  120. data/lib/ruby_llm/agents/image/transformer.rb +95 -0
  121. data/lib/ruby_llm/agents/image/upscaler/dsl.rb +83 -0
  122. data/lib/ruby_llm/agents/image/upscaler/execution.rb +219 -0
  123. data/lib/ruby_llm/agents/image/upscaler.rb +81 -0
  124. data/lib/ruby_llm/agents/image/variator/dsl.rb +62 -0
  125. data/lib/ruby_llm/agents/image/variator/execution.rb +189 -0
  126. data/lib/ruby_llm/agents/image/variator.rb +80 -0
  127. data/lib/ruby_llm/agents/{alert_manager.rb → infrastructure/alert_manager.rb} +17 -22
  128. data/lib/ruby_llm/agents/infrastructure/budget/budget_query.rb +145 -0
  129. data/lib/ruby_llm/agents/infrastructure/budget/config_resolver.rb +149 -0
  130. data/lib/ruby_llm/agents/infrastructure/budget/forecaster.rb +68 -0
  131. data/lib/ruby_llm/agents/infrastructure/budget/spend_recorder.rb +279 -0
  132. data/lib/ruby_llm/agents/infrastructure/budget_tracker.rb +275 -0
  133. data/lib/ruby_llm/agents/{execution_logger_job.rb → infrastructure/execution_logger_job.rb} +17 -1
  134. data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/executor.rb +2 -1
  135. data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/retry_strategy.rb +9 -3
  136. data/lib/ruby_llm/agents/{reliability.rb → infrastructure/reliability.rb} +11 -21
  137. data/lib/ruby_llm/agents/pipeline/builder.rb +215 -0
  138. data/lib/ruby_llm/agents/pipeline/context.rb +255 -0
  139. data/lib/ruby_llm/agents/pipeline/executor.rb +86 -0
  140. data/lib/ruby_llm/agents/pipeline/middleware/base.rb +124 -0
  141. data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +95 -0
  142. data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +171 -0
  143. data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +415 -0
  144. data/lib/ruby_llm/agents/pipeline/middleware/reliability.rb +276 -0
  145. data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +196 -0
  146. data/lib/ruby_llm/agents/pipeline.rb +68 -0
  147. data/lib/ruby_llm/agents/{engine.rb → rails/engine.rb} +79 -11
  148. data/lib/ruby_llm/agents/results/background_removal_result.rb +286 -0
  149. data/lib/ruby_llm/agents/{result.rb → results/base.rb} +73 -1
  150. data/lib/ruby_llm/agents/results/embedding_result.rb +243 -0
  151. data/lib/ruby_llm/agents/results/image_analysis_result.rb +314 -0
  152. data/lib/ruby_llm/agents/results/image_edit_result.rb +250 -0
  153. data/lib/ruby_llm/agents/results/image_generation_result.rb +346 -0
  154. data/lib/ruby_llm/agents/results/image_pipeline_result.rb +399 -0
  155. data/lib/ruby_llm/agents/results/image_transform_result.rb +251 -0
  156. data/lib/ruby_llm/agents/results/image_upscale_result.rb +255 -0
  157. data/lib/ruby_llm/agents/results/image_variation_result.rb +237 -0
  158. data/lib/ruby_llm/agents/results/moderation_result.rb +158 -0
  159. data/lib/ruby_llm/agents/results/speech_result.rb +338 -0
  160. data/lib/ruby_llm/agents/results/transcription_result.rb +408 -0
  161. data/lib/ruby_llm/agents/text/embedder.rb +444 -0
  162. data/lib/ruby_llm/agents/text/moderator.rb +237 -0
  163. data/lib/ruby_llm/agents/workflow/async.rb +220 -0
  164. data/lib/ruby_llm/agents/workflow/async_executor.rb +156 -0
  165. data/lib/ruby_llm/agents/{workflow.rb → workflow/orchestrator.rb} +6 -5
  166. data/lib/ruby_llm/agents/workflow/parallel.rb +34 -17
  167. data/lib/ruby_llm/agents/workflow/thread_pool.rb +185 -0
  168. data/lib/ruby_llm/agents.rb +86 -20
  169. metadata +172 -34
  170. data/lib/ruby_llm/agents/base/caching.rb +0 -40
  171. data/lib/ruby_llm/agents/base/cost_calculation.rb +0 -105
  172. data/lib/ruby_llm/agents/base/dsl.rb +0 -324
  173. data/lib/ruby_llm/agents/base/execution.rb +0 -366
  174. data/lib/ruby_llm/agents/base/reliability_dsl.rb +0 -82
  175. data/lib/ruby_llm/agents/base/reliability_execution.rb +0 -136
  176. data/lib/ruby_llm/agents/base/response_building.rb +0 -86
  177. data/lib/ruby_llm/agents/base/tool_tracking.rb +0 -57
  178. data/lib/ruby_llm/agents/base.rb +0 -210
  179. data/lib/ruby_llm/agents/budget_tracker.rb +0 -733
  180. data/lib/ruby_llm/agents/configuration.rb +0 -394
  181. /data/lib/ruby_llm/agents/{deprecations.rb → core/deprecations.rb} +0 -0
  182. /data/lib/ruby_llm/agents/{inflections.rb → core/inflections.rb} +0 -0
  183. /data/lib/ruby_llm/agents/{resolved_config.rb → core/resolved_config.rb} +0 -0
  184. /data/lib/ruby_llm/agents/{attempt_tracker.rb → infrastructure/attempt_tracker.rb} +0 -0
  185. /data/lib/ruby_llm/agents/{cache_helper.rb → infrastructure/cache_helper.rb} +0 -0
  186. /data/lib/ruby_llm/agents/{circuit_breaker.rb → infrastructure/circuit_breaker.rb} +0 -0
  187. /data/lib/ruby_llm/agents/{redactor.rb → infrastructure/redactor.rb} +0 -0
  188. /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/breaker_manager.rb +0 -0
  189. /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/execution_constraints.rb +0 -0
  190. /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/fallback_routing.rb +0 -0
@@ -1,26 +1,60 @@
1
1
  <div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 h-full" x-data="{ activeTab: 'agents' }">
2
2
  <div class="px-4 py-3 border-b border-gray-100 dark:border-gray-700">
3
- <div class="flex items-center justify-between">
3
+ <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2">
4
4
  <h3 class="text-base font-semibold text-gray-900 dark:text-gray-100">Performance</h3>
5
- <!-- Tab Buttons -->
6
- <div class="flex space-x-1 bg-gray-100 dark:bg-gray-700 rounded-lg p-0.5">
7
- <button type="button" @click="activeTab = 'agents'"
8
- :class="activeTab === 'agents' ? 'bg-white dark:bg-gray-600 shadow-sm' : 'hover:bg-gray-50 dark:hover:bg-gray-600'"
9
- class="px-3 py-1 text-xs font-medium rounded-md transition-colors text-gray-700 dark:text-gray-200">
10
- Agents
11
- </button>
12
- <button type="button" @click="activeTab = 'workflows'"
13
- :class="activeTab === 'workflows' ? 'bg-white dark:bg-gray-600 shadow-sm' : 'hover:bg-gray-50 dark:hover:bg-gray-600'"
14
- class="px-3 py-1 text-xs font-medium rounded-md transition-colors text-gray-700 dark:text-gray-200">
15
- Workflows
16
- </button>
5
+ <!-- Scrollable Tab Buttons -->
6
+ <div class="overflow-x-auto -mx-4 px-4 sm:mx-0 sm:px-0">
7
+ <div class="flex space-x-1 bg-gray-100 dark:bg-gray-700 rounded-lg p-0.5 min-w-max">
8
+ <button type="button" @click="activeTab = 'agents'"
9
+ :class="activeTab === 'agents' ? 'bg-white dark:bg-gray-600 shadow-sm' : 'hover:bg-gray-50 dark:hover:bg-gray-600'"
10
+ class="px-2 py-1 text-xs font-medium rounded-md transition-colors text-gray-700 dark:text-gray-200 whitespace-nowrap">
11
+ <span class="hidden sm:inline">Agents</span>
12
+ <span class="sm:hidden">🤖</span>
13
+ </button>
14
+ <button type="button" @click="activeTab = 'embedders'"
15
+ :class="activeTab === 'embedders' ? 'bg-white dark:bg-gray-600 shadow-sm' : 'hover:bg-gray-50 dark:hover:bg-gray-600'"
16
+ class="px-2 py-1 text-xs font-medium rounded-md transition-colors text-gray-700 dark:text-gray-200 whitespace-nowrap">
17
+ <span class="hidden sm:inline">Embedders</span>
18
+ <span class="sm:hidden">📊</span>
19
+ </button>
20
+ <button type="button" @click="activeTab = 'transcribers'"
21
+ :class="activeTab === 'transcribers' ? 'bg-white dark:bg-gray-600 shadow-sm' : 'hover:bg-gray-50 dark:hover:bg-gray-600'"
22
+ class="px-2 py-1 text-xs font-medium rounded-md transition-colors text-gray-700 dark:text-gray-200 whitespace-nowrap">
23
+ <span class="hidden sm:inline">Transcribers</span>
24
+ <span class="sm:hidden">🎤</span>
25
+ </button>
26
+ <button type="button" @click="activeTab = 'speakers'"
27
+ :class="activeTab === 'speakers' ? 'bg-white dark:bg-gray-600 shadow-sm' : 'hover:bg-gray-50 dark:hover:bg-gray-600'"
28
+ class="px-2 py-1 text-xs font-medium rounded-md transition-colors text-gray-700 dark:text-gray-200 whitespace-nowrap">
29
+ <span class="hidden sm:inline">Speakers</span>
30
+ <span class="sm:hidden">🔊</span>
31
+ </button>
32
+ <button type="button" @click="activeTab = 'image_generators'"
33
+ :class="activeTab === 'image_generators' ? 'bg-white dark:bg-gray-600 shadow-sm' : 'hover:bg-gray-50 dark:hover:bg-gray-600'"
34
+ class="px-2 py-1 text-xs font-medium rounded-md transition-colors text-gray-700 dark:text-gray-200 whitespace-nowrap">
35
+ <span class="hidden sm:inline">Image Gen</span>
36
+ <span class="sm:hidden">🎨</span>
37
+ </button>
38
+ <button type="button" @click="activeTab = 'moderators'"
39
+ :class="activeTab === 'moderators' ? 'bg-white dark:bg-gray-600 shadow-sm' : 'hover:bg-gray-50 dark:hover:bg-gray-600'"
40
+ class="px-2 py-1 text-xs font-medium rounded-md transition-colors text-gray-700 dark:text-gray-200 whitespace-nowrap">
41
+ <span class="hidden sm:inline">Moderators</span>
42
+ <span class="sm:hidden">🛡️</span>
43
+ </button>
44
+ <button type="button" @click="activeTab = 'workflows'"
45
+ :class="activeTab === 'workflows' ? 'bg-white dark:bg-gray-600 shadow-sm' : 'hover:bg-gray-50 dark:hover:bg-gray-600'"
46
+ class="px-2 py-1 text-xs font-medium rounded-md transition-colors text-gray-700 dark:text-gray-200 whitespace-nowrap">
47
+ <span class="hidden sm:inline">Workflows</span>
48
+ <span class="sm:hidden">⚙️</span>
49
+ </button>
50
+ </div>
17
51
  </div>
18
52
  </div>
19
53
  </div>
20
54
 
21
55
  <!-- Agents Tab -->
22
56
  <div x-show="activeTab === 'agents'">
23
- <% if agent_stats&.any? %>
57
+ <% if @agent_stats&.any? %>
24
58
  <div class="overflow-x-auto">
25
59
  <table class="w-full text-sm">
26
60
  <thead>
@@ -32,7 +66,7 @@
32
66
  </tr>
33
67
  </thead>
34
68
  <tbody class="divide-y divide-gray-100 dark:divide-gray-700">
35
- <% agent_stats.first(5).each do |agent| %>
69
+ <% @agent_stats.first(5).each do |agent| %>
36
70
  <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50">
37
71
  <td class="px-4 py-2">
38
72
  <span class="text-sm font-medium text-gray-900 dark:text-gray-100 truncate block max-w-[120px]" title="<%= agent[:agent_type] %>">
@@ -62,6 +96,226 @@
62
96
  <% end %>
63
97
  </div>
64
98
 
99
+ <!-- Embedders Tab -->
100
+ <div x-show="activeTab === 'embedders'" x-cloak>
101
+ <% if @embedder_stats&.any? %>
102
+ <div class="overflow-x-auto">
103
+ <table class="w-full text-sm">
104
+ <thead>
105
+ <tr class="text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
106
+ <th class="px-4 py-2">Embedder</th>
107
+ <th class="px-4 py-2 text-right">Runs</th>
108
+ <th class="px-4 py-2 text-right">Cost</th>
109
+ <th class="px-4 py-2 text-right">Success</th>
110
+ </tr>
111
+ </thead>
112
+ <tbody class="divide-y divide-gray-100 dark:divide-gray-700">
113
+ <% @embedder_stats.first(5).each do |embedder| %>
114
+ <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50">
115
+ <td class="px-4 py-2">
116
+ <span class="text-sm font-medium text-gray-900 dark:text-gray-100 truncate block max-w-[120px]" title="<%= embedder[:agent_type] %>">
117
+ <%= embedder[:agent_type].to_s.demodulize %>
118
+ </span>
119
+ </td>
120
+ <td class="px-4 py-2 text-right text-gray-600 dark:text-gray-300">
121
+ <%= number_with_delimiter(embedder[:executions]) %>
122
+ </td>
123
+ <td class="px-4 py-2 text-right text-gray-600 dark:text-gray-300">
124
+ $<%= number_with_precision(embedder[:total_cost], precision: 2) %>
125
+ </td>
126
+ <td class="px-4 py-2 text-right">
127
+ <span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium <%= embedder[:success_rate] >= 95 ? 'bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-300' : embedder[:success_rate] >= 80 ? 'bg-yellow-100 dark:bg-yellow-900 text-yellow-700 dark:text-yellow-300' : 'bg-red-100 dark:bg-red-900 text-red-700 dark:text-red-300' %>">
128
+ <%= embedder[:success_rate].round %>%
129
+ </span>
130
+ </td>
131
+ </tr>
132
+ <% end %>
133
+ </tbody>
134
+ </table>
135
+ </div>
136
+ <% else %>
137
+ <div class="px-4 py-8 text-center text-gray-500 dark:text-gray-400">
138
+ <p class="text-sm">No embedder data yet</p>
139
+ </div>
140
+ <% end %>
141
+ </div>
142
+
143
+ <!-- Transcribers Tab -->
144
+ <div x-show="activeTab === 'transcribers'" x-cloak>
145
+ <% if @transcriber_stats&.any? %>
146
+ <div class="overflow-x-auto">
147
+ <table class="w-full text-sm">
148
+ <thead>
149
+ <tr class="text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
150
+ <th class="px-4 py-2">Transcriber</th>
151
+ <th class="px-4 py-2 text-right">Runs</th>
152
+ <th class="px-4 py-2 text-right">Cost</th>
153
+ <th class="px-4 py-2 text-right">Success</th>
154
+ </tr>
155
+ </thead>
156
+ <tbody class="divide-y divide-gray-100 dark:divide-gray-700">
157
+ <% @transcriber_stats.first(5).each do |transcriber| %>
158
+ <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50">
159
+ <td class="px-4 py-2">
160
+ <span class="text-sm font-medium text-gray-900 dark:text-gray-100 truncate block max-w-[120px]" title="<%= transcriber[:agent_type] %>">
161
+ <%= transcriber[:agent_type].to_s.demodulize %>
162
+ </span>
163
+ </td>
164
+ <td class="px-4 py-2 text-right text-gray-600 dark:text-gray-300">
165
+ <%= number_with_delimiter(transcriber[:executions]) %>
166
+ </td>
167
+ <td class="px-4 py-2 text-right text-gray-600 dark:text-gray-300">
168
+ $<%= number_with_precision(transcriber[:total_cost], precision: 2) %>
169
+ </td>
170
+ <td class="px-4 py-2 text-right">
171
+ <span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium <%= transcriber[:success_rate] >= 95 ? 'bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-300' : transcriber[:success_rate] >= 80 ? 'bg-yellow-100 dark:bg-yellow-900 text-yellow-700 dark:text-yellow-300' : 'bg-red-100 dark:bg-red-900 text-red-700 dark:text-red-300' %>">
172
+ <%= transcriber[:success_rate].round %>%
173
+ </span>
174
+ </td>
175
+ </tr>
176
+ <% end %>
177
+ </tbody>
178
+ </table>
179
+ </div>
180
+ <% else %>
181
+ <div class="px-4 py-8 text-center text-gray-500 dark:text-gray-400">
182
+ <p class="text-sm">No transcriber data yet</p>
183
+ </div>
184
+ <% end %>
185
+ </div>
186
+
187
+ <!-- Speakers Tab -->
188
+ <div x-show="activeTab === 'speakers'" x-cloak>
189
+ <% if @speaker_stats&.any? %>
190
+ <div class="overflow-x-auto">
191
+ <table class="w-full text-sm">
192
+ <thead>
193
+ <tr class="text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
194
+ <th class="px-4 py-2">Speaker</th>
195
+ <th class="px-4 py-2 text-right">Runs</th>
196
+ <th class="px-4 py-2 text-right">Cost</th>
197
+ <th class="px-4 py-2 text-right">Success</th>
198
+ </tr>
199
+ </thead>
200
+ <tbody class="divide-y divide-gray-100 dark:divide-gray-700">
201
+ <% @speaker_stats.first(5).each do |speaker| %>
202
+ <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50">
203
+ <td class="px-4 py-2">
204
+ <span class="text-sm font-medium text-gray-900 dark:text-gray-100 truncate block max-w-[120px]" title="<%= speaker[:agent_type] %>">
205
+ <%= speaker[:agent_type].to_s.demodulize %>
206
+ </span>
207
+ </td>
208
+ <td class="px-4 py-2 text-right text-gray-600 dark:text-gray-300">
209
+ <%= number_with_delimiter(speaker[:executions]) %>
210
+ </td>
211
+ <td class="px-4 py-2 text-right text-gray-600 dark:text-gray-300">
212
+ $<%= number_with_precision(speaker[:total_cost], precision: 2) %>
213
+ </td>
214
+ <td class="px-4 py-2 text-right">
215
+ <span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium <%= speaker[:success_rate] >= 95 ? 'bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-300' : speaker[:success_rate] >= 80 ? 'bg-yellow-100 dark:bg-yellow-900 text-yellow-700 dark:text-yellow-300' : 'bg-red-100 dark:bg-red-900 text-red-700 dark:text-red-300' %>">
216
+ <%= speaker[:success_rate].round %>%
217
+ </span>
218
+ </td>
219
+ </tr>
220
+ <% end %>
221
+ </tbody>
222
+ </table>
223
+ </div>
224
+ <% else %>
225
+ <div class="px-4 py-8 text-center text-gray-500 dark:text-gray-400">
226
+ <p class="text-sm">No speaker data yet</p>
227
+ </div>
228
+ <% end %>
229
+ </div>
230
+
231
+ <!-- Image Generators Tab -->
232
+ <div x-show="activeTab === 'image_generators'" x-cloak>
233
+ <% if @image_generator_stats&.any? %>
234
+ <div class="overflow-x-auto">
235
+ <table class="w-full text-sm">
236
+ <thead>
237
+ <tr class="text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
238
+ <th class="px-4 py-2">Image Generator</th>
239
+ <th class="px-4 py-2 text-right">Runs</th>
240
+ <th class="px-4 py-2 text-right">Cost</th>
241
+ <th class="px-4 py-2 text-right">Success</th>
242
+ </tr>
243
+ </thead>
244
+ <tbody class="divide-y divide-gray-100 dark:divide-gray-700">
245
+ <% @image_generator_stats.first(5).each do |generator| %>
246
+ <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50">
247
+ <td class="px-4 py-2">
248
+ <span class="text-sm font-medium text-gray-900 dark:text-gray-100 truncate block max-w-[120px]" title="<%= generator[:agent_type] %>">
249
+ <%= generator[:agent_type].to_s.demodulize %>
250
+ </span>
251
+ </td>
252
+ <td class="px-4 py-2 text-right text-gray-600 dark:text-gray-300">
253
+ <%= number_with_delimiter(generator[:executions]) %>
254
+ </td>
255
+ <td class="px-4 py-2 text-right text-gray-600 dark:text-gray-300">
256
+ $<%= number_with_precision(generator[:total_cost], precision: 2) %>
257
+ </td>
258
+ <td class="px-4 py-2 text-right">
259
+ <span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium <%= generator[:success_rate] >= 95 ? 'bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-300' : generator[:success_rate] >= 80 ? 'bg-yellow-100 dark:bg-yellow-900 text-yellow-700 dark:text-yellow-300' : 'bg-red-100 dark:bg-red-900 text-red-700 dark:text-red-300' %>">
260
+ <%= generator[:success_rate].round %>%
261
+ </span>
262
+ </td>
263
+ </tr>
264
+ <% end %>
265
+ </tbody>
266
+ </table>
267
+ </div>
268
+ <% else %>
269
+ <div class="px-4 py-8 text-center text-gray-500 dark:text-gray-400">
270
+ <p class="text-sm">No image generator data yet</p>
271
+ </div>
272
+ <% end %>
273
+ </div>
274
+
275
+ <!-- Moderators Tab -->
276
+ <div x-show="activeTab === 'moderators'" x-cloak>
277
+ <% if @moderator_stats&.any? %>
278
+ <div class="overflow-x-auto">
279
+ <table class="w-full text-sm">
280
+ <thead>
281
+ <tr class="text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
282
+ <th class="px-4 py-2">Moderator</th>
283
+ <th class="px-4 py-2 text-right">Runs</th>
284
+ <th class="px-4 py-2 text-right">Cost</th>
285
+ <th class="px-4 py-2 text-right">Success</th>
286
+ </tr>
287
+ </thead>
288
+ <tbody class="divide-y divide-gray-100 dark:divide-gray-700">
289
+ <% @moderator_stats.first(5).each do |moderator| %>
290
+ <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50">
291
+ <td class="px-4 py-2">
292
+ <span class="text-sm font-medium text-gray-900 dark:text-gray-100 truncate block max-w-[120px]" title="<%= moderator[:agent_type] %>">
293
+ <%= moderator[:agent_type].to_s.demodulize %>
294
+ </span>
295
+ </td>
296
+ <td class="px-4 py-2 text-right text-gray-600 dark:text-gray-300">
297
+ <%= number_with_delimiter(moderator[:executions]) %>
298
+ </td>
299
+ <td class="px-4 py-2 text-right text-gray-600 dark:text-gray-300">
300
+ $<%= number_with_precision(moderator[:total_cost], precision: 2) %>
301
+ </td>
302
+ <td class="px-4 py-2 text-right">
303
+ <span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium <%= moderator[:success_rate] >= 95 ? 'bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-300' : moderator[:success_rate] >= 80 ? 'bg-yellow-100 dark:bg-yellow-900 text-yellow-700 dark:text-yellow-300' : 'bg-red-100 dark:bg-red-900 text-red-700 dark:text-red-300' %>">
304
+ <%= moderator[:success_rate].round %>%
305
+ </span>
306
+ </td>
307
+ </tr>
308
+ <% end %>
309
+ </tbody>
310
+ </table>
311
+ </div>
312
+ <% else %>
313
+ <div class="px-4 py-8 text-center text-gray-500 dark:text-gray-400">
314
+ <p class="text-sm">No moderator data yet</p>
315
+ </div>
316
+ <% end %>
317
+ </div>
318
+
65
319
  <!-- Workflows Tab -->
66
320
  <div x-show="activeTab === 'workflows'" x-cloak>
67
321
  <% if @workflow_stats&.any? %>
@@ -512,6 +512,22 @@
512
512
  Retryable
513
513
  </span>
514
514
  <% end %>
515
+ <button
516
+ type="button"
517
+ data-copy-json="<%= Base64.strict_encode64({
518
+ error_class: @execution.error_class,
519
+ error_message: @execution.error_message
520
+ }.to_json) %>"
521
+ class="copy-json-btn inline-flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md transition-colors"
522
+ >
523
+ <svg class="w-4 h-4 copy-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
524
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/>
525
+ </svg>
526
+ <svg class="w-4 h-4 check-icon hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
527
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
528
+ </svg>
529
+ <span>Copy</span>
530
+ </button>
515
531
  </div>
516
532
  </div>
517
533
 
@@ -0,0 +1,93 @@
1
+ <%
2
+ # Agent type badge with icon and color coding
3
+ # Usage: render "ruby_llm/agents/shared/agent_type_badge", agent_type: "agent"
4
+ # Options:
5
+ # size: :xs, :sm (default), :md
6
+ # show_label: true (default) or false for icon-only mode
7
+
8
+ agent_type = local_assigns[:agent_type]
9
+ size = local_assigns[:size] || :sm
10
+ show_label = local_assigns.fetch(:show_label, true)
11
+
12
+ config = case agent_type.to_s
13
+ when "agent"
14
+ {
15
+ icon: "robot",
16
+ label: "Agent",
17
+ bg: "bg-blue-100 dark:bg-blue-900/50",
18
+ text: "text-blue-700 dark:text-blue-300",
19
+ icon_char: "🤖"
20
+ }
21
+ when "embedder"
22
+ {
23
+ icon: "chart",
24
+ label: "Embedder",
25
+ bg: "bg-purple-100 dark:bg-purple-900/50",
26
+ text: "text-purple-700 dark:text-purple-300",
27
+ icon_char: "📊"
28
+ }
29
+ when "transcriber"
30
+ {
31
+ icon: "mic",
32
+ label: "Transcriber",
33
+ bg: "bg-green-100 dark:bg-green-900/50",
34
+ text: "text-green-700 dark:text-green-300",
35
+ icon_char: "🎤"
36
+ }
37
+ when "speaker"
38
+ {
39
+ icon: "speaker",
40
+ label: "Speaker",
41
+ bg: "bg-orange-100 dark:bg-orange-900/50",
42
+ text: "text-orange-700 dark:text-orange-300",
43
+ icon_char: "🔊"
44
+ }
45
+ when "image_generator"
46
+ {
47
+ icon: "palette",
48
+ label: "Image Gen",
49
+ bg: "bg-pink-100 dark:bg-pink-900/50",
50
+ text: "text-pink-700 dark:text-pink-300",
51
+ icon_char: "🎨"
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
+ else
70
+ {
71
+ icon: "question",
72
+ label: "Unknown",
73
+ bg: "bg-gray-100 dark:bg-gray-700",
74
+ text: "text-gray-700 dark:text-gray-300",
75
+ icon_char: "?"
76
+ }
77
+ end
78
+
79
+ size_classes = case size
80
+ when :xs
81
+ { badge: "px-1.5 py-0.5", icon: "text-[10px]", text: "text-[10px]" }
82
+ when :md
83
+ { badge: "px-2.5 py-1", icon: "text-sm", text: "text-sm" }
84
+ else # :sm
85
+ { badge: "px-2 py-0.5", icon: "text-xs", text: "text-xs" }
86
+ end
87
+ %>
88
+ <span class="inline-flex items-center gap-1 rounded-md font-medium <%= config[:bg] %> <%= config[:text] %> <%= size_classes[:badge] %>">
89
+ <span class="<%= size_classes[:icon] %>" aria-hidden="true"><%= config[:icon_char] %></span>
90
+ <% if show_label %>
91
+ <span class="<%= size_classes[:text] %>"><%= config[:label] %></span>
92
+ <% end %>
93
+ </span>