ruby_llm-agents 0.3.4 → 0.3.6
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.
- checksums.yaml +4 -4
- data/README.md +132 -1263
- data/app/controllers/concerns/ruby_llm/agents/filterable.rb +5 -1
- data/app/controllers/concerns/ruby_llm/agents/paginatable.rb +2 -1
- data/app/controllers/ruby_llm/agents/agents_controller.rb +23 -3
- data/app/controllers/ruby_llm/agents/dashboard_controller.rb +80 -16
- data/app/controllers/ruby_llm/agents/executions_controller.rb +83 -5
- data/app/models/ruby_llm/agents/execution/analytics.rb +17 -27
- data/app/models/ruby_llm/agents/execution/scopes.rb +25 -0
- data/app/models/ruby_llm/agents/execution/workflow.rb +299 -0
- data/app/models/ruby_llm/agents/execution.rb +9 -1
- data/app/models/ruby_llm/agents/tenant_budget.rb +165 -0
- data/app/services/ruby_llm/agents/agent_registry.rb +119 -7
- data/app/views/layouts/{rubyllm → ruby_llm}/agents/application.html.erb +91 -29
- data/app/views/{rubyllm → ruby_llm}/agents/agents/_agent.html.erb +13 -2
- data/app/views/ruby_llm/agents/agents/_empty_state.html.erb +23 -0
- data/app/views/ruby_llm/agents/agents/_workflow.html.erb +136 -0
- data/app/views/ruby_llm/agents/agents/index.html.erb +93 -0
- data/app/views/{rubyllm → ruby_llm}/agents/agents/show.html.erb +82 -20
- data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +112 -0
- data/app/views/{rubyllm → ruby_llm}/agents/dashboard/_execution_item.html.erb +7 -4
- data/app/views/ruby_llm/agents/dashboard/_tenant_budget.html.erb +115 -0
- data/app/views/{rubyllm → ruby_llm}/agents/dashboard/index.html.erb +9 -6
- data/app/views/{rubyllm → ruby_llm}/agents/executions/_execution.html.erb +1 -1
- data/app/views/{rubyllm → ruby_llm}/agents/executions/_filters.html.erb +39 -11
- data/app/views/{rubyllm → ruby_llm}/agents/executions/_list.html.erb +19 -9
- data/app/views/ruby_llm/agents/executions/_workflow_summary.html.erb +101 -0
- data/app/views/ruby_llm/agents/executions/index.html.erb +88 -0
- data/app/views/{rubyllm → ruby_llm}/agents/executions/show.html.erb +137 -126
- data/app/views/{rubyllm → ruby_llm}/agents/shared/_breadcrumbs.html.erb +2 -2
- data/app/views/ruby_llm/agents/shared/_executions_table.html.erb +251 -0
- data/app/views/{rubyllm → ruby_llm}/agents/shared/_filter_dropdown.html.erb +1 -1
- data/app/views/{rubyllm → ruby_llm}/agents/shared/_select_dropdown.html.erb +1 -1
- data/app/views/{rubyllm → ruby_llm}/agents/shared/_status_badge.html.erb +1 -1
- data/app/views/{rubyllm → ruby_llm}/agents/shared/_status_dot.html.erb +1 -1
- data/app/views/ruby_llm/agents/shared/_tenant_filter.html.erb +26 -0
- data/app/views/ruby_llm/agents/shared/_workflow_type_badge.html.erb +61 -0
- data/lib/generators/ruby_llm_agents/agent_generator.rb +9 -4
- data/lib/generators/ruby_llm_agents/multi_tenancy_generator.rb +97 -0
- data/lib/generators/ruby_llm_agents/templates/add_attempts_migration.rb.tt +3 -3
- data/lib/generators/ruby_llm_agents/templates/add_tenant_to_executions_migration.rb.tt +23 -0
- data/lib/generators/ruby_llm_agents/templates/add_tool_calls_migration.rb.tt +2 -2
- data/lib/generators/ruby_llm_agents/templates/add_workflow_migration.rb.tt +38 -0
- data/lib/generators/ruby_llm_agents/templates/create_tenant_budgets_migration.rb.tt +45 -0
- data/lib/generators/ruby_llm_agents/templates/migration.rb.tt +17 -5
- data/lib/generators/ruby_llm_agents/upgrade_generator.rb +13 -0
- data/lib/ruby_llm/agents/alert_manager.rb +20 -16
- data/lib/ruby_llm/agents/base/caching.rb +4 -7
- data/lib/ruby_llm/agents/base/cost_calculation.rb +5 -3
- data/lib/ruby_llm/agents/base/dsl.rb +11 -0
- data/lib/ruby_llm/agents/base/execution.rb +62 -9
- data/lib/ruby_llm/agents/base/reliability_execution.rb +14 -9
- data/lib/ruby_llm/agents/base.rb +26 -0
- data/lib/ruby_llm/agents/budget_tracker.rb +250 -139
- data/lib/ruby_llm/agents/cache_helper.rb +98 -0
- data/lib/ruby_llm/agents/circuit_breaker.rb +48 -30
- data/lib/ruby_llm/agents/configuration.rb +40 -1
- data/lib/ruby_llm/agents/engine.rb +65 -1
- data/lib/ruby_llm/agents/inflections.rb +14 -0
- data/lib/ruby_llm/agents/instrumentation.rb +66 -0
- data/lib/ruby_llm/agents/reliability.rb +8 -2
- data/lib/ruby_llm/agents/version.rb +1 -1
- data/lib/ruby_llm/agents/workflow/instrumentation.rb +254 -0
- data/lib/ruby_llm/agents/workflow/parallel.rb +282 -0
- data/lib/ruby_llm/agents/workflow/pipeline.rb +306 -0
- data/lib/ruby_llm/agents/workflow/result.rb +390 -0
- data/lib/ruby_llm/agents/workflow/router.rb +429 -0
- data/lib/ruby_llm/agents/workflow.rb +248 -0
- data/lib/ruby_llm/agents.rb +1 -0
- metadata +50 -60
- data/app/views/rubyllm/agents/agents/index.html.erb +0 -20
- data/app/views/rubyllm/agents/dashboard/_agent_comparison.html.erb +0 -46
- data/app/views/rubyllm/agents/executions/index.html.erb +0 -28
- data/app/views/rubyllm/agents/executions/index.turbo_stream.erb +0 -18
- data/app/views/rubyllm/agents/shared/_executions_table.html.erb +0 -193
- /data/app/views/{rubyllm → ruby_llm}/agents/agents/_version_comparison.html.erb +0 -0
- /data/app/views/{rubyllm → ruby_llm}/agents/dashboard/_action_center.html.erb +0 -0
- /data/app/views/{rubyllm → ruby_llm}/agents/dashboard/_alerts_feed.html.erb +0 -0
- /data/app/views/{rubyllm → ruby_llm}/agents/dashboard/_breaker_strip.html.erb +0 -0
- /data/app/views/{rubyllm → ruby_llm}/agents/dashboard/_budgets_bar.html.erb +0 -0
- /data/app/views/{rubyllm → ruby_llm}/agents/dashboard/_now_strip.html.erb +0 -0
- /data/app/views/{rubyllm → ruby_llm}/agents/dashboard/_top_errors.html.erb +0 -0
- /data/app/views/{rubyllm → ruby_llm}/agents/executions/dry_run.html.erb +0 -0
- /data/app/views/{rubyllm → ruby_llm}/agents/settings/show.html.erb +0 -0
- /data/app/views/{rubyllm → ruby_llm}/agents/shared/_nav_link.html.erb +0 -0
- /data/app/views/{rubyllm → ruby_llm}/agents/shared/_stat_card.html.erb +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<%= render "
|
|
1
|
+
<%= render "ruby_llm/agents/shared/breadcrumbs", items: [
|
|
2
2
|
{ label: "Dashboard", path: ruby_llm_agents.root_path },
|
|
3
3
|
{ label: "Agents", path: ruby_llm_agents.agents_path },
|
|
4
4
|
{ label: @agent_type.gsub(/Agent$/, '') }
|
|
@@ -45,6 +45,11 @@
|
|
|
45
45
|
<%= @config[:model] %> · temp <%= @config[:temperature] %>
|
|
46
46
|
· timeout <%= @config[:timeout] %>s
|
|
47
47
|
</p>
|
|
48
|
+
<% if @config[:description].present? %>
|
|
49
|
+
<p class="text-gray-600 dark:text-gray-300 mt-2">
|
|
50
|
+
<%= @config[:description] %>
|
|
51
|
+
</p>
|
|
52
|
+
<% end %>
|
|
48
53
|
<% end %>
|
|
49
54
|
</div>
|
|
50
55
|
|
|
@@ -153,14 +158,14 @@
|
|
|
153
158
|
<% success_rate_color = success_rate >= 95 ? 'text-green-600' : success_rate >= 80 ? 'text-yellow-600' : 'text-red-600' %>
|
|
154
159
|
|
|
155
160
|
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4 mb-6">
|
|
156
|
-
<%= render "
|
|
161
|
+
<%= render "ruby_llm/agents/shared/stat_card",
|
|
157
162
|
title: "Executions",
|
|
158
163
|
value: number_with_delimiter(@stats[:count]),
|
|
159
164
|
subtitle: "Today: #{@stats_today[:count]}",
|
|
160
165
|
icon: "M13 10V3L4 14h7v7l9-11h-7z",
|
|
161
166
|
icon_color: "text-blue-500" %>
|
|
162
167
|
|
|
163
|
-
<%= render "
|
|
168
|
+
<%= render "ruby_llm/agents/shared/stat_card",
|
|
164
169
|
title: "Success Rate",
|
|
165
170
|
value: "#{success_rate}%",
|
|
166
171
|
subtitle: "Error rate: #{@stats[:error_rate] || 0}%",
|
|
@@ -168,27 +173,27 @@
|
|
|
168
173
|
icon_color: "text-green-500",
|
|
169
174
|
value_color: success_rate_color %>
|
|
170
175
|
|
|
171
|
-
<%= render "
|
|
176
|
+
<%= render "ruby_llm/agents/shared/stat_card",
|
|
172
177
|
title: "Total Cost",
|
|
173
178
|
value: "$#{number_with_precision(@stats[:total_cost] || 0, precision: 4)}",
|
|
174
179
|
subtitle: "Avg: $#{number_with_precision(@stats[:avg_cost] || 0, precision: 6)}",
|
|
175
180
|
icon: "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",
|
|
176
181
|
icon_color: "text-amber-500" %>
|
|
177
182
|
|
|
178
|
-
<%= render "
|
|
183
|
+
<%= render "ruby_llm/agents/shared/stat_card",
|
|
179
184
|
title: "Total Tokens",
|
|
180
185
|
value: number_with_delimiter(@stats[:total_tokens] || 0),
|
|
181
186
|
subtitle: "Avg: #{number_with_delimiter(@stats[:avg_tokens] || 0)}",
|
|
182
187
|
icon: "M7 20l4-16m2 16l4-16M6 9h14M4 15h14",
|
|
183
188
|
icon_color: "text-indigo-500" %>
|
|
184
189
|
|
|
185
|
-
<%= render "
|
|
190
|
+
<%= render "ruby_llm/agents/shared/stat_card",
|
|
186
191
|
title: "Avg Duration",
|
|
187
192
|
value: "#{number_with_delimiter(@stats[:avg_duration_ms] || 0)} ms",
|
|
188
193
|
icon: "M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z",
|
|
189
194
|
icon_color: "text-purple-500" %>
|
|
190
195
|
|
|
191
|
-
<%= render "
|
|
196
|
+
<%= render "ruby_llm/agents/shared/stat_card",
|
|
192
197
|
title: "Cache Hit Rate",
|
|
193
198
|
value: "#{@cache_hit_rate}%",
|
|
194
199
|
icon: "M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4",
|
|
@@ -284,7 +289,7 @@
|
|
|
284
289
|
<% end %>
|
|
285
290
|
|
|
286
291
|
<!-- Version Comparison -->
|
|
287
|
-
<%= render partial: "
|
|
292
|
+
<%= render partial: "ruby_llm/agents/agents/version_comparison",
|
|
288
293
|
locals: { versions: @versions, version_comparison: @version_comparison } %>
|
|
289
294
|
|
|
290
295
|
<% if @config %>
|
|
@@ -341,8 +346,9 @@
|
|
|
341
346
|
|
|
342
347
|
<!-- Reliability Configuration -->
|
|
343
348
|
<% retries_config = @config[:retries] || {}
|
|
349
|
+
fallback_models = Array(@config[:fallback_models]).compact
|
|
344
350
|
has_retries = (retries_config[:max] || 0) > 0
|
|
345
|
-
has_fallbacks =
|
|
351
|
+
has_fallbacks = fallback_models.any?
|
|
346
352
|
has_total_timeout = @config[:total_timeout].present?
|
|
347
353
|
has_circuit_breaker = @config[:circuit_breaker].present?
|
|
348
354
|
has_any_reliability = has_retries || has_fallbacks || has_total_timeout || has_circuit_breaker %>
|
|
@@ -446,7 +452,7 @@
|
|
|
446
452
|
|
|
447
453
|
<% if has_fallbacks %>
|
|
448
454
|
<p class="text-xs text-gray-600 dark:text-gray-400 mt-1">
|
|
449
|
-
<%=
|
|
455
|
+
<%= fallback_models.join(" → ") %>
|
|
450
456
|
</p>
|
|
451
457
|
<% else %>
|
|
452
458
|
<p class="text-xs text-gray-400 dark:text-gray-500 mt-1">
|
|
@@ -601,6 +607,63 @@
|
|
|
601
607
|
</div>
|
|
602
608
|
</div>
|
|
603
609
|
<% end %>
|
|
610
|
+
|
|
611
|
+
<!-- Available Tools -->
|
|
612
|
+
<%
|
|
613
|
+
class_tools = @agent_class.respond_to?(:tools) ? (@agent_class.tools || []) : []
|
|
614
|
+
has_dynamic_tools = @agent_class.instance_methods(false).include?(:tools)
|
|
615
|
+
%>
|
|
616
|
+
<div class="border-t border-gray-100 dark:border-gray-700 pt-4">
|
|
617
|
+
<p
|
|
618
|
+
class="
|
|
619
|
+
text-xs text-gray-500 dark:text-gray-400 uppercase
|
|
620
|
+
tracking-wider mb-3
|
|
621
|
+
"
|
|
622
|
+
>
|
|
623
|
+
Available Tools
|
|
624
|
+
<% if class_tools.any? %>
|
|
625
|
+
<span class="inline-flex items-center ml-2 px-1.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 dark:bg-blue-900/50 text-blue-800 dark:text-blue-300">
|
|
626
|
+
<%= class_tools.size %>
|
|
627
|
+
</span>
|
|
628
|
+
<% elsif has_dynamic_tools %>
|
|
629
|
+
<span class="inline-flex items-center ml-2 px-1.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 dark:bg-purple-900/50 text-purple-800 dark:text-purple-300">
|
|
630
|
+
Dynamic
|
|
631
|
+
</span>
|
|
632
|
+
<% else %>
|
|
633
|
+
<span class="inline-flex items-center ml-2 px-1.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400">
|
|
634
|
+
0
|
|
635
|
+
</span>
|
|
636
|
+
<% end %>
|
|
637
|
+
</p>
|
|
638
|
+
|
|
639
|
+
<% if class_tools.any? %>
|
|
640
|
+
<div class="space-y-2">
|
|
641
|
+
<% class_tools.each do |tool_class| %>
|
|
642
|
+
<div class="flex items-center text-sm">
|
|
643
|
+
<code
|
|
644
|
+
class="
|
|
645
|
+
bg-gray-100 dark:bg-gray-700 dark:text-gray-200 px-2
|
|
646
|
+
py-0.5 rounded font-mono
|
|
647
|
+
"
|
|
648
|
+
>
|
|
649
|
+
<%= tool_class.respond_to?(:tool_name) ? tool_class.tool_name : tool_class.name.demodulize %>
|
|
650
|
+
</code>
|
|
651
|
+
</div>
|
|
652
|
+
<% end %>
|
|
653
|
+
</div>
|
|
654
|
+
<% elsif has_dynamic_tools %>
|
|
655
|
+
<p class="text-sm text-purple-600 dark:text-purple-400">
|
|
656
|
+
This agent configures tools dynamically at runtime based on context.
|
|
657
|
+
</p>
|
|
658
|
+
<p class="text-xs text-gray-400 dark:text-gray-500 mt-1">
|
|
659
|
+
Tools vary per execution based on feature flags and configuration.
|
|
660
|
+
</p>
|
|
661
|
+
<% else %>
|
|
662
|
+
<p class="text-sm text-gray-400 dark:text-gray-500 italic">
|
|
663
|
+
No tools configured for this agent.
|
|
664
|
+
</p>
|
|
665
|
+
<% end %>
|
|
666
|
+
</div>
|
|
604
667
|
</div>
|
|
605
668
|
<% end %>
|
|
606
669
|
|
|
@@ -610,7 +673,7 @@
|
|
|
610
673
|
Executions
|
|
611
674
|
</h3>
|
|
612
675
|
|
|
613
|
-
|
|
676
|
+
<div id="executions_table">
|
|
614
677
|
<%
|
|
615
678
|
has_filters = params[:statuses].present? || params[:versions].present? || params[:models].present? || params[:temperatures].present? || params[:days].present?
|
|
616
679
|
selected_statuses = params[:statuses].present? ? (params[:statuses].is_a?(Array) ? params[:statuses] : params[:statuses].split(",")) : []
|
|
@@ -635,10 +698,10 @@
|
|
|
635
698
|
]
|
|
636
699
|
%>
|
|
637
700
|
|
|
638
|
-
<%= form_with url: ruby_llm_agents.agent_path(@agent_type), method: :get,
|
|
701
|
+
<%= form_with url: ruby_llm_agents.agent_path(@agent_type), method: :get, local: true do |f| %>
|
|
639
702
|
<div class="flex flex-wrap items-center gap-3 mb-4 pb-4 border-b border-gray-100 dark:border-gray-700">
|
|
640
703
|
<%# Status Filter (Multi-select) %>
|
|
641
|
-
<%= render "
|
|
704
|
+
<%= render "ruby_llm/agents/shared/filter_dropdown",
|
|
642
705
|
name: "statuses[]",
|
|
643
706
|
filter_id: "statuses",
|
|
644
707
|
label: "Status",
|
|
@@ -648,7 +711,7 @@
|
|
|
648
711
|
|
|
649
712
|
<%# Version Filter (Multi-select) %>
|
|
650
713
|
<% if @versions.any? %>
|
|
651
|
-
<%= render "
|
|
714
|
+
<%= render "ruby_llm/agents/shared/filter_dropdown",
|
|
652
715
|
name: "versions[]",
|
|
653
716
|
filter_id: "versions",
|
|
654
717
|
label: "Version",
|
|
@@ -660,7 +723,7 @@
|
|
|
660
723
|
|
|
661
724
|
<%# Model Filter (Multi-select) %>
|
|
662
725
|
<% if @models.length > 1 %>
|
|
663
|
-
<%= render "
|
|
726
|
+
<%= render "ruby_llm/agents/shared/filter_dropdown",
|
|
664
727
|
name: "models[]",
|
|
665
728
|
filter_id: "models",
|
|
666
729
|
label: "Model",
|
|
@@ -672,7 +735,7 @@
|
|
|
672
735
|
|
|
673
736
|
<%# Temperature Filter (Multi-select) %>
|
|
674
737
|
<% if @temperatures.length > 1 %>
|
|
675
|
-
<%= render "
|
|
738
|
+
<%= render "ruby_llm/agents/shared/filter_dropdown",
|
|
676
739
|
name: "temperatures[]",
|
|
677
740
|
filter_id: "temperatures",
|
|
678
741
|
label: "Temp",
|
|
@@ -683,7 +746,7 @@
|
|
|
683
746
|
<% end %>
|
|
684
747
|
|
|
685
748
|
<%# Time Range Filter (Single-select) %>
|
|
686
|
-
<%= render "
|
|
749
|
+
<%= render "ruby_llm/agents/shared/select_dropdown",
|
|
687
750
|
name: "days",
|
|
688
751
|
filter_id: "days",
|
|
689
752
|
options: days_options,
|
|
@@ -693,7 +756,6 @@
|
|
|
693
756
|
<%# Clear Filters %>
|
|
694
757
|
<% if has_filters %>
|
|
695
758
|
<%= link_to ruby_llm_agents.agent_path(@agent_type),
|
|
696
|
-
data: { turbo_frame: "executions_table" },
|
|
697
759
|
class: "flex items-center gap-1 px-3 py-2 text-sm text-red-500 dark:text-red-400 hover:text-red-600 dark:hover:text-red-300 hover:bg-red-50 dark:hover:bg-red-900/30 rounded-lg transition-colors" do %>
|
|
698
760
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
699
761
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
|
@@ -713,6 +775,6 @@
|
|
|
713
775
|
</div>
|
|
714
776
|
<% end %>
|
|
715
777
|
|
|
716
|
-
<%= render "
|
|
717
|
-
|
|
778
|
+
<%= render "ruby_llm/agents/shared/executions_table", executions: @executions, pagination: @pagination %>
|
|
779
|
+
</div>
|
|
718
780
|
</div>
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 h-full" x-data="{ activeTab: 'agents' }">
|
|
2
|
+
<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">Performance</h3>
|
|
5
|
+
<!-- Tab Buttons -->
|
|
6
|
+
<div class="flex space-x-1 bg-gray-100 dark:bg-gray-700 rounded-lg p-0.5">
|
|
7
|
+
<button type="button" @click="activeTab = 'agents'"
|
|
8
|
+
:class="activeTab === 'agents' ? 'bg-white dark:bg-gray-600 shadow-sm' : 'hover:bg-gray-50 dark:hover:bg-gray-600'"
|
|
9
|
+
class="px-3 py-1 text-xs font-medium rounded-md transition-colors text-gray-700 dark:text-gray-200">
|
|
10
|
+
Agents
|
|
11
|
+
</button>
|
|
12
|
+
<button type="button" @click="activeTab = 'workflows'"
|
|
13
|
+
:class="activeTab === 'workflows' ? 'bg-white dark:bg-gray-600 shadow-sm' : 'hover:bg-gray-50 dark:hover:bg-gray-600'"
|
|
14
|
+
class="px-3 py-1 text-xs font-medium rounded-md transition-colors text-gray-700 dark:text-gray-200">
|
|
15
|
+
Workflows
|
|
16
|
+
</button>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<!-- Agents Tab -->
|
|
22
|
+
<div x-show="activeTab === 'agents'">
|
|
23
|
+
<% if agent_stats&.any? %>
|
|
24
|
+
<div class="overflow-x-auto">
|
|
25
|
+
<table class="w-full text-sm">
|
|
26
|
+
<thead>
|
|
27
|
+
<tr class="text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
28
|
+
<th class="px-4 py-2">Agent</th>
|
|
29
|
+
<th class="px-4 py-2 text-right">Runs</th>
|
|
30
|
+
<th class="px-4 py-2 text-right">Cost</th>
|
|
31
|
+
<th class="px-4 py-2 text-right">Success</th>
|
|
32
|
+
</tr>
|
|
33
|
+
</thead>
|
|
34
|
+
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
|
|
35
|
+
<% agent_stats.first(5).each do |agent| %>
|
|
36
|
+
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50">
|
|
37
|
+
<td class="px-4 py-2">
|
|
38
|
+
<span class="text-sm font-medium text-gray-900 dark:text-gray-100 truncate block max-w-[120px]" title="<%= agent[:agent_type] %>">
|
|
39
|
+
<%= agent[:agent_type].to_s.demodulize %>
|
|
40
|
+
</span>
|
|
41
|
+
</td>
|
|
42
|
+
<td class="px-4 py-2 text-right text-gray-600 dark:text-gray-300">
|
|
43
|
+
<%= number_with_delimiter(agent[:executions]) %>
|
|
44
|
+
</td>
|
|
45
|
+
<td class="px-4 py-2 text-right text-gray-600 dark:text-gray-300">
|
|
46
|
+
$<%= number_with_precision(agent[:total_cost], precision: 2) %>
|
|
47
|
+
</td>
|
|
48
|
+
<td class="px-4 py-2 text-right">
|
|
49
|
+
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium <%= agent[:success_rate] >= 95 ? 'bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-300' : agent[: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' %>">
|
|
50
|
+
<%= agent[:success_rate].round %>%
|
|
51
|
+
</span>
|
|
52
|
+
</td>
|
|
53
|
+
</tr>
|
|
54
|
+
<% end %>
|
|
55
|
+
</tbody>
|
|
56
|
+
</table>
|
|
57
|
+
</div>
|
|
58
|
+
<% else %>
|
|
59
|
+
<div class="px-4 py-8 text-center text-gray-500 dark:text-gray-400">
|
|
60
|
+
<p class="text-sm">No agent data yet</p>
|
|
61
|
+
</div>
|
|
62
|
+
<% end %>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<!-- Workflows Tab -->
|
|
66
|
+
<div x-show="activeTab === 'workflows'" x-cloak>
|
|
67
|
+
<% if @workflow_stats&.any? %>
|
|
68
|
+
<div class="overflow-x-auto">
|
|
69
|
+
<table class="w-full text-sm">
|
|
70
|
+
<thead>
|
|
71
|
+
<tr class="text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
72
|
+
<th class="px-4 py-2">Workflow</th>
|
|
73
|
+
<th class="px-4 py-2">Type</th>
|
|
74
|
+
<th class="px-4 py-2 text-right">Runs</th>
|
|
75
|
+
<th class="px-4 py-2 text-right">Cost</th>
|
|
76
|
+
<th class="px-4 py-2 text-right">Success</th>
|
|
77
|
+
</tr>
|
|
78
|
+
</thead>
|
|
79
|
+
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
|
|
80
|
+
<% @workflow_stats.first(5).each do |workflow| %>
|
|
81
|
+
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50">
|
|
82
|
+
<td class="px-4 py-2">
|
|
83
|
+
<span class="text-sm font-medium text-gray-900 dark:text-gray-100 truncate block max-w-[100px]" title="<%= workflow[:agent_type] %>">
|
|
84
|
+
<%= workflow[:agent_type].to_s.demodulize.gsub(/Workflow$|Pipeline$|Parallel$|Router$/, '') %>
|
|
85
|
+
</span>
|
|
86
|
+
</td>
|
|
87
|
+
<td class="px-4 py-2">
|
|
88
|
+
<%= render "ruby_llm/agents/shared/workflow_type_badge", workflow_type: workflow[:workflow_type], size: :xs, show_label: false %>
|
|
89
|
+
</td>
|
|
90
|
+
<td class="px-4 py-2 text-right text-gray-600 dark:text-gray-300">
|
|
91
|
+
<%= number_with_delimiter(workflow[:executions]) %>
|
|
92
|
+
</td>
|
|
93
|
+
<td class="px-4 py-2 text-right text-gray-600 dark:text-gray-300">
|
|
94
|
+
$<%= number_with_precision(workflow[:total_cost], precision: 2) %>
|
|
95
|
+
</td>
|
|
96
|
+
<td class="px-4 py-2 text-right">
|
|
97
|
+
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium <%= workflow[:success_rate] >= 95 ? 'bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-300' : workflow[: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' %>">
|
|
98
|
+
<%= workflow[:success_rate].round %>%
|
|
99
|
+
</span>
|
|
100
|
+
</td>
|
|
101
|
+
</tr>
|
|
102
|
+
<% end %>
|
|
103
|
+
</tbody>
|
|
104
|
+
</table>
|
|
105
|
+
</div>
|
|
106
|
+
<% else %>
|
|
107
|
+
<div class="px-4 py-8 text-center text-gray-500 dark:text-gray-400">
|
|
108
|
+
<p class="text-sm">No workflow data yet</p>
|
|
109
|
+
</div>
|
|
110
|
+
<% end %>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
<div id="execution-<%= execution.id %>" class="py-2.5 hover:bg-gray-50 dark:hover:bg-gray-700/50 -mx-2 px-2 rounded-lg transition-colors">
|
|
2
|
-
<%= link_to ruby_llm_agents.execution_path(execution),
|
|
2
|
+
<%= link_to ruby_llm_agents.execution_path(execution), class: "block" do %>
|
|
3
3
|
<!-- Row 1: Status dot + Agent + Badges + Timestamp -->
|
|
4
4
|
<div class="flex items-center justify-between gap-2">
|
|
5
5
|
<div class="flex items-center gap-2 min-w-0">
|
|
6
|
-
<%= render "
|
|
6
|
+
<%= render "ruby_llm/agents/shared/status_dot", status: execution.status %>
|
|
7
|
+
<% if execution.respond_to?(:workflow_type) && execution.workflow_type.present? %>
|
|
8
|
+
<%= render "ruby_llm/agents/shared/workflow_type_badge", workflow_type: execution.workflow_type, size: :xs, show_label: false %>
|
|
9
|
+
<% end %>
|
|
7
10
|
<span class="font-medium text-sm text-gray-900 dark:text-gray-100 truncate">
|
|
8
|
-
<%= execution.agent_type.gsub(/Agent$/, '') %>
|
|
11
|
+
<%= execution.agent_type.gsub(/Agent$|Workflow$|Pipeline$|Parallel$|Router$/, '') %>
|
|
9
12
|
</span>
|
|
10
13
|
<span class="hidden sm:inline text-xs text-gray-400 dark:text-gray-500">v<%= execution.agent_version %></span>
|
|
11
14
|
<% unless execution.status_running? %>
|
|
@@ -45,7 +48,7 @@
|
|
|
45
48
|
$<%= number_with_precision(execution.total_cost || 0, precision: 4) %>
|
|
46
49
|
<span class="mx-1.5 text-gray-300 dark:text-gray-600">·</span>
|
|
47
50
|
<%= number_to_human_short(execution.duration_ms || 0) %>ms
|
|
48
|
-
<% if execution.attempts_count && execution.attempts_count > 1 %>
|
|
51
|
+
<% if execution.respond_to?(:attempts_count) && execution.attempts_count && execution.attempts_count > 1 %>
|
|
49
52
|
<span class="mx-1.5 text-gray-300 dark:text-gray-600">·</span>
|
|
50
53
|
<span class="text-amber-600 dark:text-amber-400"><%= execution.attempts_count %> retries</span>
|
|
51
54
|
<% end %>
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
<%# Tenant Budget Widget - shows budget limits and usage for current tenant %>
|
|
2
|
+
<%# @param tenant_budget [Hash] Budget data from controller %>
|
|
3
|
+
|
|
4
|
+
<% if tenant_budget.present? %>
|
|
5
|
+
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 mb-6">
|
|
6
|
+
<div class="px-6 py-3 border-b border-gray-100 dark:border-gray-700">
|
|
7
|
+
<div class="flex items-center justify-between">
|
|
8
|
+
<div class="flex items-center gap-2">
|
|
9
|
+
<svg class="w-5 h-5 text-blue-600 dark:text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
10
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
|
|
11
|
+
</svg>
|
|
12
|
+
<h3 class="text-base font-semibold text-gray-900 dark:text-gray-100">
|
|
13
|
+
Tenant Budget: <%= tenant_budget[:tenant_id] %>
|
|
14
|
+
</h3>
|
|
15
|
+
</div>
|
|
16
|
+
<%# Enforcement badge %>
|
|
17
|
+
<% enforcement = tenant_budget[:enforcement].to_s %>
|
|
18
|
+
<% badge_class = case enforcement
|
|
19
|
+
when "hard" then "bg-red-100 dark:bg-red-900/50 text-red-700 dark:text-red-300"
|
|
20
|
+
when "soft" then "bg-yellow-100 dark:bg-yellow-900/50 text-yellow-700 dark:text-yellow-300"
|
|
21
|
+
else "bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400"
|
|
22
|
+
end %>
|
|
23
|
+
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium <%= badge_class %>">
|
|
24
|
+
<%= enforcement.capitalize %> Enforcement
|
|
25
|
+
</span>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<div class="p-6">
|
|
30
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
31
|
+
<%# Daily Budget %>
|
|
32
|
+
<div>
|
|
33
|
+
<div class="flex items-center justify-between mb-2">
|
|
34
|
+
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">Daily Budget</span>
|
|
35
|
+
<span class="text-sm text-gray-500 dark:text-gray-400">
|
|
36
|
+
$<%= number_with_precision(tenant_budget[:daily_spend], precision: 2) %>
|
|
37
|
+
<% if tenant_budget[:daily_limit] %>
|
|
38
|
+
/ $<%= number_with_precision(tenant_budget[:daily_limit], precision: 2) %>
|
|
39
|
+
<% else %>
|
|
40
|
+
(no limit)
|
|
41
|
+
<% end %>
|
|
42
|
+
</span>
|
|
43
|
+
</div>
|
|
44
|
+
<% if tenant_budget[:daily_limit] %>
|
|
45
|
+
<% daily_pct = [tenant_budget[:daily_percentage], 100].min %>
|
|
46
|
+
<% bar_color = if daily_pct >= 100
|
|
47
|
+
"bg-red-500"
|
|
48
|
+
elsif daily_pct >= 80
|
|
49
|
+
"bg-yellow-500"
|
|
50
|
+
else
|
|
51
|
+
"bg-green-500"
|
|
52
|
+
end %>
|
|
53
|
+
<div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2.5">
|
|
54
|
+
<div class="<%= bar_color %> h-2.5 rounded-full transition-all duration-300" style="width: <%= daily_pct %>%"></div>
|
|
55
|
+
</div>
|
|
56
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1"><%= tenant_budget[:daily_percentage] %>% used</p>
|
|
57
|
+
<% else %>
|
|
58
|
+
<div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2.5">
|
|
59
|
+
<div class="bg-gray-400 h-2.5 rounded-full" style="width: 0%"></div>
|
|
60
|
+
</div>
|
|
61
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">No daily limit configured</p>
|
|
62
|
+
<% end %>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<%# Monthly Budget %>
|
|
66
|
+
<div>
|
|
67
|
+
<div class="flex items-center justify-between mb-2">
|
|
68
|
+
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">Monthly Budget</span>
|
|
69
|
+
<span class="text-sm text-gray-500 dark:text-gray-400">
|
|
70
|
+
$<%= number_with_precision(tenant_budget[:monthly_spend], precision: 2) %>
|
|
71
|
+
<% if tenant_budget[:monthly_limit] %>
|
|
72
|
+
/ $<%= number_with_precision(tenant_budget[:monthly_limit], precision: 2) %>
|
|
73
|
+
<% else %>
|
|
74
|
+
(no limit)
|
|
75
|
+
<% end %>
|
|
76
|
+
</span>
|
|
77
|
+
</div>
|
|
78
|
+
<% if tenant_budget[:monthly_limit] %>
|
|
79
|
+
<% monthly_pct = [tenant_budget[:monthly_percentage], 100].min %>
|
|
80
|
+
<% bar_color = if monthly_pct >= 100
|
|
81
|
+
"bg-red-500"
|
|
82
|
+
elsif monthly_pct >= 80
|
|
83
|
+
"bg-yellow-500"
|
|
84
|
+
else
|
|
85
|
+
"bg-blue-500"
|
|
86
|
+
end %>
|
|
87
|
+
<div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2.5">
|
|
88
|
+
<div class="<%= bar_color %> h-2.5 rounded-full transition-all duration-300" style="width: <%= monthly_pct %>%"></div>
|
|
89
|
+
</div>
|
|
90
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1"><%= tenant_budget[:monthly_percentage] %>% used</p>
|
|
91
|
+
<% else %>
|
|
92
|
+
<div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2.5">
|
|
93
|
+
<div class="bg-gray-400 h-2.5 rounded-full" style="width: 0%"></div>
|
|
94
|
+
</div>
|
|
95
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">No monthly limit configured</p>
|
|
96
|
+
<% end %>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<%# Per-agent limits if configured %>
|
|
101
|
+
<% if tenant_budget[:per_agent_daily].present? %>
|
|
102
|
+
<div class="mt-4 pt-4 border-t border-gray-100 dark:border-gray-700">
|
|
103
|
+
<p class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide mb-2">Per-Agent Daily Limits</p>
|
|
104
|
+
<div class="flex flex-wrap gap-2">
|
|
105
|
+
<% tenant_budget[:per_agent_daily].each do |agent, limit| %>
|
|
106
|
+
<span class="inline-flex items-center px-2 py-1 rounded text-xs bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300">
|
|
107
|
+
<%= agent.to_s.gsub(/Agent$/, "") %>: $<%= number_with_precision(limit, precision: 2) %>
|
|
108
|
+
</span>
|
|
109
|
+
<% end %>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
<% end %>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
<% end %>
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
<!-- Action Center (only when critical alerts exist) -->
|
|
2
|
-
<%= render partial: "
|
|
2
|
+
<%= render partial: "ruby_llm/agents/dashboard/action_center", locals: { critical_alerts: @critical_alerts } %>
|
|
3
3
|
|
|
4
4
|
<!-- Now Strip -->
|
|
5
|
-
<%= render partial: "
|
|
5
|
+
<%= render partial: "ruby_llm/agents/dashboard/now_strip", locals: { now_strip: @now_strip } %>
|
|
6
|
+
|
|
7
|
+
<!-- Tenant Budget (when viewing specific tenant) -->
|
|
8
|
+
<%= render partial: "ruby_llm/agents/dashboard/tenant_budget", locals: { tenant_budget: @tenant_budget } %>
|
|
6
9
|
|
|
7
10
|
<!-- Activity Chart -->
|
|
8
11
|
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 mb-6">
|
|
@@ -129,13 +132,13 @@
|
|
|
129
132
|
<div class="px-6 py-3 border-b border-gray-100 dark:border-gray-700">
|
|
130
133
|
<div class="flex justify-between items-center">
|
|
131
134
|
<h3 class="text-base font-semibold text-gray-900 dark:text-gray-100">Recent Activity</h3>
|
|
132
|
-
<%= link_to "View All", ruby_llm_agents.executions_path,
|
|
135
|
+
<%= link_to "View All", ruby_llm_agents.executions_path, class: "text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 text-sm font-medium" %>
|
|
133
136
|
</div>
|
|
134
137
|
</div>
|
|
135
138
|
<div id="activity-feed" class="px-6 py-2">
|
|
136
139
|
<% if @recent_executions.any? %>
|
|
137
140
|
<% @recent_executions.first(5).each do |execution| %>
|
|
138
|
-
<%= render partial: "
|
|
141
|
+
<%= render partial: "ruby_llm/agents/dashboard/execution_item", locals: { execution: execution } %>
|
|
139
142
|
<% end %>
|
|
140
143
|
<% else %>
|
|
141
144
|
<div class="py-8 text-center text-gray-500 dark:text-gray-400">
|
|
@@ -147,6 +150,6 @@
|
|
|
147
150
|
|
|
148
151
|
<!-- Agent Comparison + Top Errors -->
|
|
149
152
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
150
|
-
<%= render partial: "
|
|
151
|
-
<%= render partial: "
|
|
153
|
+
<%= render partial: "ruby_llm/agents/dashboard/agent_comparison", locals: { agent_stats: @agent_stats } %>
|
|
154
|
+
<%= render partial: "ruby_llm/agents/dashboard/top_errors", locals: { top_errors: @top_errors } %>
|
|
152
155
|
</div>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<%= link_to ruby_llm_agents.execution_path(execution),
|
|
1
|
+
<%= link_to ruby_llm_agents.execution_path(execution), class: "block px-6 py-4 hover:bg-gray-50 transition-colors" do %>
|
|
2
2
|
<div class="flex items-center justify-between">
|
|
3
3
|
<div class="flex items-center space-x-4">
|
|
4
4
|
<!-- Status Badge -->
|