ruby_llm-agents 1.3.3 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +101 -334
  3. data/app/controllers/concerns/ruby_llm/agents/sortable.rb +0 -1
  4. data/app/controllers/ruby_llm/agents/agents_controller.rb +5 -56
  5. data/app/controllers/ruby_llm/agents/dashboard_controller.rb +22 -106
  6. data/app/controllers/ruby_llm/agents/executions_controller.rb +4 -114
  7. data/app/controllers/ruby_llm/agents/tenants_controller.rb +30 -2
  8. data/app/helpers/ruby_llm/agents/application_helper.rb +19 -53
  9. data/app/models/ruby_llm/agents/execution/analytics.rb +13 -54
  10. data/app/models/ruby_llm/agents/execution/scopes.rb +61 -14
  11. data/app/models/ruby_llm/agents/execution.rb +46 -10
  12. data/app/models/ruby_llm/agents/execution_detail.rb +18 -0
  13. data/app/models/ruby_llm/agents/tenant/budgetable.rb +132 -24
  14. data/app/models/ruby_llm/agents/tenant/incrementable.rb +117 -0
  15. data/app/models/ruby_llm/agents/tenant/resettable.rb +128 -0
  16. data/app/models/ruby_llm/agents/tenant/trackable.rb +46 -12
  17. data/app/models/ruby_llm/agents/tenant.rb +2 -3
  18. data/app/models/ruby_llm/agents/tenant_budget.rb +6 -3
  19. data/app/services/ruby_llm/agents/agent_registry.rb +6 -112
  20. data/app/views/layouts/ruby_llm/agents/application.html.erb +87 -252
  21. data/app/views/ruby_llm/agents/agents/_config_agent.html.erb +71 -218
  22. data/app/views/ruby_llm/agents/agents/_config_embedder.html.erb +20 -63
  23. data/app/views/ruby_llm/agents/agents/_config_image_generator.html.erb +44 -131
  24. data/app/views/ruby_llm/agents/agents/_config_moderator.html.erb +16 -57
  25. data/app/views/ruby_llm/agents/agents/_config_speaker.html.erb +39 -104
  26. data/app/views/ruby_llm/agents/agents/_config_transcriber.html.erb +29 -82
  27. data/app/views/ruby_llm/agents/agents/_empty_state.html.erb +4 -14
  28. data/app/views/ruby_llm/agents/agents/index.html.erb +105 -274
  29. data/app/views/ruby_llm/agents/agents/show.html.erb +248 -378
  30. data/app/views/ruby_llm/agents/dashboard/_action_center.html.erb +29 -52
  31. data/app/views/ruby_llm/agents/dashboard/_tenant_budget.html.erb +73 -99
  32. data/app/views/ruby_llm/agents/dashboard/index.html.erb +228 -433
  33. data/app/views/ruby_llm/agents/executions/_execution.html.erb +1 -1
  34. data/app/views/ruby_llm/agents/executions/_filters.html.erb +4 -25
  35. data/app/views/ruby_llm/agents/executions/_list.html.erb +111 -152
  36. data/app/views/ruby_llm/agents/executions/index.html.erb +5 -7
  37. data/app/views/ruby_llm/agents/executions/show.html.erb +528 -989
  38. data/app/views/ruby_llm/agents/shared/_agent_type_badge.html.erb +5 -21
  39. data/app/views/ruby_llm/agents/shared/_executions_table.html.erb +70 -191
  40. data/app/views/ruby_llm/agents/shared/_filter_dropdown.html.erb +16 -44
  41. data/app/views/ruby_llm/agents/shared/_select_dropdown.html.erb +12 -41
  42. data/app/views/ruby_llm/agents/shared/_status_badge.html.erb +11 -65
  43. data/app/views/ruby_llm/agents/shared/_tenant_filter.html.erb +6 -5
  44. data/app/views/ruby_llm/agents/system_config/show.html.erb +240 -351
  45. data/app/views/ruby_llm/agents/tenants/_form.html.erb +67 -77
  46. data/app/views/ruby_llm/agents/tenants/edit.html.erb +7 -9
  47. data/app/views/ruby_llm/agents/tenants/index.html.erb +100 -122
  48. data/app/views/ruby_llm/agents/tenants/show.html.erb +146 -336
  49. data/config/routes.rb +0 -13
  50. data/lib/generators/ruby_llm_agents/install_generator.rb +9 -14
  51. data/lib/generators/ruby_llm_agents/migrate_structure_generator.rb +2 -12
  52. data/lib/generators/ruby_llm_agents/restructure_generator.rb +0 -2
  53. data/lib/generators/ruby_llm_agents/templates/add_usage_counters_to_tenants_migration.rb.tt +37 -0
  54. data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +1 -2
  55. data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +1 -1
  56. data/lib/generators/ruby_llm_agents/templates/application_image_pipeline.rb.tt +0 -1
  57. data/lib/generators/ruby_llm_agents/templates/create_execution_details_migration.rb.tt +27 -0
  58. data/lib/generators/ruby_llm_agents/templates/create_tenants_migration.rb.tt +25 -0
  59. data/lib/generators/ruby_llm_agents/templates/image_pipeline.rb.tt +0 -1
  60. data/lib/generators/ruby_llm_agents/templates/initializer.rb.tt +9 -12
  61. data/lib/generators/ruby_llm_agents/templates/migration.rb.tt +40 -71
  62. data/lib/generators/ruby_llm_agents/templates/remove_agent_version_migration.rb.tt +13 -0
  63. data/lib/generators/ruby_llm_agents/templates/remove_workflow_columns_migration.rb.tt +19 -0
  64. data/lib/generators/ruby_llm_agents/templates/skills/AGENTS.md.tt +2 -4
  65. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_PIPELINES.md.tt +0 -1
  66. data/lib/generators/ruby_llm_agents/templates/split_execution_details_migration.rb.tt +232 -0
  67. data/lib/generators/ruby_llm_agents/upgrade_generator.rb +58 -262
  68. data/lib/ruby_llm/agents/audio/speaker.rb +0 -1
  69. data/lib/ruby_llm/agents/audio/transcriber.rb +0 -1
  70. data/lib/ruby_llm/agents/base_agent.rb +52 -6
  71. data/lib/ruby_llm/agents/core/base/callbacks.rb +142 -0
  72. data/lib/ruby_llm/agents/core/base.rb +23 -55
  73. data/lib/ruby_llm/agents/core/configuration.rb +58 -117
  74. data/lib/ruby_llm/agents/core/errors.rb +0 -58
  75. data/lib/ruby_llm/agents/core/instrumentation.rb +157 -110
  76. data/lib/ruby_llm/agents/core/llm_tenant.rb +8 -7
  77. data/lib/ruby_llm/agents/core/version.rb +1 -1
  78. data/lib/ruby_llm/agents/dsl/base.rb +157 -17
  79. data/lib/ruby_llm/agents/dsl/caching.rb +33 -2
  80. data/lib/ruby_llm/agents/dsl/reliability.rb +148 -0
  81. data/lib/ruby_llm/agents/dsl.rb +1 -2
  82. data/lib/ruby_llm/agents/image/analyzer/execution.rb +1 -2
  83. data/lib/ruby_llm/agents/image/background_remover/execution.rb +1 -2
  84. data/lib/ruby_llm/agents/image/concerns/image_operation_dsl.rb +1 -13
  85. data/lib/ruby_llm/agents/image/concerns/image_operation_execution.rb +2 -2
  86. data/lib/ruby_llm/agents/image/editor/dsl.rb +0 -14
  87. data/lib/ruby_llm/agents/image/editor/execution.rb +1 -10
  88. data/lib/ruby_llm/agents/image/editor.rb +0 -1
  89. data/lib/ruby_llm/agents/image/generator.rb +0 -21
  90. data/lib/ruby_llm/agents/image/pipeline/dsl.rb +0 -13
  91. data/lib/ruby_llm/agents/image/pipeline/execution.rb +0 -1
  92. data/lib/ruby_llm/agents/image/transformer/dsl.rb +0 -13
  93. data/lib/ruby_llm/agents/image/transformer/execution.rb +1 -10
  94. data/lib/ruby_llm/agents/image/transformer.rb +0 -1
  95. data/lib/ruby_llm/agents/image/upscaler/execution.rb +1 -2
  96. data/lib/ruby_llm/agents/image/variator/execution.rb +1 -2
  97. data/lib/ruby_llm/agents/infrastructure/alert_manager.rb +78 -173
  98. data/lib/ruby_llm/agents/infrastructure/attempt_tracker.rb +1 -0
  99. data/lib/ruby_llm/agents/infrastructure/budget/budget_query.rb +66 -2
  100. data/lib/ruby_llm/agents/infrastructure/budget/spend_recorder.rb +0 -12
  101. data/lib/ruby_llm/agents/infrastructure/circuit_breaker.rb +10 -13
  102. data/lib/ruby_llm/agents/infrastructure/reliability.rb +37 -2
  103. data/lib/ruby_llm/agents/pipeline/context.rb +0 -1
  104. data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +28 -4
  105. data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +3 -10
  106. data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +88 -55
  107. data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +5 -41
  108. data/lib/ruby_llm/agents/rails/engine.rb +6 -6
  109. data/lib/ruby_llm/agents/results/base.rb +1 -49
  110. data/lib/ruby_llm/agents/text/embedder.rb +0 -1
  111. data/lib/ruby_llm/agents.rb +1 -9
  112. data/lib/tasks/ruby_llm_agents.rake +34 -0
  113. metadata +12 -81
  114. data/app/controllers/ruby_llm/agents/api_configurations_controller.rb +0 -214
  115. data/app/controllers/ruby_llm/agents/workflows_controller.rb +0 -544
  116. data/app/mailers/ruby_llm/agents/alert_mailer.rb +0 -84
  117. data/app/mailers/ruby_llm/agents/application_mailer.rb +0 -28
  118. data/app/models/ruby_llm/agents/api_configuration.rb +0 -386
  119. data/app/models/ruby_llm/agents/execution/workflow.rb +0 -170
  120. data/app/models/ruby_llm/agents/tenant/configurable.rb +0 -135
  121. data/app/views/ruby_llm/agents/agents/_agent.html.erb +0 -98
  122. data/app/views/ruby_llm/agents/agents/_version_comparison.html.erb +0 -186
  123. data/app/views/ruby_llm/agents/agents/_workflow.html.erb +0 -126
  124. data/app/views/ruby_llm/agents/alert_mailer/alert_notification.html.erb +0 -107
  125. data/app/views/ruby_llm/agents/alert_mailer/alert_notification.text.erb +0 -18
  126. data/app/views/ruby_llm/agents/api_configurations/_api_key_field.html.erb +0 -34
  127. data/app/views/ruby_llm/agents/api_configurations/_form.html.erb +0 -288
  128. data/app/views/ruby_llm/agents/api_configurations/edit.html.erb +0 -95
  129. data/app/views/ruby_llm/agents/api_configurations/edit_tenant.html.erb +0 -97
  130. data/app/views/ruby_llm/agents/api_configurations/show.html.erb +0 -214
  131. data/app/views/ruby_llm/agents/api_configurations/tenant.html.erb +0 -179
  132. data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +0 -73
  133. data/app/views/ruby_llm/agents/dashboard/_alerts_feed.html.erb +0 -62
  134. data/app/views/ruby_llm/agents/dashboard/_breaker_strip.html.erb +0 -47
  135. data/app/views/ruby_llm/agents/dashboard/_budgets_bar.html.erb +0 -75
  136. data/app/views/ruby_llm/agents/dashboard/_model_comparison.html.erb +0 -56
  137. data/app/views/ruby_llm/agents/dashboard/_model_cost_breakdown.html.erb +0 -115
  138. data/app/views/ruby_llm/agents/dashboard/_now_strip.html.erb +0 -59
  139. data/app/views/ruby_llm/agents/dashboard/_top_errors.html.erb +0 -60
  140. data/app/views/ruby_llm/agents/executions/_workflow_summary.html.erb +0 -86
  141. data/app/views/ruby_llm/agents/executions/dry_run.html.erb +0 -149
  142. data/app/views/ruby_llm/agents/shared/_breadcrumbs.html.erb +0 -48
  143. data/app/views/ruby_llm/agents/shared/_nav_link.html.erb +0 -27
  144. data/app/views/ruby_llm/agents/shared/_stat_card.html.erb +0 -14
  145. data/app/views/ruby_llm/agents/shared/_workflow_type_badge.html.erb +0 -35
  146. data/app/views/ruby_llm/agents/workflows/_empty_state.html.erb +0 -22
  147. data/app/views/ruby_llm/agents/workflows/_step_performance.html.erb +0 -228
  148. data/app/views/ruby_llm/agents/workflows/_structure_dsl.html.erb +0 -539
  149. data/app/views/ruby_llm/agents/workflows/_structure_parallel.html.erb +0 -76
  150. data/app/views/ruby_llm/agents/workflows/_structure_pipeline.html.erb +0 -74
  151. data/app/views/ruby_llm/agents/workflows/_structure_router.html.erb +0 -108
  152. data/app/views/ruby_llm/agents/workflows/_workflow_diagram.html.erb +0 -920
  153. data/app/views/ruby_llm/agents/workflows/index.html.erb +0 -179
  154. data/app/views/ruby_llm/agents/workflows/show.html.erb +0 -467
  155. data/lib/generators/ruby_llm_agents/api_configuration_generator.rb +0 -100
  156. data/lib/generators/ruby_llm_agents/templates/add_workflow_migration.rb.tt +0 -38
  157. data/lib/generators/ruby_llm_agents/templates/application_workflow.rb.tt +0 -48
  158. data/lib/generators/ruby_llm_agents/templates/create_api_configurations_migration.rb.tt +0 -90
  159. data/lib/generators/ruby_llm_agents/templates/skills/WORKFLOWS.md.tt +0 -551
  160. data/lib/ruby_llm/agents/core/base/moderation_dsl.rb +0 -181
  161. data/lib/ruby_llm/agents/core/base/moderation_execution.rb +0 -274
  162. data/lib/ruby_llm/agents/core/resolved_config.rb +0 -348
  163. data/lib/ruby_llm/agents/image/generator/content_policy.rb +0 -95
  164. data/lib/ruby_llm/agents/infrastructure/redactor.rb +0 -130
  165. data/lib/ruby_llm/agents/results/moderation_result.rb +0 -158
  166. data/lib/ruby_llm/agents/text/moderator.rb +0 -237
  167. data/lib/ruby_llm/agents/workflow/approval.rb +0 -205
  168. data/lib/ruby_llm/agents/workflow/approval_store.rb +0 -179
  169. data/lib/ruby_llm/agents/workflow/async.rb +0 -220
  170. data/lib/ruby_llm/agents/workflow/async_executor.rb +0 -156
  171. data/lib/ruby_llm/agents/workflow/dsl/executor.rb +0 -467
  172. data/lib/ruby_llm/agents/workflow/dsl/input_schema.rb +0 -244
  173. data/lib/ruby_llm/agents/workflow/dsl/iteration_executor.rb +0 -289
  174. data/lib/ruby_llm/agents/workflow/dsl/parallel_group.rb +0 -107
  175. data/lib/ruby_llm/agents/workflow/dsl/route_builder.rb +0 -150
  176. data/lib/ruby_llm/agents/workflow/dsl/schedule_helpers.rb +0 -187
  177. data/lib/ruby_llm/agents/workflow/dsl/step_config.rb +0 -352
  178. data/lib/ruby_llm/agents/workflow/dsl/step_executor.rb +0 -415
  179. data/lib/ruby_llm/agents/workflow/dsl/wait_config.rb +0 -257
  180. data/lib/ruby_llm/agents/workflow/dsl/wait_executor.rb +0 -317
  181. data/lib/ruby_llm/agents/workflow/dsl.rb +0 -576
  182. data/lib/ruby_llm/agents/workflow/instrumentation.rb +0 -249
  183. data/lib/ruby_llm/agents/workflow/notifiers/base.rb +0 -117
  184. data/lib/ruby_llm/agents/workflow/notifiers/email.rb +0 -117
  185. data/lib/ruby_llm/agents/workflow/notifiers/slack.rb +0 -180
  186. data/lib/ruby_llm/agents/workflow/notifiers/webhook.rb +0 -121
  187. data/lib/ruby_llm/agents/workflow/notifiers.rb +0 -70
  188. data/lib/ruby_llm/agents/workflow/orchestrator.rb +0 -416
  189. data/lib/ruby_llm/agents/workflow/result.rb +0 -592
  190. data/lib/ruby_llm/agents/workflow/thread_pool.rb +0 -185
  191. data/lib/ruby_llm/agents/workflow/throttle_manager.rb +0 -206
  192. data/lib/ruby_llm/agents/workflow/wait_result.rb +0 -213
@@ -1,98 +0,0 @@
1
- <%= link_to ruby_llm_agents.agent_path(ERB::Util.url_encode(agent[:name]), tab: local_assigns[:current_tab], subtab: local_assigns[:current_subtab]), class: "block bg-white dark:bg-gray-800 rounded-lg shadow hover:shadow-md transition-shadow relative z-10", data: { turbo: false }, style: "pointer-events: auto;" do %>
2
- <div class="p-4 sm:p-5">
3
- <!-- Row 1: Agent name + badge + model/timestamp -->
4
- <div class="flex items-center justify-between gap-2">
5
- <div class="flex items-center gap-2 min-w-0">
6
- <h3 class="font-semibold text-gray-900 dark:text-gray-100 truncate">
7
- <% name_parts = agent[:name].gsub(/Agent$/, '').split('::') %>
8
- <% if name_parts.length > 1 %>
9
- <span class="text-gray-400 dark:text-gray-500 font-normal"><%= name_parts[0..-2].join('::') %>::</span>
10
- <% end %>
11
- <%= name_parts.last %>
12
- </h3>
13
- <span class="hidden sm:inline text-sm text-gray-500 dark:text-gray-400">v<%= agent[:version] %></span>
14
- <% if agent[:active] %>
15
- <span class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-green-100 dark:bg-green-900/50 text-green-800 dark:text-green-300">Active</span>
16
- <% else %>
17
- <span class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400">Deleted</span>
18
- <% end %>
19
- </div>
20
- <!-- Desktop: model -->
21
- <span class="hidden sm:block text-sm text-gray-500 dark:text-gray-400"><%= agent[:model] %></span>
22
- <!-- Mobile: last executed -->
23
- <span class="sm:hidden text-xs text-gray-400 dark:text-gray-500 whitespace-nowrap">
24
- <% if agent[:last_executed] %>
25
- <%= time_ago_in_words(agent[:last_executed]) %> ago
26
- <% else %>
27
- Never
28
- <% end %>
29
- </span>
30
- </div>
31
-
32
- <!-- Description -->
33
- <% if agent[:description].present? %>
34
- <p class="mt-1 text-sm text-gray-500 dark:text-gray-400 line-clamp-2">
35
- <%= agent[:description] %>
36
- </p>
37
- <% end %>
38
-
39
- <!-- Row 2: Stats -->
40
- <div class="mt-2 sm:mt-3 sm:border-t sm:border-gray-100 sm:dark:border-gray-700 sm:pt-3">
41
- <!-- Mobile: compact inline -->
42
- <% success_rate = agent[:success_rate] || 0 %>
43
- <div class="sm:hidden text-xs text-gray-500 dark:text-gray-400">
44
- <%= number_with_delimiter(agent[:execution_count]) %> runs
45
- <span class="mx-1 text-gray-300 dark:text-gray-600">·</span>
46
- $<%= number_with_precision(agent[:total_cost] || 0, precision: 2) %>
47
- <span class="mx-1 text-gray-300 dark:text-gray-600">·</span>
48
- <span class="<%= success_rate >= 95 ? 'text-green-600 dark:text-green-400' : success_rate >= 80 ? 'text-yellow-600 dark:text-yellow-400' : 'text-red-600 dark:text-red-400' %>">
49
- <%= success_rate %>%
50
- </span>
51
- </div>
52
-
53
- <!-- Desktop: full stats with icons -->
54
- <div class="hidden sm:flex items-center justify-between text-sm">
55
- <div class="flex items-center space-x-6">
56
- <!-- Executions -->
57
- <div class="flex items-center text-gray-600 dark:text-gray-300">
58
- <svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
59
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
60
- </svg>
61
- <span><%= number_with_delimiter(agent[:execution_count]) %> executions</span>
62
- </div>
63
-
64
- <!-- Cost -->
65
- <div class="flex items-center text-gray-600 dark:text-gray-300">
66
- <svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
67
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
68
- </svg>
69
- <span>$<%= number_with_precision(agent[:total_cost] || 0, precision: 4) %></span>
70
- </div>
71
-
72
- <!-- Success Rate -->
73
- <div class="flex items-center">
74
- <% if success_rate >= 95 %>
75
- <span class="w-2 h-2 rounded-full bg-green-500 mr-1.5"></span>
76
- <span class="text-green-600 dark:text-green-400"><%= success_rate %>%</span>
77
- <% elsif success_rate >= 80 %>
78
- <span class="w-2 h-2 rounded-full bg-yellow-500 mr-1.5"></span>
79
- <span class="text-yellow-600 dark:text-yellow-400"><%= success_rate %>%</span>
80
- <% else %>
81
- <span class="w-2 h-2 rounded-full bg-red-500 mr-1.5"></span>
82
- <span class="text-red-600 dark:text-red-400"><%= success_rate %>%</span>
83
- <% end %>
84
- </div>
85
- </div>
86
-
87
- <!-- Last Executed -->
88
- <div class="text-gray-400 dark:text-gray-500">
89
- <% if agent[:last_executed] %>
90
- <%= time_ago_in_words(agent[:last_executed]) %> ago
91
- <% else %>
92
- Never executed
93
- <% end %>
94
- </div>
95
- </div>
96
- </div>
97
- </div>
98
- <% end %>
@@ -1,186 +0,0 @@
1
- <%# Collapsible version comparison UI - simplified for quick scanning %>
2
- <% if versions.size >= 2 %>
3
- <%
4
- v1 = version_comparison&.dig(:v1)
5
- v2 = version_comparison&.dig(:v2)
6
- data = version_comparison&.dig(:data) || {}
7
- v1_stats = data[:v1] || {}
8
- v2_stats = data[:v2] || {}
9
-
10
- # Calculate metrics with changes
11
- metrics = [
12
- { name: "Success Rate", v1: v1_stats[:success_rate] || 0, v2: v2_stats[:success_rate] || 0, format: :pct, better: :higher },
13
- { name: "Avg Cost", v1: v1_stats[:avg_cost] || 0, v2: v2_stats[:avg_cost] || 0, format: :cost, better: :lower },
14
- { name: "Avg Tokens", v1: v1_stats[:avg_tokens] || 0, v2: v2_stats[:avg_tokens] || 0, format: :num, better: :lower },
15
- { name: "Avg Duration", v1: v1_stats[:avg_duration_ms] || 0, v2: v2_stats[:avg_duration_ms] || 0, format: :ms, better: :lower },
16
- { name: "Executions", v1: v1_stats[:count] || 0, v2: v2_stats[:count] || 0, format: :num, better: :higher }
17
- ]
18
-
19
- # Count improvements/regressions for summary
20
- improvements = 0
21
- regressions = 0
22
- metrics.each do |m|
23
- next if m[:v1].zero? && m[:v2].zero?
24
- if m[:better] == :higher
25
- improvements += 1 if m[:v2] > m[:v1]
26
- regressions += 1 if m[:v2] < m[:v1]
27
- else
28
- improvements += 1 if m[:v2] < m[:v1]
29
- regressions += 1 if m[:v2] > m[:v1]
30
- end
31
- end
32
- %>
33
-
34
- <div class="bg-white dark:bg-gray-800 rounded-lg shadow mb-6">
35
- <!-- Collapsible Header -->
36
- <button
37
- type="button"
38
- onclick="toggleVersionComparison()"
39
- class="w-full px-6 py-4 flex items-center justify-between hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors rounded-lg"
40
- >
41
- <div class="flex items-center gap-3">
42
- <svg id="version-chevron" class="w-5 h-5 text-gray-400 transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
43
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
44
- </svg>
45
- <h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">Version Comparison</h3>
46
- <span class="text-sm text-gray-500 dark:text-gray-400">v<%= v1 %> → v<%= v2 %></span>
47
- </div>
48
-
49
- <!-- Quick Summary Pills -->
50
- <div class="flex items-center gap-2">
51
- <% if improvements > 0 %>
52
- <span class="px-2 py-0.5 text-xs font-medium text-green-700 dark:text-green-300 bg-green-100 dark:bg-green-900/50 rounded-full">
53
- <%= improvements %> improved
54
- </span>
55
- <% end %>
56
- <% if regressions > 0 %>
57
- <span class="px-2 py-0.5 text-xs font-medium text-red-700 dark:text-red-300 bg-red-100 dark:bg-red-900/50 rounded-full">
58
- <%= regressions %> regressed
59
- </span>
60
- <% end %>
61
- <% if improvements == 0 && regressions == 0 %>
62
- <span class="px-2 py-0.5 text-xs font-medium text-gray-600 dark:text-gray-400 bg-gray-100 dark:bg-gray-700 rounded-full">
63
- No changes
64
- </span>
65
- <% end %>
66
- </div>
67
- </button>
68
-
69
- <!-- Collapsible Content -->
70
- <div id="version-comparison-content" class="hidden border-t border-gray-100 dark:border-gray-700">
71
- <div class="p-6">
72
- <!-- Version Selectors -->
73
- <div class="flex items-center gap-4 mb-4">
74
- <select id="compare-v1" onchange="updateVersionComparison()" class="flex-1 px-3 py-2 text-sm bg-gray-50 dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-lg dark:text-gray-200">
75
- <% versions.each do |ver| %>
76
- <option value="<%= ver %>" <%= ver == v1 ? 'selected' : '' %>>v<%= ver %></option>
77
- <% end %>
78
- </select>
79
- <span class="text-gray-400">→</span>
80
- <select id="compare-v2" onchange="updateVersionComparison()" class="flex-1 px-3 py-2 text-sm bg-gray-50 dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-lg dark:text-gray-200">
81
- <% versions.each do |ver| %>
82
- <option value="<%= ver %>" <%= ver == v2 ? 'selected' : '' %>>v<%= ver %></option>
83
- <% end %>
84
- </select>
85
- </div>
86
-
87
- <% if version_comparison && version_comparison[:data] %>
88
- <!-- Simple Comparison Table -->
89
- <table class="w-full text-sm">
90
- <thead>
91
- <tr class="text-xs text-gray-500 dark:text-gray-400 uppercase">
92
- <th class="text-left py-2">Metric</th>
93
- <th class="text-right py-2">v<%= v1 %></th>
94
- <th class="text-right py-2">v<%= v2 %></th>
95
- <th class="text-right py-2">Change</th>
96
- </tr>
97
- </thead>
98
- <tbody class="divide-y divide-gray-100 dark:divide-gray-700">
99
- <% metrics.each do |m| %>
100
- <%
101
- # Format values
102
- case m[:format]
103
- when :pct
104
- f_v1 = "#{m[:v1].round(1)}%"
105
- f_v2 = "#{m[:v2].round(1)}%"
106
- diff = m[:v2] - m[:v1]
107
- change = "#{diff >= 0 ? '+' : ''}#{diff.round(1)}pp"
108
- when :cost
109
- f_v1 = "$#{number_with_precision(m[:v1], precision: 4)}"
110
- f_v2 = "$#{number_with_precision(m[:v2], precision: 4)}"
111
- pct = m[:v1] > 0 ? ((m[:v2] - m[:v1]) / m[:v1] * 100) : 0
112
- change = "#{pct >= 0 ? '+' : ''}#{pct.round(1)}%"
113
- when :ms
114
- f_v1 = "#{number_with_delimiter(m[:v1].round)}ms"
115
- f_v2 = "#{number_with_delimiter(m[:v2].round)}ms"
116
- pct = m[:v1] > 0 ? ((m[:v2] - m[:v1]) / m[:v1] * 100) : 0
117
- change = "#{pct >= 0 ? '+' : ''}#{pct.round(1)}%"
118
- else
119
- f_v1 = number_with_delimiter(m[:v1].round)
120
- f_v2 = number_with_delimiter(m[:v2].round)
121
- pct = m[:v1] > 0 ? ((m[:v2] - m[:v1]) / m[:v1] * 100) : 0
122
- change = "#{pct >= 0 ? '+' : ''}#{pct.round(1)}%"
123
- end
124
-
125
- # Determine if change is good/bad
126
- is_better = m[:better] == :higher ? m[:v2] > m[:v1] : m[:v2] < m[:v1]
127
- is_worse = m[:better] == :higher ? m[:v2] < m[:v1] : m[:v2] > m[:v1]
128
- change_class = is_better ? "text-green-600 dark:text-green-400" : is_worse ? "text-red-600 dark:text-red-400" : "text-gray-500"
129
- %>
130
- <tr>
131
- <td class="py-2 text-gray-700 dark:text-gray-300"><%= m[:name] %></td>
132
- <td class="py-2 text-right text-gray-900 dark:text-gray-100 font-medium"><%= f_v1 %></td>
133
- <td class="py-2 text-right text-gray-900 dark:text-gray-100 font-medium"><%= f_v2 %></td>
134
- <td class="py-2 text-right font-medium <%= change_class %>"><%= change %></td>
135
- </tr>
136
- <% end %>
137
- </tbody>
138
- </table>
139
-
140
- <p class="mt-4 text-xs text-gray-400 dark:text-gray-500 text-center">
141
- Based on this month's data
142
- </p>
143
- <% else %>
144
- <p class="text-sm text-gray-500 dark:text-gray-400 text-center py-4">
145
- No data available for selected versions
146
- </p>
147
- <% end %>
148
- </div>
149
- </div>
150
- </div>
151
-
152
- <script>
153
- function toggleVersionComparison() {
154
- const content = document.getElementById('version-comparison-content');
155
- const chevron = document.getElementById('version-chevron');
156
- content.classList.toggle('hidden');
157
- chevron.classList.toggle('-rotate-180');
158
- }
159
-
160
- function updateVersionComparison() {
161
- const v1 = document.getElementById('compare-v1').value;
162
- const v2 = document.getElementById('compare-v2').value;
163
- const url = new URL(window.location.href);
164
- url.searchParams.set('compare_v1', v1);
165
- url.searchParams.set('compare_v2', v2);
166
- window.location.href = url.toString();
167
- }
168
-
169
- // Restore collapsed state from localStorage
170
- document.addEventListener('DOMContentLoaded', function() {
171
- const expanded = localStorage.getItem('version_comparison_expanded');
172
- if (expanded === 'true') {
173
- document.getElementById('version-comparison-content').classList.remove('hidden');
174
- document.getElementById('version-chevron').classList.add('-rotate-180');
175
- }
176
- });
177
-
178
- // Save state when toggling
179
- const originalToggle = toggleVersionComparison;
180
- toggleVersionComparison = function() {
181
- originalToggle();
182
- const isExpanded = !document.getElementById('version-comparison-content').classList.contains('hidden');
183
- localStorage.setItem('version_comparison_expanded', isExpanded);
184
- };
185
- </script>
186
- <% end %>
@@ -1,126 +0,0 @@
1
- <%
2
- # All workflows use unified emerald color scheme
3
- colors = { border: "border-l-emerald-500", icon_bg: "bg-emerald-100 dark:bg-emerald-900/50", icon_text: "text-emerald-600 dark:text-emerald-300" }
4
- child_label = "steps"
5
- %>
6
-
7
- <div x-data="{ expanded: false }" class="block bg-white dark:bg-gray-800 rounded-lg shadow hover:shadow-md transition-shadow border-l-4 <%= colors[:border] %>">
8
- <%= link_to ruby_llm_agents.workflow_path(ERB::Util.url_encode(workflow[:name]), tab: local_assigns[:current_tab]), class: "block p-4 sm:p-5 relative z-10", data: { turbo: false }, style: "pointer-events: auto;" do %>
9
- <!-- Header Row -->
10
- <div class="flex items-center justify-between gap-2">
11
- <div class="flex items-center gap-2 min-w-0">
12
- <%= render "ruby_llm/agents/shared/workflow_type_badge", workflow_type: workflow[:workflow_type], size: :sm %>
13
- <h3 class="font-semibold text-gray-900 dark:text-gray-100 truncate">
14
- <% name_parts = workflow[:name].split('::') %>
15
- <% if name_parts.length > 1 %>
16
- <span class="text-gray-400 dark:text-gray-500 font-normal"><%= name_parts[0..-2].join('::') %>::</span>
17
- <% end %>
18
- <%= name_parts.last %>
19
- </h3>
20
- <span class="hidden sm:inline text-sm text-gray-500 dark:text-gray-400">v<%= workflow[:version] %></span>
21
- <% if workflow[:active] %>
22
- <span class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-green-100 dark:bg-green-900/50 text-green-800 dark:text-green-300">Active</span>
23
- <% else %>
24
- <span class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400">Deleted</span>
25
- <% end %>
26
- </div>
27
- <span class="hidden sm:block text-sm text-gray-500 dark:text-gray-400">
28
- <%= workflow[:workflow_children]&.size || 0 %> <%= child_label %>
29
- </span>
30
- </div>
31
-
32
- <!-- Description -->
33
- <% if workflow[:description].present? %>
34
- <p class="mt-1 text-sm text-gray-500 dark:text-gray-400 line-clamp-2">
35
- <%= workflow[:description] %>
36
- </p>
37
- <% end %>
38
-
39
- <!-- Stats Row -->
40
- <div class="mt-2 sm:mt-3 sm:border-t sm:border-gray-100 sm:dark:border-gray-700 sm:pt-3">
41
- <% success_rate = workflow[:success_rate] || 0 %>
42
- <!-- Mobile: compact inline -->
43
- <div class="sm:hidden text-xs text-gray-500 dark:text-gray-400">
44
- <%= number_with_delimiter(workflow[:execution_count]) %> runs
45
- <span class="mx-1 text-gray-300 dark:text-gray-600">-</span>
46
- $<%= number_with_precision(workflow[:total_cost] || 0, precision: 2) %>
47
- <span class="mx-1 text-gray-300 dark:text-gray-600">-</span>
48
- <span class="<%= success_rate >= 95 ? 'text-green-600 dark:text-green-400' : success_rate >= 80 ? 'text-yellow-600 dark:text-yellow-400' : 'text-red-600 dark:text-red-400' %>">
49
- <%= success_rate %>%
50
- </span>
51
- </div>
52
-
53
- <!-- Desktop: full stats with icons -->
54
- <div class="hidden sm:flex items-center justify-between text-sm">
55
- <div class="flex items-center space-x-6">
56
- <div class="flex items-center text-gray-600 dark:text-gray-300">
57
- <svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
58
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
59
- </svg>
60
- <span><%= number_with_delimiter(workflow[:execution_count]) %> executions</span>
61
- </div>
62
- <div class="flex items-center text-gray-600 dark:text-gray-300">
63
- <svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
64
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
65
- </svg>
66
- <span>$<%= number_with_precision(workflow[:total_cost] || 0, precision: 4) %></span>
67
- </div>
68
- <div class="flex items-center">
69
- <% if success_rate >= 95 %>
70
- <span class="w-2 h-2 rounded-full bg-green-500 mr-1.5"></span>
71
- <span class="text-green-600 dark:text-green-400"><%= success_rate %>%</span>
72
- <% elsif success_rate >= 80 %>
73
- <span class="w-2 h-2 rounded-full bg-yellow-500 mr-1.5"></span>
74
- <span class="text-yellow-600 dark:text-yellow-400"><%= success_rate %>%</span>
75
- <% else %>
76
- <span class="w-2 h-2 rounded-full bg-red-500 mr-1.5"></span>
77
- <span class="text-red-600 dark:text-red-400"><%= success_rate %>%</span>
78
- <% end %>
79
- </div>
80
- </div>
81
- <div class="text-gray-400 dark:text-gray-500">
82
- <% if workflow[:last_executed] %>
83
- <%= time_ago_in_words(workflow[:last_executed]) %> ago
84
- <% else %>
85
- Never executed
86
- <% end %>
87
- </div>
88
- </div>
89
- </div>
90
- <% end %>
91
-
92
- <!-- Expandable Child Agents Section -->
93
- <% if workflow[:workflow_children].present? && workflow[:workflow_children].any? %>
94
- <div class="border-t border-gray-100 dark:border-gray-700 px-4 sm:px-5 py-2">
95
- <button type="button" @click.prevent="expanded = !expanded" class="w-full flex items-center justify-between text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300">
96
- <span class="font-medium">
97
- <%= child_label.capitalize %> (<%= workflow[:workflow_children].size %>)
98
- </span>
99
- <svg class="w-4 h-4 transition-transform duration-200" :class="{ 'rotate-180': expanded }" fill="none" stroke="currentColor" viewBox="0 0 24 24">
100
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
101
- </svg>
102
- </button>
103
-
104
- <div x-show="expanded" x-cloak x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0 -translate-y-1" x-transition:enter-end="opacity-100 translate-y-0" class="mt-2 space-y-1">
105
- <% workflow[:workflow_children].each_with_index do |child, index| %>
106
- <div class="flex items-center gap-2 text-sm py-1.5 px-2 rounded bg-gray-50 dark:bg-gray-900/50">
107
- <span class="w-5 h-5 flex items-center justify-center rounded-full <%= colors[:icon_bg] %> text-xs <%= colors[:icon_text] %>">
108
- <%= index + 1 %>
109
- </span>
110
- <span class="font-medium text-gray-700 dark:text-gray-300"><%= child[:name] %></span>
111
- <span class="text-gray-400 dark:text-gray-500">-></span>
112
- <span class="text-gray-600 dark:text-gray-400 font-mono text-xs"><%= child[:agent] %></span>
113
- <% if child[:optional] %>
114
- <span class="text-xs text-gray-400 dark:text-gray-500 italic">(optional)</span>
115
- <% end %>
116
- <% if child[:description].present? %>
117
- <span class="text-xs text-gray-400 dark:text-gray-500 truncate max-w-xs" title="<%= child[:description] %>">
118
- - <%= child[:description].truncate(30) %>
119
- </span>
120
- <% end %>
121
- </div>
122
- <% end %>
123
- </div>
124
- </div>
125
- <% end %>
126
- </div>
@@ -1,107 +0,0 @@
1
- <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title><%= @title %></title>
7
- <style>
8
- body {
9
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
10
- line-height: 1.6;
11
- color: #333;
12
- max-width: 600px;
13
- margin: 0 auto;
14
- padding: 20px;
15
- }
16
- .header {
17
- border-left: 4px solid <%= @color %>;
18
- padding-left: 16px;
19
- margin-bottom: 24px;
20
- }
21
- .title {
22
- font-size: 24px;
23
- font-weight: 600;
24
- margin: 0 0 8px 0;
25
- }
26
- .severity {
27
- display: inline-block;
28
- padding: 4px 12px;
29
- border-radius: 4px;
30
- font-size: 12px;
31
- font-weight: 600;
32
- text-transform: uppercase;
33
- color: white;
34
- background-color: <%= @color %>;
35
- }
36
- .timestamp {
37
- color: #666;
38
- font-size: 14px;
39
- margin-top: 8px;
40
- }
41
- .details {
42
- background-color: #f8f9fa;
43
- border-radius: 8px;
44
- padding: 16px;
45
- margin-top: 24px;
46
- }
47
- .details-title {
48
- font-size: 14px;
49
- font-weight: 600;
50
- text-transform: uppercase;
51
- color: #666;
52
- margin: 0 0 12px 0;
53
- }
54
- .detail-row {
55
- display: flex;
56
- padding: 8px 0;
57
- border-bottom: 1px solid #e9ecef;
58
- }
59
- .detail-row:last-child {
60
- border-bottom: none;
61
- }
62
- .detail-key {
63
- font-weight: 500;
64
- color: #495057;
65
- min-width: 140px;
66
- }
67
- .detail-value {
68
- color: #212529;
69
- word-break: break-word;
70
- }
71
- .footer {
72
- margin-top: 32px;
73
- padding-top: 16px;
74
- border-top: 1px solid #e9ecef;
75
- font-size: 12px;
76
- color: #666;
77
- }
78
- </style>
79
- </head>
80
- <body>
81
- <div class="header">
82
- <h1 class="title"><%= @title %></h1>
83
- <span class="severity"><%= @severity %></span>
84
- <div class="timestamp">
85
- <%= @timestamp.strftime("%B %d, %Y at %I:%M %p %Z") %>
86
- </div>
87
- </div>
88
-
89
- <% if @payload.present? %>
90
- <div class="details">
91
- <h2 class="details-title">Event Details</h2>
92
- <% @payload.except(:event).each do |key, value| %>
93
- <div class="detail-row">
94
- <div class="detail-key"><%= key.to_s.titleize %></div>
95
- <div class="detail-value"><%= value.is_a?(Hash) ? value.to_json : value %></div>
96
- </div>
97
- <% end %>
98
- </div>
99
- <% end %>
100
-
101
- <div class="footer">
102
- This alert was sent by RubyLLM::Agents.
103
- <br>
104
- Event type: <code><%= @event %></code>
105
- </div>
106
- </body>
107
- </html>
@@ -1,18 +0,0 @@
1
- RubyLLM::Agents Alert
2
- =====================
3
-
4
- <%= @title %>
5
- Severity: <%= @severity %>
6
- Time: <%= @timestamp.strftime("%B %d, %Y at %I:%M %p %Z") %>
7
-
8
- <% if @payload.present? %>
9
- Event Details
10
- -------------
11
- <% @payload.except(:event).each do |key, value| %>
12
- <%= key.to_s.titleize %>: <%= value.is_a?(Hash) ? value.to_json : value %>
13
- <% end %>
14
- <% end %>
15
-
16
- ---
17
- This alert was sent by RubyLLM::Agents.
18
- Event type: <%= @event %>
@@ -1,34 +0,0 @@
1
- <% # Partial for API key input field %>
2
- <% # Local variables: f (form builder), config (ApiConfiguration), resolved (ResolvedConfig, optional), attr (symbol), label (string), placeholder (string, optional), hint (string, optional) %>
3
- <% resolved = local_assigns[:resolved] %>
4
- <% config_value = resolved&.ruby_llm_value_for(attr) %>
5
- <% db_value = config.send(attr) %>
6
-
7
- <div>
8
- <%= f.label attr, label, class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
9
- <div class="mt-1 relative rounded-md shadow-sm">
10
- <%= f.password_field attr,
11
- class: "block w-full px-3 py-2 rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm pr-10",
12
- placeholder: config.has_value?(attr) ? "[Key set - leave blank to keep]" : (local_assigns[:placeholder] || "Enter API key..."),
13
- autocomplete: "off",
14
- value: db_value,
15
- data: { key_field: attr, config_value: config_value, db_value: db_value } %>
16
- <% if config.has_value?(attr) %>
17
- <div class="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
18
- <svg class="h-5 w-5 text-green-500" fill="currentColor" viewBox="0 0 20 20">
19
- <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
20
- </svg>
21
- </div>
22
- <% end %>
23
- </div>
24
- <% if local_assigns[:hint] %>
25
- <p class="mt-1 text-xs text-gray-500 dark:text-gray-400"><%= hint %></p>
26
- <% end %>
27
- <% if config_value.present? %>
28
- <p class="mt-1 text-xs text-gray-500 dark:text-gray-400 config-value-hint"
29
- data-full-value="<%= config_value %>"
30
- data-masked-value="<%= resolved.mask_string(config_value) %>">
31
- From config: <code class="bg-gray-100 dark:bg-gray-700 px-1 rounded config-value-display"><%= resolved.mask_string(config_value) %></code>
32
- </p>
33
- <% end %>
34
- </div>