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
@@ -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
 
@@ -743,6 +759,88 @@
743
759
  </div>
744
760
  <% end %>
745
761
 
762
+ <!-- Conversation Context -->
763
+ <% if @execution.respond_to?(:messages_count) && @execution.messages_count.to_i > 0 %>
764
+ <%
765
+ messages_summary = @execution.messages_summary || {}
766
+ # Handle both string and symbol keys
767
+ first_message = messages_summary["first"] || messages_summary[:first]
768
+ last_message = messages_summary["last"] || messages_summary[:last]
769
+ max_len = RubyLLM::Agents.configuration.messages_summary_max_length || 500
770
+ %>
771
+ <div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-6 mb-6">
772
+ <div class="flex items-center gap-2 mb-4">
773
+ <svg class="w-5 h-5 text-indigo-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
774
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/>
775
+ </svg>
776
+ <h3 class="text-sm font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">Conversation Context</h3>
777
+ <span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-indigo-100 dark:bg-indigo-900/50 text-indigo-800 dark:text-indigo-300">
778
+ <%= @execution.messages_count %> message<%= @execution.messages_count == 1 ? '' : 's' %>
779
+ </span>
780
+ </div>
781
+
782
+ <div class="space-y-4">
783
+ <% if first_message %>
784
+ <%
785
+ first_role = first_message["role"] || first_message[:role] || "unknown"
786
+ first_content = first_message["content"] || first_message[:content] || ""
787
+ first_truncated = first_content.length >= max_len
788
+ role_badge_class = case first_role.to_s
789
+ when "user" then "bg-blue-100 dark:bg-blue-900/50 text-blue-800 dark:text-blue-300"
790
+ when "assistant" then "bg-green-100 dark:bg-green-900/50 text-green-800 dark:text-green-300"
791
+ when "system" then "bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-300"
792
+ else "bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-300"
793
+ end
794
+ %>
795
+ <div class="border border-gray-100 dark:border-gray-700 rounded-lg p-4">
796
+ <div class="flex items-center gap-2 mb-2">
797
+ <span class="text-xs text-gray-500 dark:text-gray-400 font-medium">First Message</span>
798
+ <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium <%= role_badge_class %>">
799
+ <%= first_role %>
800
+ </span>
801
+ <% if first_truncated %>
802
+ <span class="text-xs text-gray-400 dark:text-gray-500 italic">(truncated)</span>
803
+ <% end %>
804
+ </div>
805
+ <p class="text-sm text-gray-700 dark:text-gray-300 font-mono bg-gray-50 dark:bg-gray-900 rounded-lg p-3 whitespace-pre-wrap break-words"><%= first_content %></p>
806
+ </div>
807
+ <% end %>
808
+
809
+ <% if last_message %>
810
+ <%
811
+ last_role = last_message["role"] || last_message[:role] || "unknown"
812
+ last_content = last_message["content"] || last_message[:content] || ""
813
+ last_truncated = last_content.length >= max_len
814
+ role_badge_class = case last_role.to_s
815
+ when "user" then "bg-blue-100 dark:bg-blue-900/50 text-blue-800 dark:text-blue-300"
816
+ when "assistant" then "bg-green-100 dark:bg-green-900/50 text-green-800 dark:text-green-300"
817
+ when "system" then "bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-300"
818
+ else "bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-300"
819
+ end
820
+ %>
821
+ <div class="border border-gray-100 dark:border-gray-700 rounded-lg p-4">
822
+ <div class="flex items-center gap-2 mb-2">
823
+ <span class="text-xs text-gray-500 dark:text-gray-400 font-medium">Last Message</span>
824
+ <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium <%= role_badge_class %>">
825
+ <%= last_role %>
826
+ </span>
827
+ <% if last_truncated %>
828
+ <span class="text-xs text-gray-400 dark:text-gray-500 italic">(truncated)</span>
829
+ <% end %>
830
+ </div>
831
+ <p class="text-sm text-gray-700 dark:text-gray-300 font-mono bg-gray-50 dark:bg-gray-900 rounded-lg p-3 whitespace-pre-wrap break-words"><%= last_content %></p>
832
+ </div>
833
+ <% end %>
834
+
835
+ <% if @execution.messages_count > 2 %>
836
+ <p class="text-xs text-gray-400 dark:text-gray-500 text-center">
837
+ + <%= @execution.messages_count - 2 %> more message<%= @execution.messages_count - 2 == 1 ? '' : 's' %> in between
838
+ </p>
839
+ <% end %>
840
+ </div>
841
+ </div>
842
+ <% end %>
843
+
746
844
  <!-- Diagnostics Panel -->
747
845
  <div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-6 mb-6">
748
846
  <div class="flex items-center justify-between mb-4">
@@ -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>
@@ -7,7 +7,7 @@
7
7
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
8
8
  </svg>
9
9
  <div>
10
- <h1 class="text-2xl font-bold text-gray-900 dark:text-gray-100">Global Configuration</h1>
10
+ <h1 class="text-2xl font-bold text-gray-900 dark:text-gray-100">System Configuration</h1>
11
11
  <p class="text-sm text-gray-500 dark:text-gray-400">Settings configured in <code class="bg-gray-100 dark:bg-gray-700 px-1 rounded">config/initializers/ruby_llm_agents.rb</code></p>
12
12
  </div>
13
13
  </div>