ruby_llm-agents 1.3.3 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +101 -334
- 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 +46 -10
- 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 +87 -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 +528 -989
- 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 +9 -14
- 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 +9 -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 +58 -262
- 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 +52 -6
- 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 +58 -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/attempt_tracker.rb +1 -0
- 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/reliability.rb +37 -2
- 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 +12 -81
- 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
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
{
|
|
15
15
|
icon: "robot",
|
|
16
16
|
label: "Agent",
|
|
17
|
-
bg: "bg-blue-100 dark:bg-blue-
|
|
17
|
+
bg: "bg-blue-100 dark:bg-blue-500/20",
|
|
18
18
|
text: "text-blue-700 dark:text-blue-300",
|
|
19
19
|
icon_char: "🤖"
|
|
20
20
|
}
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
{
|
|
23
23
|
icon: "chart",
|
|
24
24
|
label: "Embedder",
|
|
25
|
-
bg: "bg-purple-100 dark:bg-purple-
|
|
25
|
+
bg: "bg-purple-100 dark:bg-purple-500/20",
|
|
26
26
|
text: "text-purple-700 dark:text-purple-300",
|
|
27
27
|
icon_char: "📊"
|
|
28
28
|
}
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
{
|
|
31
31
|
icon: "mic",
|
|
32
32
|
label: "Transcriber",
|
|
33
|
-
bg: "bg-green-100 dark:bg-green-
|
|
33
|
+
bg: "bg-green-100 dark:bg-green-500/20",
|
|
34
34
|
text: "text-green-700 dark:text-green-300",
|
|
35
35
|
icon_char: "🎤"
|
|
36
36
|
}
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
{
|
|
39
39
|
icon: "speaker",
|
|
40
40
|
label: "Speaker",
|
|
41
|
-
bg: "bg-orange-100 dark:bg-orange-
|
|
41
|
+
bg: "bg-orange-100 dark:bg-orange-500/20",
|
|
42
42
|
text: "text-orange-700 dark:text-orange-300",
|
|
43
43
|
icon_char: "🔊"
|
|
44
44
|
}
|
|
@@ -46,26 +46,10 @@
|
|
|
46
46
|
{
|
|
47
47
|
icon: "palette",
|
|
48
48
|
label: "Image Gen",
|
|
49
|
-
bg: "bg-pink-100 dark:bg-pink-
|
|
49
|
+
bg: "bg-pink-100 dark:bg-pink-500/20",
|
|
50
50
|
text: "text-pink-700 dark:text-pink-300",
|
|
51
51
|
icon_char: "🎨"
|
|
52
52
|
}
|
|
53
|
-
when "moderator"
|
|
54
|
-
{
|
|
55
|
-
icon: "shield",
|
|
56
|
-
label: "Moderator",
|
|
57
|
-
bg: "bg-red-100 dark:bg-red-900/50",
|
|
58
|
-
text: "text-red-700 dark:text-red-300",
|
|
59
|
-
icon_char: "🛡️"
|
|
60
|
-
}
|
|
61
|
-
when "workflow"
|
|
62
|
-
{
|
|
63
|
-
icon: "gear",
|
|
64
|
-
label: "Workflow",
|
|
65
|
-
bg: "bg-indigo-100 dark:bg-indigo-900/50",
|
|
66
|
-
text: "text-indigo-700 dark:text-indigo-300",
|
|
67
|
-
icon_char: "⚙️"
|
|
68
|
-
}
|
|
69
53
|
else
|
|
70
54
|
{
|
|
71
55
|
icon: "question",
|
|
@@ -1,182 +1,66 @@
|
|
|
1
1
|
<%# Show tenant column when multi-tenancy is enabled and no specific tenant is selected %>
|
|
2
2
|
<% show_tenant_column = tenant_filter_enabled? && current_tenant_id.blank? %>
|
|
3
|
+
<% show_agent_column = !local_assigns.fetch(:hide_agent_column, false) %>
|
|
3
4
|
|
|
4
5
|
<% if executions.empty? %>
|
|
5
|
-
<
|
|
6
|
+
<div class="font-mono text-xs text-gray-400 dark:text-gray-600 py-4 px-2">no executions found</div>
|
|
6
7
|
<% else %>
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
</thead>
|
|
23
|
-
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-100 dark:divide-gray-700">
|
|
24
|
-
<% executions.each do |execution| %>
|
|
25
|
-
<%
|
|
26
|
-
is_workflow = execution.workflow_type.present?
|
|
27
|
-
children = execution.child_executions.sort_by(&:created_at)
|
|
28
|
-
has_children = children.any?
|
|
29
|
-
%>
|
|
30
|
-
|
|
31
|
-
<%# Parent/Main Row %>
|
|
32
|
-
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors">
|
|
33
|
-
<%# Status %>
|
|
34
|
-
<td class="px-4 py-3 whitespace-nowrap">
|
|
35
|
-
<%= render "ruby_llm/agents/shared/status_badge", status: execution.status, size: :sm %>
|
|
36
|
-
</td>
|
|
37
|
-
|
|
38
|
-
<%# Agent Name with Workflow Badge %>
|
|
39
|
-
<td class="px-4 py-3 whitespace-nowrap">
|
|
40
|
-
<div class="flex items-center gap-2">
|
|
41
|
-
<%= link_to ruby_llm_agents.execution_path(execution), class: "font-medium text-gray-900 dark:text-gray-100 hover:text-blue-600 dark:hover:text-blue-400" do %>
|
|
42
|
-
<%= execution.agent_type.gsub(/Agent$/, "") %>
|
|
43
|
-
<% end %>
|
|
44
|
-
<% if is_workflow %>
|
|
45
|
-
<span class="text-xs text-emerald-600 dark:text-emerald-400">◈</span>
|
|
46
|
-
<% end %>
|
|
47
|
-
</div>
|
|
48
|
-
</td>
|
|
49
|
-
|
|
50
|
-
<%# Tenant (only when viewing all tenants) %>
|
|
51
|
-
<% if show_tenant_column %>
|
|
52
|
-
<td class="px-4 py-3 whitespace-nowrap">
|
|
53
|
-
<% if execution.tenant_id.present? %>
|
|
54
|
-
<a href="<%= url_for(request.query_parameters.merge(tenant_id: execution.tenant_id)) %>"
|
|
55
|
-
class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-blue-50 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 hover:bg-blue-100 dark:hover:bg-blue-900/50">
|
|
56
|
-
<%= truncate(execution.tenant_id, length: 15) %>
|
|
57
|
-
</a>
|
|
58
|
-
<% else %>
|
|
59
|
-
<span class="text-gray-400 dark:text-gray-500 text-xs">—</span>
|
|
60
|
-
<% end %>
|
|
61
|
-
</td>
|
|
62
|
-
<% end %>
|
|
63
|
-
|
|
64
|
-
<%# Model %>
|
|
65
|
-
<td class="px-4 py-3 whitespace-nowrap text-gray-500 dark:text-gray-400 font-mono text-xs">
|
|
66
|
-
<%= execution.model_id %>
|
|
67
|
-
</td>
|
|
68
|
-
|
|
69
|
-
<%# Duration %>
|
|
70
|
-
<td class="px-4 py-3 whitespace-nowrap text-right text-gray-900 dark:text-gray-100 tabular-nums">
|
|
71
|
-
<%= execution.duration_ms ? "#{number_with_delimiter(execution.duration_ms)}ms" : "-" %>
|
|
72
|
-
</td>
|
|
73
|
-
|
|
74
|
-
<%# Tokens %>
|
|
75
|
-
<td class="px-4 py-3 whitespace-nowrap text-right text-gray-900 dark:text-gray-100 tabular-nums">
|
|
76
|
-
<%= number_with_delimiter(execution.total_tokens || 0) %>
|
|
77
|
-
</td>
|
|
78
|
-
|
|
79
|
-
<%# Cost %>
|
|
80
|
-
<td class="px-4 py-3 whitespace-nowrap text-right text-gray-900 dark:text-gray-100 tabular-nums">
|
|
81
|
-
$<%= number_with_precision(execution.total_cost || 0, precision: 4) %>
|
|
82
|
-
</td>
|
|
83
|
-
|
|
84
|
-
<%# Time %>
|
|
85
|
-
<td class="px-4 py-3 whitespace-nowrap text-right text-gray-500 dark:text-gray-400">
|
|
86
|
-
<%= time_ago_in_words(execution.created_at) %> ago
|
|
87
|
-
</td>
|
|
88
|
-
</tr>
|
|
89
|
-
|
|
90
|
-
<%# Error Row (if applicable) %>
|
|
91
|
-
<% if execution.status_error? && execution.error_message.present? %>
|
|
92
|
-
<tr class="bg-red-50/50 dark:bg-red-900/20">
|
|
93
|
-
<td></td>
|
|
94
|
-
<td colspan="<%= show_tenant_column ? 7 : 6 %>" class="px-4 py-2">
|
|
95
|
-
<p class="text-xs text-red-600 dark:text-red-400">
|
|
96
|
-
<span class="font-medium"><%= execution.error_class %>:</span>
|
|
97
|
-
<%= truncate(execution.error_message, length: 120) %>
|
|
98
|
-
</p>
|
|
99
|
-
</td>
|
|
100
|
-
</tr>
|
|
101
|
-
<% end %>
|
|
102
|
-
|
|
103
|
-
<%# Child Rows (for workflows) %>
|
|
104
|
-
<% if has_children %>
|
|
105
|
-
<% children.each_with_index do |child, index| %>
|
|
106
|
-
<% is_last = index == children.size - 1 %>
|
|
107
|
-
<tr class="bg-gray-50/50 dark:bg-gray-900/30 hover:bg-gray-100/50 dark:hover:bg-gray-800/50 transition-colors">
|
|
108
|
-
<%# Status with tree line %>
|
|
109
|
-
<td class="px-4 py-2 whitespace-nowrap">
|
|
110
|
-
<div class="flex items-center">
|
|
111
|
-
<span class="text-gray-300 dark:text-gray-600 mr-2 font-mono text-xs"><%= is_last ? "└─" : "├─" %></span>
|
|
112
|
-
<% case child.status
|
|
113
|
-
when "success" %>
|
|
114
|
-
<span class="text-green-600 dark:text-green-400">✓</span>
|
|
115
|
-
<% when "error" %>
|
|
116
|
-
<span class="text-red-600 dark:text-red-400">✗</span>
|
|
117
|
-
<% when "timeout" %>
|
|
118
|
-
<span class="text-orange-600 dark:text-orange-400">⏱</span>
|
|
119
|
-
<% when "running" %>
|
|
120
|
-
<span class="text-blue-600 dark:text-blue-400 animate-pulse">●</span>
|
|
121
|
-
<% else %>
|
|
122
|
-
<span class="text-gray-400">○</span>
|
|
123
|
-
<% end %>
|
|
124
|
-
</div>
|
|
125
|
-
</td>
|
|
126
|
-
|
|
127
|
-
<%# Step Name %>
|
|
128
|
-
<td class="px-4 py-2 whitespace-nowrap">
|
|
129
|
-
<%= link_to ruby_llm_agents.execution_path(child), class: "text-gray-600 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400" do %>
|
|
130
|
-
<span class="text-gray-400 dark:text-gray-500 text-xs"><%= index + 1 %>.</span>
|
|
131
|
-
<%= child.workflow_step || child.agent_type.gsub(/Agent$/, "") %>
|
|
132
|
-
<% end %>
|
|
133
|
-
</td>
|
|
134
|
-
|
|
135
|
-
<%# Tenant - empty for child rows %>
|
|
136
|
-
<% if show_tenant_column %>
|
|
137
|
-
<td class="px-4 py-2"></td>
|
|
138
|
-
<% end %>
|
|
139
|
-
|
|
140
|
-
<%# Model %>
|
|
141
|
-
<td class="px-4 py-2 whitespace-nowrap text-gray-400 dark:text-gray-500 font-mono text-xs">
|
|
142
|
-
<%= child.model_id %>
|
|
143
|
-
</td>
|
|
144
|
-
|
|
145
|
-
<%# Duration %>
|
|
146
|
-
<td class="px-4 py-2 whitespace-nowrap text-right text-gray-600 dark:text-gray-300 tabular-nums text-xs">
|
|
147
|
-
<%= child.duration_ms ? "#{number_with_delimiter(child.duration_ms)}ms" : "-" %>
|
|
148
|
-
</td>
|
|
149
|
-
|
|
150
|
-
<%# Tokens %>
|
|
151
|
-
<td class="px-4 py-2 whitespace-nowrap text-right text-gray-600 dark:text-gray-300 tabular-nums text-xs">
|
|
152
|
-
<%= number_with_delimiter(child.total_tokens || 0) %>
|
|
153
|
-
</td>
|
|
154
|
-
|
|
155
|
-
<%# Cost %>
|
|
156
|
-
<td class="px-4 py-2 whitespace-nowrap text-right text-gray-600 dark:text-gray-300 tabular-nums text-xs">
|
|
157
|
-
$<%= number_with_precision(child.total_cost || 0, precision: 4) %>
|
|
158
|
-
</td>
|
|
159
|
-
|
|
160
|
-
<%# Time - empty for children %>
|
|
161
|
-
<td class="px-4 py-2"></td>
|
|
162
|
-
</tr>
|
|
8
|
+
<!-- Column headers -->
|
|
9
|
+
<div class="flex items-center gap-3 px-2 -mx-2 font-mono text-[10px] text-gray-400 dark:text-gray-600 uppercase tracking-wider mb-1">
|
|
10
|
+
<span class="w-20 flex-shrink-0">status</span>
|
|
11
|
+
<% if show_agent_column %>
|
|
12
|
+
<span class="flex-1 min-w-0">agent</span>
|
|
13
|
+
<% end %>
|
|
14
|
+
<% if show_tenant_column %>
|
|
15
|
+
<span class="flex-1 min-w-0 hidden sm:block">tenant</span>
|
|
16
|
+
<% end %>
|
|
17
|
+
<span class="flex-1 min-w-0 hidden lg:block">model</span>
|
|
18
|
+
<span class="w-16 flex-shrink-0 text-right">duration</span>
|
|
19
|
+
<span class="w-16 flex-shrink-0 text-right hidden md:block">tokens</span>
|
|
20
|
+
<span class="w-16 flex-shrink-0 text-right hidden md:block">cost</span>
|
|
21
|
+
<span class="w-24 flex-shrink-0 text-right">time</span>
|
|
22
|
+
</div>
|
|
163
23
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
24
|
+
<!-- Rows -->
|
|
25
|
+
<div class="font-mono text-xs space-y-px">
|
|
26
|
+
<% executions.each do |execution| %>
|
|
27
|
+
<%
|
|
28
|
+
status_badge_class = case execution.status.to_s
|
|
29
|
+
when "running" then "badge-running"
|
|
30
|
+
when "success" then "badge-success"
|
|
31
|
+
when "error" then "badge-error"
|
|
32
|
+
when "timeout" then "badge-timeout"
|
|
33
|
+
else "badge-timeout"
|
|
34
|
+
end
|
|
35
|
+
%>
|
|
36
|
+
<div class="group flex items-center gap-3 py-1.5 px-2 -mx-2 rounded hover:bg-gray-100 dark:hover:bg-gray-800/50 cursor-pointer"
|
|
37
|
+
onclick="window.location='<%= ruby_llm_agents.execution_path(execution) %>'">
|
|
38
|
+
<span class="w-20 flex-shrink-0"><span class="badge badge-sm <%= status_badge_class %>"><%= execution.status %></span></span>
|
|
39
|
+
<% if show_agent_column %>
|
|
40
|
+
<span class="flex-1 min-w-0 truncate text-gray-900 dark:text-gray-200"><%= execution.agent_type.gsub(/Agent$/, "") %></span>
|
|
41
|
+
<% end %>
|
|
42
|
+
<% if show_tenant_column %>
|
|
43
|
+
<span class="flex-1 min-w-0 truncate text-gray-400 dark:text-gray-600 hidden sm:inline"><%= execution.tenant_id.present? ? truncate(execution.tenant_id, length: 30) : "—" %></span>
|
|
177
44
|
<% end %>
|
|
178
|
-
|
|
179
|
-
|
|
45
|
+
<span class="flex-1 min-w-0 truncate text-gray-400 dark:text-gray-600 hidden lg:inline"><%= execution.model_id %></span>
|
|
46
|
+
<span class="w-16 flex-shrink-0 text-right text-gray-500 dark:text-gray-400">
|
|
47
|
+
<% if execution.status_running? %>
|
|
48
|
+
<span class="text-blue-500 animate-pulse">...</span>
|
|
49
|
+
<% else %>
|
|
50
|
+
<%= execution.duration_ms ? format_duration_ms(execution.duration_ms) : "—" %>
|
|
51
|
+
<% end %>
|
|
52
|
+
</span>
|
|
53
|
+
<span class="w-16 flex-shrink-0 text-right text-gray-500 dark:text-gray-400 hidden md:inline"><%= number_with_delimiter(execution.total_tokens || 0) %></span>
|
|
54
|
+
<span class="w-16 flex-shrink-0 text-right text-gray-500 dark:text-gray-400 hidden md:inline">$<%= number_with_precision(execution.total_cost || 0, precision: 4) %></span>
|
|
55
|
+
<span class="w-24 flex-shrink-0 text-gray-400 dark:text-gray-600 text-right whitespace-nowrap"><%= time_ago_in_words(execution.created_at) %></span>
|
|
56
|
+
</div>
|
|
57
|
+
<% if execution.status_error? && execution.error_message.present? %>
|
|
58
|
+
<div class="flex items-center gap-1 pl-5 py-0.5 text-red-400 dark:text-red-500/70 text-xs font-mono">
|
|
59
|
+
<span class="text-gray-300 dark:text-gray-700">└</span>
|
|
60
|
+
<span class="truncate"><%= execution.error_class %>: <%= truncate(execution.error_message, length: 100) %></span>
|
|
61
|
+
</div>
|
|
62
|
+
<% end %>
|
|
63
|
+
<% end %>
|
|
180
64
|
</div>
|
|
181
65
|
|
|
182
66
|
<%# Pagination %>
|
|
@@ -186,31 +70,24 @@
|
|
|
186
70
|
total_pages = pagination[:total_pages]
|
|
187
71
|
total_count = pagination[:total_count]
|
|
188
72
|
per_page = pagination[:per_page]
|
|
189
|
-
|
|
190
73
|
from_record = ((current_page - 1) * per_page) + 1
|
|
191
74
|
to_record = [current_page * per_page, total_count].min
|
|
192
75
|
%>
|
|
193
|
-
<div class="
|
|
194
|
-
<
|
|
195
|
-
|
|
196
|
-
</p>
|
|
197
|
-
<nav class="flex items-center space-x-1">
|
|
76
|
+
<div class="flex items-center justify-between font-mono text-xs mt-4 pt-4 border-t border-gray-200 dark:border-gray-800">
|
|
77
|
+
<span class="text-gray-400 dark:text-gray-600"><%= from_record %>-<%= to_record %> of <%= number_with_delimiter(total_count) %></span>
|
|
78
|
+
<nav class="flex items-center gap-1">
|
|
198
79
|
<% if current_page > 1 %>
|
|
199
|
-
<%= link_to "
|
|
80
|
+
<%= link_to "prev", url_for(request.query_parameters.merge(page: current_page - 1)),
|
|
81
|
+
class: "px-2 py-0.5 text-gray-400 dark:text-gray-500 hover:text-gray-700 dark:hover:text-gray-300" %>
|
|
200
82
|
<% else %>
|
|
201
|
-
<span class="px-
|
|
83
|
+
<span class="px-2 py-0.5 text-gray-300 dark:text-gray-700">prev</span>
|
|
202
84
|
<% end %>
|
|
203
85
|
|
|
204
86
|
<%
|
|
205
87
|
window = 2
|
|
206
|
-
left_edge = 1
|
|
207
|
-
right_edge = 1
|
|
208
|
-
|
|
209
88
|
pages_to_show = []
|
|
210
89
|
(1..total_pages).each do |page|
|
|
211
|
-
if page <=
|
|
212
|
-
page > total_pages - right_edge ||
|
|
213
|
-
(page >= current_page - window && page <= current_page + window)
|
|
90
|
+
if page <= 1 || page >= total_pages || (page >= current_page - window && page <= current_page + window)
|
|
214
91
|
pages_to_show << page
|
|
215
92
|
elsif pages_to_show.last != :gap
|
|
216
93
|
pages_to_show << :gap
|
|
@@ -220,18 +97,20 @@
|
|
|
220
97
|
|
|
221
98
|
<% pages_to_show.each do |page| %>
|
|
222
99
|
<% if page == :gap %>
|
|
223
|
-
<span class="px-
|
|
100
|
+
<span class="px-1 text-gray-400 dark:text-gray-600">...</span>
|
|
224
101
|
<% elsif page == current_page %>
|
|
225
|
-
<span class="px-
|
|
102
|
+
<span class="px-2 py-0.5 text-gray-900 dark:text-gray-100"><%= page %></span>
|
|
226
103
|
<% else %>
|
|
227
|
-
<%= link_to page, url_for(request.query_parameters.merge(page: page)),
|
|
104
|
+
<%= link_to page, url_for(request.query_parameters.merge(page: page)),
|
|
105
|
+
class: "px-2 py-0.5 text-gray-400 dark:text-gray-500 hover:text-gray-700 dark:hover:text-gray-300" %>
|
|
228
106
|
<% end %>
|
|
229
107
|
<% end %>
|
|
230
108
|
|
|
231
109
|
<% if current_page < total_pages %>
|
|
232
|
-
<%= link_to "
|
|
110
|
+
<%= link_to "next", url_for(request.query_parameters.merge(page: current_page + 1)),
|
|
111
|
+
class: "px-2 py-0.5 text-gray-400 dark:text-gray-500 hover:text-gray-700 dark:hover:text-gray-300" %>
|
|
233
112
|
<% else %>
|
|
234
|
-
<span class="px-
|
|
113
|
+
<span class="px-2 py-0.5 text-gray-300 dark:text-gray-700">next</span>
|
|
235
114
|
<% end %>
|
|
236
115
|
</nav>
|
|
237
116
|
</div>
|
|
@@ -10,18 +10,12 @@
|
|
|
10
10
|
# { value: "success", label: "Success", color: "bg-green-500" },
|
|
11
11
|
# { value: "error", label: "Error", color: "bg-red-500" }
|
|
12
12
|
# ],
|
|
13
|
-
# selected: ["success"]
|
|
14
|
-
# icon: "M9.75 17L9 20l-1...", # optional SVG path
|
|
15
|
-
# width: "w-48", # optional, default w-48
|
|
16
|
-
# full_width: false # optional, for mobile
|
|
13
|
+
# selected: ["success"]
|
|
17
14
|
|
|
18
15
|
selected = local_assigns[:selected] || []
|
|
19
|
-
width = local_assigns[:width] || "w-48"
|
|
20
|
-
full_width = local_assigns[:full_width] || false
|
|
21
16
|
show_all = local_assigns.fetch(:show_all_option, true)
|
|
22
17
|
all_label = local_assigns[:all_label] || "All"
|
|
23
18
|
|
|
24
|
-
# Determine current label
|
|
25
19
|
current_label = if selected.empty?
|
|
26
20
|
label
|
|
27
21
|
elsif selected.length == 1
|
|
@@ -31,7 +25,6 @@
|
|
|
31
25
|
"#{selected.length} #{label}"
|
|
32
26
|
end
|
|
33
27
|
|
|
34
|
-
# Get color for single selection (if applicable)
|
|
35
28
|
current_color = if selected.length == 1
|
|
36
29
|
opt = options.find { |o| o[:value].to_s == selected.first.to_s }
|
|
37
30
|
opt&.dig(:color)
|
|
@@ -40,41 +33,28 @@
|
|
|
40
33
|
has_selection = selected.any?
|
|
41
34
|
%>
|
|
42
35
|
<div class="relative" x-data="{ open: false }" @click.outside="open = false" data-filter="<%= filter_id %>">
|
|
43
|
-
<%# Trigger button %>
|
|
44
36
|
<button type="button" @click="open = !open"
|
|
45
|
-
class="
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
<svg class="w-4 h-4 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
54
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="<%= icon %>"/>
|
|
55
|
-
</svg>
|
|
56
|
-
<% elsif !has_selection && options.first&.dig(:color) %>
|
|
57
|
-
<span class="w-2 h-2 rounded-full bg-gray-400"></span>
|
|
58
|
-
<% end %>
|
|
59
|
-
<span class="text-gray-700 dark:text-gray-200"><%= current_label %></span>
|
|
60
|
-
</div>
|
|
61
|
-
<svg class="w-4 h-4 text-gray-400 dark:text-gray-500 flex-shrink-0 transition-transform" :class="{ 'rotate-180': open }" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
37
|
+
class="flex items-center gap-1.5 px-2 py-1 font-mono text-xs rounded
|
|
38
|
+
hover:bg-gray-100 dark:hover:bg-gray-800/50 transition-colors
|
|
39
|
+
<%= has_selection ? 'text-gray-900 dark:text-gray-100' : 'text-gray-400 dark:text-gray-500' %>">
|
|
40
|
+
<% if current_color.present? %>
|
|
41
|
+
<span class="w-1.5 h-1.5 rounded-full <%= current_color %> <%= current_color.include?('blue') && selected.first == 'running' ? 'animate-pulse' : '' %>"></span>
|
|
42
|
+
<% end %>
|
|
43
|
+
<span><%= current_label %></span>
|
|
44
|
+
<svg class="w-3 h-3 text-gray-400 dark:text-gray-600 transition-transform" :class="{ 'rotate-180': open }" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
62
45
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
|
63
46
|
</svg>
|
|
64
47
|
</button>
|
|
65
48
|
|
|
66
|
-
<%# Dropdown menu %>
|
|
67
49
|
<div x-show="open" x-cloak x-transition:enter="transition ease-out duration-100"
|
|
68
50
|
x-transition:enter-start="opacity-0 scale-95" x-transition:enter-end="opacity-100 scale-100"
|
|
69
51
|
x-transition:leave="transition ease-in duration-75"
|
|
70
52
|
x-transition:leave-start="opacity-100 scale-100" x-transition:leave-end="opacity-0 scale-95"
|
|
71
|
-
class="absolute z-20 mt-1
|
|
72
|
-
bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 py-1
|
|
73
|
-
max-h-64 overflow-y-auto">
|
|
53
|
+
class="absolute z-20 mt-1 min-w-max bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 py-1 max-h-64 overflow-y-auto">
|
|
74
54
|
|
|
75
55
|
<% if show_all %>
|
|
76
|
-
<div class="px-3 py-
|
|
77
|
-
<label class="flex items-center gap-2 cursor-pointer">
|
|
56
|
+
<div class="px-3 py-1.5 border-b border-gray-100 dark:border-gray-700">
|
|
57
|
+
<label class="flex items-center gap-2 cursor-pointer font-mono text-xs">
|
|
78
58
|
<input type="checkbox"
|
|
79
59
|
class="rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500 dark:bg-gray-700"
|
|
80
60
|
<%= selected.empty? ? 'checked' : '' %>
|
|
@@ -82,24 +62,20 @@
|
|
|
82
62
|
$el.closest('[data-filter]').querySelectorAll('.filter-cb').forEach(c => c.checked = false);
|
|
83
63
|
$el.closest('form').requestSubmit();
|
|
84
64
|
">
|
|
85
|
-
<span class="text-
|
|
65
|
+
<span class="text-gray-700 dark:text-gray-200"><%= all_label %></span>
|
|
86
66
|
</label>
|
|
87
67
|
</div>
|
|
88
68
|
<% end %>
|
|
89
69
|
|
|
90
70
|
<% options.each do |option| %>
|
|
91
|
-
<div class="flex items-center gap-2 px-3 py-
|
|
92
|
-
hover:bg-gray-50 dark:hover:bg-gray-700">
|
|
93
|
-
<%# Checkbox: toggle this item, dropdown stays open %>
|
|
71
|
+
<div class="flex items-center gap-2 px-3 py-1.5 font-mono text-xs text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700">
|
|
94
72
|
<input type="checkbox"
|
|
95
73
|
name="<%= name %>"
|
|
96
74
|
value="<%= option[:value] %>"
|
|
97
75
|
class="filter-cb rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500 dark:bg-gray-700"
|
|
98
76
|
<%= selected.include?(option[:value].to_s) ? 'checked' : '' %>
|
|
99
77
|
@change="$el.closest('form').requestSubmit()">
|
|
100
|
-
|
|
101
|
-
<%# Label: click to select ONLY this item AND close dropdown %>
|
|
102
|
-
<span class="flex-1 flex items-center gap-2 cursor-pointer"
|
|
78
|
+
<span class="flex-1 flex items-center gap-1.5 cursor-pointer"
|
|
103
79
|
@click.prevent="
|
|
104
80
|
$el.closest('[data-filter]').querySelectorAll('.filter-cb').forEach(c => c.checked = false);
|
|
105
81
|
$el.previousElementSibling.checked = true;
|
|
@@ -107,11 +83,7 @@
|
|
|
107
83
|
$el.closest('form').requestSubmit();
|
|
108
84
|
">
|
|
109
85
|
<% if option[:color].present? %>
|
|
110
|
-
<span class="w-
|
|
111
|
-
<% elsif option[:icon].present? %>
|
|
112
|
-
<svg class="w-4 h-4 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
113
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="<%= option[:icon] %>"/>
|
|
114
|
-
</svg>
|
|
86
|
+
<span class="w-1.5 h-1.5 rounded-full <%= option[:color] %> <%= option[:color].include?('blue') && option[:value] == 'running' ? 'animate-pulse' : '' %>"></span>
|
|
115
87
|
<% end %>
|
|
116
88
|
<%= option[:label] %>
|
|
117
89
|
</span>
|
|
@@ -6,58 +6,34 @@
|
|
|
6
6
|
# filter_id: "days",
|
|
7
7
|
# options: [
|
|
8
8
|
# { value: "", label: "All Time" },
|
|
9
|
-
# { value: "
|
|
10
|
-
# { value: "7", label: "Last 7 Days" },
|
|
11
|
-
# { value: "30", label: "Last 30 Days" }
|
|
9
|
+
# { value: "7", label: "Last 7 Days" }
|
|
12
10
|
# ],
|
|
13
|
-
# selected: "7"
|
|
14
|
-
# icon: "M8 7V3m8 4V3...", # optional SVG path
|
|
15
|
-
# width: "w-40", # optional, default w-40
|
|
16
|
-
# full_width: false # optional, for mobile
|
|
11
|
+
# selected: "7"
|
|
17
12
|
|
|
18
13
|
selected = local_assigns[:selected].to_s
|
|
19
|
-
width = local_assigns[:width] || "w-40"
|
|
20
|
-
full_width = local_assigns[:full_width] || false
|
|
21
|
-
|
|
22
|
-
# Find current option
|
|
23
14
|
current_option = options.find { |o| o[:value].to_s == selected } || options.first
|
|
24
15
|
current_label = current_option[:label]
|
|
25
|
-
|
|
26
16
|
has_selection = selected.present?
|
|
27
17
|
%>
|
|
28
18
|
<div class="relative" x-data="{ open: false }" @click.outside="open = false" data-filter="<%= filter_id %>">
|
|
29
|
-
<%# Trigger button %>
|
|
30
19
|
<button type="button" @click="open = !open"
|
|
31
|
-
class="
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
<
|
|
36
|
-
<% if local_assigns[:icon].present? %>
|
|
37
|
-
<svg class="w-4 h-4 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
38
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="<%= icon %>"/>
|
|
39
|
-
</svg>
|
|
40
|
-
<% end %>
|
|
41
|
-
<span class="text-gray-700 dark:text-gray-200"><%= current_label %></span>
|
|
42
|
-
</div>
|
|
43
|
-
<svg class="w-4 h-4 text-gray-400 dark:text-gray-500 flex-shrink-0 transition-transform" :class="{ 'rotate-180': open }" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
20
|
+
class="flex items-center gap-1.5 px-2 py-1 font-mono text-xs rounded
|
|
21
|
+
hover:bg-gray-100 dark:hover:bg-gray-800/50 transition-colors
|
|
22
|
+
<%= has_selection ? 'text-gray-900 dark:text-gray-100' : 'text-gray-400 dark:text-gray-500' %>">
|
|
23
|
+
<span><%= current_label %></span>
|
|
24
|
+
<svg class="w-3 h-3 text-gray-400 dark:text-gray-600 transition-transform" :class="{ 'rotate-180': open }" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
44
25
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
|
45
26
|
</svg>
|
|
46
27
|
</button>
|
|
47
28
|
|
|
48
|
-
<%# Dropdown menu %>
|
|
49
29
|
<div x-show="open" x-cloak x-transition:enter="transition ease-out duration-100"
|
|
50
30
|
x-transition:enter-start="opacity-0 scale-95" x-transition:enter-end="opacity-100 scale-100"
|
|
51
31
|
x-transition:leave="transition ease-in duration-75"
|
|
52
32
|
x-transition:leave-start="opacity-100 scale-100" x-transition:leave-end="opacity-0 scale-95"
|
|
53
|
-
class="absolute z-20 mt-1
|
|
54
|
-
bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 py-1">
|
|
33
|
+
class="absolute z-20 mt-1 min-w-max bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 py-1">
|
|
55
34
|
<% options.each do |option| %>
|
|
56
35
|
<%
|
|
57
36
|
is_selected = option[:value].to_s == selected
|
|
58
|
-
item_classes = is_selected ?
|
|
59
|
-
"bg-blue-50 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300" :
|
|
60
|
-
"text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700"
|
|
61
37
|
%>
|
|
62
38
|
<button type="button"
|
|
63
39
|
@click="
|
|
@@ -65,17 +41,12 @@
|
|
|
65
41
|
open = false;
|
|
66
42
|
$el.closest('form').requestSubmit();
|
|
67
43
|
"
|
|
68
|
-
class="w-full flex items-center gap-2 px-3 py-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="<%= option[:icon] %>"/>
|
|
72
|
-
</svg>
|
|
73
|
-
<% end %>
|
|
44
|
+
class="w-full flex items-center gap-2 px-3 py-1.5 font-mono text-xs text-left
|
|
45
|
+
<%= is_selected ? 'text-gray-900 dark:text-gray-100' : 'text-gray-500 dark:text-gray-400' %>
|
|
46
|
+
hover:bg-gray-50 dark:hover:bg-gray-700">
|
|
74
47
|
<%= option[:label] %>
|
|
75
48
|
<% if is_selected %>
|
|
76
|
-
<
|
|
77
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
|
78
|
-
</svg>
|
|
49
|
+
<span class="ml-auto text-gray-400 dark:text-gray-600">✓</span>
|
|
79
50
|
<% end %>
|
|
80
51
|
</button>
|
|
81
52
|
<% end %>
|