ruby_llm-agents 1.3.4 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +112 -336
  3. data/app/controllers/concerns/ruby_llm/agents/sortable.rb +0 -1
  4. data/app/controllers/ruby_llm/agents/agents_controller.rb +5 -56
  5. data/app/controllers/ruby_llm/agents/dashboard_controller.rb +22 -106
  6. data/app/controllers/ruby_llm/agents/executions_controller.rb +4 -114
  7. data/app/controllers/ruby_llm/agents/tenants_controller.rb +30 -2
  8. data/app/helpers/ruby_llm/agents/application_helper.rb +19 -53
  9. data/app/models/ruby_llm/agents/execution/analytics.rb +13 -54
  10. data/app/models/ruby_llm/agents/execution/scopes.rb +61 -14
  11. data/app/models/ruby_llm/agents/execution.rb +52 -12
  12. data/app/models/ruby_llm/agents/execution_detail.rb +18 -0
  13. data/app/models/ruby_llm/agents/tenant/budgetable.rb +132 -24
  14. data/app/models/ruby_llm/agents/tenant/incrementable.rb +117 -0
  15. data/app/models/ruby_llm/agents/tenant/resettable.rb +128 -0
  16. data/app/models/ruby_llm/agents/tenant/trackable.rb +46 -12
  17. data/app/models/ruby_llm/agents/tenant.rb +2 -3
  18. data/app/models/ruby_llm/agents/tenant_budget.rb +6 -3
  19. data/app/services/ruby_llm/agents/agent_registry.rb +6 -112
  20. data/app/views/layouts/ruby_llm/agents/application.html.erb +89 -252
  21. data/app/views/ruby_llm/agents/agents/_config_agent.html.erb +71 -218
  22. data/app/views/ruby_llm/agents/agents/_config_embedder.html.erb +20 -63
  23. data/app/views/ruby_llm/agents/agents/_config_image_generator.html.erb +44 -131
  24. data/app/views/ruby_llm/agents/agents/_config_moderator.html.erb +16 -57
  25. data/app/views/ruby_llm/agents/agents/_config_speaker.html.erb +39 -104
  26. data/app/views/ruby_llm/agents/agents/_config_transcriber.html.erb +29 -82
  27. data/app/views/ruby_llm/agents/agents/_empty_state.html.erb +4 -14
  28. data/app/views/ruby_llm/agents/agents/index.html.erb +105 -274
  29. data/app/views/ruby_llm/agents/agents/show.html.erb +248 -378
  30. data/app/views/ruby_llm/agents/dashboard/_action_center.html.erb +29 -52
  31. data/app/views/ruby_llm/agents/dashboard/_tenant_budget.html.erb +73 -99
  32. data/app/views/ruby_llm/agents/dashboard/index.html.erb +228 -433
  33. data/app/views/ruby_llm/agents/executions/_execution.html.erb +1 -1
  34. data/app/views/ruby_llm/agents/executions/_filters.html.erb +4 -25
  35. data/app/views/ruby_llm/agents/executions/_list.html.erb +111 -152
  36. data/app/views/ruby_llm/agents/executions/index.html.erb +5 -7
  37. data/app/views/ruby_llm/agents/executions/show.html.erb +526 -1037
  38. data/app/views/ruby_llm/agents/shared/_agent_type_badge.html.erb +5 -21
  39. data/app/views/ruby_llm/agents/shared/_executions_table.html.erb +70 -191
  40. data/app/views/ruby_llm/agents/shared/_filter_dropdown.html.erb +16 -44
  41. data/app/views/ruby_llm/agents/shared/_select_dropdown.html.erb +12 -41
  42. data/app/views/ruby_llm/agents/shared/_status_badge.html.erb +11 -65
  43. data/app/views/ruby_llm/agents/shared/_tenant_filter.html.erb +6 -5
  44. data/app/views/ruby_llm/agents/system_config/show.html.erb +240 -351
  45. data/app/views/ruby_llm/agents/tenants/_form.html.erb +67 -77
  46. data/app/views/ruby_llm/agents/tenants/edit.html.erb +7 -9
  47. data/app/views/ruby_llm/agents/tenants/index.html.erb +100 -122
  48. data/app/views/ruby_llm/agents/tenants/show.html.erb +146 -336
  49. data/config/routes.rb +0 -13
  50. data/lib/generators/ruby_llm_agents/install_generator.rb +13 -17
  51. data/lib/generators/ruby_llm_agents/migrate_structure_generator.rb +2 -12
  52. data/lib/generators/ruby_llm_agents/restructure_generator.rb +0 -2
  53. data/lib/generators/ruby_llm_agents/templates/add_usage_counters_to_tenants_migration.rb.tt +37 -0
  54. data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +1 -2
  55. data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +1 -1
  56. data/lib/generators/ruby_llm_agents/templates/application_image_pipeline.rb.tt +0 -1
  57. data/lib/generators/ruby_llm_agents/templates/create_execution_details_migration.rb.tt +27 -0
  58. data/lib/generators/ruby_llm_agents/templates/create_tenants_migration.rb.tt +25 -0
  59. data/lib/generators/ruby_llm_agents/templates/image_pipeline.rb.tt +0 -1
  60. data/lib/generators/ruby_llm_agents/templates/initializer.rb.tt +33 -12
  61. data/lib/generators/ruby_llm_agents/templates/migration.rb.tt +40 -71
  62. data/lib/generators/ruby_llm_agents/templates/remove_agent_version_migration.rb.tt +13 -0
  63. data/lib/generators/ruby_llm_agents/templates/remove_workflow_columns_migration.rb.tt +19 -0
  64. data/lib/generators/ruby_llm_agents/templates/skills/AGENTS.md.tt +2 -4
  65. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_PIPELINES.md.tt +0 -1
  66. data/lib/generators/ruby_llm_agents/templates/split_execution_details_migration.rb.tt +232 -0
  67. data/lib/generators/ruby_llm_agents/upgrade_generator.rb +77 -259
  68. data/lib/ruby_llm/agents/audio/speaker.rb +0 -1
  69. data/lib/ruby_llm/agents/audio/transcriber.rb +0 -1
  70. data/lib/ruby_llm/agents/base_agent.rb +54 -23
  71. data/lib/ruby_llm/agents/core/base/callbacks.rb +142 -0
  72. data/lib/ruby_llm/agents/core/base.rb +23 -55
  73. data/lib/ruby_llm/agents/core/configuration.rb +97 -117
  74. data/lib/ruby_llm/agents/core/errors.rb +0 -58
  75. data/lib/ruby_llm/agents/core/instrumentation.rb +157 -110
  76. data/lib/ruby_llm/agents/core/llm_tenant.rb +8 -7
  77. data/lib/ruby_llm/agents/core/version.rb +1 -1
  78. data/lib/ruby_llm/agents/dsl/base.rb +157 -17
  79. data/lib/ruby_llm/agents/dsl/caching.rb +33 -2
  80. data/lib/ruby_llm/agents/dsl/reliability.rb +148 -0
  81. data/lib/ruby_llm/agents/dsl.rb +1 -2
  82. data/lib/ruby_llm/agents/image/analyzer/execution.rb +1 -2
  83. data/lib/ruby_llm/agents/image/background_remover/execution.rb +1 -2
  84. data/lib/ruby_llm/agents/image/concerns/image_operation_dsl.rb +1 -13
  85. data/lib/ruby_llm/agents/image/concerns/image_operation_execution.rb +2 -2
  86. data/lib/ruby_llm/agents/image/editor/dsl.rb +0 -14
  87. data/lib/ruby_llm/agents/image/editor/execution.rb +1 -10
  88. data/lib/ruby_llm/agents/image/editor.rb +0 -1
  89. data/lib/ruby_llm/agents/image/generator.rb +0 -21
  90. data/lib/ruby_llm/agents/image/pipeline/dsl.rb +0 -13
  91. data/lib/ruby_llm/agents/image/pipeline/execution.rb +0 -1
  92. data/lib/ruby_llm/agents/image/transformer/dsl.rb +0 -13
  93. data/lib/ruby_llm/agents/image/transformer/execution.rb +1 -10
  94. data/lib/ruby_llm/agents/image/transformer.rb +0 -1
  95. data/lib/ruby_llm/agents/image/upscaler/execution.rb +1 -2
  96. data/lib/ruby_llm/agents/image/variator/execution.rb +1 -2
  97. data/lib/ruby_llm/agents/infrastructure/alert_manager.rb +78 -173
  98. data/lib/ruby_llm/agents/infrastructure/budget/budget_query.rb +66 -2
  99. data/lib/ruby_llm/agents/infrastructure/budget/spend_recorder.rb +0 -12
  100. data/lib/ruby_llm/agents/infrastructure/circuit_breaker.rb +10 -13
  101. data/lib/ruby_llm/agents/infrastructure/execution_logger_job.rb +8 -0
  102. data/lib/ruby_llm/agents/pipeline/context.rb +0 -1
  103. data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +28 -4
  104. data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +3 -10
  105. data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +88 -55
  106. data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +5 -41
  107. data/lib/ruby_llm/agents/rails/engine.rb +6 -6
  108. data/lib/ruby_llm/agents/results/base.rb +1 -49
  109. data/lib/ruby_llm/agents/text/embedder.rb +0 -1
  110. data/lib/ruby_llm/agents.rb +1 -9
  111. data/lib/tasks/ruby_llm_agents.rake +34 -0
  112. metadata +14 -83
  113. data/app/controllers/ruby_llm/agents/api_configurations_controller.rb +0 -214
  114. data/app/controllers/ruby_llm/agents/workflows_controller.rb +0 -544
  115. data/app/mailers/ruby_llm/agents/alert_mailer.rb +0 -84
  116. data/app/mailers/ruby_llm/agents/application_mailer.rb +0 -28
  117. data/app/models/ruby_llm/agents/api_configuration.rb +0 -386
  118. data/app/models/ruby_llm/agents/execution/workflow.rb +0 -170
  119. data/app/models/ruby_llm/agents/tenant/configurable.rb +0 -135
  120. data/app/views/ruby_llm/agents/agents/_agent.html.erb +0 -98
  121. data/app/views/ruby_llm/agents/agents/_version_comparison.html.erb +0 -186
  122. data/app/views/ruby_llm/agents/agents/_workflow.html.erb +0 -126
  123. data/app/views/ruby_llm/agents/alert_mailer/alert_notification.html.erb +0 -107
  124. data/app/views/ruby_llm/agents/alert_mailer/alert_notification.text.erb +0 -18
  125. data/app/views/ruby_llm/agents/api_configurations/_api_key_field.html.erb +0 -34
  126. data/app/views/ruby_llm/agents/api_configurations/_form.html.erb +0 -288
  127. data/app/views/ruby_llm/agents/api_configurations/edit.html.erb +0 -95
  128. data/app/views/ruby_llm/agents/api_configurations/edit_tenant.html.erb +0 -97
  129. data/app/views/ruby_llm/agents/api_configurations/show.html.erb +0 -214
  130. data/app/views/ruby_llm/agents/api_configurations/tenant.html.erb +0 -179
  131. data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +0 -73
  132. data/app/views/ruby_llm/agents/dashboard/_alerts_feed.html.erb +0 -62
  133. data/app/views/ruby_llm/agents/dashboard/_breaker_strip.html.erb +0 -47
  134. data/app/views/ruby_llm/agents/dashboard/_budgets_bar.html.erb +0 -75
  135. data/app/views/ruby_llm/agents/dashboard/_model_comparison.html.erb +0 -56
  136. data/app/views/ruby_llm/agents/dashboard/_model_cost_breakdown.html.erb +0 -115
  137. data/app/views/ruby_llm/agents/dashboard/_now_strip.html.erb +0 -59
  138. data/app/views/ruby_llm/agents/dashboard/_top_errors.html.erb +0 -60
  139. data/app/views/ruby_llm/agents/executions/_workflow_summary.html.erb +0 -86
  140. data/app/views/ruby_llm/agents/executions/dry_run.html.erb +0 -149
  141. data/app/views/ruby_llm/agents/shared/_breadcrumbs.html.erb +0 -48
  142. data/app/views/ruby_llm/agents/shared/_nav_link.html.erb +0 -27
  143. data/app/views/ruby_llm/agents/shared/_stat_card.html.erb +0 -14
  144. data/app/views/ruby_llm/agents/shared/_workflow_type_badge.html.erb +0 -35
  145. data/app/views/ruby_llm/agents/workflows/_empty_state.html.erb +0 -22
  146. data/app/views/ruby_llm/agents/workflows/_step_performance.html.erb +0 -228
  147. data/app/views/ruby_llm/agents/workflows/_structure_dsl.html.erb +0 -539
  148. data/app/views/ruby_llm/agents/workflows/_structure_parallel.html.erb +0 -76
  149. data/app/views/ruby_llm/agents/workflows/_structure_pipeline.html.erb +0 -74
  150. data/app/views/ruby_llm/agents/workflows/_structure_router.html.erb +0 -108
  151. data/app/views/ruby_llm/agents/workflows/_workflow_diagram.html.erb +0 -920
  152. data/app/views/ruby_llm/agents/workflows/index.html.erb +0 -179
  153. data/app/views/ruby_llm/agents/workflows/show.html.erb +0 -467
  154. data/lib/generators/ruby_llm_agents/api_configuration_generator.rb +0 -100
  155. data/lib/generators/ruby_llm_agents/templates/add_workflow_migration.rb.tt +0 -38
  156. data/lib/generators/ruby_llm_agents/templates/application_workflow.rb.tt +0 -48
  157. data/lib/generators/ruby_llm_agents/templates/create_api_configurations_migration.rb.tt +0 -90
  158. data/lib/generators/ruby_llm_agents/templates/skills/WORKFLOWS.md.tt +0 -551
  159. data/lib/ruby_llm/agents/core/base/moderation_dsl.rb +0 -181
  160. data/lib/ruby_llm/agents/core/base/moderation_execution.rb +0 -274
  161. data/lib/ruby_llm/agents/core/resolved_config.rb +0 -348
  162. data/lib/ruby_llm/agents/image/generator/content_policy.rb +0 -95
  163. data/lib/ruby_llm/agents/infrastructure/redactor.rb +0 -130
  164. data/lib/ruby_llm/agents/results/moderation_result.rb +0 -158
  165. data/lib/ruby_llm/agents/text/moderator.rb +0 -237
  166. data/lib/ruby_llm/agents/workflow/approval.rb +0 -205
  167. data/lib/ruby_llm/agents/workflow/approval_store.rb +0 -179
  168. data/lib/ruby_llm/agents/workflow/async.rb +0 -220
  169. data/lib/ruby_llm/agents/workflow/async_executor.rb +0 -156
  170. data/lib/ruby_llm/agents/workflow/dsl/executor.rb +0 -467
  171. data/lib/ruby_llm/agents/workflow/dsl/input_schema.rb +0 -244
  172. data/lib/ruby_llm/agents/workflow/dsl/iteration_executor.rb +0 -289
  173. data/lib/ruby_llm/agents/workflow/dsl/parallel_group.rb +0 -107
  174. data/lib/ruby_llm/agents/workflow/dsl/route_builder.rb +0 -150
  175. data/lib/ruby_llm/agents/workflow/dsl/schedule_helpers.rb +0 -187
  176. data/lib/ruby_llm/agents/workflow/dsl/step_config.rb +0 -352
  177. data/lib/ruby_llm/agents/workflow/dsl/step_executor.rb +0 -415
  178. data/lib/ruby_llm/agents/workflow/dsl/wait_config.rb +0 -257
  179. data/lib/ruby_llm/agents/workflow/dsl/wait_executor.rb +0 -317
  180. data/lib/ruby_llm/agents/workflow/dsl.rb +0 -576
  181. data/lib/ruby_llm/agents/workflow/instrumentation.rb +0 -249
  182. data/lib/ruby_llm/agents/workflow/notifiers/base.rb +0 -117
  183. data/lib/ruby_llm/agents/workflow/notifiers/email.rb +0 -117
  184. data/lib/ruby_llm/agents/workflow/notifiers/slack.rb +0 -180
  185. data/lib/ruby_llm/agents/workflow/notifiers/webhook.rb +0 -121
  186. data/lib/ruby_llm/agents/workflow/notifiers.rb +0 -70
  187. data/lib/ruby_llm/agents/workflow/orchestrator.rb +0 -416
  188. data/lib/ruby_llm/agents/workflow/result.rb +0 -592
  189. data/lib/ruby_llm/agents/workflow/thread_pool.rb +0 -185
  190. data/lib/ruby_llm/agents/workflow/throttle_manager.rb +0 -206
  191. data/lib/ruby_llm/agents/workflow/wait_result.rb +0 -213
@@ -1,56 +0,0 @@
1
- <div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 h-full">
2
- <div class="px-4 py-3 border-b border-gray-100 dark:border-gray-700">
3
- <div class="flex items-center justify-between">
4
- <h3 class="text-base font-semibold text-gray-900 dark:text-gray-100">Model Performance</h3>
5
- <span class="text-xs text-gray-500 dark:text-gray-400">By cost</span>
6
- </div>
7
- </div>
8
-
9
- <% if model_stats.any? %>
10
- <div class="overflow-x-auto">
11
- <table class="w-full text-sm">
12
- <thead>
13
- <tr class="text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
14
- <th class="px-4 py-2">Model</th>
15
- <th class="px-4 py-2 text-right">Runs</th>
16
- <th class="px-4 py-2 text-right">$/1K tok</th>
17
- <th class="px-4 py-2 text-right">Avg Time</th>
18
- <th class="px-4 py-2 text-right">Success</th>
19
- </tr>
20
- </thead>
21
- <tbody class="divide-y divide-gray-100 dark:divide-gray-700">
22
- <% model_stats.first(8).each do |model| %>
23
- <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50">
24
- <td class="px-4 py-2">
25
- <span class="font-mono text-xs font-medium text-gray-900 dark:text-gray-100 truncate max-w-[120px] block" title="<%= model[:model_id] %>">
26
- <%= model[:model_id].to_s.split('/').last.truncate(20) %>
27
- </span>
28
- </td>
29
- <td class="px-4 py-2 text-right text-gray-600 dark:text-gray-300">
30
- <%= number_with_delimiter(model[:executions]) %>
31
- </td>
32
- <td class="px-4 py-2 text-right text-gray-600 dark:text-gray-300">
33
- $<%= number_with_precision(model[:cost_per_1k_tokens], precision: 4) %>
34
- </td>
35
- <td class="px-4 py-2 text-right text-gray-600 dark:text-gray-300">
36
- <%= format_duration_ms(model[:avg_duration_ms]) %>
37
- </td>
38
- <td class="px-4 py-2 text-right">
39
- <span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium <%= model[:success_rate] >= 95 ? 'bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-300' : model[: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' %>">
40
- <%= model[:success_rate].round %>%
41
- </span>
42
- </td>
43
- </tr>
44
- <% end %>
45
- </tbody>
46
- </table>
47
- </div>
48
- <% else %>
49
- <div class="px-4 py-8 text-center text-gray-500 dark:text-gray-400">
50
- <svg class="w-8 h-8 mx-auto mb-2 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
51
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z"/>
52
- </svg>
53
- <p class="text-sm">No model data yet</p>
54
- </div>
55
- <% end %>
56
- </div>
@@ -1,115 +0,0 @@
1
- <div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 h-full">
2
- <div class="px-4 py-3 border-b border-gray-100 dark:border-gray-700">
3
- <div class="flex items-center justify-between">
4
- <h3 class="text-base font-semibold text-gray-900 dark:text-gray-100">Cost by Model</h3>
5
- <span class="text-xs text-gray-500 dark:text-gray-400">
6
- $<%= number_with_precision(model_stats.sum { |m| m[:total_cost] }, precision: 2) %> total
7
- </span>
8
- </div>
9
- </div>
10
-
11
- <% if model_stats.any? && model_stats.sum { |m| m[:total_cost] } > 0 %>
12
- <div class="p-4">
13
- <div id="model-cost-chart" style="width: 100%; height: 200px;"></div>
14
-
15
- <!-- Legend -->
16
- <div class="mt-4 space-y-2">
17
- <% colors = ['#6366F1', '#8B5CF6', '#EC4899', '#F59E0B', '#10B981', '#3B82F6', '#EF4444', '#6B7280'] %>
18
- <% model_stats.first(5).each_with_index do |model, i| %>
19
- <div class="flex items-center justify-between text-sm">
20
- <div class="flex items-center gap-2">
21
- <span class="w-3 h-3 rounded-full flex-shrink-0" style="background-color: <%= colors[i % colors.length] %>"></span>
22
- <span class="text-gray-700 dark:text-gray-300 truncate max-w-[140px]" title="<%= model[:model_id] %>">
23
- <%= model[:model_id].to_s.split('/').last.truncate(18) %>
24
- </span>
25
- </div>
26
- <div class="flex items-center gap-2">
27
- <span class="text-gray-900 dark:text-gray-100 font-medium">$<%= number_with_precision(model[:total_cost], precision: 2) %></span>
28
- <span class="text-gray-500 dark:text-gray-400 text-xs w-12 text-right"><%= model[:cost_percentage] %>%</span>
29
- </div>
30
- </div>
31
- <% end %>
32
- <% if model_stats.length > 5 %>
33
- <% other_cost = model_stats[5..].sum { |m| m[:total_cost] } %>
34
- <% other_percentage = model_stats[5..].sum { |m| m[:cost_percentage] } %>
35
- <div class="flex items-center justify-between text-sm">
36
- <div class="flex items-center gap-2">
37
- <span class="w-3 h-3 rounded-full flex-shrink-0 bg-gray-400"></span>
38
- <span class="text-gray-700 dark:text-gray-300">Other (<%= model_stats.length - 5 %> models)</span>
39
- </div>
40
- <div class="flex items-center gap-2">
41
- <span class="text-gray-900 dark:text-gray-100 font-medium">$<%= number_with_precision(other_cost, precision: 2) %></span>
42
- <span class="text-gray-500 dark:text-gray-400 text-xs w-12 text-right"><%= other_percentage.round(1) %>%</span>
43
- </div>
44
- </div>
45
- <% end %>
46
- </div>
47
- </div>
48
-
49
- <script>
50
- (function() {
51
- const colors = ['#6366F1', '#8B5CF6', '#EC4899', '#F59E0B', '#10B981', '#3B82F6', '#EF4444', '#6B7280'];
52
- const data = [
53
- <% model_stats.first(5).each_with_index do |model, i| %>
54
- { name: '<%= model[:model_id].to_s.split('/').last.truncate(15).gsub("'", "\\'") %>', y: <%= model[:total_cost] %>, color: colors[<%= i %>] },
55
- <% end %>
56
- <% if model_stats.length > 5 %>
57
- { name: 'Other', y: <%= model_stats[5..].sum { |m| m[:total_cost] } %>, color: '#9CA3AF' },
58
- <% end %>
59
- ];
60
-
61
- function initChart() {
62
- if (typeof Highcharts === 'undefined') {
63
- setTimeout(initChart, 100);
64
- return;
65
- }
66
-
67
- Highcharts.chart('model-cost-chart', {
68
- chart: {
69
- type: 'pie',
70
- backgroundColor: 'transparent',
71
- spacing: [0, 0, 0, 0]
72
- },
73
- title: { text: null },
74
- credits: { enabled: false },
75
- tooltip: {
76
- backgroundColor: 'rgba(17, 24, 39, 0.95)',
77
- borderColor: 'transparent',
78
- borderRadius: 8,
79
- style: { color: '#F3F4F6', fontSize: '12px' },
80
- pointFormat: '<b>${point.y:.2f}</b> ({point.percentage:.1f}%)'
81
- },
82
- plotOptions: {
83
- pie: {
84
- innerSize: '60%',
85
- dataLabels: { enabled: false },
86
- borderWidth: 0,
87
- states: {
88
- hover: { brightness: 0.1 }
89
- }
90
- }
91
- },
92
- series: [{
93
- name: 'Cost',
94
- data: data
95
- }]
96
- });
97
- }
98
-
99
- if (document.readyState === 'loading') {
100
- document.addEventListener('DOMContentLoaded', initChart);
101
- } else {
102
- initChart();
103
- }
104
- })();
105
- </script>
106
- <% else %>
107
- <div class="px-4 py-8 text-center text-gray-500 dark:text-gray-400">
108
- <svg class="w-8 h-8 mx-auto mb-2 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
109
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M11 3.055A9.001 9.001 0 1020.945 13H11V3.055z"/>
110
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M20.488 9H15V3.512A9.025 9.025 0 0120.488 9z"/>
111
- </svg>
112
- <p class="text-sm">No cost data yet</p>
113
- </div>
114
- <% end %>
115
- </div>
@@ -1,59 +0,0 @@
1
- <div class="mb-6">
2
- <div class="flex items-center justify-end mb-4">
3
- <div class="flex space-x-1 bg-gray-100 dark:bg-gray-700 rounded-lg p-1">
4
- <%= link_to "Today", ruby_llm_agents.root_path(range: "today"),
5
- class: "px-3 py-1 text-xs font-medium rounded-md transition-colors #{@selected_range == 'today' ? 'bg-white dark:bg-gray-600 text-gray-900 dark:text-white shadow-sm' : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white'}" %>
6
- <%= link_to "7 Days", ruby_llm_agents.root_path(range: "7d"),
7
- class: "px-3 py-1 text-xs font-medium rounded-md transition-colors #{@selected_range == '7d' ? 'bg-white dark:bg-gray-600 text-gray-900 dark:text-white shadow-sm' : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white'}" %>
8
- <%= link_to "30 Days", ruby_llm_agents.root_path(range: "30d"),
9
- class: "px-3 py-1 text-xs font-medium rounded-md transition-colors #{@selected_range == '30d' ? 'bg-white dark:bg-gray-600 text-gray-900 dark:text-white shadow-sm' : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white'}" %>
10
- </div>
11
- </div>
12
-
13
- <div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-4">
14
- <!-- Success -->
15
- <div class="bg-white dark:bg-gray-800 rounded-lg p-5 shadow-sm border-t-2 border-green-500">
16
- <p class="text-2xl font-bold text-gray-900 dark:text-gray-100">
17
- <%= now_strip[:success_today] %>
18
- <%= comparison_indicator(now_strip.dig(:comparisons, :success_change), metric_type: :success) %>
19
- </p>
20
- <p class="text-sm text-gray-500 dark:text-gray-400 mt-1">Success</p>
21
- </div>
22
-
23
- <!-- Errors -->
24
- <div class="bg-white dark:bg-gray-800 rounded-lg p-5 shadow-sm border-t-2 border-red-500">
25
- <p class="text-2xl font-bold <%= now_strip[:errors_today] > 0 ? 'text-red-600 dark:text-red-400' : 'text-gray-900 dark:text-gray-100' %>">
26
- <%= now_strip[:errors_today] %>
27
- <%= comparison_indicator(now_strip.dig(:comparisons, :errors_change), metric_type: :errors) %>
28
- </p>
29
- <p class="text-sm text-gray-500 dark:text-gray-400 mt-1">Errors</p>
30
- </div>
31
-
32
- <!-- Cost -->
33
- <div class="bg-white dark:bg-gray-800 rounded-lg p-5 shadow-sm border-t-2 border-amber-500">
34
- <p class="text-2xl font-bold text-gray-900 dark:text-gray-100">
35
- $<%= number_with_precision(now_strip[:cost_today], precision: 4) %>
36
- <%= comparison_indicator(now_strip.dig(:comparisons, :cost_change), metric_type: :cost) %>
37
- </p>
38
- <p class="text-sm text-gray-500 dark:text-gray-400 mt-1">Cost</p>
39
- </div>
40
-
41
- <!-- Avg Duration -->
42
- <div class="bg-white dark:bg-gray-800 rounded-lg p-5 shadow-sm border-t-2 border-purple-500">
43
- <p class="text-2xl font-bold text-gray-900 dark:text-gray-100">
44
- <%= format_duration_ms(now_strip[:avg_duration_ms]) %>
45
- <%= comparison_indicator(now_strip.dig(:comparisons, :duration_change), metric_type: :duration) %>
46
- </p>
47
- <p class="text-sm text-gray-500 dark:text-gray-400 mt-1">Avg Duration</p>
48
- </div>
49
-
50
- <!-- Tokens -->
51
- <div class="bg-white dark:bg-gray-800 rounded-lg p-5 shadow-sm border-t-2 border-indigo-500">
52
- <p class="text-2xl font-bold text-gray-900 dark:text-gray-100">
53
- <%= number_to_human_short(now_strip[:total_tokens]) %>
54
- <%= comparison_indicator(now_strip.dig(:comparisons, :tokens_change), metric_type: :tokens) %>
55
- </p>
56
- <p class="text-sm text-gray-500 dark:text-gray-400 mt-1">Tokens</p>
57
- </div>
58
- </div>
59
- </div>
@@ -1,60 +0,0 @@
1
- <div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 h-full">
2
- <div class="px-4 py-3 border-b border-gray-100 dark:border-gray-700">
3
- <h3 class="text-base font-semibold text-gray-900 dark:text-gray-100">Top Errors</h3>
4
- </div>
5
-
6
- <% if top_errors.any? %>
7
- <div class="overflow-x-auto">
8
- <table class="w-full text-sm">
9
- <thead>
10
- <tr class="text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
11
- <th class="px-4 py-2">Error</th>
12
- <th class="px-4 py-2 text-right">Count</th>
13
- <th class="px-4 py-2 text-right">Last Seen</th>
14
- </tr>
15
- </thead>
16
- <tbody class="divide-y divide-gray-100 dark:divide-gray-700">
17
- <% top_errors.first(5).each do |error| %>
18
- <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50">
19
- <td class="px-4 py-2">
20
- <div>
21
- <span class="text-sm font-medium text-gray-900 dark:text-gray-100 truncate block max-w-[180px]" title="<%= error[:error_class] %>">
22
- <%= error[:error_class].to_s.split("::").last %>
23
- </span>
24
- <!-- Progress bar showing error distribution -->
25
- <div class="mt-1 w-full h-1.5 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
26
- <div class="h-full bg-red-500 rounded-full transition-all duration-300" style="width: <%= error[:percentage] %>%"></div>
27
- </div>
28
- </div>
29
- </td>
30
- <td class="px-4 py-2 text-right">
31
- <div class="flex items-center justify-end gap-1">
32
- <span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-red-100 dark:bg-red-900 text-red-700 dark:text-red-300">
33
- <%= number_with_delimiter(error[:count]) %>
34
- </span>
35
- <span class="text-xs text-gray-500 dark:text-gray-400">
36
- (<%= error[:percentage] %>%)
37
- </span>
38
- </div>
39
- </td>
40
- <td class="px-4 py-2 text-right text-xs text-gray-500 dark:text-gray-400">
41
- <% if error[:last_seen] %>
42
- <%= time_ago_in_words(error[:last_seen]) %>
43
- <% else %>
44
- -
45
- <% end %>
46
- </td>
47
- </tr>
48
- <% end %>
49
- </tbody>
50
- </table>
51
- </div>
52
- <% else %>
53
- <div class="px-4 py-8 text-center text-green-500 dark:text-green-400">
54
- <svg class="w-8 h-8 mx-auto mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
55
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
56
- </svg>
57
- <p class="text-sm">No errors</p>
58
- </div>
59
- <% end %>
60
- </div>
@@ -1,86 +0,0 @@
1
- <%# Simplified Workflow Summary - Clean table approach %>
2
- <%# @param execution [RubyLLM::Agents::Execution] The workflow execution to display %>
3
-
4
- <% if execution.root_workflow? %>
5
- <% stats = execution.workflow_aggregate_stats %>
6
- <% steps = execution.workflow_steps.to_a %>
7
-
8
- <div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden mb-6">
9
- <!-- Header -->
10
- <div class="px-4 py-3 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
11
- <div class="flex items-center gap-2">
12
- <span class="text-emerald-600 dark:text-emerald-400">◈</span>
13
- <span class="font-medium text-gray-900 dark:text-gray-100">Workflow</span>
14
- <span class="text-gray-500 dark:text-gray-400">· <%= stats[:steps_count] %> steps</span>
15
- </div>
16
-
17
- <% overall_status = execution.workflow_overall_status %>
18
- <%= render "ruby_llm/agents/shared/status_badge", status: overall_status.to_s, size: :sm %>
19
- </div>
20
-
21
- <!-- Table -->
22
- <div class="overflow-x-auto">
23
- <table class="min-w-full text-sm">
24
- <thead class="bg-gray-50 dark:bg-gray-900/50">
25
- <tr>
26
- <th class="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Step</th>
27
- <th class="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Status</th>
28
- <th class="px-4 py-2 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Duration</th>
29
- <th class="px-4 py-2 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Tokens</th>
30
- <th class="px-4 py-2 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Cost</th>
31
- </tr>
32
- </thead>
33
- <tbody class="divide-y divide-gray-100 dark:divide-gray-700">
34
- <% steps.each_with_index do |step, index| %>
35
- <tr class="hover:bg-gray-50 dark:hover:bg-gray-800/50">
36
- <td class="px-4 py-2">
37
- <%= link_to ruby_llm_agents.execution_path(step.id), class: "text-blue-600 dark:text-blue-400 hover:underline" do %>
38
- <span class="text-gray-400 dark:text-gray-500"><%= index + 1 %>.</span>
39
- <%= step.workflow_step || step.agent_type.gsub(/Agent$/, "") %>
40
- <% end %>
41
- </td>
42
- <td class="px-4 py-2">
43
- <% case step.status
44
- when "success" %>
45
- <span class="text-green-600 dark:text-green-400">✓</span>
46
- <% when "error" %>
47
- <span class="text-red-600 dark:text-red-400">✗</span>
48
- <% when "timeout" %>
49
- <span class="text-orange-600 dark:text-orange-400">⏱</span>
50
- <% when "running" %>
51
- <span class="text-blue-600 dark:text-blue-400 animate-pulse">●</span>
52
- <% else %>
53
- <span class="text-gray-400">○</span>
54
- <% end %>
55
- </td>
56
- <td class="px-4 py-2 text-right text-gray-600 dark:text-gray-300 tabular-nums">
57
- <%= step.duration_ms ? "#{number_with_delimiter(step.duration_ms)}ms" : "-" %>
58
- </td>
59
- <td class="px-4 py-2 text-right text-gray-600 dark:text-gray-300 tabular-nums">
60
- <%= number_with_delimiter(step.total_tokens || 0) %>
61
- </td>
62
- <td class="px-4 py-2 text-right text-gray-600 dark:text-gray-300 tabular-nums">
63
- $<%= number_with_precision(step.total_cost || 0, precision: 4) %>
64
- </td>
65
- </tr>
66
- <% end %>
67
- </tbody>
68
- <tfoot class="bg-gray-50 dark:bg-gray-900/50 font-medium">
69
- <tr>
70
- <td class="px-4 py-2 text-gray-700 dark:text-gray-300">Total</td>
71
- <td class="px-4 py-2"></td>
72
- <td class="px-4 py-2 text-right text-gray-700 dark:text-gray-300 tabular-nums">
73
- <%= stats[:wall_clock_ms] ? "#{number_with_delimiter(stats[:wall_clock_ms])}ms" : "-" %>
74
- </td>
75
- <td class="px-4 py-2 text-right text-gray-700 dark:text-gray-300 tabular-nums">
76
- <%= number_with_delimiter(stats[:total_tokens]) %>
77
- </td>
78
- <td class="px-4 py-2 text-right text-gray-700 dark:text-gray-300 tabular-nums">
79
- $<%= number_with_precision(stats[:total_cost], precision: 4) %>
80
- </td>
81
- </tr>
82
- </tfoot>
83
- </table>
84
- </div>
85
- </div>
86
- <% end %>
@@ -1,149 +0,0 @@
1
- <div class="mb-6">
2
- <%= link_to ruby_llm_agents.execution_path(@execution), class: "inline-flex items-center text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200" do %>
3
- <svg
4
- class="w-4 h-4 mr-1"
5
- fill="none"
6
- stroke="currentColor"
7
- viewBox="0 0 24 24"
8
- >
9
- <path
10
- stroke-linecap="round"
11
- stroke-linejoin="round"
12
- stroke-width="2"
13
- d="M10 19l-7-7m0 0l7-7m-7 7h18"
14
- />
15
- </svg>
16
- Back to Execution
17
- <% end %>
18
- </div>
19
-
20
- <!-- Header -->
21
- <div class="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-xl p-6 mb-6">
22
- <div class="flex items-center gap-3 mb-2">
23
- <svg class="w-6 h-6 text-blue-600 dark:text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
24
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
25
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
26
- </svg>
27
- <h2 class="text-xl font-bold text-blue-900 dark:text-blue-100">Dry Run Preview</h2>
28
- </div>
29
- <p class="text-sm text-blue-700 dark:text-blue-300">
30
- This is a preview of what would be sent to the LLM. No API call was made and no execution was recorded.
31
- </p>
32
- </div>
33
-
34
- <!-- Agent Info -->
35
- <div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-6 mb-6">
36
- <h3 class="text-sm font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide mb-4">Agent Configuration</h3>
37
-
38
- <div class="grid grid-cols-2 md:grid-cols-4 gap-4">
39
- <div>
40
- <p class="text-xs text-gray-500 dark:text-gray-400">Agent</p>
41
- <p class="text-sm font-medium text-gray-900 dark:text-gray-100"><%= @dry_run_result[:agent] || @execution.agent_type %></p>
42
- </div>
43
- <div>
44
- <p class="text-xs text-gray-500 dark:text-gray-400">Model</p>
45
- <p class="text-sm font-medium text-gray-900 dark:text-gray-100"><%= @dry_run_result[:model] || 'N/A' %></p>
46
- </div>
47
- <div>
48
- <p class="text-xs text-gray-500 dark:text-gray-400">Temperature</p>
49
- <p class="text-sm font-medium text-gray-900 dark:text-gray-100"><%= @dry_run_result[:temperature] || 'N/A' %></p>
50
- </div>
51
- <div>
52
- <p class="text-xs text-gray-500 dark:text-gray-400">Version</p>
53
- <p class="text-sm font-medium text-gray-900 dark:text-gray-100"><%= @dry_run_result[:version] || 'N/A' %></p>
54
- </div>
55
- </div>
56
- </div>
57
-
58
- <!-- System Prompt -->
59
- <% if @dry_run_result[:system_prompt].present? %>
60
- <div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-6 mb-6">
61
- <div class="flex items-center justify-between mb-4">
62
- <h3 class="text-sm font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">System Prompt</h3>
63
- <button
64
- type="button"
65
- onclick="copyToClipboard(this, 'system-prompt-content')"
66
- class="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"
67
- >
68
- <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
69
- <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"/>
70
- </svg>
71
- <span>Copy</span>
72
- </button>
73
- </div>
74
- <pre id="system-prompt-content" class="bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 rounded-lg p-4 text-sm overflow-x-auto whitespace-pre-wrap"><%= @dry_run_result[:system_prompt] %></pre>
75
- </div>
76
- <% end %>
77
-
78
- <!-- User Prompt -->
79
- <% if @dry_run_result[:user_prompt].present? %>
80
- <div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-6 mb-6">
81
- <div class="flex items-center justify-between mb-4">
82
- <h3 class="text-sm font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">User Prompt</h3>
83
- <button
84
- type="button"
85
- onclick="copyToClipboard(this, 'user-prompt-content')"
86
- class="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"
87
- >
88
- <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
89
- <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"/>
90
- </svg>
91
- <span>Copy</span>
92
- </button>
93
- </div>
94
- <pre id="user-prompt-content" class="bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 rounded-lg p-4 text-sm overflow-x-auto whitespace-pre-wrap"><%= @dry_run_result[:user_prompt] %></pre>
95
- </div>
96
- <% end %>
97
-
98
- <!-- Parameters -->
99
- <% if @dry_run_result[:parameters].present? %>
100
- <div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-6 mb-6">
101
- <div class="flex items-center justify-between mb-4">
102
- <h3 class="text-sm font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">Parameters</h3>
103
- </div>
104
- <pre class="bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 rounded-lg p-4 text-sm overflow-x-auto font-mono"><%= highlight_json(@dry_run_result[:parameters]) %></pre>
105
- </div>
106
- <% end %>
107
-
108
- <!-- Actions -->
109
- <div class="flex items-center gap-4">
110
- <%= link_to execution_path(@execution), class: "inline-flex items-center gap-1.5 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-lg hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors" do %>
111
- <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
112
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"/>
113
- </svg>
114
- Back to Execution
115
- <% end %>
116
-
117
- <%= button_to rerun_execution_path(@execution),
118
- method: :post,
119
- class: "inline-flex items-center gap-1.5 px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors",
120
- data: { confirm: "This will make a real API call and create a new execution. Are you sure?" } do %>
121
- <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
122
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
123
- </svg>
124
- Execute for Real
125
- <% end %>
126
- </div>
127
-
128
- <script>
129
- function copyToClipboard(button, elementId) {
130
- const content = document.getElementById(elementId).textContent;
131
- const span = button.querySelector('span');
132
-
133
- navigator.clipboard.writeText(content).then(function() {
134
- span.textContent = 'Copied!';
135
- button.classList.add('text-green-600');
136
-
137
- setTimeout(function() {
138
- span.textContent = 'Copy';
139
- button.classList.remove('text-green-600');
140
- }, 2000);
141
- }).catch(function(err) {
142
- console.error('Failed to copy:', err);
143
- span.textContent = 'Failed';
144
- setTimeout(function() {
145
- span.textContent = 'Copy';
146
- }, 2000);
147
- });
148
- }
149
- </script>
@@ -1,48 +0,0 @@
1
- <%
2
- # Breadcrumb navigation partial
3
- # Usage:
4
- # render "ruby_llm/agents/shared/breadcrumbs", items: [
5
- # { label: "Dashboard", path: ruby_llm_agents.root_path },
6
- # { label: "Executions", path: ruby_llm_agents.executions_path },
7
- # { label: "#123" } # Current page (no path)
8
- # ]
9
- #
10
- # Or with simple array:
11
- # render "ruby_llm/agents/shared/breadcrumbs", items: [
12
- # ["Dashboard", ruby_llm_agents.root_path],
13
- # ["Executions", ruby_llm_agents.executions_path],
14
- # ["#123"]
15
- # ]
16
-
17
- items = local_assigns[:items] || []
18
- %>
19
- <nav class="flex items-center text-sm mb-4" aria-label="Breadcrumb">
20
- <ol class="flex items-center space-x-1">
21
- <% items.each_with_index do |item, index| %>
22
- <%
23
- # Normalize item format
24
- if item.is_a?(Array)
25
- label = item[0]
26
- path = item[1]
27
- else
28
- label = item[:label]
29
- path = item[:path]
30
- end
31
- is_last = index == items.length - 1
32
- %>
33
- <li class="flex items-center">
34
- <% if index > 0 %>
35
- <svg class="w-4 h-4 text-gray-400 dark:text-gray-500 mx-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
36
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
37
- </svg>
38
- <% end %>
39
-
40
- <% if path.present? && !is_last %>
41
- <%= link_to label, path, class: "text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition-colors" %>
42
- <% else %>
43
- <span class="text-gray-900 dark:text-gray-100 font-medium"><%= label %></span>
44
- <% end %>
45
- </li>
46
- <% end %>
47
- </ol>
48
- </nav>
@@ -1,27 +0,0 @@
1
- <%# Reusable navigation link for desktop and mobile %>
2
- <%
3
- # Determine if link is active
4
- is_active = if path == ruby_llm_agents.root_path
5
- current_page?(path)
6
- elsif path == ruby_llm_agents.agents_path
7
- request.path.start_with?(path)
8
- else
9
- current_page?(path)
10
- end
11
-
12
- # Style classes
13
- base_classes = mobile ? "flex items-center px-3 py-2 text-base font-medium rounded-md" : "inline-flex items-center px-3 py-1.5 text-sm font-medium rounded-md"
14
- active_classes = "bg-gray-200 dark:bg-gray-700 text-gray-900 dark:text-gray-100"
15
- inactive_classes = "text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-gray-100 dark:hover:bg-gray-700"
16
- icon_classes = mobile ? "w-5 h-5 mr-3" : "w-4 h-4 mr-1.5"
17
-
18
- link_options = { class: "#{base_classes} #{is_active ? active_classes : inactive_classes}" }
19
- link_options["x-on:click"] = "mobileMenuOpen = false" if mobile
20
- %>
21
-
22
- <%= link_to path, **link_options do %>
23
- <svg class="<%= icon_classes %>" fill="none" stroke="currentColor" viewBox="0 0 24 24">
24
- <%= icon.html_safe %>
25
- </svg>
26
- <%= label %>
27
- <% end %>
@@ -1,14 +0,0 @@
1
- <div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-4">
2
- <div class="flex items-center justify-between">
3
- <p class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide font-medium"><%= title %></p>
4
- <span class="<%= local_assigns[:icon_color] || 'text-gray-400 dark:text-gray-500' %>">
5
- <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
6
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="<%= icon %>"/>
7
- </svg>
8
- </span>
9
- </div>
10
- <p class="text-xl font-semibold <%= local_assigns[:value_color] || 'text-gray-900 dark:text-gray-100' %> mt-2"><%= value %></p>
11
- <% if local_assigns[:subtitle].present? %>
12
- <p class="text-xs text-gray-400 dark:text-gray-500"><%= subtitle %></p>
13
- <% end %>
14
- </div>