ruby_llm-agents 1.0.0 → 1.2.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 (152) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/ruby_llm/agents/paginatable.rb +9 -3
  3. data/app/controllers/concerns/ruby_llm/agents/sortable.rb +58 -0
  4. data/app/controllers/ruby_llm/agents/agents_controller.rb +59 -16
  5. data/app/controllers/ruby_llm/agents/dashboard_controller.rb +144 -20
  6. data/app/controllers/ruby_llm/agents/executions_controller.rb +13 -16
  7. data/app/controllers/ruby_llm/agents/workflows_controller.rb +279 -90
  8. data/app/helpers/ruby_llm/agents/application_helper.rb +100 -0
  9. data/app/mailers/ruby_llm/agents/alert_mailer.rb +84 -0
  10. data/app/mailers/ruby_llm/agents/application_mailer.rb +28 -0
  11. data/app/models/ruby_llm/agents/execution/analytics.rb +170 -20
  12. data/app/models/ruby_llm/agents/execution/scopes.rb +0 -31
  13. data/app/models/ruby_llm/agents/execution/workflow.rb +0 -129
  14. data/app/models/ruby_llm/agents/execution.rb +50 -14
  15. data/app/models/ruby_llm/agents/tenant/budgetable.rb +277 -0
  16. data/app/models/ruby_llm/agents/tenant/configurable.rb +135 -0
  17. data/app/models/ruby_llm/agents/tenant/trackable.rb +310 -0
  18. data/app/models/ruby_llm/agents/tenant.rb +146 -0
  19. data/app/models/ruby_llm/agents/tenant_budget.rb +12 -253
  20. data/app/services/ruby_llm/agents/agent_registry.rb +18 -12
  21. data/app/views/layouts/ruby_llm/agents/application.html.erb +72 -76
  22. data/app/views/ruby_llm/agents/agents/_agent.html.erb +0 -12
  23. data/app/views/ruby_llm/agents/agents/_sortable_header.html.erb +56 -0
  24. data/app/views/ruby_llm/agents/agents/_workflow.html.erb +5 -15
  25. data/app/views/ruby_llm/agents/agents/index.html.erb +271 -100
  26. data/app/views/ruby_llm/agents/agents/show.html.erb +1 -0
  27. data/app/views/ruby_llm/agents/alert_mailer/alert_notification.html.erb +107 -0
  28. data/app/views/ruby_llm/agents/alert_mailer/alert_notification.text.erb +18 -0
  29. data/app/views/ruby_llm/agents/api_configurations/show.html.erb +4 -1
  30. data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +66 -359
  31. data/app/views/ruby_llm/agents/dashboard/_model_comparison.html.erb +56 -0
  32. data/app/views/ruby_llm/agents/dashboard/_model_cost_breakdown.html.erb +115 -0
  33. data/app/views/ruby_llm/agents/dashboard/_now_strip.html.erb +35 -60
  34. data/app/views/ruby_llm/agents/dashboard/_top_errors.html.erb +17 -6
  35. data/app/views/ruby_llm/agents/dashboard/index.html.erb +373 -72
  36. data/app/views/ruby_llm/agents/executions/_execution.html.erb +0 -1
  37. data/app/views/ruby_llm/agents/executions/_filters.html.erb +51 -39
  38. data/app/views/ruby_llm/agents/executions/_list.html.erb +53 -195
  39. data/app/views/ruby_llm/agents/executions/_workflow_summary.html.erb +5 -20
  40. data/app/views/ruby_llm/agents/executions/index.html.erb +7 -83
  41. data/app/views/ruby_llm/agents/executions/show.html.erb +10 -20
  42. data/app/views/ruby_llm/agents/shared/_agent_type_badge.html.erb +2 -1
  43. data/app/views/ruby_llm/agents/shared/_doc_link.html.erb +12 -0
  44. data/app/views/ruby_llm/agents/shared/_executions_table.html.erb +3 -15
  45. data/app/views/ruby_llm/agents/shared/_filter_dropdown.html.erb +1 -1
  46. data/app/views/ruby_llm/agents/shared/_select_dropdown.html.erb +1 -1
  47. data/app/views/ruby_llm/agents/shared/_sortable_header.html.erb +53 -0
  48. data/app/views/ruby_llm/agents/shared/_status_badge.html.erb +7 -0
  49. data/app/views/ruby_llm/agents/shared/_status_dot.html.erb +1 -1
  50. data/app/views/ruby_llm/agents/shared/_workflow_type_badge.html.erb +9 -35
  51. data/app/views/ruby_llm/agents/system_config/show.html.erb +4 -1
  52. data/app/views/ruby_llm/agents/tenants/index.html.erb +4 -1
  53. data/app/views/ruby_llm/agents/workflows/_step_performance.html.erb +7 -15
  54. data/app/views/ruby_llm/agents/workflows/_structure_dsl.html.erb +539 -0
  55. data/app/views/ruby_llm/agents/workflows/_workflow_diagram.html.erb +920 -0
  56. data/app/views/ruby_llm/agents/workflows/index.html.erb +179 -0
  57. data/app/views/ruby_llm/agents/workflows/show.html.erb +164 -139
  58. data/config/routes.rb +1 -1
  59. data/lib/generators/ruby_llm_agents/agent_generator.rb +6 -36
  60. data/lib/generators/ruby_llm_agents/background_remover_generator.rb +7 -37
  61. data/lib/generators/ruby_llm_agents/embedder_generator.rb +5 -38
  62. data/lib/generators/ruby_llm_agents/image_analyzer_generator.rb +7 -37
  63. data/lib/generators/ruby_llm_agents/image_editor_generator.rb +7 -37
  64. data/lib/generators/ruby_llm_agents/image_generator_generator.rb +8 -41
  65. data/lib/generators/ruby_llm_agents/image_pipeline_generator.rb +18 -46
  66. data/lib/generators/ruby_llm_agents/image_transformer_generator.rb +7 -37
  67. data/lib/generators/ruby_llm_agents/image_upscaler_generator.rb +7 -37
  68. data/lib/generators/ruby_llm_agents/image_variator_generator.rb +7 -37
  69. data/lib/generators/ruby_llm_agents/install_generator.rb +33 -56
  70. data/lib/generators/ruby_llm_agents/migrate_structure_generator.rb +480 -0
  71. data/lib/generators/ruby_llm_agents/multi_tenancy_generator.rb +42 -22
  72. data/lib/generators/ruby_llm_agents/restructure_generator.rb +2 -2
  73. data/lib/generators/ruby_llm_agents/speaker_generator.rb +8 -39
  74. data/lib/generators/ruby_llm_agents/templates/add_tenant_to_executions_migration.rb.tt +13 -2
  75. data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +5 -8
  76. data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +40 -42
  77. data/lib/generators/ruby_llm_agents/templates/application_background_remover.rb.tt +20 -22
  78. data/lib/generators/ruby_llm_agents/templates/application_embedder.rb.tt +24 -26
  79. data/lib/generators/ruby_llm_agents/templates/application_image_analyzer.rb.tt +20 -22
  80. data/lib/generators/ruby_llm_agents/templates/application_image_editor.rb.tt +19 -17
  81. data/lib/generators/ruby_llm_agents/templates/application_image_generator.rb.tt +31 -33
  82. data/lib/generators/ruby_llm_agents/templates/application_image_pipeline.rb.tt +125 -127
  83. data/lib/generators/ruby_llm_agents/templates/application_image_transformer.rb.tt +20 -18
  84. data/lib/generators/ruby_llm_agents/templates/application_image_upscaler.rb.tt +19 -17
  85. data/lib/generators/ruby_llm_agents/templates/application_image_variator.rb.tt +19 -17
  86. data/lib/generators/ruby_llm_agents/templates/application_speaker.rb.tt +38 -40
  87. data/lib/generators/ruby_llm_agents/templates/application_transcriber.rb.tt +42 -44
  88. data/lib/generators/ruby_llm_agents/templates/application_workflow.rb.tt +48 -0
  89. data/lib/generators/ruby_llm_agents/templates/background_remover.rb.tt +19 -21
  90. data/lib/generators/ruby_llm_agents/templates/create_tenant_budgets_migration.rb.tt +11 -0
  91. data/lib/generators/ruby_llm_agents/templates/create_tenants_migration.rb.tt +72 -0
  92. data/lib/generators/ruby_llm_agents/templates/embedder.rb.tt +19 -21
  93. data/lib/generators/ruby_llm_agents/templates/image_analyzer.rb.tt +20 -22
  94. data/lib/generators/ruby_llm_agents/templates/image_editor.rb.tt +15 -17
  95. data/lib/generators/ruby_llm_agents/templates/image_generator.rb.tt +25 -27
  96. data/lib/generators/ruby_llm_agents/templates/image_pipeline.rb.tt +19 -21
  97. data/lib/generators/ruby_llm_agents/templates/image_transformer.rb.tt +20 -22
  98. data/lib/generators/ruby_llm_agents/templates/image_upscaler.rb.tt +17 -19
  99. data/lib/generators/ruby_llm_agents/templates/image_variator.rb.tt +15 -17
  100. data/lib/generators/ruby_llm_agents/templates/rename_tenant_budgets_to_tenants_migration.rb.tt +34 -0
  101. data/lib/generators/ruby_llm_agents/templates/skills/AGENTS.md.tt +87 -24
  102. data/lib/generators/ruby_llm_agents/templates/skills/BACKGROUND_REMOVERS.md.tt +21 -27
  103. data/lib/generators/ruby_llm_agents/templates/skills/EMBEDDERS.md.tt +46 -54
  104. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_ANALYZERS.md.tt +31 -39
  105. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_EDITORS.md.tt +22 -28
  106. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_GENERATORS.md.tt +53 -63
  107. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_PIPELINES.md.tt +46 -56
  108. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_TRANSFORMERS.md.tt +23 -31
  109. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_UPSCALERS.md.tt +22 -30
  110. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_VARIATORS.md.tt +23 -31
  111. data/lib/generators/ruby_llm_agents/templates/skills/SPEAKERS.md.tt +38 -46
  112. data/lib/generators/ruby_llm_agents/templates/skills/TOOLS.md.tt +7 -7
  113. data/lib/generators/ruby_llm_agents/templates/skills/TRANSCRIBERS.md.tt +59 -71
  114. data/lib/generators/ruby_llm_agents/templates/skills/WORKFLOWS.md.tt +274 -23
  115. data/lib/generators/ruby_llm_agents/templates/speaker.rb.tt +29 -31
  116. data/lib/generators/ruby_llm_agents/templates/transcriber.rb.tt +28 -30
  117. data/lib/generators/ruby_llm_agents/transcriber_generator.rb +10 -43
  118. data/lib/generators/ruby_llm_agents/upgrade_generator.rb +26 -0
  119. data/lib/ruby_llm/agents/core/configuration.rb +55 -43
  120. data/lib/ruby_llm/agents/core/llm_tenant.rb +60 -60
  121. data/lib/ruby_llm/agents/core/version.rb +1 -1
  122. data/lib/ruby_llm/agents/infrastructure/alert_manager.rb +26 -0
  123. data/lib/ruby_llm/agents/infrastructure/budget/config_resolver.rb +4 -2
  124. data/lib/ruby_llm/agents/pipeline.rb +69 -0
  125. data/lib/ruby_llm/agents/workflow/approval.rb +205 -0
  126. data/lib/ruby_llm/agents/workflow/approval_store.rb +179 -0
  127. data/lib/ruby_llm/agents/workflow/dsl/executor.rb +467 -0
  128. data/lib/ruby_llm/agents/workflow/dsl/input_schema.rb +244 -0
  129. data/lib/ruby_llm/agents/workflow/dsl/iteration_executor.rb +289 -0
  130. data/lib/ruby_llm/agents/workflow/dsl/parallel_group.rb +107 -0
  131. data/lib/ruby_llm/agents/workflow/dsl/route_builder.rb +150 -0
  132. data/lib/ruby_llm/agents/workflow/dsl/schedule_helpers.rb +187 -0
  133. data/lib/ruby_llm/agents/workflow/dsl/step_config.rb +352 -0
  134. data/lib/ruby_llm/agents/workflow/dsl/step_executor.rb +415 -0
  135. data/lib/ruby_llm/agents/workflow/dsl/wait_config.rb +257 -0
  136. data/lib/ruby_llm/agents/workflow/dsl/wait_executor.rb +317 -0
  137. data/lib/ruby_llm/agents/workflow/dsl.rb +576 -0
  138. data/lib/ruby_llm/agents/workflow/instrumentation.rb +2 -7
  139. data/lib/ruby_llm/agents/workflow/notifiers/base.rb +117 -0
  140. data/lib/ruby_llm/agents/workflow/notifiers/email.rb +117 -0
  141. data/lib/ruby_llm/agents/workflow/notifiers/slack.rb +180 -0
  142. data/lib/ruby_llm/agents/workflow/notifiers/webhook.rb +121 -0
  143. data/lib/ruby_llm/agents/workflow/notifiers.rb +70 -0
  144. data/lib/ruby_llm/agents/workflow/orchestrator.rb +190 -23
  145. data/lib/ruby_llm/agents/workflow/result.rb +202 -0
  146. data/lib/ruby_llm/agents/workflow/throttle_manager.rb +206 -0
  147. data/lib/ruby_llm/agents/workflow/wait_result.rb +213 -0
  148. metadata +43 -6
  149. data/app/views/ruby_llm/agents/dashboard/_execution_item.html.erb +0 -66
  150. data/lib/ruby_llm/agents/workflow/parallel.rb +0 -299
  151. data/lib/ruby_llm/agents/workflow/pipeline.rb +0 -306
  152. data/lib/ruby_llm/agents/workflow/router.rb +0 -429
@@ -30,6 +30,7 @@
30
30
  <h2 class="text-lg font-bold text-gray-900 dark:text-gray-100 truncate">
31
31
  <%= @execution.agent_type.gsub(/Agent$/, '') %>
32
32
  </h2>
33
+ <%= render "ruby_llm/agents/shared/doc_link" %>
33
34
  <%= render "ruby_llm/agents/shared/status_badge", status: @execution.status, size: :md %>
34
35
  <% if secondary_badges.any? %>
35
36
  <div class="relative" x-data="{ showDetails: false }">
@@ -216,28 +217,17 @@
216
217
  <% if (@execution.workflow_type.present? || @execution.workflow_step.present? || @execution.routed_to.present?) && !(@execution.respond_to?(:root_workflow?) && @execution.root_workflow?) %>
217
218
  <div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-4 sm:p-5 mb-6">
218
219
  <div class="flex items-center gap-2 mb-4">
219
- <% case @execution.workflow_type
220
- when "pipeline" %>
221
- <span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md text-sm font-medium bg-indigo-100 dark:bg-indigo-900/50 text-indigo-700 dark:text-indigo-300">
222
- <span class="text-base">→</span> Pipeline Workflow
220
+ <% if @execution.workflow_type.present? %>
221
+ <span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md text-sm font-medium bg-emerald-100 dark:bg-emerald-900/50 text-emerald-700 dark:text-emerald-300">
222
+ <span class="text-base">◈</span> Workflow
223
223
  </span>
224
- <% when "parallel" %>
225
- <span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md text-sm font-medium bg-cyan-100 dark:bg-cyan-900/50 text-cyan-700 dark:text-cyan-300">
226
- <span class="text-base">⫴</span> Parallel Workflow
227
- </span>
228
- <% when "router" %>
229
- <span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md text-sm font-medium bg-amber-100 dark:bg-amber-900/50 text-amber-700 dark:text-amber-300">
230
- <span class="text-base">⑂</span> Router Workflow
224
+ <% elsif @execution.workflow_step.present? %>
225
+ <span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md text-sm font-medium bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300">
226
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
227
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 5l7 7-7 7M5 5l7 7-7 7"/>
228
+ </svg>
229
+ Workflow Step
231
230
  </span>
232
- <% else %>
233
- <% if @execution.workflow_step.present? %>
234
- <span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md text-sm font-medium bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300">
235
- <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
236
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 5l7 7-7 7M5 5l7 7-7 7"/>
237
- </svg>
238
- Workflow Step
239
- </span>
240
- <% end %>
241
231
  <% end %>
242
232
  <% if @execution.workflow_id.present? %>
243
233
  <span class="text-xs text-gray-400 dark:text-gray-500 font-mono" title="Workflow ID: <%= @execution.workflow_id %>">
@@ -85,7 +85,8 @@
85
85
  { badge: "px-2 py-0.5", icon: "text-xs", text: "text-xs" }
86
86
  end
87
87
  %>
88
- <span class="inline-flex items-center gap-1 rounded-md font-medium <%= config[:bg] %> <%= config[:text] %> <%= size_classes[:badge] %>">
88
+ <span class="inline-flex items-center gap-1 rounded-md font-medium <%= config[:bg] %> <%= config[:text] %> <%= size_classes[:badge] %>"
89
+ title="<%= config[:label] %>">
89
90
  <span class="<%= size_classes[:icon] %>" aria-hidden="true"><%= config[:icon_char] %></span>
90
91
  <% if show_label %>
91
92
  <span class="<%= size_classes[:text] %>"><%= config[:label] %></span>
@@ -0,0 +1,12 @@
1
+ <% doc_url = documentation_url %>
2
+ <% if doc_url %>
3
+ <%= link_to doc_url,
4
+ target: "_blank",
5
+ rel: "noopener noreferrer",
6
+ class: "text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300 transition-colors",
7
+ title: "View documentation" do %>
8
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
9
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/>
10
+ </svg>
11
+ <% end %>
12
+ <% end %>
@@ -42,17 +42,7 @@
42
42
  <%= execution.agent_type.gsub(/Agent$/, "") %>
43
43
  <% end %>
44
44
  <% if is_workflow %>
45
- <% badge_style = case execution.workflow_type
46
- when "pipeline" then "text-indigo-600 dark:text-indigo-400"
47
- when "parallel" then "text-cyan-600 dark:text-cyan-400"
48
- when "router" then "text-amber-600 dark:text-amber-400"
49
- end %>
50
- <% badge_icon = case execution.workflow_type
51
- when "pipeline" then "→"
52
- when "parallel" then "⫴"
53
- when "router" then "⑂"
54
- end %>
55
- <span class="text-xs <%= badge_style %>"><%= badge_icon %></span>
45
+ <span class="text-xs text-emerald-600 dark:text-emerald-400">◈</span>
56
46
  <% end %>
57
47
  </div>
58
48
  </td>
@@ -134,12 +124,10 @@
134
124
  </div>
135
125
  </td>
136
126
 
137
- <%# Step/Branch Name %>
127
+ <%# Step Name %>
138
128
  <td class="px-4 py-2 whitespace-nowrap">
139
129
  <%= link_to ruby_llm_agents.execution_path(child), class: "text-gray-600 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400" do %>
140
- <% if execution.pipeline_workflow? %>
141
- <span class="text-gray-400 dark:text-gray-500 text-xs"><%= index + 1 %>.</span>
142
- <% end %>
130
+ <span class="text-gray-400 dark:text-gray-500 text-xs"><%= index + 1 %>.</span>
143
131
  <%= child.workflow_step || child.agent_type.gsub(/Agent$/, "") %>
144
132
  <% end %>
145
133
  </td>
@@ -45,7 +45,7 @@
45
45
  class="<%= full_width ? 'w-full md:w-auto justify-between md:justify-start' : '' %> flex items-center gap-2 px-3 py-2 text-sm
46
46
  bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-lg
47
47
  hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors
48
- <%= has_selection ? 'ring-2 ring-blue-500 dark:ring-offset-gray-900' : '' %>">
48
+ <%= has_selection ? 'border-blue-300 dark:border-blue-600' : '' %>">
49
49
  <div class="flex items-center gap-2">
50
50
  <% if current_color.present? %>
51
51
  <span class="w-2 h-2 rounded-full <%= current_color %> <%= current_color.include?('blue') && selected.first == 'running' ? 'animate-pulse' : '' %>"></span>
@@ -31,7 +31,7 @@
31
31
  class="<%= full_width ? 'w-full md:w-auto justify-between md:justify-start' : '' %> flex items-center gap-2 px-3 py-2 text-sm
32
32
  bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-lg
33
33
  hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors
34
- <%= has_selection ? 'ring-2 ring-blue-500 dark:ring-offset-gray-900' : '' %>">
34
+ <%= has_selection ? 'border-blue-300 dark:border-blue-600' : '' %>">
35
35
  <div class="flex items-center gap-2">
36
36
  <% if local_assigns[:icon].present? %>
37
37
  <svg class="w-4 h-4 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -0,0 +1,53 @@
1
+ <%#
2
+ Sortable column header component
3
+
4
+ Creates a clickable table header that toggles sort direction and
5
+ displays visual feedback for the current sort state.
6
+
7
+ Usage:
8
+ render "ruby_llm/agents/shared/sortable_header",
9
+ column: "created_at",
10
+ label: "Time",
11
+ current_sort: @sort_params[:column],
12
+ current_direction: @sort_params[:direction],
13
+ align: "right"
14
+
15
+ Parameters:
16
+ column - (String) The column name used in sort URL parameter
17
+ label - (String) Display text for the header
18
+ current_sort - (String) Currently active sort column
19
+ current_direction - (String) Current sort direction ('asc' or 'desc')
20
+ align - (String) Text alignment: 'left' (default), 'center', or 'right'
21
+ %>
22
+ <%
23
+ align = local_assigns[:align] || "left"
24
+ is_active = column == current_sort
25
+ next_direction = is_active && current_direction == "asc" ? "desc" : "asc"
26
+ sort_url = url_for(request.query_parameters.merge(sort: column, direction: next_direction, page: 1))
27
+
28
+ align_class = case align
29
+ when "right" then "text-right justify-end"
30
+ when "center" then "text-center justify-center"
31
+ else "text-left justify-start"
32
+ end
33
+ %>
34
+ <th scope="col" class="px-4 py-3">
35
+ <a href="<%= sort_url %>"
36
+ class="group inline-flex items-center gap-1 <%= align_class %> w-full
37
+ text-xs font-semibold uppercase tracking-wider
38
+ <%= is_active ? 'text-blue-600 dark:text-blue-400' : 'text-gray-500 dark:text-gray-400' %>
39
+ hover:text-blue-600 dark:hover:text-blue-400 transition-colors">
40
+ <span><%= label %></span>
41
+ <span class="<%= is_active ? 'opacity-100' : 'opacity-0 group-hover:opacity-50' %> transition-opacity">
42
+ <% if is_active && current_direction == "asc" %>
43
+ <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
44
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15l7-7 7 7"/>
45
+ </svg>
46
+ <% else %>
47
+ <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
48
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
49
+ </svg>
50
+ <% end %>
51
+ </span>
52
+ </a>
53
+ </th>
@@ -62,4 +62,11 @@
62
62
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="<%= config[:icon] %>"/>
63
63
  </svg>
64
64
  <span class="<%= size_classes[:text] %>"><%= status.to_s.capitalize %></span>
65
+ <% if local_assigns[:attempts_count] && attempts_count.to_i > 1 %>
66
+ <span class="opacity-70" title="<%= attempts_count %> attempts (retried)">
67
+ <svg class="<%= size_classes[:icon] %>" fill="none" stroke="currentColor" viewBox="0 0 24 24">
68
+ <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"/>
69
+ </svg>
70
+ </span>
71
+ <% end %>
65
72
  </span>
@@ -15,4 +15,4 @@
15
15
  { color: "bg-gray-500", animate: false }
16
16
  end
17
17
  %>
18
- <span class="w-2.5 h-2.5 rounded-full <%= config[:color] %> <%= config[:animate] ? 'animate-pulse' : '' %> flex-shrink-0"></span>
18
+ <span class="inline-block w-2.5 h-2.5 rounded-full <%= config[:color] %> <%= config[:animate] ? 'animate-pulse' : '' %> flex-shrink-0"></span>
@@ -1,6 +1,6 @@
1
1
  <%
2
2
  # Workflow type badge with icon and color coding
3
- # Usage: render "ruby_llm/agents/shared/workflow_type_badge", workflow_type: "pipeline"
3
+ # Usage: render "ruby_llm/agents/shared/workflow_type_badge", workflow_type: "workflow"
4
4
  # Options:
5
5
  # size: :xs, :sm (default), :md
6
6
  # show_label: true (default) or false for icon-only mode
@@ -9,40 +9,14 @@
9
9
  size = local_assigns[:size] || :sm
10
10
  show_label = local_assigns.fetch(:show_label, true)
11
11
 
12
- config = case workflow_type.to_s
13
- when "pipeline"
14
- {
15
- icon: "arrow-right",
16
- label: "Pipeline",
17
- bg: "bg-indigo-100 dark:bg-indigo-900/50",
18
- text: "text-indigo-700 dark:text-indigo-300",
19
- icon_char: "→"
20
- }
21
- when "parallel"
22
- {
23
- icon: "parallel",
24
- label: "Parallel",
25
- bg: "bg-cyan-100 dark:bg-cyan-900/50",
26
- text: "text-cyan-700 dark:text-cyan-300",
27
- icon_char: "⫿"
28
- }
29
- when "router"
30
- {
31
- icon: "router",
32
- label: "Router",
33
- bg: "bg-amber-100 dark:bg-amber-900/50",
34
- text: "text-amber-700 dark:text-amber-300",
35
- icon_char: "⌂"
36
- }
37
- else
38
- {
39
- icon: "workflow",
40
- label: "Workflow",
41
- bg: "bg-gray-100 dark:bg-gray-700",
42
- text: "text-gray-700 dark:text-gray-300",
43
- icon_char: "⚙"
44
- }
45
- end
12
+ # All workflows use unified DSL workflow style
13
+ config = {
14
+ icon: "workflow",
15
+ label: "Workflow",
16
+ bg: "bg-emerald-100 dark:bg-emerald-900/50",
17
+ text: "text-emerald-700 dark:text-emerald-300",
18
+ icon_char: ""
19
+ }
46
20
 
47
21
  size_classes = case size
48
22
  when :xs
@@ -7,7 +7,10 @@
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">System Configuration</h1>
10
+ <div class="flex items-center gap-2">
11
+ <h1 class="text-2xl font-bold text-gray-900 dark:text-gray-100">System Configuration</h1>
12
+ <%= render "ruby_llm/agents/shared/doc_link" %>
13
+ </div>
11
14
  <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
15
  </div>
13
16
  </div>
@@ -1,5 +1,8 @@
1
1
  <div class="mb-6">
2
- <h1 class="text-2xl font-bold text-gray-900 dark:text-gray-100">Tenants</h1>
2
+ <div class="flex items-center gap-2">
3
+ <h1 class="text-2xl font-bold text-gray-900 dark:text-gray-100">Tenants</h1>
4
+ <%= render "ruby_llm/agents/shared/doc_link" %>
5
+ </div>
3
6
  <p class="text-gray-500 dark:text-gray-400 mt-1">Manage tenant budget configurations</p>
4
7
  </div>
5
8
 
@@ -1,28 +1,20 @@
1
1
  <%
2
- # Step/Branch/Route Performance Analytics
2
+ # Step Performance Analytics
3
3
  # Shows per-step metrics in a table format
4
4
  workflow_type = local_assigns[:workflow_type]
5
5
  step_stats = local_assigns[:step_stats] || []
6
6
  route_distribution = local_assigns[:route_distribution] || {}
7
7
 
8
- # Determine labels based on workflow type
9
- section_title = case workflow_type
10
- when "pipeline" then "Step Performance"
11
- when "parallel" then "Branch Performance"
12
- when "router" then "Route Distribution"
13
- else "Step Performance"
14
- end
15
-
16
- column_label = case workflow_type
17
- when "pipeline" then "Step"
18
- when "parallel" then "Branch"
19
- when "router" then "Route"
20
- else "Step"
21
- end
8
+ # Unified labels
9
+ section_title = "Step Performance"
10
+ column_label = "Step"
22
11
 
23
12
  # Calculate max values for bar charts
24
13
  max_duration = step_stats.map { |s| s[:avg_duration_ms] }.compact.max || 1
25
14
  max_cost = step_stats.map { |s| s[:avg_cost] }.compact.max || 0.001
15
+
16
+ # Check if any steps are parallel
17
+ has_parallel = step_stats.any? { |s| s[:name].to_s.include?("parallel") || workflow_type == "parallel" }
26
18
  %>
27
19
 
28
20
  <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6 mb-6">