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