ruby_llm-agents 0.4.0 → 1.0.0.beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +225 -34
- data/app/controllers/ruby_llm/agents/agents_controller.rb +136 -16
- data/app/controllers/ruby_llm/agents/api_configurations_controller.rb +214 -0
- data/app/controllers/ruby_llm/agents/dashboard_controller.rb +29 -9
- data/app/controllers/ruby_llm/agents/{settings_controller.rb → system_config_controller.rb} +3 -3
- data/app/controllers/ruby_llm/agents/tenants_controller.rb +109 -0
- data/app/controllers/ruby_llm/agents/workflows_controller.rb +355 -0
- data/app/helpers/ruby_llm/agents/application_helper.rb +25 -0
- data/app/models/ruby_llm/agents/api_configuration.rb +386 -0
- data/app/models/ruby_llm/agents/execution.rb +3 -0
- data/app/models/ruby_llm/agents/tenant_budget.rb +112 -14
- data/app/services/ruby_llm/agents/agent_registry.rb +51 -12
- data/app/views/layouts/ruby_llm/agents/application.html.erb +5 -30
- data/app/views/ruby_llm/agents/agents/_agent.html.erb +13 -1
- data/app/views/ruby_llm/agents/agents/_config_agent.html.erb +235 -0
- data/app/views/ruby_llm/agents/agents/_config_embedder.html.erb +70 -0
- data/app/views/ruby_llm/agents/agents/_config_image_generator.html.erb +152 -0
- data/app/views/ruby_llm/agents/agents/_config_moderator.html.erb +63 -0
- data/app/views/ruby_llm/agents/agents/_config_speaker.html.erb +108 -0
- data/app/views/ruby_llm/agents/agents/_config_transcriber.html.erb +91 -0
- data/app/views/ruby_llm/agents/agents/_workflow.html.erb +1 -1
- data/app/views/ruby_llm/agents/agents/index.html.erb +74 -9
- data/app/views/ruby_llm/agents/agents/show.html.erb +18 -378
- data/app/views/ruby_llm/agents/api_configurations/_api_key_field.html.erb +34 -0
- data/app/views/ruby_llm/agents/api_configurations/_form.html.erb +288 -0
- data/app/views/ruby_llm/agents/api_configurations/edit.html.erb +95 -0
- data/app/views/ruby_llm/agents/api_configurations/edit_tenant.html.erb +97 -0
- data/app/views/ruby_llm/agents/api_configurations/show.html.erb +211 -0
- data/app/views/ruby_llm/agents/api_configurations/tenant.html.erb +179 -0
- data/app/views/ruby_llm/agents/dashboard/_action_center.html.erb +1 -1
- data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +269 -15
- data/app/views/ruby_llm/agents/executions/show.html.erb +98 -0
- data/app/views/ruby_llm/agents/shared/_agent_type_badge.html.erb +93 -0
- data/app/views/ruby_llm/agents/{settings → system_config}/show.html.erb +1 -1
- data/app/views/ruby_llm/agents/tenants/_form.html.erb +150 -0
- data/app/views/ruby_llm/agents/tenants/edit.html.erb +13 -0
- data/app/views/ruby_llm/agents/tenants/index.html.erb +129 -0
- data/app/views/ruby_llm/agents/tenants/show.html.erb +374 -0
- data/app/views/ruby_llm/agents/workflows/_step_performance.html.erb +236 -0
- data/app/views/ruby_llm/agents/workflows/_structure_parallel.html.erb +76 -0
- data/app/views/ruby_llm/agents/workflows/_structure_pipeline.html.erb +74 -0
- data/app/views/ruby_llm/agents/workflows/_structure_router.html.erb +108 -0
- data/app/views/ruby_llm/agents/workflows/show.html.erb +442 -0
- data/config/routes.rb +13 -1
- data/lib/generators/ruby_llm_agents/agent_generator.rb +56 -7
- data/lib/generators/ruby_llm_agents/api_configuration_generator.rb +100 -0
- data/lib/generators/ruby_llm_agents/background_remover_generator.rb +110 -0
- data/lib/generators/ruby_llm_agents/embedder_generator.rb +107 -0
- data/lib/generators/ruby_llm_agents/image_analyzer_generator.rb +115 -0
- data/lib/generators/ruby_llm_agents/image_editor_generator.rb +108 -0
- data/lib/generators/ruby_llm_agents/image_generator_generator.rb +116 -0
- data/lib/generators/ruby_llm_agents/image_pipeline_generator.rb +178 -0
- data/lib/generators/ruby_llm_agents/image_transformer_generator.rb +109 -0
- data/lib/generators/ruby_llm_agents/image_upscaler_generator.rb +103 -0
- data/lib/generators/ruby_llm_agents/image_variator_generator.rb +102 -0
- data/lib/generators/ruby_llm_agents/install_generator.rb +76 -4
- data/lib/generators/ruby_llm_agents/restructure_generator.rb +292 -0
- data/lib/generators/ruby_llm_agents/speaker_generator.rb +121 -0
- data/lib/generators/ruby_llm_agents/templates/add_execution_type_migration.rb.tt +8 -0
- data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +99 -84
- data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +42 -40
- data/lib/generators/ruby_llm_agents/templates/application_background_remover.rb.tt +26 -0
- data/lib/generators/ruby_llm_agents/templates/application_embedder.rb.tt +50 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_analyzer.rb.tt +26 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_editor.rb.tt +20 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_generator.rb.tt +38 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_pipeline.rb.tt +139 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_transformer.rb.tt +21 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_upscaler.rb.tt +20 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_variator.rb.tt +20 -0
- data/lib/generators/ruby_llm_agents/templates/application_speaker.rb.tt +49 -0
- data/lib/generators/ruby_llm_agents/templates/application_transcriber.rb.tt +53 -0
- data/lib/generators/ruby_llm_agents/templates/background_remover.rb.tt +44 -0
- data/lib/generators/ruby_llm_agents/templates/create_api_configurations_migration.rb.tt +90 -0
- data/lib/generators/ruby_llm_agents/templates/embedder.rb.tt +41 -0
- data/lib/generators/ruby_llm_agents/templates/image_analyzer.rb.tt +45 -0
- data/lib/generators/ruby_llm_agents/templates/image_editor.rb.tt +35 -0
- data/lib/generators/ruby_llm_agents/templates/image_generator.rb.tt +47 -0
- data/lib/generators/ruby_llm_agents/templates/image_pipeline.rb.tt +50 -0
- data/lib/generators/ruby_llm_agents/templates/image_transformer.rb.tt +44 -0
- data/lib/generators/ruby_llm_agents/templates/image_upscaler.rb.tt +38 -0
- data/lib/generators/ruby_llm_agents/templates/image_variator.rb.tt +33 -0
- data/lib/generators/ruby_llm_agents/templates/skills/AGENTS.md.tt +228 -0
- data/lib/generators/ruby_llm_agents/templates/skills/BACKGROUND_REMOVERS.md.tt +131 -0
- data/lib/generators/ruby_llm_agents/templates/skills/EMBEDDERS.md.tt +255 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_ANALYZERS.md.tt +120 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_EDITORS.md.tt +102 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_GENERATORS.md.tt +282 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_PIPELINES.md.tt +228 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_TRANSFORMERS.md.tt +120 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_UPSCALERS.md.tt +110 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_VARIATORS.md.tt +120 -0
- data/lib/generators/ruby_llm_agents/templates/skills/SPEAKERS.md.tt +212 -0
- data/lib/generators/ruby_llm_agents/templates/skills/TOOLS.md.tt +227 -0
- data/lib/generators/ruby_llm_agents/templates/skills/TRANSCRIBERS.md.tt +251 -0
- data/lib/generators/ruby_llm_agents/templates/skills/WORKFLOWS.md.tt +300 -0
- data/lib/generators/ruby_llm_agents/templates/speaker.rb.tt +56 -0
- data/lib/generators/ruby_llm_agents/templates/transcriber.rb.tt +51 -0
- data/lib/generators/ruby_llm_agents/transcriber_generator.rb +107 -0
- data/lib/generators/ruby_llm_agents/upgrade_generator.rb +152 -1
- data/lib/ruby_llm/agents/audio/speaker.rb +553 -0
- data/lib/ruby_llm/agents/audio/transcriber.rb +669 -0
- data/lib/ruby_llm/agents/base_agent.rb +675 -0
- data/lib/ruby_llm/agents/core/base/moderation_dsl.rb +181 -0
- data/lib/ruby_llm/agents/core/base/moderation_execution.rb +274 -0
- data/lib/ruby_llm/agents/core/base.rb +135 -0
- data/lib/ruby_llm/agents/core/configuration.rb +981 -0
- data/lib/ruby_llm/agents/core/errors.rb +150 -0
- data/lib/ruby_llm/agents/{instrumentation.rb → core/instrumentation.rb} +93 -4
- data/lib/ruby_llm/agents/core/llm_tenant.rb +358 -0
- data/lib/ruby_llm/agents/core/resolved_config.rb +348 -0
- data/lib/ruby_llm/agents/{version.rb → core/version.rb} +1 -1
- data/lib/ruby_llm/agents/dsl/base.rb +110 -0
- data/lib/ruby_llm/agents/dsl/caching.rb +142 -0
- data/lib/ruby_llm/agents/dsl/reliability.rb +307 -0
- data/lib/ruby_llm/agents/dsl.rb +41 -0
- data/lib/ruby_llm/agents/image/analyzer/dsl.rb +130 -0
- data/lib/ruby_llm/agents/image/analyzer/execution.rb +402 -0
- data/lib/ruby_llm/agents/image/analyzer.rb +90 -0
- data/lib/ruby_llm/agents/image/background_remover/dsl.rb +154 -0
- data/lib/ruby_llm/agents/image/background_remover/execution.rb +240 -0
- data/lib/ruby_llm/agents/image/background_remover.rb +89 -0
- data/lib/ruby_llm/agents/image/concerns/image_operation_dsl.rb +91 -0
- data/lib/ruby_llm/agents/image/concerns/image_operation_execution.rb +165 -0
- data/lib/ruby_llm/agents/image/editor/dsl.rb +56 -0
- data/lib/ruby_llm/agents/image/editor/execution.rb +207 -0
- data/lib/ruby_llm/agents/image/editor.rb +92 -0
- data/lib/ruby_llm/agents/image/generator/active_storage_support.rb +127 -0
- data/lib/ruby_llm/agents/image/generator/content_policy.rb +95 -0
- data/lib/ruby_llm/agents/image/generator/pricing.rb +353 -0
- data/lib/ruby_llm/agents/image/generator/templates.rb +124 -0
- data/lib/ruby_llm/agents/image/generator.rb +455 -0
- data/lib/ruby_llm/agents/image/pipeline/dsl.rb +213 -0
- data/lib/ruby_llm/agents/image/pipeline/execution.rb +382 -0
- data/lib/ruby_llm/agents/image/pipeline.rb +97 -0
- data/lib/ruby_llm/agents/image/transformer/dsl.rb +148 -0
- data/lib/ruby_llm/agents/image/transformer/execution.rb +223 -0
- data/lib/ruby_llm/agents/image/transformer.rb +95 -0
- data/lib/ruby_llm/agents/image/upscaler/dsl.rb +83 -0
- data/lib/ruby_llm/agents/image/upscaler/execution.rb +219 -0
- data/lib/ruby_llm/agents/image/upscaler.rb +81 -0
- data/lib/ruby_llm/agents/image/variator/dsl.rb +62 -0
- data/lib/ruby_llm/agents/image/variator/execution.rb +189 -0
- data/lib/ruby_llm/agents/image/variator.rb +80 -0
- data/lib/ruby_llm/agents/{alert_manager.rb → infrastructure/alert_manager.rb} +17 -22
- data/lib/ruby_llm/agents/infrastructure/budget/budget_query.rb +145 -0
- data/lib/ruby_llm/agents/infrastructure/budget/config_resolver.rb +149 -0
- data/lib/ruby_llm/agents/infrastructure/budget/forecaster.rb +68 -0
- data/lib/ruby_llm/agents/infrastructure/budget/spend_recorder.rb +279 -0
- data/lib/ruby_llm/agents/infrastructure/budget_tracker.rb +275 -0
- data/lib/ruby_llm/agents/{execution_logger_job.rb → infrastructure/execution_logger_job.rb} +17 -1
- data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/executor.rb +2 -1
- data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/retry_strategy.rb +9 -3
- data/lib/ruby_llm/agents/{reliability.rb → infrastructure/reliability.rb} +11 -21
- data/lib/ruby_llm/agents/pipeline/builder.rb +215 -0
- data/lib/ruby_llm/agents/pipeline/context.rb +255 -0
- data/lib/ruby_llm/agents/pipeline/executor.rb +86 -0
- data/lib/ruby_llm/agents/pipeline/middleware/base.rb +124 -0
- data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +95 -0
- data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +171 -0
- data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +415 -0
- data/lib/ruby_llm/agents/pipeline/middleware/reliability.rb +276 -0
- data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +196 -0
- data/lib/ruby_llm/agents/pipeline.rb +68 -0
- data/lib/ruby_llm/agents/{engine.rb → rails/engine.rb} +79 -10
- data/lib/ruby_llm/agents/results/background_removal_result.rb +286 -0
- data/lib/ruby_llm/agents/{result.rb → results/base.rb} +73 -1
- data/lib/ruby_llm/agents/results/embedding_result.rb +243 -0
- data/lib/ruby_llm/agents/results/image_analysis_result.rb +314 -0
- data/lib/ruby_llm/agents/results/image_edit_result.rb +250 -0
- data/lib/ruby_llm/agents/results/image_generation_result.rb +346 -0
- data/lib/ruby_llm/agents/results/image_pipeline_result.rb +399 -0
- data/lib/ruby_llm/agents/results/image_transform_result.rb +251 -0
- data/lib/ruby_llm/agents/results/image_upscale_result.rb +255 -0
- data/lib/ruby_llm/agents/results/image_variation_result.rb +237 -0
- data/lib/ruby_llm/agents/results/moderation_result.rb +158 -0
- data/lib/ruby_llm/agents/results/speech_result.rb +338 -0
- data/lib/ruby_llm/agents/results/transcription_result.rb +408 -0
- data/lib/ruby_llm/agents/text/embedder.rb +444 -0
- data/lib/ruby_llm/agents/text/moderator.rb +237 -0
- data/lib/ruby_llm/agents/workflow/async.rb +220 -0
- data/lib/ruby_llm/agents/workflow/async_executor.rb +156 -0
- data/lib/ruby_llm/agents/{workflow.rb → workflow/orchestrator.rb} +6 -5
- data/lib/ruby_llm/agents/workflow/parallel.rb +34 -17
- data/lib/ruby_llm/agents/workflow/thread_pool.rb +185 -0
- data/lib/ruby_llm/agents.rb +86 -20
- metadata +189 -35
- data/lib/ruby_llm/agents/base/caching.rb +0 -40
- data/lib/ruby_llm/agents/base/cost_calculation.rb +0 -105
- data/lib/ruby_llm/agents/base/dsl.rb +0 -324
- data/lib/ruby_llm/agents/base/execution.rb +0 -283
- data/lib/ruby_llm/agents/base/reliability_dsl.rb +0 -82
- data/lib/ruby_llm/agents/base/reliability_execution.rb +0 -136
- data/lib/ruby_llm/agents/base/response_building.rb +0 -86
- data/lib/ruby_llm/agents/base/tool_tracking.rb +0 -57
- data/lib/ruby_llm/agents/base.rb +0 -209
- data/lib/ruby_llm/agents/budget_tracker.rb +0 -471
- data/lib/ruby_llm/agents/configuration.rb +0 -357
- /data/lib/ruby_llm/agents/{deprecations.rb → core/deprecations.rb} +0 -0
- /data/lib/ruby_llm/agents/{inflections.rb → core/inflections.rb} +0 -0
- /data/lib/ruby_llm/agents/{attempt_tracker.rb → infrastructure/attempt_tracker.rb} +0 -0
- /data/lib/ruby_llm/agents/{cache_helper.rb → infrastructure/cache_helper.rb} +0 -0
- /data/lib/ruby_llm/agents/{circuit_breaker.rb → infrastructure/circuit_breaker.rb} +0 -0
- /data/lib/ruby_llm/agents/{redactor.rb → infrastructure/redactor.rb} +0 -0
- /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/breaker_manager.rb +0 -0
- /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/execution_constraints.rb +0 -0
- /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/fallback_routing.rb +0 -0
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
<%= render "ruby_llm/agents/shared/breadcrumbs", items: [
|
|
2
|
+
{ label: "Dashboard", path: ruby_llm_agents.root_path },
|
|
3
|
+
{ label: "Tenants", path: ruby_llm_agents.tenants_path },
|
|
4
|
+
{ label: @tenant.display_name }
|
|
5
|
+
] %>
|
|
6
|
+
|
|
7
|
+
<!-- Header -->
|
|
8
|
+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6 mb-6">
|
|
9
|
+
<div class="flex items-start justify-between">
|
|
10
|
+
<div>
|
|
11
|
+
<div class="flex items-center space-x-3">
|
|
12
|
+
<div class="flex-shrink-0 h-10 w-10 rounded-full bg-blue-100 dark:bg-blue-900/50 flex items-center justify-center">
|
|
13
|
+
<svg class="w-5 h-5 text-blue-600 dark:text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
14
|
+
<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" />
|
|
15
|
+
</svg>
|
|
16
|
+
</div>
|
|
17
|
+
<div>
|
|
18
|
+
<h1 class="text-2xl font-bold text-gray-900 dark:text-gray-100">
|
|
19
|
+
<%= @tenant.display_name %>
|
|
20
|
+
</h1>
|
|
21
|
+
<% if @tenant.name.present? && @tenant.name != @tenant.tenant_id %>
|
|
22
|
+
<p class="text-sm text-gray-500 dark:text-gray-400 font-mono"><%= @tenant.tenant_id %></p>
|
|
23
|
+
<% end %>
|
|
24
|
+
</div>
|
|
25
|
+
<%
|
|
26
|
+
enforcement = @tenant.effective_enforcement.to_s
|
|
27
|
+
badge_class = case enforcement
|
|
28
|
+
when "hard" then "bg-red-100 dark:bg-red-900/50 text-red-800 dark:text-red-200"
|
|
29
|
+
when "soft" then "bg-yellow-100 dark:bg-yellow-900/50 text-yellow-800 dark:text-yellow-200"
|
|
30
|
+
else "bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400"
|
|
31
|
+
end
|
|
32
|
+
%>
|
|
33
|
+
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium <%= badge_class %>">
|
|
34
|
+
<%= enforcement.capitalize %> Enforcement
|
|
35
|
+
</span>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<div class="flex items-center space-x-3">
|
|
40
|
+
<%= link_to tenant_api_configuration_path(@tenant.tenant_id), class: "inline-flex items-center px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors" do %>
|
|
41
|
+
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
42
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" />
|
|
43
|
+
</svg>
|
|
44
|
+
API Keys
|
|
45
|
+
<% end %>
|
|
46
|
+
|
|
47
|
+
<%= link_to edit_tenant_path(@tenant), class: "inline-flex items-center px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors" do %>
|
|
48
|
+
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
49
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
|
50
|
+
</svg>
|
|
51
|
+
Edit
|
|
52
|
+
<% end %>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<!-- Budget Usage Stats -->
|
|
58
|
+
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
|
|
59
|
+
<!-- Daily Spend -->
|
|
60
|
+
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-4">
|
|
61
|
+
<div class="flex items-center justify-between mb-2">
|
|
62
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide font-medium">Daily Spend</p>
|
|
63
|
+
<span class="text-amber-500">
|
|
64
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
65
|
+
<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"/>
|
|
66
|
+
</svg>
|
|
67
|
+
</span>
|
|
68
|
+
</div>
|
|
69
|
+
<p class="text-xl font-semibold text-gray-900 dark:text-gray-100">
|
|
70
|
+
$<%= number_with_precision(@usage_stats[:daily_spend], precision: 4) %>
|
|
71
|
+
</p>
|
|
72
|
+
<% if @tenant.effective_daily_limit %>
|
|
73
|
+
<div class="mt-2">
|
|
74
|
+
<div class="flex items-center justify-between text-xs text-gray-500 dark:text-gray-400 mb-1">
|
|
75
|
+
<span>of $<%= number_with_precision(@tenant.effective_daily_limit, precision: 2) %></span>
|
|
76
|
+
<span><%= @usage_stats[:daily_spend_percentage] %>%</span>
|
|
77
|
+
</div>
|
|
78
|
+
<div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-1.5">
|
|
79
|
+
<%
|
|
80
|
+
pct = [@usage_stats[:daily_spend_percentage], 100].min
|
|
81
|
+
bar_color = pct >= 90 ? 'bg-red-500' : pct >= 70 ? 'bg-yellow-500' : 'bg-green-500'
|
|
82
|
+
%>
|
|
83
|
+
<div class="<%= bar_color %> h-1.5 rounded-full transition-all" style="width: <%= pct %>%"></div>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
<% else %>
|
|
87
|
+
<p class="text-xs text-gray-400 dark:text-gray-500 mt-1">No limit set</p>
|
|
88
|
+
<% end %>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<!-- Monthly Spend -->
|
|
92
|
+
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-4">
|
|
93
|
+
<div class="flex items-center justify-between mb-2">
|
|
94
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide font-medium">Monthly Spend</p>
|
|
95
|
+
<span class="text-amber-500">
|
|
96
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
97
|
+
<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"/>
|
|
98
|
+
</svg>
|
|
99
|
+
</span>
|
|
100
|
+
</div>
|
|
101
|
+
<p class="text-xl font-semibold text-gray-900 dark:text-gray-100">
|
|
102
|
+
$<%= number_with_precision(@usage_stats[:monthly_spend], precision: 4) %>
|
|
103
|
+
</p>
|
|
104
|
+
<% if @tenant.effective_monthly_limit %>
|
|
105
|
+
<div class="mt-2">
|
|
106
|
+
<div class="flex items-center justify-between text-xs text-gray-500 dark:text-gray-400 mb-1">
|
|
107
|
+
<span>of $<%= number_with_precision(@tenant.effective_monthly_limit, precision: 2) %></span>
|
|
108
|
+
<span><%= @usage_stats[:monthly_spend_percentage] %>%</span>
|
|
109
|
+
</div>
|
|
110
|
+
<div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-1.5">
|
|
111
|
+
<%
|
|
112
|
+
pct = [@usage_stats[:monthly_spend_percentage], 100].min
|
|
113
|
+
bar_color = pct >= 90 ? 'bg-red-500' : pct >= 70 ? 'bg-yellow-500' : 'bg-green-500'
|
|
114
|
+
%>
|
|
115
|
+
<div class="<%= bar_color %> h-1.5 rounded-full transition-all" style="width: <%= pct %>%"></div>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
<% else %>
|
|
119
|
+
<p class="text-xs text-gray-400 dark:text-gray-500 mt-1">No limit set</p>
|
|
120
|
+
<% end %>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<!-- Daily Tokens -->
|
|
124
|
+
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-4">
|
|
125
|
+
<div class="flex items-center justify-between mb-2">
|
|
126
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide font-medium">Daily Tokens</p>
|
|
127
|
+
<span class="text-indigo-500">
|
|
128
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
129
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 20l4-16m2 16l4-16M6 9h14M4 15h14"/>
|
|
130
|
+
</svg>
|
|
131
|
+
</span>
|
|
132
|
+
</div>
|
|
133
|
+
<p class="text-xl font-semibold text-gray-900 dark:text-gray-100">
|
|
134
|
+
<%= number_with_delimiter(@usage_stats[:daily_tokens]) %>
|
|
135
|
+
</p>
|
|
136
|
+
<% if @tenant.effective_daily_token_limit %>
|
|
137
|
+
<div class="mt-2">
|
|
138
|
+
<div class="flex items-center justify-between text-xs text-gray-500 dark:text-gray-400 mb-1">
|
|
139
|
+
<span>of <%= number_with_delimiter(@tenant.effective_daily_token_limit) %></span>
|
|
140
|
+
<span><%= @usage_stats[:daily_token_percentage] %>%</span>
|
|
141
|
+
</div>
|
|
142
|
+
<div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-1.5">
|
|
143
|
+
<%
|
|
144
|
+
pct = [@usage_stats[:daily_token_percentage], 100].min
|
|
145
|
+
bar_color = pct >= 90 ? 'bg-red-500' : pct >= 70 ? 'bg-yellow-500' : 'bg-green-500'
|
|
146
|
+
%>
|
|
147
|
+
<div class="<%= bar_color %> h-1.5 rounded-full transition-all" style="width: <%= pct %>%"></div>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
<% else %>
|
|
151
|
+
<p class="text-xs text-gray-400 dark:text-gray-500 mt-1">No limit set</p>
|
|
152
|
+
<% end %>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
<!-- Monthly Tokens -->
|
|
156
|
+
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-4">
|
|
157
|
+
<div class="flex items-center justify-between mb-2">
|
|
158
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide font-medium">Monthly Tokens</p>
|
|
159
|
+
<span class="text-indigo-500">
|
|
160
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
161
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 20l4-16m2 16l4-16M6 9h14M4 15h14"/>
|
|
162
|
+
</svg>
|
|
163
|
+
</span>
|
|
164
|
+
</div>
|
|
165
|
+
<p class="text-xl font-semibold text-gray-900 dark:text-gray-100">
|
|
166
|
+
<%= number_with_delimiter(@usage_stats[:monthly_tokens]) %>
|
|
167
|
+
</p>
|
|
168
|
+
<% if @tenant.effective_monthly_token_limit %>
|
|
169
|
+
<div class="mt-2">
|
|
170
|
+
<div class="flex items-center justify-between text-xs text-gray-500 dark:text-gray-400 mb-1">
|
|
171
|
+
<span>of <%= number_with_delimiter(@tenant.effective_monthly_token_limit) %></span>
|
|
172
|
+
<span><%= @usage_stats[:monthly_token_percentage] %>%</span>
|
|
173
|
+
</div>
|
|
174
|
+
<div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-1.5">
|
|
175
|
+
<%
|
|
176
|
+
pct = [@usage_stats[:monthly_token_percentage], 100].min
|
|
177
|
+
bar_color = pct >= 90 ? 'bg-red-500' : pct >= 70 ? 'bg-yellow-500' : 'bg-green-500'
|
|
178
|
+
%>
|
|
179
|
+
<div class="<%= bar_color %> h-1.5 rounded-full transition-all" style="width: <%= pct %>%"></div>
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
<% else %>
|
|
183
|
+
<p class="text-xs text-gray-400 dark:text-gray-500 mt-1">No limit set</p>
|
|
184
|
+
<% end %>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
<!-- Summary Stats -->
|
|
189
|
+
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
|
190
|
+
<%= render "ruby_llm/agents/shared/stat_card",
|
|
191
|
+
title: "Total Executions",
|
|
192
|
+
value: number_with_delimiter(@usage_stats[:total_executions]),
|
|
193
|
+
icon: "M13 10V3L4 14h7v7l9-11h-7z",
|
|
194
|
+
icon_color: "text-blue-500" %>
|
|
195
|
+
|
|
196
|
+
<%= render "ruby_llm/agents/shared/stat_card",
|
|
197
|
+
title: "Total Cost",
|
|
198
|
+
value: "$#{number_with_precision(@usage_stats[:total_cost], precision: 4)}",
|
|
199
|
+
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",
|
|
200
|
+
icon_color: "text-amber-500" %>
|
|
201
|
+
|
|
202
|
+
<%= render "ruby_llm/agents/shared/stat_card",
|
|
203
|
+
title: "Total Tokens",
|
|
204
|
+
value: number_with_delimiter(@usage_stats[:total_tokens]),
|
|
205
|
+
icon: "M7 20l4-16m2 16l4-16M6 9h14M4 15h14",
|
|
206
|
+
icon_color: "text-indigo-500" %>
|
|
207
|
+
</div>
|
|
208
|
+
|
|
209
|
+
<!-- Budget Configuration -->
|
|
210
|
+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6 mb-6">
|
|
211
|
+
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">Budget Configuration</h3>
|
|
212
|
+
|
|
213
|
+
<div class="grid grid-cols-2 md:grid-cols-4 gap-6">
|
|
214
|
+
<div>
|
|
215
|
+
<p class="text-sm text-gray-500 dark:text-gray-400">Daily Limit (USD)</p>
|
|
216
|
+
<p class="font-medium text-gray-900 dark:text-gray-100 mt-1">
|
|
217
|
+
<% if @tenant.effective_daily_limit %>
|
|
218
|
+
$<%= number_with_precision(@tenant.effective_daily_limit, precision: 2) %>
|
|
219
|
+
<% else %>
|
|
220
|
+
<span class="text-gray-400 dark:text-gray-500">Not set</span>
|
|
221
|
+
<% end %>
|
|
222
|
+
</p>
|
|
223
|
+
</div>
|
|
224
|
+
|
|
225
|
+
<div>
|
|
226
|
+
<p class="text-sm text-gray-500 dark:text-gray-400">Monthly Limit (USD)</p>
|
|
227
|
+
<p class="font-medium text-gray-900 dark:text-gray-100 mt-1">
|
|
228
|
+
<% if @tenant.effective_monthly_limit %>
|
|
229
|
+
$<%= number_with_precision(@tenant.effective_monthly_limit, precision: 2) %>
|
|
230
|
+
<% else %>
|
|
231
|
+
<span class="text-gray-400 dark:text-gray-500">Not set</span>
|
|
232
|
+
<% end %>
|
|
233
|
+
</p>
|
|
234
|
+
</div>
|
|
235
|
+
|
|
236
|
+
<div>
|
|
237
|
+
<p class="text-sm text-gray-500 dark:text-gray-400">Daily Token Limit</p>
|
|
238
|
+
<p class="font-medium text-gray-900 dark:text-gray-100 mt-1">
|
|
239
|
+
<% if @tenant.effective_daily_token_limit %>
|
|
240
|
+
<%= number_with_delimiter(@tenant.effective_daily_token_limit) %>
|
|
241
|
+
<% else %>
|
|
242
|
+
<span class="text-gray-400 dark:text-gray-500">Not set</span>
|
|
243
|
+
<% end %>
|
|
244
|
+
</p>
|
|
245
|
+
</div>
|
|
246
|
+
|
|
247
|
+
<div>
|
|
248
|
+
<p class="text-sm text-gray-500 dark:text-gray-400">Monthly Token Limit</p>
|
|
249
|
+
<p class="font-medium text-gray-900 dark:text-gray-100 mt-1">
|
|
250
|
+
<% if @tenant.effective_monthly_token_limit %>
|
|
251
|
+
<%= number_with_delimiter(@tenant.effective_monthly_token_limit) %>
|
|
252
|
+
<% else %>
|
|
253
|
+
<span class="text-gray-400 dark:text-gray-500">Not set</span>
|
|
254
|
+
<% end %>
|
|
255
|
+
</p>
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
|
|
259
|
+
<div class="border-t border-gray-100 dark:border-gray-700 mt-4 pt-4">
|
|
260
|
+
<div class="grid grid-cols-2 gap-6">
|
|
261
|
+
<div>
|
|
262
|
+
<p class="text-sm text-gray-500 dark:text-gray-400">Enforcement Mode</p>
|
|
263
|
+
<p class="font-medium text-gray-900 dark:text-gray-100 mt-1">
|
|
264
|
+
<%= @tenant.effective_enforcement.to_s.capitalize %>
|
|
265
|
+
<span class="text-xs text-gray-500 dark:text-gray-400 ml-2">
|
|
266
|
+
<% case @tenant.effective_enforcement.to_s %>
|
|
267
|
+
<% when "hard" %>
|
|
268
|
+
(blocks requests when limits exceeded)
|
|
269
|
+
<% when "soft" %>
|
|
270
|
+
(logs warnings when limits exceeded)
|
|
271
|
+
<% else %>
|
|
272
|
+
(no enforcement)
|
|
273
|
+
<% end %>
|
|
274
|
+
</span>
|
|
275
|
+
</p>
|
|
276
|
+
</div>
|
|
277
|
+
|
|
278
|
+
<div>
|
|
279
|
+
<p class="text-sm text-gray-500 dark:text-gray-400">Inherit Global Defaults</p>
|
|
280
|
+
<p class="font-medium text-gray-900 dark:text-gray-100 mt-1">
|
|
281
|
+
<%= @tenant.inherit_global_defaults ? "Yes" : "No" %>
|
|
282
|
+
</p>
|
|
283
|
+
</div>
|
|
284
|
+
</div>
|
|
285
|
+
</div>
|
|
286
|
+
|
|
287
|
+
<% if @tenant.per_agent_daily.present? || @tenant.per_agent_monthly.present? %>
|
|
288
|
+
<div class="border-t border-gray-100 dark:border-gray-700 mt-4 pt-4">
|
|
289
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-3">Per-Agent Limits</p>
|
|
290
|
+
|
|
291
|
+
<div class="space-y-2">
|
|
292
|
+
<% (@tenant.per_agent_daily || {}).each do |agent, limit| %>
|
|
293
|
+
<div class="flex items-center text-sm">
|
|
294
|
+
<code class="bg-gray-100 dark:bg-gray-700 dark:text-gray-200 px-2 py-0.5 rounded font-mono">
|
|
295
|
+
<%= agent %>
|
|
296
|
+
</code>
|
|
297
|
+
<span class="ml-2 text-gray-600 dark:text-gray-300">
|
|
298
|
+
Daily: $<%= number_with_precision(limit, precision: 2) %>
|
|
299
|
+
</span>
|
|
300
|
+
</div>
|
|
301
|
+
<% end %>
|
|
302
|
+
|
|
303
|
+
<% (@tenant.per_agent_monthly || {}).each do |agent, limit| %>
|
|
304
|
+
<div class="flex items-center text-sm">
|
|
305
|
+
<code class="bg-gray-100 dark:bg-gray-700 dark:text-gray-200 px-2 py-0.5 rounded font-mono">
|
|
306
|
+
<%= agent %>
|
|
307
|
+
</code>
|
|
308
|
+
<span class="ml-2 text-gray-600 dark:text-gray-300">
|
|
309
|
+
Monthly: $<%= number_with_precision(limit, precision: 2) %>
|
|
310
|
+
</span>
|
|
311
|
+
</div>
|
|
312
|
+
<% end %>
|
|
313
|
+
</div>
|
|
314
|
+
</div>
|
|
315
|
+
<% end %>
|
|
316
|
+
</div>
|
|
317
|
+
|
|
318
|
+
<!-- Recent Executions -->
|
|
319
|
+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
|
320
|
+
<div class="flex items-center justify-between mb-4">
|
|
321
|
+
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">Recent Executions</h3>
|
|
322
|
+
<%= link_to ruby_llm_agents.executions_path(tenant_id: @tenant.tenant_id), class: "text-sm text-blue-600 dark:text-blue-400 hover:underline" do %>
|
|
323
|
+
View all
|
|
324
|
+
<% end %>
|
|
325
|
+
</div>
|
|
326
|
+
|
|
327
|
+
<% if @executions.empty? %>
|
|
328
|
+
<p class="text-gray-500 dark:text-gray-400 text-sm">No executions found for this tenant.</p>
|
|
329
|
+
<% else %>
|
|
330
|
+
<div class="overflow-x-auto">
|
|
331
|
+
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
|
332
|
+
<thead>
|
|
333
|
+
<tr>
|
|
334
|
+
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Agent</th>
|
|
335
|
+
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Status</th>
|
|
336
|
+
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Cost</th>
|
|
337
|
+
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Tokens</th>
|
|
338
|
+
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Time</th>
|
|
339
|
+
</tr>
|
|
340
|
+
</thead>
|
|
341
|
+
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
|
342
|
+
<% @executions.each do |execution| %>
|
|
343
|
+
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50 cursor-pointer" data-href="<%= ruby_llm_agents.execution_path(execution) %>">
|
|
344
|
+
<td class="px-4 py-3 whitespace-nowrap">
|
|
345
|
+
<span class="text-sm font-medium text-gray-900 dark:text-gray-100"><%= execution.agent_type.gsub(/Agent$/, '') %></span>
|
|
346
|
+
</td>
|
|
347
|
+
<td class="px-4 py-3 whitespace-nowrap">
|
|
348
|
+
<%
|
|
349
|
+
status_class = case execution.status
|
|
350
|
+
when "success" then "badge-success"
|
|
351
|
+
when "error" then "badge-error"
|
|
352
|
+
when "running" then "badge-running"
|
|
353
|
+
when "timeout" then "badge-timeout"
|
|
354
|
+
else "bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400"
|
|
355
|
+
end
|
|
356
|
+
%>
|
|
357
|
+
<span class="badge <%= status_class %>"><%= execution.status %></span>
|
|
358
|
+
</td>
|
|
359
|
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
|
|
360
|
+
$<%= number_with_precision(execution.total_cost || 0, precision: 6) %>
|
|
361
|
+
</td>
|
|
362
|
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
|
|
363
|
+
<%= number_with_delimiter(execution.total_tokens || 0) %>
|
|
364
|
+
</td>
|
|
365
|
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
|
|
366
|
+
<%= time_ago_in_words(execution.created_at) %> ago
|
|
367
|
+
</td>
|
|
368
|
+
</tr>
|
|
369
|
+
<% end %>
|
|
370
|
+
</tbody>
|
|
371
|
+
</table>
|
|
372
|
+
</div>
|
|
373
|
+
<% end %>
|
|
374
|
+
</div>
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
<%
|
|
2
|
+
# Step/Branch/Route Performance Analytics
|
|
3
|
+
# Shows per-step metrics in a table format
|
|
4
|
+
workflow_type = local_assigns[:workflow_type]
|
|
5
|
+
step_stats = local_assigns[:step_stats] || []
|
|
6
|
+
route_distribution = local_assigns[:route_distribution] || {}
|
|
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
|
|
22
|
+
|
|
23
|
+
# Calculate max values for bar charts
|
|
24
|
+
max_duration = step_stats.map { |s| s[:avg_duration_ms] }.compact.max || 1
|
|
25
|
+
max_cost = step_stats.map { |s| s[:avg_cost] }.compact.max || 0.001
|
|
26
|
+
%>
|
|
27
|
+
|
|
28
|
+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6 mb-6">
|
|
29
|
+
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
|
|
30
|
+
<%= section_title %> <span class="text-sm font-normal text-gray-500 dark:text-gray-400">(last 30 days)</span>
|
|
31
|
+
</h3>
|
|
32
|
+
|
|
33
|
+
<% if workflow_type == "router" && route_distribution.present? %>
|
|
34
|
+
<!-- Route Distribution Bar Chart -->
|
|
35
|
+
<div class="mb-6">
|
|
36
|
+
<div class="space-y-3">
|
|
37
|
+
<% route_distribution.each do |route_name, data| %>
|
|
38
|
+
<div class="flex items-center gap-3">
|
|
39
|
+
<span class="text-sm font-medium text-gray-700 dark:text-gray-300 w-24 truncate" title="<%= route_name %>">
|
|
40
|
+
<%= route_name %>
|
|
41
|
+
</span>
|
|
42
|
+
<div class="flex-1 h-6 bg-gray-100 dark:bg-gray-700 rounded-lg overflow-hidden">
|
|
43
|
+
<div class="h-full bg-amber-500 dark:bg-amber-400 rounded-lg transition-all duration-300"
|
|
44
|
+
style="width: <%= data[:percentage] %>%">
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
<span class="text-sm text-gray-600 dark:text-gray-400 w-24 text-right">
|
|
48
|
+
<%= data[:count] %> (<%= data[:percentage] %>%)
|
|
49
|
+
</span>
|
|
50
|
+
</div>
|
|
51
|
+
<% end %>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
<% end %>
|
|
55
|
+
|
|
56
|
+
<% if step_stats.any? %>
|
|
57
|
+
<!-- Performance Table -->
|
|
58
|
+
<div class="overflow-x-auto">
|
|
59
|
+
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
|
60
|
+
<thead>
|
|
61
|
+
<tr>
|
|
62
|
+
<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
63
|
+
<%= column_label %>
|
|
64
|
+
</th>
|
|
65
|
+
<th scope="col" class="px-4 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
66
|
+
Executions
|
|
67
|
+
</th>
|
|
68
|
+
<th scope="col" class="px-4 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
69
|
+
Success
|
|
70
|
+
</th>
|
|
71
|
+
<th scope="col" class="px-4 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
72
|
+
Avg Duration
|
|
73
|
+
</th>
|
|
74
|
+
<th scope="col" class="px-4 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
75
|
+
Avg Cost
|
|
76
|
+
</th>
|
|
77
|
+
<th scope="col" class="px-4 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
78
|
+
Avg Tokens
|
|
79
|
+
</th>
|
|
80
|
+
<% if workflow_type == "parallel" %>
|
|
81
|
+
<th scope="col" class="px-4 py-3 text-center text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
82
|
+
Notes
|
|
83
|
+
</th>
|
|
84
|
+
<% end %>
|
|
85
|
+
</tr>
|
|
86
|
+
</thead>
|
|
87
|
+
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
|
|
88
|
+
<%
|
|
89
|
+
# For parallel workflows, find fastest/slowest
|
|
90
|
+
durations = step_stats.map { |s| s[:avg_duration_ms] }.compact
|
|
91
|
+
min_duration = durations.min
|
|
92
|
+
max_duration_val = durations.max
|
|
93
|
+
%>
|
|
94
|
+
|
|
95
|
+
<% step_stats.each do |stat| %>
|
|
96
|
+
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50">
|
|
97
|
+
<td class="px-4 py-3">
|
|
98
|
+
<div class="flex items-center gap-2">
|
|
99
|
+
<span class="font-medium text-gray-900 dark:text-gray-100">
|
|
100
|
+
<%= stat[:name] %>
|
|
101
|
+
</span>
|
|
102
|
+
<% if stat[:agent_type].present? %>
|
|
103
|
+
<span class="text-xs text-gray-400 dark:text-gray-500 font-mono">
|
|
104
|
+
(<%= stat[:agent_type].gsub(/Agent$/, '') %>)
|
|
105
|
+
</span>
|
|
106
|
+
<% end %>
|
|
107
|
+
</div>
|
|
108
|
+
</td>
|
|
109
|
+
<td class="px-4 py-3 text-right text-sm text-gray-600 dark:text-gray-300">
|
|
110
|
+
<%= number_with_delimiter(stat[:execution_count]) %>
|
|
111
|
+
</td>
|
|
112
|
+
<td class="px-4 py-3 text-right">
|
|
113
|
+
<% rate = stat[:success_rate] %>
|
|
114
|
+
<span class="text-sm font-medium <%= rate >= 95 ? 'text-green-600 dark:text-green-400' : rate >= 80 ? 'text-yellow-600 dark:text-yellow-400' : 'text-red-600 dark:text-red-400' %>">
|
|
115
|
+
<%= rate %>%
|
|
116
|
+
</span>
|
|
117
|
+
</td>
|
|
118
|
+
<td class="px-4 py-3 text-right">
|
|
119
|
+
<div class="flex items-center justify-end gap-2">
|
|
120
|
+
<div class="w-16 h-2 bg-gray-100 dark:bg-gray-700 rounded-full overflow-hidden">
|
|
121
|
+
<div class="h-full bg-purple-500 rounded-full"
|
|
122
|
+
style="width: <%= max_duration > 0 ? (stat[:avg_duration_ms].to_f / max_duration * 100).round : 0 %>%">
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
<span class="text-sm text-gray-600 dark:text-gray-300 w-20 text-right">
|
|
126
|
+
<%= number_with_delimiter(stat[:avg_duration_ms]) %> ms
|
|
127
|
+
</span>
|
|
128
|
+
</div>
|
|
129
|
+
</td>
|
|
130
|
+
<td class="px-4 py-3 text-right">
|
|
131
|
+
<div class="flex items-center justify-end gap-2">
|
|
132
|
+
<div class="w-12 h-2 bg-gray-100 dark:bg-gray-700 rounded-full overflow-hidden">
|
|
133
|
+
<div class="h-full bg-amber-500 rounded-full"
|
|
134
|
+
style="width: <%= max_cost > 0 ? (stat[:avg_cost].to_f / max_cost * 100).round : 0 %>%">
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
<span class="text-sm text-gray-600 dark:text-gray-300 w-16 text-right">
|
|
138
|
+
$<%= number_with_precision(stat[:avg_cost], precision: 4) %>
|
|
139
|
+
</span>
|
|
140
|
+
</div>
|
|
141
|
+
</td>
|
|
142
|
+
<td class="px-4 py-3 text-right text-sm text-gray-600 dark:text-gray-300">
|
|
143
|
+
<%= number_with_delimiter(stat[:avg_tokens]) %>
|
|
144
|
+
</td>
|
|
145
|
+
<% if workflow_type == "parallel" %>
|
|
146
|
+
<td class="px-4 py-3 text-center">
|
|
147
|
+
<% if step_stats.size > 1 %>
|
|
148
|
+
<% if stat[:avg_duration_ms] == min_duration %>
|
|
149
|
+
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 dark:bg-green-900/50 text-green-700 dark:text-green-300">
|
|
150
|
+
fastest
|
|
151
|
+
</span>
|
|
152
|
+
<% elsif stat[:avg_duration_ms] == max_duration_val && min_duration != max_duration_val %>
|
|
153
|
+
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-yellow-100 dark:bg-yellow-900/50 text-yellow-700 dark:text-yellow-300">
|
|
154
|
+
slowest
|
|
155
|
+
</span>
|
|
156
|
+
<% end %>
|
|
157
|
+
<% end %>
|
|
158
|
+
</td>
|
|
159
|
+
<% end %>
|
|
160
|
+
</tr>
|
|
161
|
+
<% end %>
|
|
162
|
+
|
|
163
|
+
<!-- Totals Row -->
|
|
164
|
+
<%
|
|
165
|
+
total_executions = step_stats.sum { |s| s[:execution_count] }
|
|
166
|
+
total_cost = step_stats.sum { |s| s[:total_cost] }
|
|
167
|
+
total_tokens = step_stats.sum { |s| s[:total_tokens] }
|
|
168
|
+
avg_duration_all = step_stats.any? ? (step_stats.sum { |s| s[:avg_duration_ms] * s[:execution_count] }.to_f / total_executions).round : 0
|
|
169
|
+
avg_cost_all = total_executions > 0 ? total_cost / total_executions : 0
|
|
170
|
+
avg_tokens_all = total_executions > 0 ? total_tokens / total_executions : 0
|
|
171
|
+
|
|
172
|
+
# Calculate overall success rate
|
|
173
|
+
total_success = step_stats.sum { |s| (s[:success_rate] * s[:execution_count] / 100.0).round }
|
|
174
|
+
overall_success_rate = total_executions > 0 ? (total_success.to_f / total_executions * 100).round(1) : 0
|
|
175
|
+
%>
|
|
176
|
+
<tr class="bg-gray-50 dark:bg-gray-700/50 font-medium">
|
|
177
|
+
<td class="px-4 py-3 text-gray-700 dark:text-gray-300">
|
|
178
|
+
Totals
|
|
179
|
+
</td>
|
|
180
|
+
<td class="px-4 py-3 text-right text-sm text-gray-700 dark:text-gray-300">
|
|
181
|
+
<%= number_with_delimiter(total_executions) %>
|
|
182
|
+
</td>
|
|
183
|
+
<td class="px-4 py-3 text-right">
|
|
184
|
+
<span class="text-sm <%= overall_success_rate >= 95 ? 'text-green-600 dark:text-green-400' : overall_success_rate >= 80 ? 'text-yellow-600 dark:text-yellow-400' : 'text-red-600 dark:text-red-400' %>">
|
|
185
|
+
<%= overall_success_rate %>%
|
|
186
|
+
</span>
|
|
187
|
+
</td>
|
|
188
|
+
<td class="px-4 py-3 text-right text-sm text-gray-700 dark:text-gray-300">
|
|
189
|
+
<%= number_with_delimiter(avg_duration_all) %> ms
|
|
190
|
+
</td>
|
|
191
|
+
<td class="px-4 py-3 text-right text-sm text-gray-700 dark:text-gray-300">
|
|
192
|
+
$<%= number_with_precision(avg_cost_all, precision: 4) %>
|
|
193
|
+
</td>
|
|
194
|
+
<td class="px-4 py-3 text-right text-sm text-gray-700 dark:text-gray-300">
|
|
195
|
+
<%= number_with_delimiter(avg_tokens_all.round) %>
|
|
196
|
+
</td>
|
|
197
|
+
<% if workflow_type == "parallel" %>
|
|
198
|
+
<td class="px-4 py-3"></td>
|
|
199
|
+
<% end %>
|
|
200
|
+
</tr>
|
|
201
|
+
</tbody>
|
|
202
|
+
</table>
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
<% if workflow_type == "parallel" && step_stats.size > 1 %>
|
|
206
|
+
<%
|
|
207
|
+
sum_duration = step_stats.sum { |s| s[:avg_duration_ms] }
|
|
208
|
+
wall_clock = step_stats.map { |s| s[:avg_duration_ms] }.max
|
|
209
|
+
time_saved = sum_duration - wall_clock
|
|
210
|
+
%>
|
|
211
|
+
<div class="mt-4 pt-4 border-t border-gray-100 dark:border-gray-700">
|
|
212
|
+
<p class="text-sm text-gray-500 dark:text-gray-400">
|
|
213
|
+
Wall-clock avg: <span class="font-medium text-gray-700 dark:text-gray-300"><%= number_with_delimiter(wall_clock) %> ms</span>
|
|
214
|
+
<span class="text-gray-400 dark:text-gray-500 mx-2">|</span>
|
|
215
|
+
Parallel saves <span class="font-medium text-green-600 dark:text-green-400"><%= number_with_delimiter(time_saved) %> ms</span> vs sequential
|
|
216
|
+
</p>
|
|
217
|
+
</div>
|
|
218
|
+
<% end %>
|
|
219
|
+
|
|
220
|
+
<% if workflow_type == "router" && route_distribution.present? %>
|
|
221
|
+
<%
|
|
222
|
+
# Calculate classification cost if we have stats
|
|
223
|
+
# This is approximate since we don't have exact classification costs
|
|
224
|
+
%>
|
|
225
|
+
<div class="mt-4 pt-4 border-t border-gray-100 dark:border-gray-700">
|
|
226
|
+
<p class="text-sm text-gray-500 dark:text-gray-400">
|
|
227
|
+
Route distribution based on <%= route_distribution.values.sum { |v| v[:count] } %> classified requests
|
|
228
|
+
</p>
|
|
229
|
+
</div>
|
|
230
|
+
<% end %>
|
|
231
|
+
<% else %>
|
|
232
|
+
<p class="text-gray-500 dark:text-gray-400 italic py-4">
|
|
233
|
+
No <%= column_label.downcase %> performance data available for the last 30 days.
|
|
234
|
+
</p>
|
|
235
|
+
<% end %>
|
|
236
|
+
</div>
|