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
|
@@ -1,622 +1,291 @@
|
|
|
1
1
|
<div id="execution-detail" data-execution-id="<%= @execution.id %>" data-status="<%= @execution.status %>">
|
|
2
|
-
<%= render "ruby_llm/agents/shared/breadcrumbs", items: [
|
|
3
|
-
{ label: "Dashboard", path: ruby_llm_agents.root_path },
|
|
4
|
-
{ label: "Executions", path: ruby_llm_agents.executions_path },
|
|
5
|
-
{ label: "##{@execution.id}" }
|
|
6
|
-
] %>
|
|
7
2
|
|
|
8
|
-
<!--
|
|
3
|
+
<!-- Back link -->
|
|
4
|
+
<nav class="font-mono text-xs mb-6">
|
|
5
|
+
<%= link_to "← executions", ruby_llm_agents.executions_path, class: "text-gray-400 dark:text-gray-500 hover:text-gray-700 dark:hover:text-gray-300" %>
|
|
6
|
+
</nav>
|
|
7
|
+
|
|
8
|
+
<!-- ── header ──────────────────────── -->
|
|
9
9
|
<%
|
|
10
|
-
# Collect secondary badges
|
|
11
10
|
secondary_badges = []
|
|
12
|
-
secondary_badges << { label: "
|
|
13
|
-
secondary_badges << { label: "
|
|
11
|
+
secondary_badges << { label: "stream", css: "badge-cyan" } if @execution.streaming?
|
|
12
|
+
secondary_badges << { label: "cached", css: "badge-purple" } if @execution.cache_hit
|
|
14
13
|
if @execution.finish_reason.present?
|
|
15
|
-
|
|
16
|
-
when 'stop' then '
|
|
17
|
-
when 'length' then '
|
|
18
|
-
when 'content_filter' then '
|
|
19
|
-
when 'tool_calls' then '
|
|
20
|
-
else '
|
|
14
|
+
finish_css = case @execution.finish_reason
|
|
15
|
+
when 'stop' then 'badge-success'
|
|
16
|
+
when 'length' then 'badge-orange'
|
|
17
|
+
when 'content_filter' then 'badge-error'
|
|
18
|
+
when 'tool_calls' then 'badge-cyan'
|
|
19
|
+
else 'badge-timeout'
|
|
21
20
|
end
|
|
22
|
-
secondary_badges << { label: @execution.finish_reason,
|
|
21
|
+
secondary_badges << { label: @execution.finish_reason, css: finish_css }
|
|
23
22
|
end
|
|
24
|
-
secondary_badges << { label: "
|
|
23
|
+
secondary_badges << { label: "rate limited", css: "badge-orange" } if @execution.respond_to?(:rate_limited?) && @execution.rate_limited?
|
|
25
24
|
%>
|
|
26
|
-
<div class="
|
|
27
|
-
|
|
28
|
-
<div class="
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
type="button"
|
|
39
|
-
@mouseenter="showDetails = true"
|
|
40
|
-
@mouseleave="showDetails = false"
|
|
41
|
-
class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors"
|
|
42
|
-
>
|
|
43
|
-
+<%= secondary_badges.size %>
|
|
44
|
-
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
45
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
46
|
-
</svg>
|
|
47
|
-
</button>
|
|
48
|
-
<div
|
|
49
|
-
x-show="showDetails"
|
|
50
|
-
x-cloak
|
|
51
|
-
x-transition:enter="transition ease-out duration-100"
|
|
52
|
-
x-transition:enter-start="opacity-0 scale-95"
|
|
53
|
-
x-transition:enter-end="opacity-100 scale-100"
|
|
54
|
-
class="absolute left-0 mt-1 z-10 bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 p-2 min-w-max"
|
|
55
|
-
>
|
|
56
|
-
<div class="flex flex-wrap gap-1.5">
|
|
57
|
-
<% secondary_badges.each do |badge| %>
|
|
58
|
-
<% badge_classes = case badge[:color]
|
|
59
|
-
when 'cyan' then 'bg-cyan-100 dark:bg-cyan-900/50 text-cyan-800 dark:text-cyan-300'
|
|
60
|
-
when 'purple' then 'bg-purple-100 dark:bg-purple-900/50 text-purple-800 dark:text-purple-300'
|
|
61
|
-
when 'green' then 'bg-green-100 dark:bg-green-900/50 text-green-800 dark:text-green-300'
|
|
62
|
-
when 'yellow' then 'bg-yellow-100 dark:bg-yellow-900/50 text-yellow-800 dark:text-yellow-300'
|
|
63
|
-
when 'red' then 'bg-red-100 dark:bg-red-900/50 text-red-800 dark:text-red-300'
|
|
64
|
-
when 'blue' then 'bg-blue-100 dark:bg-blue-900/50 text-blue-800 dark:text-blue-300'
|
|
65
|
-
when 'orange' then 'bg-orange-100 dark:bg-orange-900/50 text-orange-800 dark:text-orange-300'
|
|
66
|
-
else 'bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-300'
|
|
67
|
-
end %>
|
|
68
|
-
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium <%= badge_classes %>"><%= badge[:label] %></span>
|
|
69
|
-
<% end %>
|
|
70
|
-
</div>
|
|
71
|
-
</div>
|
|
72
|
-
</div>
|
|
73
|
-
<% end %>
|
|
74
|
-
</div>
|
|
75
|
-
<div class="flex items-center gap-3 flex-shrink-0">
|
|
76
|
-
<%= button_to rerun_execution_path(@execution, dry_run: true),
|
|
77
|
-
method: :post,
|
|
78
|
-
class: "inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors",
|
|
79
|
-
title: "Preview what would be sent without making an API call" do %>
|
|
80
|
-
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
81
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|
82
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
|
|
83
|
-
</svg>
|
|
84
|
-
Dry Run
|
|
85
|
-
<% end %>
|
|
86
|
-
<button
|
|
87
|
-
type="button"
|
|
88
|
-
onclick="confirmRerun()"
|
|
89
|
-
class="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-md transition-colors"
|
|
90
|
-
title="Re-execute this agent with the same parameters"
|
|
91
|
-
>
|
|
92
|
-
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
93
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
|
|
94
|
-
</svg>
|
|
95
|
-
Rerun
|
|
96
|
-
</button>
|
|
97
|
-
<span class="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap"><%= @execution.created_at.strftime("%b %d, %H:%M") %></span>
|
|
98
|
-
</div>
|
|
99
|
-
</div>
|
|
100
|
-
|
|
101
|
-
<!-- Desktop: Row 2 - Info line + relative time -->
|
|
102
|
-
<div class="hidden sm:flex sm:items-center sm:justify-between mt-1.5">
|
|
103
|
-
<p class="text-xs text-gray-500 dark:text-gray-400">
|
|
104
|
-
#<%= @execution.id %> · v<%= @execution.agent_version %>
|
|
105
|
-
<% if @execution.model_provider.present? %>
|
|
106
|
-
· <%= @execution.model_provider %>
|
|
107
|
-
<% end %>
|
|
108
|
-
</p>
|
|
109
|
-
<span class="text-xs text-gray-400 dark:text-gray-500"><%= time_ago_in_words(@execution.created_at) %> ago</span>
|
|
110
|
-
</div>
|
|
111
|
-
|
|
112
|
-
<!-- Mobile: Stacked layout -->
|
|
113
|
-
<div class="sm:hidden">
|
|
114
|
-
<h2 class="text-lg font-bold text-gray-900 dark:text-gray-100 truncate">
|
|
115
|
-
<%= @execution.agent_type.gsub(/Agent$/, '') %>
|
|
116
|
-
</h2>
|
|
117
|
-
<div class="flex flex-wrap items-center gap-2 mt-2">
|
|
118
|
-
<%= render "ruby_llm/agents/shared/status_badge", status: @execution.status, size: :md %>
|
|
119
|
-
<% secondary_badges.each do |badge| %>
|
|
120
|
-
<% badge_classes = case badge[:color]
|
|
121
|
-
when 'cyan' then 'bg-cyan-100 dark:bg-cyan-900/50 text-cyan-800 dark:text-cyan-300'
|
|
122
|
-
when 'purple' then 'bg-purple-100 dark:bg-purple-900/50 text-purple-800 dark:text-purple-300'
|
|
123
|
-
when 'green' then 'bg-green-100 dark:bg-green-900/50 text-green-800 dark:text-green-300'
|
|
124
|
-
when 'yellow' then 'bg-yellow-100 dark:bg-yellow-900/50 text-yellow-800 dark:text-yellow-300'
|
|
125
|
-
when 'red' then 'bg-red-100 dark:bg-red-900/50 text-red-800 dark:text-red-300'
|
|
126
|
-
when 'blue' then 'bg-blue-100 dark:bg-blue-900/50 text-blue-800 dark:text-blue-300'
|
|
127
|
-
when 'orange' then 'bg-orange-100 dark:bg-orange-900/50 text-orange-800 dark:text-orange-300'
|
|
128
|
-
else 'bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-300'
|
|
129
|
-
end %>
|
|
130
|
-
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-medium <%= badge_classes %>"><%= badge[:label] %></span>
|
|
131
|
-
<% end %>
|
|
132
|
-
</div>
|
|
133
|
-
<p class="text-xs text-gray-500 dark:text-gray-400 mt-2">
|
|
134
|
-
#<%= @execution.id %> · v<%= @execution.agent_version %>
|
|
135
|
-
<% if @execution.model_provider.present? %>
|
|
136
|
-
· <%= @execution.model_provider %>
|
|
137
|
-
<% end %>
|
|
138
|
-
</p>
|
|
139
|
-
|
|
140
|
-
<!-- Mobile: Date + Buttons -->
|
|
141
|
-
<div class="mt-4 pt-4 border-t border-gray-100 dark:border-gray-700">
|
|
142
|
-
<p class="text-xs text-gray-500 dark:text-gray-400 mb-3">
|
|
143
|
-
<%= @execution.created_at.strftime("%b %d, %Y at %H:%M") %>
|
|
144
|
-
<span class="text-gray-400 dark:text-gray-500">· <%= time_ago_in_words(@execution.created_at) %> ago</span>
|
|
145
|
-
</p>
|
|
146
|
-
<div class="flex items-center gap-2">
|
|
147
|
-
<%= button_to rerun_execution_path(@execution, dry_run: true),
|
|
148
|
-
method: :post,
|
|
149
|
-
class: "flex-1 inline-flex items-center justify-center gap-1.5 px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors",
|
|
150
|
-
title: "Preview what would be sent without making an API call" do %>
|
|
151
|
-
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
152
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|
153
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
|
|
154
|
-
</svg>
|
|
155
|
-
Dry Run
|
|
156
|
-
<% end %>
|
|
157
|
-
<button
|
|
158
|
-
type="button"
|
|
159
|
-
onclick="confirmRerun()"
|
|
160
|
-
class="flex-1 inline-flex items-center justify-center gap-1.5 px-3 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors"
|
|
161
|
-
title="Re-execute this agent with the same parameters"
|
|
162
|
-
>
|
|
163
|
-
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
164
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
|
|
165
|
-
</svg>
|
|
166
|
-
Rerun
|
|
167
|
-
</button>
|
|
168
|
-
</div>
|
|
169
|
-
</div>
|
|
25
|
+
<div class="flex flex-wrap items-center gap-3 mb-1.5">
|
|
26
|
+
<span class="text-[10px] font-medium text-gray-400 dark:text-gray-600 uppercase tracking-widest font-mono"><%= @execution.agent_type.gsub(/Agent$/, '') %></span>
|
|
27
|
+
<div class="font-mono text-xs text-gray-400 dark:text-gray-500 flex flex-wrap items-center gap-1.5">
|
|
28
|
+
<%= render "ruby_llm/agents/shared/status_badge", status: @execution.status %>
|
|
29
|
+
<% secondary_badges.each do |badge| %>
|
|
30
|
+
<span class="badge badge-sm <%= badge[:css] %>"><%= badge[:label] %></span>
|
|
31
|
+
<% end %>
|
|
32
|
+
<span class="text-gray-800 dark:text-gray-200"><%= @execution.model_id %></span>
|
|
33
|
+
<% if @execution.model_provider.present? %>
|
|
34
|
+
<span class="text-gray-300 dark:text-gray-700">·</span>
|
|
35
|
+
<span><%= @execution.model_provider %></span>
|
|
36
|
+
<% end %>
|
|
170
37
|
</div>
|
|
38
|
+
<%= render "ruby_llm/agents/shared/doc_link" %>
|
|
39
|
+
<div class="flex-1 border-t border-gray-200 dark:border-gray-800"></div>
|
|
171
40
|
</div>
|
|
172
41
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
<
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
<div class="sm:flex sm:items-start">
|
|
180
|
-
<div class="flex items-center justify-center flex-shrink-0 w-12 h-12 mx-auto bg-blue-100 dark:bg-blue-900/30 rounded-full sm:mx-0 sm:h-10 sm:w-10">
|
|
181
|
-
<svg class="w-6 h-6 text-blue-600 dark:text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
182
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
|
|
183
|
-
</svg>
|
|
184
|
-
</div>
|
|
185
|
-
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
|
186
|
-
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100">Confirm Rerun</h3>
|
|
187
|
-
<div class="mt-2">
|
|
188
|
-
<p class="text-sm text-gray-500 dark:text-gray-400">
|
|
189
|
-
This will re-execute the agent with the original parameters. A new execution record will be created and the agent will make a real API call.
|
|
190
|
-
</p>
|
|
191
|
-
<p class="mt-2 text-sm text-amber-600 dark:text-amber-400">
|
|
192
|
-
This action may incur API costs.
|
|
193
|
-
</p>
|
|
194
|
-
</div>
|
|
195
|
-
</div>
|
|
196
|
-
</div>
|
|
197
|
-
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse gap-3">
|
|
198
|
-
<%= button_to rerun_execution_path(@execution),
|
|
199
|
-
method: :post,
|
|
200
|
-
class: "inline-flex justify-center w-full px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:w-auto" do %>
|
|
201
|
-
Confirm Rerun
|
|
202
|
-
<% end %>
|
|
203
|
-
<button type="button" onclick="closeRerunModal()" class="inline-flex justify-center w-full px-4 py-2 mt-3 text-sm font-medium text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none sm:mt-0 sm:w-auto">
|
|
204
|
-
Cancel
|
|
205
|
-
</button>
|
|
206
|
-
</div>
|
|
207
|
-
</div>
|
|
208
|
-
</div>
|
|
42
|
+
<div class="font-mono text-xs text-gray-400 dark:text-gray-500 mb-1">
|
|
43
|
+
#<%= @execution.id %>
|
|
44
|
+
<span class="text-gray-300 dark:text-gray-700">·</span>
|
|
45
|
+
<%= @execution.created_at.strftime("%b %d, %H:%M") %>
|
|
46
|
+
<span class="text-gray-300 dark:text-gray-700">·</span>
|
|
47
|
+
<%= time_ago_in_words(@execution.created_at) %> ago
|
|
209
48
|
</div>
|
|
210
49
|
|
|
211
|
-
<!--
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
<div class="flex items-center gap-2 mb-4">
|
|
220
|
-
<% if @execution.workflow_type.present? %>
|
|
221
|
-
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md text-sm font-medium bg-emerald-100 dark:bg-emerald-900/50 text-emerald-700 dark:text-emerald-300">
|
|
222
|
-
<span class="text-base">◈</span> Workflow
|
|
223
|
-
</span>
|
|
224
|
-
<% elsif @execution.workflow_step.present? %>
|
|
225
|
-
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md text-sm font-medium bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300">
|
|
226
|
-
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
227
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 5l7 7-7 7M5 5l7 7-7 7"/>
|
|
228
|
-
</svg>
|
|
229
|
-
Workflow Step
|
|
230
|
-
</span>
|
|
231
|
-
<% end %>
|
|
232
|
-
<% if @execution.workflow_id.present? %>
|
|
233
|
-
<span class="text-xs text-gray-400 dark:text-gray-500 font-mono" title="Workflow ID: <%= @execution.workflow_id %>">
|
|
234
|
-
<%= @execution.workflow_id.to_s.truncate(12) %>
|
|
235
|
-
</span>
|
|
236
|
-
<% end %>
|
|
237
|
-
</div>
|
|
238
|
-
|
|
239
|
-
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
240
|
-
<% if @execution.workflow_step.present? %>
|
|
241
|
-
<div>
|
|
242
|
-
<p class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide">Step Name</p>
|
|
243
|
-
<p class="text-sm font-medium text-gray-900 dark:text-gray-100"><%= @execution.workflow_step %></p>
|
|
244
|
-
</div>
|
|
245
|
-
<% end %>
|
|
246
|
-
|
|
247
|
-
<% if @execution.routed_to.present? %>
|
|
248
|
-
<div>
|
|
249
|
-
<p class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide">Routed To</p>
|
|
250
|
-
<p class="text-sm font-medium text-amber-600 dark:text-amber-400"><%= @execution.routed_to %></p>
|
|
251
|
-
</div>
|
|
252
|
-
<% end %>
|
|
253
|
-
|
|
254
|
-
<% if @execution.classification_result.present? %>
|
|
255
|
-
<%
|
|
256
|
-
classification = if @execution.classification_result.is_a?(String)
|
|
257
|
-
begin
|
|
258
|
-
JSON.parse(@execution.classification_result)
|
|
259
|
-
rescue
|
|
260
|
-
{}
|
|
261
|
-
end
|
|
262
|
-
else
|
|
263
|
-
@execution.classification_result || {}
|
|
264
|
-
end
|
|
265
|
-
%>
|
|
266
|
-
<% if classification["method"].present? %>
|
|
267
|
-
<div>
|
|
268
|
-
<p class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide">Classification</p>
|
|
269
|
-
<p class="text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
270
|
-
<%= classification["method"] == "llm" ? "LLM" : "Rule-based" %>
|
|
271
|
-
<% if classification["classification_time_ms"].present? %>
|
|
272
|
-
<span class="text-xs text-gray-400 dark:text-gray-500">(<%= classification["classification_time_ms"] %>ms)</span>
|
|
273
|
-
<% end %>
|
|
274
|
-
</p>
|
|
275
|
-
</div>
|
|
276
|
-
<% end %>
|
|
277
|
-
<% if classification["classifier_model"].present? %>
|
|
278
|
-
<div>
|
|
279
|
-
<p class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide">Classifier Model</p>
|
|
280
|
-
<p class="text-sm font-medium text-gray-900 dark:text-gray-100 font-mono"><%= classification["classifier_model"] %></p>
|
|
281
|
-
</div>
|
|
282
|
-
<% end %>
|
|
283
|
-
<% end %>
|
|
284
|
-
</div>
|
|
285
|
-
|
|
286
|
-
<% if @execution.parent_execution_id.present? %>
|
|
287
|
-
<div class="mt-4 pt-4 border-t border-gray-100 dark:border-gray-700">
|
|
288
|
-
<span class="text-xs text-gray-500 dark:text-gray-400">Part of workflow:</span>
|
|
289
|
-
<%= link_to "##{@execution.parent_execution_id}",
|
|
290
|
-
ruby_llm_agents.execution_path(@execution.parent_execution_id),
|
|
291
|
-
class: "ml-2 text-blue-600 dark:text-blue-400 hover:underline font-mono text-sm" %>
|
|
292
|
-
</div>
|
|
293
|
-
<% end %>
|
|
294
|
-
</div>
|
|
295
|
-
<% end %>
|
|
296
|
-
|
|
297
|
-
<!-- Stats Grid -->
|
|
298
|
-
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
|
|
299
|
-
<%= render "ruby_llm/agents/shared/stat_card",
|
|
300
|
-
title: "Model",
|
|
301
|
-
value: @execution.model_id,
|
|
302
|
-
icon: "M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z",
|
|
303
|
-
icon_color: "text-blue-500" %>
|
|
304
|
-
|
|
305
|
-
<%= render "ruby_llm/agents/shared/stat_card",
|
|
306
|
-
title: "Duration",
|
|
307
|
-
value: "#{number_to_human_short(@execution.duration_ms || 0)} ms",
|
|
308
|
-
icon: "M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z",
|
|
309
|
-
icon_color: "text-purple-500" %>
|
|
310
|
-
|
|
311
|
-
<%= render "ruby_llm/agents/shared/stat_card",
|
|
312
|
-
title: "Total Tokens",
|
|
313
|
-
value: number_to_human_short(@execution.total_tokens || 0),
|
|
314
|
-
icon: "M7 20l4-16m2 16l4-16M6 9h14M4 15h14",
|
|
315
|
-
icon_color: "text-indigo-500" %>
|
|
316
|
-
|
|
317
|
-
<%= render "ruby_llm/agents/shared/stat_card",
|
|
318
|
-
title: "Total Cost",
|
|
319
|
-
value: number_to_human_short(@execution.total_cost || 0, prefix: "$", precision: 2),
|
|
320
|
-
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",
|
|
321
|
-
icon_color: "text-amber-500" %>
|
|
50
|
+
<!-- Stats inline row -->
|
|
51
|
+
<div class="flex flex-wrap items-center gap-x-4 gap-y-1 font-mono text-xs text-gray-400 dark:text-gray-500 mb-2">
|
|
52
|
+
<span><span class="text-gray-800 dark:text-gray-200"><%= number_to_human_short(@execution.duration_ms || 0) %>ms</span> duration</span>
|
|
53
|
+
<span><span class="text-gray-800 dark:text-gray-200"><%= number_to_human_short(@execution.total_tokens || 0) %></span> tokens</span>
|
|
54
|
+
<span><span class="text-gray-800 dark:text-gray-200"><%= number_to_human_short(@execution.total_cost || 0, prefix: "$", precision: 2) %></span> cost</span>
|
|
55
|
+
<% if @execution.tokens_per_second %>
|
|
56
|
+
<span><span class="text-gray-800 dark:text-gray-200"><%= @execution.tokens_per_second.round(1) %></span> tok/s</span>
|
|
57
|
+
<% end %>
|
|
322
58
|
</div>
|
|
323
59
|
|
|
324
|
-
<!--
|
|
325
|
-
|
|
326
|
-
|
|
60
|
+
<!-- ── tokens ──────────────────────── -->
|
|
61
|
+
<%
|
|
62
|
+
input_tokens = @execution.input_tokens || 0
|
|
63
|
+
output_tokens = @execution.output_tokens || 0
|
|
64
|
+
total = input_tokens + output_tokens
|
|
65
|
+
input_pct = total > 0 ? (input_tokens.to_f / total * 100).round(1) : 0
|
|
66
|
+
output_pct = total > 0 ? (output_tokens.to_f / total * 100).round(1) : 0
|
|
67
|
+
%>
|
|
68
|
+
<div class="flex items-center gap-3 mt-6 mb-3">
|
|
69
|
+
<span class="text-[10px] font-medium text-gray-400 dark:text-gray-600 uppercase tracking-widest font-mono">tokens</span>
|
|
70
|
+
<div class="flex-1 border-t border-gray-200 dark:border-gray-800"></div>
|
|
71
|
+
</div>
|
|
327
72
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
total = input_tokens + output_tokens
|
|
333
|
-
input_pct = total > 0 ? (input_tokens.to_f / total * 100).round(1) : 0
|
|
334
|
-
output_pct = total > 0 ? (output_tokens.to_f / total * 100).round(1) : 0
|
|
335
|
-
%>
|
|
336
|
-
<div class="mb-6">
|
|
337
|
-
<div class="flex justify-between text-xs mb-1.5">
|
|
338
|
-
<span class="text-blue-600 dark:text-blue-400 font-medium">Input: <%= number_to_human_short(input_tokens) %> (<%= input_pct %>%)</span>
|
|
339
|
-
<span class="text-green-600 dark:text-green-400 font-medium">Output: <%= number_to_human_short(output_tokens) %> (<%= output_pct %>%)</span>
|
|
340
|
-
</div>
|
|
341
|
-
<div class="h-2.5 bg-gray-100 dark:bg-gray-700 rounded-full overflow-hidden flex">
|
|
342
|
-
<div class="bg-blue-500 transition-all" style="width: <%= input_pct %>%"></div>
|
|
343
|
-
<div class="bg-green-500 transition-all" style="width: <%= output_pct %>%"></div>
|
|
344
|
-
</div>
|
|
73
|
+
<div class="mb-2">
|
|
74
|
+
<div class="flex justify-between text-xs font-mono mb-1">
|
|
75
|
+
<span class="text-blue-600 dark:text-blue-400">input: <%= number_to_human_short(input_tokens) %> (<%= input_pct %>%)</span>
|
|
76
|
+
<span class="text-green-600 dark:text-green-400">output: <%= number_to_human_short(output_tokens) %> (<%= output_pct %>%)</span>
|
|
345
77
|
</div>
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
<div>
|
|
350
|
-
<p class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide">Input</p>
|
|
351
|
-
<p class="text-lg font-semibold text-gray-900 dark:text-gray-100"><%= number_to_human_short(@execution.input_tokens || 0) %></p>
|
|
352
|
-
<p class="text-xs text-gray-400 dark:text-gray-500"><%= number_to_human_short(@execution.input_cost || 0, prefix: "$", precision: 4) %></p>
|
|
353
|
-
</div>
|
|
354
|
-
|
|
355
|
-
<div>
|
|
356
|
-
<p class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide">Output</p>
|
|
357
|
-
<p class="text-lg font-semibold text-gray-900 dark:text-gray-100"><%= number_to_human_short(@execution.output_tokens || 0) %></p>
|
|
358
|
-
<p class="text-xs text-gray-400 dark:text-gray-500"><%= number_to_human_short(@execution.output_cost || 0, prefix: "$", precision: 4) %></p>
|
|
359
|
-
</div>
|
|
360
|
-
|
|
361
|
-
<div>
|
|
362
|
-
<p class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide">Cached</p>
|
|
363
|
-
<p class="text-lg font-semibold text-gray-900 dark:text-gray-100"><%= number_to_human_short(@execution.cached_tokens || 0) %></p>
|
|
364
|
-
</div>
|
|
365
|
-
|
|
366
|
-
<div>
|
|
367
|
-
<p class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide">Cache Creation</p>
|
|
368
|
-
<p class="text-lg font-semibold text-gray-900 dark:text-gray-100"><%= number_to_human_short(@execution.cache_creation_tokens || 0) %></p>
|
|
369
|
-
</div>
|
|
370
|
-
|
|
371
|
-
<div>
|
|
372
|
-
<p class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide">Tokens/Sec</p>
|
|
373
|
-
<p class="text-lg font-semibold text-gray-900 dark:text-gray-100"><%= @execution.tokens_per_second || 'N/A' %></p>
|
|
374
|
-
</div>
|
|
78
|
+
<div class="h-1.5 bg-gray-100 dark:bg-gray-800 rounded-full overflow-hidden flex">
|
|
79
|
+
<div class="bg-blue-500 transition-all" style="width: <%= input_pct %>%"></div>
|
|
80
|
+
<div class="bg-green-500 transition-all" style="width: <%= output_pct %>%"></div>
|
|
375
81
|
</div>
|
|
376
82
|
</div>
|
|
377
83
|
|
|
378
|
-
|
|
84
|
+
<div class="flex flex-wrap items-center gap-x-4 gap-y-1 font-mono text-xs text-gray-400 dark:text-gray-500">
|
|
85
|
+
<span><span class="text-gray-800 dark:text-gray-200"><%= number_to_human_short(input_tokens) %></span> input <span class="text-gray-300 dark:text-gray-700">(<%= number_to_human_short(@execution.input_cost || 0, prefix: "$", precision: 4) %>)</span></span>
|
|
86
|
+
<span><span class="text-gray-800 dark:text-gray-200"><%= number_to_human_short(output_tokens) %></span> output <span class="text-gray-300 dark:text-gray-700">(<%= number_to_human_short(@execution.output_cost || 0, prefix: "$", precision: 4) %>)</span></span>
|
|
87
|
+
<span><span class="text-gray-800 dark:text-gray-200"><%= number_to_human_short(@execution.cached_tokens || 0) %></span> cached</span>
|
|
88
|
+
<span><span class="text-gray-800 dark:text-gray-200"><%= number_to_human_short(@execution.cache_creation_tokens || 0) %></span> cache creation</span>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<!-- ── attempts ──────────────────────── -->
|
|
379
92
|
<% if @execution.respond_to?(:attempts) && @execution.attempts.present? %>
|
|
380
|
-
<div class="
|
|
381
|
-
<
|
|
382
|
-
|
|
383
|
-
<h3 class="text-sm font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">Attempts</h3>
|
|
384
|
-
<p class="text-xs text-gray-400 dark:text-gray-500 mt-1">
|
|
385
|
-
<%= @execution.respond_to?(:attempts_count) && @execution.attempts_count ? @execution.attempts_count : @execution.attempts.size %> attempt(s)
|
|
386
|
-
<% if @execution.used_fallback? %>
|
|
387
|
-
· <span class="text-amber-500">Used fallback model</span>
|
|
388
|
-
<% end %>
|
|
389
|
-
<% if @execution.has_retries? %>
|
|
390
|
-
· <span class="text-blue-500">Retried</span>
|
|
391
|
-
<% end %>
|
|
392
|
-
</p>
|
|
393
|
-
</div>
|
|
93
|
+
<div class="flex items-center gap-3 mt-6 mb-3">
|
|
94
|
+
<span class="text-[10px] font-medium text-gray-400 dark:text-gray-600 uppercase tracking-widest font-mono">attempts (<%= @execution.respond_to?(:attempts_count) && @execution.attempts_count ? @execution.attempts_count : @execution.attempts.size %>)</span>
|
|
95
|
+
<div class="font-mono text-xs text-gray-400 dark:text-gray-500 flex items-center gap-1.5">
|
|
394
96
|
<% if @execution.used_fallback? %>
|
|
395
|
-
<span class="
|
|
396
|
-
|
|
397
|
-
|
|
97
|
+
<span class="badge badge-sm badge-orange">fallback: <%= @execution.chosen_model_id %></span>
|
|
98
|
+
<% end %>
|
|
99
|
+
<% if @execution.has_retries? %>
|
|
100
|
+
<span class="badge badge-sm badge-cyan">retried</span>
|
|
398
101
|
<% end %>
|
|
399
102
|
</div>
|
|
103
|
+
<div class="flex-1 border-t border-gray-200 dark:border-gray-800"></div>
|
|
104
|
+
</div>
|
|
400
105
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
106
|
+
<% error_attempts = @execution.attempts.select { |a| a['error_class'].present? } %>
|
|
107
|
+
<% if error_attempts.any? %>
|
|
108
|
+
<script type="application/json" id="all-errors-data">
|
|
109
|
+
<%= raw error_attempts.map { |a|
|
|
110
|
+
lines = ["#{a['error_class']}: #{a['error_message']}"]
|
|
111
|
+
lines += (a['error_backtrace'] || [])
|
|
112
|
+
"Model: #{a['model_id']}\n#{lines.join("\n")}"
|
|
113
|
+
}.join("\n\n---\n\n").to_json %>
|
|
114
|
+
</script>
|
|
115
|
+
<div class="flex justify-end mb-2">
|
|
116
|
+
<button onclick="var text = JSON.parse(document.getElementById('all-errors-data').textContent); navigator.clipboard.writeText(text).then(function() { var btn = event.currentTarget; btn.textContent = 'copied!'; setTimeout(function() { btn.textContent = 'copy all errors'; }, 2000); });"
|
|
117
|
+
class="font-mono text-xs text-gray-400 dark:text-gray-500 hover:text-gray-700 dark:hover:text-gray-300">
|
|
118
|
+
copy all errors
|
|
119
|
+
</button>
|
|
120
|
+
</div>
|
|
121
|
+
<% end %>
|
|
122
|
+
|
|
123
|
+
<div class="overflow-x-auto">
|
|
124
|
+
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-800">
|
|
125
|
+
<thead>
|
|
126
|
+
<tr>
|
|
127
|
+
<th class="px-3 py-2 text-left text-[10px] font-medium text-gray-400 dark:text-gray-600 uppercase tracking-wider font-mono">#</th>
|
|
128
|
+
<th class="px-3 py-2 text-left text-[10px] font-medium text-gray-400 dark:text-gray-600 uppercase tracking-wider font-mono">model</th>
|
|
129
|
+
<th class="px-3 py-2 text-left text-[10px] font-medium text-gray-400 dark:text-gray-600 uppercase tracking-wider font-mono">status</th>
|
|
130
|
+
<th class="px-3 py-2 text-left text-[10px] font-medium text-gray-400 dark:text-gray-600 uppercase tracking-wider font-mono">duration</th>
|
|
131
|
+
<th class="px-3 py-2 text-left text-[10px] font-medium text-gray-400 dark:text-gray-600 uppercase tracking-wider font-mono">tokens</th>
|
|
132
|
+
<th class="px-3 py-2 text-left text-[10px] font-medium text-gray-400 dark:text-gray-600 uppercase tracking-wider font-mono">error</th>
|
|
133
|
+
</tr>
|
|
134
|
+
</thead>
|
|
135
|
+
<tbody class="divide-y divide-gray-100 dark:divide-gray-800">
|
|
136
|
+
<% @execution.attempts.each_with_index do |attempt, index| %>
|
|
137
|
+
<tr class="<%= attempt['short_circuited'] ? 'bg-gray-50 dark:bg-gray-900/50' : '' %>">
|
|
138
|
+
<td class="px-3 py-2 text-xs font-mono text-gray-500 dark:text-gray-400"><%= index + 1 %></td>
|
|
139
|
+
<td class="px-3 py-2 text-xs font-mono text-gray-900 dark:text-gray-100"><%= attempt['model_id'] %></td>
|
|
140
|
+
<td class="px-3 py-2">
|
|
141
|
+
<% if attempt['short_circuited'] %>
|
|
142
|
+
<span class="badge badge-sm badge-timeout">blocked</span>
|
|
143
|
+
<% elsif attempt['error_class'].present? %>
|
|
144
|
+
<span class="badge badge-sm badge-error">failed</span>
|
|
145
|
+
<% else %>
|
|
146
|
+
<span class="badge badge-sm badge-success">success</span>
|
|
147
|
+
<% end %>
|
|
148
|
+
</td>
|
|
149
|
+
<td class="px-3 py-2 text-xs font-mono text-gray-500 dark:text-gray-400">
|
|
150
|
+
<%= attempt['duration_ms'] ? "#{attempt['duration_ms']}ms" : '-' %>
|
|
151
|
+
</td>
|
|
152
|
+
<td class="px-3 py-2 text-xs font-mono text-gray-500 dark:text-gray-400">
|
|
153
|
+
<% if attempt['input_tokens'] || attempt['output_tokens'] %>
|
|
154
|
+
<span class="text-blue-600 dark:text-blue-400"><%= attempt['input_tokens'] || 0 %></span>
|
|
155
|
+
/
|
|
156
|
+
<span class="text-green-600 dark:text-green-400"><%= attempt['output_tokens'] || 0 %></span>
|
|
157
|
+
<% else %>
|
|
158
|
+
-
|
|
159
|
+
<% end %>
|
|
160
|
+
</td>
|
|
161
|
+
<td class="px-3 py-2 text-xs max-w-xs">
|
|
162
|
+
<% if attempt['error_class'].present? %>
|
|
163
|
+
<span class="text-red-600 dark:text-red-400 font-mono font-medium"><%= attempt['error_class'].split('::').last %></span>
|
|
164
|
+
<p class="text-red-500 dark:text-red-400 text-xs mt-0.5 break-words"><%= attempt['error_message'].to_s.truncate(150) %></p>
|
|
165
|
+
<% if attempt['error_backtrace'].present? %>
|
|
166
|
+
<button onclick="var el = document.getElementById('backtrace-<%= index %>'); el.classList.toggle('hidden');"
|
|
167
|
+
class="text-xs font-mono text-gray-400 dark:text-gray-500 hover:text-red-500 dark:hover:text-red-400 mt-1 underline">
|
|
168
|
+
stack trace
|
|
169
|
+
</button>
|
|
465
170
|
<% end %>
|
|
171
|
+
<% else %>
|
|
172
|
+
<span class="text-gray-400">-</span>
|
|
173
|
+
<% end %>
|
|
174
|
+
</td>
|
|
175
|
+
</tr>
|
|
176
|
+
<% if attempt['error_backtrace'].present? %>
|
|
177
|
+
<tr id="backtrace-<%= index %>" class="hidden">
|
|
178
|
+
<td colspan="6" class="px-3 py-2">
|
|
179
|
+
<div class="bg-gray-900 text-gray-100 text-xs font-mono p-3 rounded max-h-64 overflow-auto whitespace-pre-wrap relative group/bt">
|
|
180
|
+
<button onclick="var el = document.getElementById('backtrace-text-<%= index %>'); navigator.clipboard.writeText(el.textContent.trim()).then(function() { var btn = event.currentTarget; btn.textContent = 'copied!'; setTimeout(function() { btn.textContent = 'copy'; }, 2000); });"
|
|
181
|
+
class="absolute top-2 right-2 text-xs font-mono text-gray-400 hover:text-white transition-colors">
|
|
182
|
+
copy
|
|
183
|
+
</button>
|
|
184
|
+
<div id="backtrace-text-<%= index %>">
|
|
185
|
+
<div class="font-semibold text-red-400 mb-1"><%= attempt['error_class'] %>: <%= attempt['error_message'].to_s.truncate(200) %></div>
|
|
186
|
+
<% attempt['error_backtrace'].each do |line| %>
|
|
187
|
+
<div class="text-gray-300 leading-relaxed"><%= line %></div>
|
|
188
|
+
<% end %>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
466
191
|
</td>
|
|
467
192
|
</tr>
|
|
468
193
|
<% end %>
|
|
469
|
-
</tbody>
|
|
470
|
-
</table>
|
|
471
|
-
</div>
|
|
472
|
-
|
|
473
|
-
<% if @execution.fallback_chain.present? && @execution.fallback_chain.any? %>
|
|
474
|
-
<div class="mt-4 pt-4 border-t border-gray-100 dark:border-gray-700">
|
|
475
|
-
<p class="text-xs text-gray-500 dark:text-gray-400">
|
|
476
|
-
<span class="font-medium">Fallback chain:</span>
|
|
477
|
-
<%= @execution.fallback_chain.join(' → ') %>
|
|
478
|
-
</p>
|
|
479
|
-
<% if @execution.fallback_reason.present? %>
|
|
480
|
-
<p class="text-xs text-amber-600 dark:text-amber-400 mt-1">
|
|
481
|
-
Fallback reason: <%= @execution.fallback_reason %>
|
|
482
|
-
</p>
|
|
483
194
|
<% end %>
|
|
484
|
-
</
|
|
485
|
-
|
|
195
|
+
</tbody>
|
|
196
|
+
</table>
|
|
486
197
|
</div>
|
|
487
|
-
<% end %>
|
|
488
198
|
|
|
489
|
-
<% if @execution.
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
Rate Limited
|
|
498
|
-
</span>
|
|
499
|
-
<% end %>
|
|
500
|
-
<% if @execution.retryable %>
|
|
501
|
-
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300">
|
|
502
|
-
Retryable
|
|
503
|
-
</span>
|
|
504
|
-
<% end %>
|
|
505
|
-
<button
|
|
506
|
-
type="button"
|
|
507
|
-
data-copy-json="<%= Base64.strict_encode64({
|
|
508
|
-
error_class: @execution.error_class,
|
|
509
|
-
error_message: @execution.error_message
|
|
510
|
-
}.to_json) %>"
|
|
511
|
-
class="copy-json-btn inline-flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md transition-colors"
|
|
512
|
-
>
|
|
513
|
-
<svg class="w-4 h-4 copy-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
514
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/>
|
|
515
|
-
</svg>
|
|
516
|
-
<svg class="w-4 h-4 check-icon hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
517
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
|
518
|
-
</svg>
|
|
519
|
-
<span>Copy</span>
|
|
520
|
-
</button>
|
|
521
|
-
</div>
|
|
199
|
+
<% if @execution.fallback_chain.present? && @execution.fallback_chain.any? %>
|
|
200
|
+
<div class="mt-3 font-mono text-xs text-gray-400 dark:text-gray-500">
|
|
201
|
+
<span class="text-gray-800 dark:text-gray-200">fallback chain:</span>
|
|
202
|
+
<%= @execution.fallback_chain.join(' → ') %>
|
|
203
|
+
<% if @execution.fallback_reason.present? %>
|
|
204
|
+
<span class="text-gray-300 dark:text-gray-700">·</span>
|
|
205
|
+
<span class="text-amber-600 dark:text-amber-400"><%= @execution.fallback_reason %></span>
|
|
206
|
+
<% end %>
|
|
522
207
|
</div>
|
|
523
|
-
|
|
524
|
-
<p class="font-mono text-sm text-red-700 dark:text-red-400 mb-2">
|
|
525
|
-
<%= @execution.error_class %>
|
|
526
|
-
</p>
|
|
527
|
-
|
|
528
|
-
<pre class="bg-red-100 dark:bg-red-900/50 rounded p-4 text-sm text-red-900 dark:text-red-200 overflow-x-auto"><%= @execution.error_message %></pre>
|
|
529
|
-
</div>
|
|
208
|
+
<% end %>
|
|
530
209
|
<% end %>
|
|
531
210
|
|
|
532
|
-
<!--
|
|
533
|
-
|
|
534
|
-
<div class="flex items-center
|
|
535
|
-
<
|
|
211
|
+
<!-- ── error ──────────────────────── -->
|
|
212
|
+
<% if @execution.status_error? %>
|
|
213
|
+
<div class="flex items-center gap-3 mt-6 mb-3">
|
|
214
|
+
<span class="text-[10px] font-medium text-gray-400 dark:text-gray-600 uppercase tracking-widest font-mono">error</span>
|
|
215
|
+
<div class="font-mono text-xs text-gray-400 dark:text-gray-500 flex items-center gap-1.5">
|
|
216
|
+
<% if @execution.rate_limited? %>
|
|
217
|
+
<span class="badge badge-sm badge-orange">rate limited</span>
|
|
218
|
+
<% end %>
|
|
219
|
+
<% if @execution.retryable %>
|
|
220
|
+
<span class="badge badge-sm badge-cyan">retryable</span>
|
|
221
|
+
<% end %>
|
|
222
|
+
</div>
|
|
223
|
+
<div class="flex-1 border-t border-gray-200 dark:border-gray-800"></div>
|
|
536
224
|
<button
|
|
537
225
|
type="button"
|
|
538
|
-
data-copy-json="<%= Base64.strict_encode64(
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
<svg class="w-4 h-4 check-icon hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
545
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
|
546
|
-
</svg>
|
|
547
|
-
<span>Copy</span>
|
|
548
|
-
</button>
|
|
226
|
+
data-copy-json="<%= Base64.strict_encode64({
|
|
227
|
+
error_class: @execution.error_class,
|
|
228
|
+
error_message: @execution.error_message
|
|
229
|
+
}.to_json) %>"
|
|
230
|
+
class="copy-json-btn font-mono text-xs text-gray-400 dark:text-gray-500 hover:text-gray-700 dark:hover:text-gray-300"
|
|
231
|
+
>copy</button>
|
|
549
232
|
</div>
|
|
550
|
-
|
|
233
|
+
|
|
234
|
+
<p class="font-mono text-xs text-red-700 dark:text-red-400 mb-2"><%= @execution.error_class %></p>
|
|
235
|
+
<pre class="bg-red-50 dark:bg-red-500/10 text-red-900 dark:text-red-200 rounded p-4 text-xs overflow-x-auto font-mono"><%= @execution.error_message %></pre>
|
|
236
|
+
<% end %>
|
|
237
|
+
|
|
238
|
+
<!-- ── parameters ──────────────────── -->
|
|
239
|
+
<div class="flex items-center gap-3 mt-6 mb-3">
|
|
240
|
+
<span class="text-[10px] font-medium text-gray-400 dark:text-gray-600 uppercase tracking-widest font-mono">parameters</span>
|
|
241
|
+
<div class="flex-1 border-t border-gray-200 dark:border-gray-800"></div>
|
|
242
|
+
<button
|
|
243
|
+
type="button"
|
|
244
|
+
data-copy-json="<%= Base64.strict_encode64(JSON.pretty_generate(@execution.parameters || {})) %>"
|
|
245
|
+
class="copy-json-btn font-mono text-xs text-gray-400 dark:text-gray-500 hover:text-gray-700 dark:hover:text-gray-300"
|
|
246
|
+
>copy</button>
|
|
551
247
|
</div>
|
|
248
|
+
<pre class="bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 rounded p-4 text-xs overflow-x-auto font-mono"><%= highlight_json(@execution.parameters || {}) %></pre>
|
|
552
249
|
|
|
553
|
-
<!--
|
|
250
|
+
<!-- ── response ──────────────────────── -->
|
|
554
251
|
<% if @execution.response.present? %>
|
|
555
|
-
<div class="
|
|
556
|
-
<
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
<svg class="w-4 h-4 copy-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
564
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/>
|
|
565
|
-
</svg>
|
|
566
|
-
<svg class="w-4 h-4 check-icon hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
567
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
|
568
|
-
</svg>
|
|
569
|
-
<span>Copy</span>
|
|
570
|
-
</button>
|
|
571
|
-
</div>
|
|
572
|
-
<pre class="bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 rounded-lg p-4 text-sm overflow-x-auto max-h-96 font-mono"><%= highlight_json(@execution.response) %></pre>
|
|
252
|
+
<div class="flex items-center gap-3 mt-6 mb-3">
|
|
253
|
+
<span class="text-[10px] font-medium text-gray-400 dark:text-gray-600 uppercase tracking-widest font-mono">response</span>
|
|
254
|
+
<div class="flex-1 border-t border-gray-200 dark:border-gray-800"></div>
|
|
255
|
+
<button
|
|
256
|
+
type="button"
|
|
257
|
+
data-copy-json="<%= Base64.strict_encode64(JSON.pretty_generate(@execution.response)) %>"
|
|
258
|
+
class="copy-json-btn font-mono text-xs text-gray-400 dark:text-gray-500 hover:text-gray-700 dark:hover:text-gray-300"
|
|
259
|
+
>copy</button>
|
|
573
260
|
</div>
|
|
261
|
+
<pre class="bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 rounded p-4 text-xs overflow-x-auto max-h-96 font-mono"><%= highlight_json(@execution.response) %></pre>
|
|
574
262
|
<% end %>
|
|
575
263
|
|
|
576
|
-
<!--
|
|
264
|
+
<!-- ── tool calls ──────────────────── -->
|
|
577
265
|
<% tool_calls = @execution.tool_calls || [] %>
|
|
578
266
|
<% tool_call_count = tool_calls.size %>
|
|
579
|
-
<div
|
|
580
|
-
<div class="flex items-center
|
|
581
|
-
<
|
|
582
|
-
|
|
583
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
|
|
584
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|
585
|
-
</svg>
|
|
586
|
-
<h3 class="text-sm font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">Tool Calls</h3>
|
|
587
|
-
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-100 dark:bg-blue-900/50 text-blue-800 dark:text-blue-300">
|
|
588
|
-
<%= tool_call_count %>
|
|
589
|
-
</span>
|
|
590
|
-
</div>
|
|
267
|
+
<div x-data="{ expanded: <%= tool_call_count <= 3 && tool_call_count > 0 %> }">
|
|
268
|
+
<div class="flex items-center gap-3 mt-6 mb-3">
|
|
269
|
+
<span class="text-[10px] font-medium text-gray-400 dark:text-gray-600 uppercase tracking-widest font-mono">tool calls (<%= tool_call_count %>)</span>
|
|
270
|
+
<div class="flex-1 border-t border-gray-200 dark:border-gray-800"></div>
|
|
591
271
|
<% if tool_call_count > 0 %>
|
|
592
|
-
<
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
</svg>
|
|
601
|
-
<svg class="w-4 h-4 check-icon hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
602
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
|
603
|
-
</svg>
|
|
604
|
-
<span>Copy</span>
|
|
272
|
+
<button
|
|
273
|
+
type="button"
|
|
274
|
+
data-copy-json="<%= Base64.strict_encode64(JSON.pretty_generate(tool_calls)) %>"
|
|
275
|
+
class="copy-json-btn font-mono text-xs text-gray-400 dark:text-gray-500 hover:text-gray-700 dark:hover:text-gray-300"
|
|
276
|
+
>copy</button>
|
|
277
|
+
<% if tool_call_count > 3 %>
|
|
278
|
+
<button type="button" @click="expanded = !expanded" class="font-mono text-xs text-gray-400 dark:text-gray-500 hover:text-gray-700 dark:hover:text-gray-300">
|
|
279
|
+
<span x-text="expanded ? 'collapse' : 'expand'">expand</span>
|
|
605
280
|
</button>
|
|
606
|
-
|
|
607
|
-
<button type="button" @click="expanded = !expanded" class="text-xs text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">
|
|
608
|
-
<span x-text="expanded ? 'Collapse' : 'Expand'">Expand</span>
|
|
609
|
-
</button>
|
|
610
|
-
<% end %>
|
|
611
|
-
</div>
|
|
281
|
+
<% end %>
|
|
612
282
|
<% end %>
|
|
613
283
|
</div>
|
|
614
284
|
|
|
615
285
|
<% if tool_call_count > 0 %>
|
|
616
|
-
<div class="space-y-
|
|
286
|
+
<div class="space-y-3" x-show="expanded" x-cloak>
|
|
617
287
|
<% tool_calls.each_with_index do |tool_call, index| %>
|
|
618
288
|
<%
|
|
619
|
-
# Handle both symbol and string keys for backward compatibility
|
|
620
289
|
tool_id = tool_call['id'] || tool_call[:id]
|
|
621
290
|
tool_name = tool_call['name'] || tool_call[:name]
|
|
622
291
|
tool_args = tool_call['arguments'] || tool_call[:arguments] || {}
|
|
@@ -626,480 +295,403 @@
|
|
|
626
295
|
tool_duration = tool_call['duration_ms'] || tool_call[:duration_ms]
|
|
627
296
|
tool_called_at = tool_call['called_at'] || tool_call[:called_at]
|
|
628
297
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
when '
|
|
632
|
-
|
|
633
|
-
else 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300'
|
|
298
|
+
status_css = case tool_status
|
|
299
|
+
when 'success' then 'badge-success'
|
|
300
|
+
when 'error' then 'badge-error'
|
|
301
|
+
else 'badge-timeout'
|
|
634
302
|
end
|
|
635
303
|
%>
|
|
636
|
-
<div class="border border-gray-
|
|
637
|
-
|
|
638
|
-
<div class="bg-gray-50 dark:bg-gray-900/50 px-4 py-3">
|
|
304
|
+
<div class="border border-gray-200 dark:border-gray-800 rounded overflow-hidden">
|
|
305
|
+
<div class="bg-gray-50 dark:bg-gray-900/50 px-3 py-2">
|
|
639
306
|
<div class="flex items-center justify-between">
|
|
640
|
-
<div class="flex items-center gap-
|
|
641
|
-
<span class="
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
<code class="text-sm font-semibold text-gray-900 dark:text-gray-100"><%= tool_name %></code>
|
|
645
|
-
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium <%= status_badge_class %>">
|
|
646
|
-
<%= tool_status %>
|
|
647
|
-
</span>
|
|
307
|
+
<div class="flex items-center gap-2 font-mono text-xs">
|
|
308
|
+
<span class="text-gray-400 dark:text-gray-600"><%= index + 1 %></span>
|
|
309
|
+
<code class="font-semibold text-gray-900 dark:text-gray-100"><%= tool_name %></code>
|
|
310
|
+
<span class="badge badge-sm <%= status_css %>"><%= tool_status %></span>
|
|
648
311
|
<% if tool_duration.present? %>
|
|
649
|
-
<span class="
|
|
650
|
-
<%= tool_duration %>ms
|
|
651
|
-
</span>
|
|
312
|
+
<span class="badge badge-sm badge-purple"><%= tool_duration %>ms</span>
|
|
652
313
|
<% end %>
|
|
653
314
|
</div>
|
|
654
|
-
<div class="flex items-center gap-
|
|
315
|
+
<div class="flex items-center gap-2 font-mono text-xs text-gray-400 dark:text-gray-600">
|
|
655
316
|
<% if tool_called_at.present? %>
|
|
656
|
-
<span
|
|
317
|
+
<span title="Called at"><%= Time.parse(tool_called_at).strftime("%H:%M:%S.%L") rescue tool_called_at %></span>
|
|
657
318
|
<% end %>
|
|
658
319
|
<% if tool_id.present? %>
|
|
659
|
-
<span class="
|
|
660
|
-
<%= tool_id.to_s.truncate(16) %>
|
|
661
|
-
</span>
|
|
320
|
+
<span class="truncate max-w-[120px]" title="<%= tool_id %>"><%= tool_id.to_s.truncate(16) %></span>
|
|
662
321
|
<% end %>
|
|
663
322
|
</div>
|
|
664
323
|
</div>
|
|
665
324
|
</div>
|
|
666
325
|
|
|
667
|
-
<!-- Tool Call Arguments -->
|
|
668
326
|
<% if tool_args.present? && tool_args.any? %>
|
|
669
|
-
<div class="px-
|
|
670
|
-
<p class="text-
|
|
671
|
-
<pre class="bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 rounded
|
|
327
|
+
<div class="px-3 py-2 border-t border-gray-100 dark:border-gray-800">
|
|
328
|
+
<p class="text-[10px] text-gray-400 dark:text-gray-600 uppercase tracking-wider font-mono mb-1">arguments</p>
|
|
329
|
+
<pre class="bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 rounded p-2 text-xs overflow-x-auto font-mono"><%= highlight_json(tool_args) %></pre>
|
|
672
330
|
</div>
|
|
673
331
|
<% else %>
|
|
674
|
-
<div class="px-
|
|
675
|
-
<p class="text-xs text-gray-400 dark:text-gray-
|
|
332
|
+
<div class="px-3 py-2 border-t border-gray-100 dark:border-gray-800">
|
|
333
|
+
<p class="text-xs text-gray-400 dark:text-gray-600 font-mono italic">no arguments</p>
|
|
676
334
|
</div>
|
|
677
335
|
<% end %>
|
|
678
336
|
|
|
679
|
-
<!-- Tool Call Result (NEW) -->
|
|
680
337
|
<% if tool_result.present? %>
|
|
681
|
-
<div class="px-
|
|
682
|
-
<p class="text-
|
|
683
|
-
<div class="bg-gray-50 dark:bg-gray-900 rounded
|
|
684
|
-
<pre class="text-
|
|
338
|
+
<div class="px-3 py-2 border-t border-gray-100 dark:border-gray-800">
|
|
339
|
+
<p class="text-[10px] text-gray-400 dark:text-gray-600 uppercase tracking-wider font-mono mb-1">result</p>
|
|
340
|
+
<div class="bg-gray-50 dark:bg-gray-900 rounded p-2 max-h-48 overflow-y-auto">
|
|
341
|
+
<pre class="text-xs text-gray-900 dark:text-gray-100 font-mono whitespace-pre-wrap break-words"><%= tool_result.is_a?(String) ? tool_result : JSON.pretty_generate(tool_result) rescue tool_result.to_s %></pre>
|
|
685
342
|
</div>
|
|
686
343
|
</div>
|
|
687
344
|
<% end %>
|
|
688
345
|
|
|
689
|
-
<!-- Tool Call Error (NEW) -->
|
|
690
346
|
<% if tool_status == 'error' && tool_error.present? %>
|
|
691
|
-
<div class="px-
|
|
692
|
-
<p class="text-
|
|
693
|
-
<pre class="text-
|
|
347
|
+
<div class="px-3 py-2 border-t border-red-100 dark:border-red-500/30 bg-red-50 dark:bg-red-500/10">
|
|
348
|
+
<p class="text-[10px] text-red-600 dark:text-red-400 uppercase tracking-wider font-mono mb-1">error</p>
|
|
349
|
+
<pre class="text-xs text-red-700 dark:text-red-300 font-mono whitespace-pre-wrap break-words"><%= tool_error %></pre>
|
|
694
350
|
</div>
|
|
695
351
|
<% end %>
|
|
696
352
|
</div>
|
|
697
353
|
<% end %>
|
|
698
354
|
</div>
|
|
699
355
|
<% else %>
|
|
700
|
-
<p class="text-
|
|
356
|
+
<p class="text-xs text-gray-400 dark:text-gray-600 font-mono italic">no tool calls</p>
|
|
701
357
|
<% end %>
|
|
702
358
|
</div>
|
|
703
359
|
|
|
704
|
-
<!--
|
|
360
|
+
<!-- ── metadata ──────────────────────── -->
|
|
705
361
|
<% if @execution.metadata.present? && @execution.metadata.any? %>
|
|
706
|
-
<div
|
|
707
|
-
<div class="flex items-center
|
|
708
|
-
<
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
<svg class="w-4 h-4 copy-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
719
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/>
|
|
720
|
-
</svg>
|
|
721
|
-
<svg class="w-4 h-4 check-icon hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
722
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
|
723
|
-
</svg>
|
|
724
|
-
<span>Copy</span>
|
|
725
|
-
</button>
|
|
726
|
-
<button type="button" @click="expanded = !expanded" class="text-xs text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">
|
|
727
|
-
<span x-text="expanded ? 'Collapse' : 'Expand'">Expand</span>
|
|
728
|
-
</button>
|
|
729
|
-
</div>
|
|
362
|
+
<div x-data="{ expanded: false }">
|
|
363
|
+
<div class="flex items-center gap-3 mt-6 mb-3">
|
|
364
|
+
<span class="text-[10px] font-medium text-gray-400 dark:text-gray-600 uppercase tracking-widest font-mono">metadata (<%= @execution.metadata.keys.count %>)</span>
|
|
365
|
+
<div class="flex-1 border-t border-gray-200 dark:border-gray-800"></div>
|
|
366
|
+
<button
|
|
367
|
+
type="button"
|
|
368
|
+
data-copy-json="<%= Base64.strict_encode64(JSON.pretty_generate(@execution.metadata)) %>"
|
|
369
|
+
class="copy-json-btn font-mono text-xs text-gray-400 dark:text-gray-500 hover:text-gray-700 dark:hover:text-gray-300"
|
|
370
|
+
>copy</button>
|
|
371
|
+
<button type="button" @click="expanded = !expanded" class="font-mono text-xs text-gray-400 dark:text-gray-500 hover:text-gray-700 dark:hover:text-gray-300">
|
|
372
|
+
<span x-text="expanded ? 'collapse' : 'expand'">expand</span>
|
|
373
|
+
</button>
|
|
730
374
|
</div>
|
|
731
|
-
<div x-show="expanded" x-cloak
|
|
732
|
-
<pre class="bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 rounded
|
|
375
|
+
<div x-show="expanded" x-cloak>
|
|
376
|
+
<pre class="bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 rounded p-4 text-xs overflow-x-auto font-mono"><%= highlight_json(@execution.metadata) %></pre>
|
|
733
377
|
</div>
|
|
734
378
|
</div>
|
|
735
379
|
<% end %>
|
|
736
380
|
|
|
737
|
-
<!--
|
|
381
|
+
<!-- ── hierarchy ──────────────────────── -->
|
|
738
382
|
<% if @execution.parent_execution_id.present? || (@execution.respond_to?(:child_executions) && @execution.child_executions.any?) %>
|
|
739
|
-
<div class="
|
|
740
|
-
<
|
|
383
|
+
<div class="flex items-center gap-3 mt-6 mb-3">
|
|
384
|
+
<span class="text-[10px] font-medium text-gray-400 dark:text-gray-600 uppercase tracking-widest font-mono">hierarchy</span>
|
|
385
|
+
<div class="flex-1 border-t border-gray-200 dark:border-gray-800"></div>
|
|
386
|
+
</div>
|
|
741
387
|
|
|
388
|
+
<div class="font-mono text-xs space-y-1.5">
|
|
742
389
|
<% if @execution.parent_execution_id.present? %>
|
|
743
|
-
<div class="
|
|
744
|
-
|
|
390
|
+
<div class="text-gray-400 dark:text-gray-500">
|
|
391
|
+
parent:
|
|
745
392
|
<%= link_to "##{@execution.parent_execution_id}",
|
|
746
393
|
ruby_llm_agents.execution_path(@execution.parent_execution_id),
|
|
747
|
-
class: "
|
|
394
|
+
class: "text-blue-600 dark:text-blue-400 hover:underline" %>
|
|
748
395
|
</div>
|
|
749
396
|
<% end %>
|
|
750
397
|
|
|
751
398
|
<% if @execution.respond_to?(:child_executions) && @execution.child_executions.any? %>
|
|
752
|
-
<div>
|
|
753
|
-
|
|
754
|
-
<
|
|
399
|
+
<div class="text-gray-400 dark:text-gray-500">
|
|
400
|
+
children (<%= @execution.child_executions.count %>):
|
|
401
|
+
<span class="inline-flex flex-wrap gap-1.5 ml-1">
|
|
755
402
|
<% @execution.child_executions.limit(10).each do |child| %>
|
|
756
403
|
<%= link_to "##{child.id}",
|
|
757
404
|
ruby_llm_agents.execution_path(child),
|
|
758
|
-
class: "
|
|
405
|
+
class: "text-blue-600 dark:text-blue-400 hover:underline" %>
|
|
759
406
|
<% end %>
|
|
760
407
|
<% if @execution.child_executions.count > 10 %>
|
|
761
|
-
<span
|
|
408
|
+
<span>+<%= @execution.child_executions.count - 10 %> more</span>
|
|
762
409
|
<% end %>
|
|
763
|
-
</
|
|
410
|
+
</span>
|
|
764
411
|
</div>
|
|
765
412
|
<% end %>
|
|
766
413
|
</div>
|
|
767
414
|
<% end %>
|
|
768
415
|
|
|
769
|
-
<!--
|
|
416
|
+
<!-- ── system prompt ──────────────────── -->
|
|
770
417
|
<% if @execution.system_prompt.present? %>
|
|
771
|
-
<div class="
|
|
772
|
-
<
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
</div>
|
|
778
|
-
<p id="system-prompt-preview" class="text-sm text-gray-600 dark:text-gray-300 font-mono bg-gray-50 dark:bg-gray-900 rounded-lg p-3 truncate"><%= @execution.system_prompt.truncate(150) %></p>
|
|
779
|
-
<pre id="system-prompt-content" class="hidden bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 rounded-lg p-4 text-sm overflow-x-auto max-h-96 font-mono whitespace-pre-wrap"><%= @execution.system_prompt %></pre>
|
|
418
|
+
<div class="flex items-center gap-3 mt-6 mb-3">
|
|
419
|
+
<span class="text-[10px] font-medium text-gray-400 dark:text-gray-600 uppercase tracking-widest font-mono">system prompt</span>
|
|
420
|
+
<div class="flex-1 border-t border-gray-200 dark:border-gray-800"></div>
|
|
421
|
+
<button type="button" onclick="togglePrompt('system')" class="font-mono text-xs text-gray-400 dark:text-gray-500 hover:text-gray-700 dark:hover:text-gray-300">
|
|
422
|
+
<span id="system-prompt-toggle">expand</span>
|
|
423
|
+
</button>
|
|
780
424
|
</div>
|
|
425
|
+
<p id="system-prompt-preview" class="text-xs text-gray-600 dark:text-gray-300 font-mono bg-gray-50 dark:bg-gray-900 rounded p-3 truncate"><%= @execution.system_prompt.truncate(150) %></p>
|
|
426
|
+
<pre id="system-prompt-content" class="hidden bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 rounded p-4 text-xs overflow-x-auto max-h-96 font-mono whitespace-pre-wrap"><%= @execution.system_prompt %></pre>
|
|
781
427
|
<% end %>
|
|
782
428
|
|
|
783
|
-
<!--
|
|
429
|
+
<!-- ── user prompt ──────────────────── -->
|
|
784
430
|
<% if @execution.user_prompt.present? %>
|
|
785
|
-
<div class="
|
|
786
|
-
<
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
</div>
|
|
792
|
-
<p id="user-prompt-preview" class="text-sm text-gray-600 dark:text-gray-300 font-mono bg-gray-50 dark:bg-gray-900 rounded-lg p-3 truncate"><%= @execution.user_prompt.truncate(150) %></p>
|
|
793
|
-
<pre id="user-prompt-content" class="hidden bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 rounded-lg p-4 text-sm overflow-x-auto max-h-96 font-mono whitespace-pre-wrap"><%= @execution.user_prompt %></pre>
|
|
431
|
+
<div class="flex items-center gap-3 mt-6 mb-3">
|
|
432
|
+
<span class="text-[10px] font-medium text-gray-400 dark:text-gray-600 uppercase tracking-widest font-mono">user prompt</span>
|
|
433
|
+
<div class="flex-1 border-t border-gray-200 dark:border-gray-800"></div>
|
|
434
|
+
<button type="button" onclick="togglePrompt('user')" class="font-mono text-xs text-gray-400 dark:text-gray-500 hover:text-gray-700 dark:hover:text-gray-300">
|
|
435
|
+
<span id="user-prompt-toggle">expand</span>
|
|
436
|
+
</button>
|
|
794
437
|
</div>
|
|
438
|
+
<p id="user-prompt-preview" class="text-xs text-gray-600 dark:text-gray-300 font-mono bg-gray-50 dark:bg-gray-900 rounded p-3 truncate"><%= @execution.user_prompt.truncate(150) %></p>
|
|
439
|
+
<pre id="user-prompt-content" class="hidden bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 rounded p-4 text-xs overflow-x-auto max-h-96 font-mono whitespace-pre-wrap"><%= @execution.user_prompt %></pre>
|
|
795
440
|
<% end %>
|
|
796
441
|
|
|
797
|
-
<!--
|
|
442
|
+
<!-- ── conversation ──────────────────── -->
|
|
798
443
|
<% if @execution.respond_to?(:messages_count) && @execution.messages_count.to_i > 0 %>
|
|
799
444
|
<%
|
|
800
445
|
messages_summary = @execution.messages_summary || {}
|
|
801
|
-
# Handle both string and symbol keys
|
|
802
446
|
first_message = messages_summary["first"] || messages_summary[:first]
|
|
803
447
|
last_message = messages_summary["last"] || messages_summary[:last]
|
|
804
448
|
max_len = RubyLLM::Agents.configuration.messages_summary_max_length || 500
|
|
805
449
|
%>
|
|
806
|
-
<div class="
|
|
807
|
-
<
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
</svg>
|
|
811
|
-
<h3 class="text-sm font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">Conversation Context</h3>
|
|
812
|
-
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-indigo-100 dark:bg-indigo-900/50 text-indigo-800 dark:text-indigo-300">
|
|
813
|
-
<%= @execution.messages_count %> message<%= @execution.messages_count == 1 ? '' : 's' %>
|
|
814
|
-
</span>
|
|
815
|
-
</div>
|
|
450
|
+
<div class="flex items-center gap-3 mt-6 mb-3">
|
|
451
|
+
<span class="text-[10px] font-medium text-gray-400 dark:text-gray-600 uppercase tracking-widest font-mono">conversation (<%= @execution.messages_count %>)</span>
|
|
452
|
+
<div class="flex-1 border-t border-gray-200 dark:border-gray-800"></div>
|
|
453
|
+
</div>
|
|
816
454
|
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
</span>
|
|
836
|
-
|
|
837
|
-
<span class="text-xs text-gray-400 dark:text-gray-500 italic">(truncated)</span>
|
|
838
|
-
<% end %>
|
|
839
|
-
</div>
|
|
840
|
-
<p class="text-sm text-gray-700 dark:text-gray-300 font-mono bg-gray-50 dark:bg-gray-900 rounded-lg p-3 whitespace-pre-wrap break-words"><%= first_content %></p>
|
|
455
|
+
<div class="space-y-3">
|
|
456
|
+
<% if first_message %>
|
|
457
|
+
<%
|
|
458
|
+
first_role = first_message["role"] || first_message[:role] || "unknown"
|
|
459
|
+
first_content = first_message["content"] || first_message[:content] || ""
|
|
460
|
+
first_truncated = first_content.length >= max_len
|
|
461
|
+
role_css = case first_role.to_s
|
|
462
|
+
when "user" then "badge-cyan"
|
|
463
|
+
when "assistant" then "badge-success"
|
|
464
|
+
when "system" then "badge-timeout"
|
|
465
|
+
else "badge-timeout"
|
|
466
|
+
end
|
|
467
|
+
%>
|
|
468
|
+
<div>
|
|
469
|
+
<div class="flex items-center gap-2 mb-1 font-mono text-xs">
|
|
470
|
+
<span class="text-gray-400 dark:text-gray-600">first</span>
|
|
471
|
+
<span class="badge badge-sm <%= role_css %>"><%= first_role %></span>
|
|
472
|
+
<% if first_truncated %>
|
|
473
|
+
<span class="text-gray-400 dark:text-gray-600 italic">(truncated)</span>
|
|
474
|
+
<% end %>
|
|
841
475
|
</div>
|
|
842
|
-
|
|
476
|
+
<p class="text-xs text-gray-700 dark:text-gray-300 font-mono bg-gray-50 dark:bg-gray-900 rounded p-3 whitespace-pre-wrap break-words"><%= first_content %></p>
|
|
477
|
+
</div>
|
|
478
|
+
<% end %>
|
|
843
479
|
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
</span>
|
|
862
|
-
|
|
863
|
-
<span class="text-xs text-gray-400 dark:text-gray-500 italic">(truncated)</span>
|
|
864
|
-
<% end %>
|
|
865
|
-
</div>
|
|
866
|
-
<p class="text-sm text-gray-700 dark:text-gray-300 font-mono bg-gray-50 dark:bg-gray-900 rounded-lg p-3 whitespace-pre-wrap break-words"><%= last_content %></p>
|
|
480
|
+
<% if last_message %>
|
|
481
|
+
<%
|
|
482
|
+
last_role = last_message["role"] || last_message[:role] || "unknown"
|
|
483
|
+
last_content = last_message["content"] || last_message[:content] || ""
|
|
484
|
+
last_truncated = last_content.length >= max_len
|
|
485
|
+
role_css = case last_role.to_s
|
|
486
|
+
when "user" then "badge-cyan"
|
|
487
|
+
when "assistant" then "badge-success"
|
|
488
|
+
when "system" then "badge-timeout"
|
|
489
|
+
else "badge-timeout"
|
|
490
|
+
end
|
|
491
|
+
%>
|
|
492
|
+
<div>
|
|
493
|
+
<div class="flex items-center gap-2 mb-1 font-mono text-xs">
|
|
494
|
+
<span class="text-gray-400 dark:text-gray-600">last</span>
|
|
495
|
+
<span class="badge badge-sm <%= role_css %>"><%= last_role %></span>
|
|
496
|
+
<% if last_truncated %>
|
|
497
|
+
<span class="text-gray-400 dark:text-gray-600 italic">(truncated)</span>
|
|
498
|
+
<% end %>
|
|
867
499
|
</div>
|
|
868
|
-
|
|
500
|
+
<p class="text-xs text-gray-700 dark:text-gray-300 font-mono bg-gray-50 dark:bg-gray-900 rounded p-3 whitespace-pre-wrap break-words"><%= last_content %></p>
|
|
501
|
+
</div>
|
|
502
|
+
<% end %>
|
|
869
503
|
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
</div>
|
|
504
|
+
<% if @execution.messages_count > 2 %>
|
|
505
|
+
<p class="text-xs text-gray-400 dark:text-gray-600 font-mono text-center">
|
|
506
|
+
+ <%= @execution.messages_count - 2 %> more message<%= @execution.messages_count - 2 == 1 ? '' : 's' %> in between
|
|
507
|
+
</p>
|
|
508
|
+
<% end %>
|
|
876
509
|
</div>
|
|
877
510
|
<% end %>
|
|
878
511
|
|
|
879
|
-
<!--
|
|
880
|
-
<div
|
|
881
|
-
<div class="flex items-center
|
|
882
|
-
<
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
<button
|
|
889
|
-
type="button"
|
|
890
|
-
id="toggle-diagnostics-btn"
|
|
891
|
-
onclick="toggleDiagnostics()"
|
|
892
|
-
class="inline-flex items-center gap-1 px-2 py-1 text-xs font-medium text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition-colors"
|
|
893
|
-
>
|
|
894
|
-
<svg id="diagnostics-expand-icon" class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
895
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
|
896
|
-
</svg>
|
|
897
|
-
<span id="diagnostics-toggle-text">Expand</span>
|
|
512
|
+
<!-- ── diagnostics ──────────────────── -->
|
|
513
|
+
<div x-data="{ expanded: localStorage.getItem('ruby_llm_agents_diagnostics_expanded') !== 'false' }">
|
|
514
|
+
<div class="flex items-center gap-3 mt-6 mb-3">
|
|
515
|
+
<span class="text-[10px] font-medium text-gray-400 dark:text-gray-600 uppercase tracking-widest font-mono">diagnostics</span>
|
|
516
|
+
<div class="flex-1 border-t border-gray-200 dark:border-gray-800"></div>
|
|
517
|
+
<button type="button"
|
|
518
|
+
@click="expanded = !expanded; localStorage.setItem('ruby_llm_agents_diagnostics_expanded', expanded)"
|
|
519
|
+
class="font-mono text-xs text-gray-400 dark:text-gray-500 hover:text-gray-700 dark:hover:text-gray-300">
|
|
520
|
+
<span x-text="expanded ? 'collapse' : 'expand'">expand</span>
|
|
898
521
|
</button>
|
|
899
522
|
</div>
|
|
900
523
|
|
|
901
|
-
<!-- Quick
|
|
902
|
-
<div class="
|
|
903
|
-
<
|
|
904
|
-
|
|
905
|
-
<
|
|
906
|
-
|
|
524
|
+
<!-- Quick info (always visible) -->
|
|
525
|
+
<div class="flex flex-wrap items-center gap-x-4 gap-y-1 font-mono text-xs text-gray-400 dark:text-gray-500">
|
|
526
|
+
<span><span class="text-gray-800 dark:text-gray-200"><%= @execution.model_id %></span></span>
|
|
527
|
+
<% if @execution.temperature %>
|
|
528
|
+
<span>temp <span class="text-gray-800 dark:text-gray-200"><%= @execution.temperature %></span></span>
|
|
529
|
+
<% end %>
|
|
530
|
+
<span><span class="text-gray-800 dark:text-gray-200"><%= @execution.status %></span></span>
|
|
531
|
+
</div>
|
|
532
|
+
|
|
533
|
+
<!-- Expanded details -->
|
|
534
|
+
<div x-show="expanded" x-cloak class="mt-4 space-y-4">
|
|
535
|
+
<!-- Timing -->
|
|
907
536
|
<div>
|
|
908
|
-
<
|
|
909
|
-
<
|
|
537
|
+
<p class="text-[10px] text-gray-400 dark:text-gray-600 uppercase tracking-wider font-mono mb-1.5">timing</p>
|
|
538
|
+
<dl class="font-mono text-xs space-y-1">
|
|
539
|
+
<div class="flex justify-between">
|
|
540
|
+
<dt class="text-gray-400 dark:text-gray-500">started</dt>
|
|
541
|
+
<dd class="text-gray-900 dark:text-gray-100"><%= @execution.started_at&.strftime("%Y-%m-%d %H:%M:%S.%L") || 'N/A' %></dd>
|
|
542
|
+
</div>
|
|
543
|
+
<div class="flex justify-between">
|
|
544
|
+
<dt class="text-gray-400 dark:text-gray-500">completed</dt>
|
|
545
|
+
<dd class="text-gray-900 dark:text-gray-100"><%= @execution.completed_at&.strftime("%Y-%m-%d %H:%M:%S.%L") || 'N/A' %></dd>
|
|
546
|
+
</div>
|
|
547
|
+
<div class="flex justify-between">
|
|
548
|
+
<dt class="text-gray-400 dark:text-gray-500">duration</dt>
|
|
549
|
+
<dd class="text-gray-900 dark:text-gray-100"><%= @execution.duration_ms ? "#{@execution.duration_ms}ms" : 'N/A' %></dd>
|
|
550
|
+
</div>
|
|
551
|
+
<% if @execution.streaming? && @execution.time_to_first_token_ms %>
|
|
552
|
+
<div class="flex justify-between">
|
|
553
|
+
<dt class="text-gray-400 dark:text-gray-500">time to first token</dt>
|
|
554
|
+
<dd class="text-gray-900 dark:text-gray-100"><%= @execution.time_to_first_token_ms %>ms</dd>
|
|
555
|
+
</div>
|
|
556
|
+
<% end %>
|
|
557
|
+
<div class="flex justify-between">
|
|
558
|
+
<dt class="text-gray-400 dark:text-gray-500">created at</dt>
|
|
559
|
+
<dd class="text-gray-900 dark:text-gray-100"><%= @execution.created_at.strftime("%Y-%m-%d %H:%M:%S") %></dd>
|
|
560
|
+
</div>
|
|
561
|
+
</dl>
|
|
910
562
|
</div>
|
|
563
|
+
|
|
564
|
+
<!-- Performance -->
|
|
911
565
|
<div>
|
|
912
|
-
<
|
|
913
|
-
<
|
|
566
|
+
<p class="text-[10px] text-gray-400 dark:text-gray-600 uppercase tracking-wider font-mono mb-1.5">performance</p>
|
|
567
|
+
<dl class="font-mono text-xs space-y-1">
|
|
568
|
+
<div class="flex justify-between">
|
|
569
|
+
<dt class="text-gray-400 dark:text-gray-500">tokens/second</dt>
|
|
570
|
+
<dd class="text-gray-900 dark:text-gray-100"><%= @execution.tokens_per_second&.round(1) || 'N/A' %></dd>
|
|
571
|
+
</div>
|
|
572
|
+
<div class="flex justify-between">
|
|
573
|
+
<dt class="text-gray-400 dark:text-gray-500">input tokens</dt>
|
|
574
|
+
<dd class="text-gray-900 dark:text-gray-100"><%= @execution.input_tokens || 0 %></dd>
|
|
575
|
+
</div>
|
|
576
|
+
<div class="flex justify-between">
|
|
577
|
+
<dt class="text-gray-400 dark:text-gray-500">output tokens</dt>
|
|
578
|
+
<dd class="text-gray-900 dark:text-gray-100"><%= @execution.output_tokens || 0 %></dd>
|
|
579
|
+
</div>
|
|
580
|
+
<div class="flex justify-between">
|
|
581
|
+
<dt class="text-gray-400 dark:text-gray-500">cached tokens</dt>
|
|
582
|
+
<dd class="text-gray-900 dark:text-gray-100"><%= @execution.cached_tokens || 0 %></dd>
|
|
583
|
+
</div>
|
|
584
|
+
</dl>
|
|
914
585
|
</div>
|
|
586
|
+
|
|
587
|
+
<!-- Cost -->
|
|
915
588
|
<div>
|
|
916
|
-
<
|
|
917
|
-
<
|
|
589
|
+
<p class="text-[10px] text-gray-400 dark:text-gray-600 uppercase tracking-wider font-mono mb-1.5">cost</p>
|
|
590
|
+
<dl class="font-mono text-xs space-y-1">
|
|
591
|
+
<div class="flex justify-between">
|
|
592
|
+
<dt class="text-gray-400 dark:text-gray-500">input cost</dt>
|
|
593
|
+
<dd class="text-gray-900 dark:text-gray-100">$<%= number_with_precision(@execution.input_cost || 0, precision: 6) %></dd>
|
|
594
|
+
</div>
|
|
595
|
+
<div class="flex justify-between">
|
|
596
|
+
<dt class="text-gray-400 dark:text-gray-500">output cost</dt>
|
|
597
|
+
<dd class="text-gray-900 dark:text-gray-100">$<%= number_with_precision(@execution.output_cost || 0, precision: 6) %></dd>
|
|
598
|
+
</div>
|
|
599
|
+
<div class="flex justify-between">
|
|
600
|
+
<dt class="text-gray-400 dark:text-gray-500">total cost</dt>
|
|
601
|
+
<dd class="text-gray-800 dark:text-gray-200 font-semibold">$<%= number_with_precision(@execution.total_cost || 0, precision: 6) %></dd>
|
|
602
|
+
</div>
|
|
603
|
+
</dl>
|
|
918
604
|
</div>
|
|
919
|
-
</div>
|
|
920
605
|
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
606
|
+
<!-- Configuration -->
|
|
607
|
+
<div>
|
|
608
|
+
<p class="text-[10px] text-gray-400 dark:text-gray-600 uppercase tracking-wider font-mono mb-1.5">configuration</p>
|
|
609
|
+
<dl class="font-mono text-xs space-y-1">
|
|
610
|
+
<div class="flex justify-between">
|
|
611
|
+
<dt class="text-gray-400 dark:text-gray-500">agent type</dt>
|
|
612
|
+
<dd class="text-gray-900 dark:text-gray-100"><%= @execution.agent_type %></dd>
|
|
613
|
+
</div>
|
|
614
|
+
<div class="flex justify-between">
|
|
615
|
+
<dt class="text-gray-400 dark:text-gray-500">temperature</dt>
|
|
616
|
+
<dd class="text-gray-900 dark:text-gray-100"><%= @execution.temperature || 'default' %></dd>
|
|
617
|
+
</div>
|
|
618
|
+
<% if @execution.respond_to?(:chosen_model_id) && @execution.chosen_model_id.present? && @execution.chosen_model_id != @execution.model_id %>
|
|
932
619
|
<div class="flex justify-between">
|
|
933
|
-
<dt class="text-gray-
|
|
934
|
-
<dd class="
|
|
620
|
+
<dt class="text-gray-400 dark:text-gray-500">chosen model</dt>
|
|
621
|
+
<dd class="text-amber-600 dark:text-amber-400"><%= @execution.chosen_model_id %></dd>
|
|
935
622
|
</div>
|
|
623
|
+
<% end %>
|
|
624
|
+
<% if @execution.respond_to?(:attempts_count) && @execution.attempts_count && @execution.attempts_count > 1 %>
|
|
936
625
|
<div class="flex justify-between">
|
|
937
|
-
<dt class="text-gray-
|
|
938
|
-
<dd class="
|
|
626
|
+
<dt class="text-gray-400 dark:text-gray-500">retry count</dt>
|
|
627
|
+
<dd class="text-blue-600 dark:text-blue-400"><%= @execution.attempts_count - 1 %></dd>
|
|
939
628
|
</div>
|
|
940
|
-
|
|
629
|
+
<% end %>
|
|
630
|
+
</dl>
|
|
631
|
+
</div>
|
|
632
|
+
|
|
633
|
+
<!-- Tracing -->
|
|
634
|
+
<% if @execution.trace_id.present? || @execution.request_id.present? %>
|
|
635
|
+
<div>
|
|
636
|
+
<p class="text-[10px] text-gray-400 dark:text-gray-600 uppercase tracking-wider font-mono mb-1.5">tracing</p>
|
|
637
|
+
<dl class="font-mono text-xs space-y-1">
|
|
638
|
+
<% if @execution.request_id.present? %>
|
|
941
639
|
<div class="flex justify-between">
|
|
942
|
-
<dt class="text-gray-
|
|
943
|
-
<dd class="
|
|
640
|
+
<dt class="text-gray-400 dark:text-gray-500">request id</dt>
|
|
641
|
+
<dd class="text-gray-900 dark:text-gray-100 text-[11px]"><%= @execution.request_id %></dd>
|
|
642
|
+
</div>
|
|
643
|
+
<% end %>
|
|
644
|
+
<% if @execution.trace_id.present? %>
|
|
645
|
+
<div class="flex justify-between">
|
|
646
|
+
<dt class="text-gray-400 dark:text-gray-500">trace id</dt>
|
|
647
|
+
<dd class="text-gray-900 dark:text-gray-100 text-[11px]"><%= @execution.trace_id %></dd>
|
|
648
|
+
</div>
|
|
649
|
+
<% end %>
|
|
650
|
+
<% if @execution.span_id.present? %>
|
|
651
|
+
<div class="flex justify-between">
|
|
652
|
+
<dt class="text-gray-400 dark:text-gray-500">span id</dt>
|
|
653
|
+
<dd class="text-gray-900 dark:text-gray-100 text-[11px]"><%= @execution.span_id %></dd>
|
|
944
654
|
</div>
|
|
945
655
|
<% end %>
|
|
946
|
-
<div class="flex justify-between">
|
|
947
|
-
<dt class="text-gray-500 dark:text-gray-400">Created At</dt>
|
|
948
|
-
<dd class="font-mono text-gray-900 dark:text-gray-100"><%= @execution.created_at.strftime("%Y-%m-%d %H:%M:%S") %></dd>
|
|
949
|
-
</div>
|
|
950
|
-
</dl>
|
|
951
|
-
</div>
|
|
952
|
-
|
|
953
|
-
<!-- Performance Metrics -->
|
|
954
|
-
<div class="bg-gray-50 dark:bg-gray-900/50 rounded-lg p-4">
|
|
955
|
-
<h4 class="text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wide mb-3">Performance</h4>
|
|
956
|
-
<dl class="space-y-2 text-sm">
|
|
957
|
-
<div class="flex justify-between">
|
|
958
|
-
<dt class="text-gray-500 dark:text-gray-400">Tokens/Second</dt>
|
|
959
|
-
<dd class="font-mono text-gray-900 dark:text-gray-100"><%= @execution.tokens_per_second&.round(1) || 'N/A' %></dd>
|
|
960
|
-
</div>
|
|
961
|
-
<div class="flex justify-between">
|
|
962
|
-
<dt class="text-gray-500 dark:text-gray-400">Input Tokens</dt>
|
|
963
|
-
<dd class="font-mono text-gray-900 dark:text-gray-100"><%= @execution.input_tokens || 0 %></dd>
|
|
964
|
-
</div>
|
|
965
|
-
<div class="flex justify-between">
|
|
966
|
-
<dt class="text-gray-500 dark:text-gray-400">Output Tokens</dt>
|
|
967
|
-
<dd class="font-mono text-gray-900 dark:text-gray-100"><%= @execution.output_tokens || 0 %></dd>
|
|
968
|
-
</div>
|
|
969
|
-
<div class="flex justify-between">
|
|
970
|
-
<dt class="text-gray-500 dark:text-gray-400">Cached Tokens</dt>
|
|
971
|
-
<dd class="font-mono text-gray-900 dark:text-gray-100"><%= @execution.cached_tokens || 0 %></dd>
|
|
972
|
-
</div>
|
|
973
|
-
</dl>
|
|
974
|
-
</div>
|
|
975
|
-
|
|
976
|
-
<!-- Cost Breakdown -->
|
|
977
|
-
<div class="bg-gray-50 dark:bg-gray-900/50 rounded-lg p-4">
|
|
978
|
-
<h4 class="text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wide mb-3">Cost</h4>
|
|
979
|
-
<dl class="space-y-2 text-sm">
|
|
980
|
-
<div class="flex justify-between">
|
|
981
|
-
<dt class="text-gray-500 dark:text-gray-400">Input Cost</dt>
|
|
982
|
-
<dd class="font-mono text-gray-900 dark:text-gray-100">$<%= number_with_precision(@execution.input_cost || 0, precision: 6) %></dd>
|
|
983
|
-
</div>
|
|
984
|
-
<div class="flex justify-between">
|
|
985
|
-
<dt class="text-gray-500 dark:text-gray-400">Output Cost</dt>
|
|
986
|
-
<dd class="font-mono text-gray-900 dark:text-gray-100">$<%= number_with_precision(@execution.output_cost || 0, precision: 6) %></dd>
|
|
987
|
-
</div>
|
|
988
|
-
<div class="flex justify-between font-semibold">
|
|
989
|
-
<dt class="text-gray-600 dark:text-gray-300">Total Cost</dt>
|
|
990
|
-
<dd class="font-mono text-gray-900 dark:text-gray-100">$<%= number_with_precision(@execution.total_cost || 0, precision: 6) %></dd>
|
|
991
|
-
</div>
|
|
992
656
|
</dl>
|
|
993
657
|
</div>
|
|
658
|
+
<% end %>
|
|
994
659
|
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
<
|
|
999
|
-
|
|
1000
|
-
<dt class="text-gray-500 dark:text-gray-400">Agent Type</dt>
|
|
1001
|
-
<dd class="font-mono text-gray-900 dark:text-gray-100"><%= @execution.agent_type %></dd>
|
|
1002
|
-
</div>
|
|
660
|
+
<!-- Caching -->
|
|
661
|
+
<% if @execution.cache_hit || @execution.response_cache_key.present? %>
|
|
662
|
+
<div>
|
|
663
|
+
<p class="text-[10px] text-gray-400 dark:text-gray-600 uppercase tracking-wider font-mono mb-1.5">caching</p>
|
|
664
|
+
<dl class="font-mono text-xs space-y-1">
|
|
1003
665
|
<div class="flex justify-between">
|
|
1004
|
-
<dt class="text-gray-
|
|
1005
|
-
<dd class="
|
|
666
|
+
<dt class="text-gray-400 dark:text-gray-500">cache hit</dt>
|
|
667
|
+
<dd class="<%= @execution.cache_hit ? 'text-green-600 dark:text-green-400' : 'text-gray-400' %>"><%= @execution.cache_hit ? 'yes' : 'no' %></dd>
|
|
1006
668
|
</div>
|
|
1007
|
-
<% if @execution.
|
|
669
|
+
<% if @execution.response_cache_key.present? %>
|
|
1008
670
|
<div class="flex justify-between">
|
|
1009
|
-
<dt class="text-gray-
|
|
1010
|
-
<dd class="
|
|
671
|
+
<dt class="text-gray-400 dark:text-gray-500">cache key</dt>
|
|
672
|
+
<dd class="text-gray-900 dark:text-gray-100 text-[11px] truncate max-w-xs" title="<%= @execution.response_cache_key %>"><%= @execution.response_cache_key.truncate(30) %></dd>
|
|
1011
673
|
</div>
|
|
1012
674
|
<% end %>
|
|
1013
|
-
<% if @execution.
|
|
675
|
+
<% if @execution.cached_at.present? %>
|
|
1014
676
|
<div class="flex justify-between">
|
|
1015
|
-
<dt class="text-gray-
|
|
1016
|
-
<dd class="
|
|
677
|
+
<dt class="text-gray-400 dark:text-gray-500">cached at</dt>
|
|
678
|
+
<dd class="text-gray-900 dark:text-gray-100"><%= @execution.cached_at.strftime("%Y-%m-%d %H:%M:%S") %></dd>
|
|
1017
679
|
</div>
|
|
1018
680
|
<% end %>
|
|
1019
681
|
</dl>
|
|
1020
682
|
</div>
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
<!-- Second Row: Tracing and Caching -->
|
|
1024
|
-
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mt-6">
|
|
1025
|
-
<!-- Tracing Information -->
|
|
1026
|
-
<% if @execution.trace_id.present? || @execution.request_id.present? %>
|
|
1027
|
-
<div class="bg-gray-50 dark:bg-gray-900/50 rounded-lg p-4">
|
|
1028
|
-
<h4 class="text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wide mb-3">Tracing</h4>
|
|
1029
|
-
<dl class="space-y-2 text-sm">
|
|
1030
|
-
<% if @execution.request_id.present? %>
|
|
1031
|
-
<div class="flex justify-between">
|
|
1032
|
-
<dt class="text-gray-500 dark:text-gray-400">Request ID</dt>
|
|
1033
|
-
<dd class="font-mono text-gray-900 dark:text-gray-100 text-xs"><%= @execution.request_id %></dd>
|
|
1034
|
-
</div>
|
|
1035
|
-
<% end %>
|
|
1036
|
-
<% if @execution.trace_id.present? %>
|
|
1037
|
-
<div class="flex justify-between">
|
|
1038
|
-
<dt class="text-gray-500 dark:text-gray-400">Trace ID</dt>
|
|
1039
|
-
<dd class="font-mono text-gray-900 dark:text-gray-100 text-xs"><%= @execution.trace_id %></dd>
|
|
1040
|
-
</div>
|
|
1041
|
-
<% end %>
|
|
1042
|
-
<% if @execution.span_id.present? %>
|
|
1043
|
-
<div class="flex justify-between">
|
|
1044
|
-
<dt class="text-gray-500 dark:text-gray-400">Span ID</dt>
|
|
1045
|
-
<dd class="font-mono text-gray-900 dark:text-gray-100 text-xs"><%= @execution.span_id %></dd>
|
|
1046
|
-
</div>
|
|
1047
|
-
<% end %>
|
|
1048
|
-
</dl>
|
|
1049
|
-
</div>
|
|
1050
|
-
<% end %>
|
|
1051
|
-
|
|
1052
|
-
<!-- Caching Information -->
|
|
1053
|
-
<% if @execution.cache_hit || @execution.response_cache_key.present? %>
|
|
1054
|
-
<div class="bg-gray-50 dark:bg-gray-900/50 rounded-lg p-4">
|
|
1055
|
-
<h4 class="text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wide mb-3">Caching</h4>
|
|
1056
|
-
<dl class="space-y-2 text-sm">
|
|
1057
|
-
<div class="flex justify-between">
|
|
1058
|
-
<dt class="text-gray-500 dark:text-gray-400">Cache Hit</dt>
|
|
1059
|
-
<dd class="font-mono text-gray-900 dark:text-gray-100">
|
|
1060
|
-
<% if @execution.cache_hit %>
|
|
1061
|
-
<span class="text-green-600 dark:text-green-400">Yes</span>
|
|
1062
|
-
<% else %>
|
|
1063
|
-
<span class="text-gray-400">No</span>
|
|
1064
|
-
<% end %>
|
|
1065
|
-
</dd>
|
|
1066
|
-
</div>
|
|
1067
|
-
<% if @execution.response_cache_key.present? %>
|
|
1068
|
-
<div class="flex justify-between">
|
|
1069
|
-
<dt class="text-gray-500 dark:text-gray-400">Cache Key</dt>
|
|
1070
|
-
<dd class="font-mono text-gray-900 dark:text-gray-100 text-xs truncate max-w-xs" title="<%= @execution.response_cache_key %>">
|
|
1071
|
-
<%= @execution.response_cache_key.truncate(30) %>
|
|
1072
|
-
</dd>
|
|
1073
|
-
</div>
|
|
1074
|
-
<% end %>
|
|
1075
|
-
<% if @execution.cached_at.present? %>
|
|
1076
|
-
<div class="flex justify-between">
|
|
1077
|
-
<dt class="text-gray-500 dark:text-gray-400">Cached At</dt>
|
|
1078
|
-
<dd class="font-mono text-gray-900 dark:text-gray-100"><%= @execution.cached_at.strftime("%Y-%m-%d %H:%M:%S") %></dd>
|
|
1079
|
-
</div>
|
|
1080
|
-
<% end %>
|
|
1081
|
-
</dl>
|
|
1082
|
-
</div>
|
|
1083
|
-
<% end %>
|
|
1084
|
-
</div>
|
|
683
|
+
<% end %>
|
|
1085
684
|
|
|
1086
|
-
<!--
|
|
1087
|
-
<div class="
|
|
1088
|
-
<
|
|
1089
|
-
<span class="text-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
>
|
|
1097
|
-
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
1098
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/>
|
|
1099
|
-
</svg>
|
|
1100
|
-
Copy All
|
|
1101
|
-
</button>
|
|
1102
|
-
</div>
|
|
685
|
+
<!-- Footer: id + copy all -->
|
|
686
|
+
<div class="flex items-center justify-between pt-3 border-t border-gray-200 dark:border-gray-800">
|
|
687
|
+
<span class="font-mono text-xs text-gray-400 dark:text-gray-500">
|
|
688
|
+
id: <span class="text-gray-800 dark:text-gray-200"><%= @execution.id %></span>
|
|
689
|
+
</span>
|
|
690
|
+
<button
|
|
691
|
+
type="button"
|
|
692
|
+
onclick="copyDiagnostics()"
|
|
693
|
+
class="font-mono text-xs text-gray-400 dark:text-gray-500 hover:text-gray-700 dark:hover:text-gray-300"
|
|
694
|
+
>copy all</button>
|
|
1103
695
|
</div>
|
|
1104
696
|
</div>
|
|
1105
697
|
</div>
|
|
@@ -1113,88 +705,36 @@
|
|
|
1113
705
|
const isHidden = content.classList.contains('hidden');
|
|
1114
706
|
content.classList.toggle('hidden', !isHidden);
|
|
1115
707
|
if (preview) preview.classList.toggle('hidden', isHidden);
|
|
1116
|
-
toggle.textContent = isHidden ? '
|
|
708
|
+
toggle.textContent = isHidden ? 'collapse' : 'expand';
|
|
1117
709
|
}
|
|
1118
710
|
|
|
1119
711
|
document.addEventListener('DOMContentLoaded', function() {
|
|
1120
712
|
document.querySelectorAll('.copy-json-btn').forEach(function(button) {
|
|
1121
713
|
button.addEventListener('click', function() {
|
|
1122
714
|
const jsonText = atob(this.getAttribute('data-copy-json'));
|
|
1123
|
-
const
|
|
1124
|
-
const
|
|
1125
|
-
const checkIcon = this.querySelector('.check-icon');
|
|
715
|
+
const btn = this;
|
|
716
|
+
const originalText = btn.textContent;
|
|
1126
717
|
|
|
1127
718
|
navigator.clipboard.writeText(jsonText).then(function() {
|
|
1128
|
-
|
|
1129
|
-
copyIcon.classList.add('hidden');
|
|
1130
|
-
checkIcon.classList.remove('hidden');
|
|
1131
|
-
button.classList.add('text-green-600');
|
|
1132
|
-
|
|
719
|
+
btn.textContent = 'copied!';
|
|
1133
720
|
setTimeout(function() {
|
|
1134
|
-
|
|
1135
|
-
copyIcon.classList.remove('hidden');
|
|
1136
|
-
checkIcon.classList.add('hidden');
|
|
1137
|
-
button.classList.remove('text-green-600');
|
|
721
|
+
btn.textContent = originalText;
|
|
1138
722
|
}, 2000);
|
|
1139
723
|
}).catch(function(err) {
|
|
1140
724
|
console.error('Failed to copy:', err);
|
|
1141
|
-
|
|
725
|
+
btn.textContent = 'failed';
|
|
1142
726
|
setTimeout(function() {
|
|
1143
|
-
|
|
727
|
+
btn.textContent = originalText;
|
|
1144
728
|
}, 2000);
|
|
1145
729
|
});
|
|
1146
730
|
});
|
|
1147
731
|
});
|
|
1148
732
|
});
|
|
1149
733
|
|
|
1150
|
-
// Rerun modal functions
|
|
1151
|
-
function confirmRerun() {
|
|
1152
|
-
document.getElementById('rerun-modal').classList.remove('hidden');
|
|
1153
|
-
document.body.classList.add('overflow-hidden');
|
|
1154
|
-
}
|
|
1155
|
-
|
|
1156
|
-
function closeRerunModal() {
|
|
1157
|
-
document.getElementById('rerun-modal').classList.add('hidden');
|
|
1158
|
-
document.body.classList.remove('overflow-hidden');
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
// Close modal on Escape key
|
|
1162
|
-
document.addEventListener('keydown', function(e) {
|
|
1163
|
-
if (e.key === 'Escape') {
|
|
1164
|
-
closeRerunModal();
|
|
1165
|
-
}
|
|
1166
|
-
});
|
|
1167
|
-
|
|
1168
|
-
// Diagnostics panel toggle (default to expanded)
|
|
1169
|
-
let diagnosticsExpanded = localStorage.getItem('ruby_llm_agents_diagnostics_expanded') !== 'false';
|
|
1170
|
-
|
|
1171
|
-
function toggleDiagnostics() {
|
|
1172
|
-
diagnosticsExpanded = !diagnosticsExpanded;
|
|
1173
|
-
localStorage.setItem('ruby_llm_agents_diagnostics_expanded', diagnosticsExpanded);
|
|
1174
|
-
updateDiagnosticsUI();
|
|
1175
|
-
}
|
|
1176
|
-
|
|
1177
|
-
function updateDiagnosticsUI() {
|
|
1178
|
-
const details = document.getElementById('diagnostics-details');
|
|
1179
|
-
const toggleText = document.getElementById('diagnostics-toggle-text');
|
|
1180
|
-
const expandIcon = document.getElementById('diagnostics-expand-icon');
|
|
1181
|
-
|
|
1182
|
-
if (diagnosticsExpanded) {
|
|
1183
|
-
details.classList.remove('hidden');
|
|
1184
|
-
toggleText.textContent = 'Collapse';
|
|
1185
|
-
expandIcon.style.transform = 'rotate(180deg)';
|
|
1186
|
-
} else {
|
|
1187
|
-
details.classList.add('hidden');
|
|
1188
|
-
toggleText.textContent = 'Expand';
|
|
1189
|
-
expandIcon.style.transform = 'rotate(0deg)';
|
|
1190
|
-
}
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
734
|
function copyDiagnostics() {
|
|
1194
735
|
const diagnostics = {
|
|
1195
736
|
execution_id: <%= @execution.id %>,
|
|
1196
737
|
agent_type: "<%= @execution.agent_type %>",
|
|
1197
|
-
agent_version: "<%= @execution.agent_version || '1.0' %>",
|
|
1198
738
|
model_id: "<%= @execution.model_id %>",
|
|
1199
739
|
status: "<%= @execution.status %>",
|
|
1200
740
|
temperature: <%= @execution.temperature || 'null' %>,
|
|
@@ -1211,16 +751,15 @@
|
|
|
1211
751
|
created_at: "<%= @execution.created_at.iso8601 %>"
|
|
1212
752
|
};
|
|
1213
753
|
|
|
754
|
+
const btn = event.currentTarget;
|
|
1214
755
|
navigator.clipboard.writeText(JSON.stringify(diagnostics, null, 2)).then(function() {
|
|
1215
|
-
|
|
756
|
+
btn.textContent = 'copied!';
|
|
757
|
+
setTimeout(function() {
|
|
758
|
+
btn.textContent = 'copy all';
|
|
759
|
+
}, 2000);
|
|
1216
760
|
}).catch(function(err) {
|
|
1217
761
|
console.error('Failed to copy diagnostics:', err);
|
|
1218
762
|
});
|
|
1219
763
|
}
|
|
1220
|
-
|
|
1221
|
-
// Initialize diagnostics panel on page load
|
|
1222
|
-
document.addEventListener('DOMContentLoaded', function() {
|
|
1223
|
-
updateDiagnosticsUI();
|
|
1224
|
-
});
|
|
1225
764
|
</script>
|
|
1226
765
|
</div>
|