ruby_llm-agents 1.3.4 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +112 -336
- data/app/controllers/concerns/ruby_llm/agents/sortable.rb +0 -1
- data/app/controllers/ruby_llm/agents/agents_controller.rb +5 -56
- data/app/controllers/ruby_llm/agents/dashboard_controller.rb +22 -106
- data/app/controllers/ruby_llm/agents/executions_controller.rb +4 -114
- data/app/controllers/ruby_llm/agents/tenants_controller.rb +30 -2
- data/app/helpers/ruby_llm/agents/application_helper.rb +19 -53
- data/app/models/ruby_llm/agents/execution/analytics.rb +13 -54
- data/app/models/ruby_llm/agents/execution/scopes.rb +61 -14
- data/app/models/ruby_llm/agents/execution.rb +52 -12
- data/app/models/ruby_llm/agents/execution_detail.rb +18 -0
- data/app/models/ruby_llm/agents/tenant/budgetable.rb +132 -24
- data/app/models/ruby_llm/agents/tenant/incrementable.rb +117 -0
- data/app/models/ruby_llm/agents/tenant/resettable.rb +128 -0
- data/app/models/ruby_llm/agents/tenant/trackable.rb +46 -12
- data/app/models/ruby_llm/agents/tenant.rb +2 -3
- data/app/models/ruby_llm/agents/tenant_budget.rb +6 -3
- data/app/services/ruby_llm/agents/agent_registry.rb +6 -112
- data/app/views/layouts/ruby_llm/agents/application.html.erb +89 -252
- data/app/views/ruby_llm/agents/agents/_config_agent.html.erb +71 -218
- data/app/views/ruby_llm/agents/agents/_config_embedder.html.erb +20 -63
- data/app/views/ruby_llm/agents/agents/_config_image_generator.html.erb +44 -131
- data/app/views/ruby_llm/agents/agents/_config_moderator.html.erb +16 -57
- data/app/views/ruby_llm/agents/agents/_config_speaker.html.erb +39 -104
- data/app/views/ruby_llm/agents/agents/_config_transcriber.html.erb +29 -82
- data/app/views/ruby_llm/agents/agents/_empty_state.html.erb +4 -14
- data/app/views/ruby_llm/agents/agents/index.html.erb +105 -274
- data/app/views/ruby_llm/agents/agents/show.html.erb +248 -378
- data/app/views/ruby_llm/agents/dashboard/_action_center.html.erb +29 -52
- data/app/views/ruby_llm/agents/dashboard/_tenant_budget.html.erb +73 -99
- data/app/views/ruby_llm/agents/dashboard/index.html.erb +228 -433
- data/app/views/ruby_llm/agents/executions/_execution.html.erb +1 -1
- data/app/views/ruby_llm/agents/executions/_filters.html.erb +4 -25
- data/app/views/ruby_llm/agents/executions/_list.html.erb +111 -152
- data/app/views/ruby_llm/agents/executions/index.html.erb +5 -7
- data/app/views/ruby_llm/agents/executions/show.html.erb +526 -1037
- data/app/views/ruby_llm/agents/shared/_agent_type_badge.html.erb +5 -21
- data/app/views/ruby_llm/agents/shared/_executions_table.html.erb +70 -191
- data/app/views/ruby_llm/agents/shared/_filter_dropdown.html.erb +16 -44
- data/app/views/ruby_llm/agents/shared/_select_dropdown.html.erb +12 -41
- data/app/views/ruby_llm/agents/shared/_status_badge.html.erb +11 -65
- data/app/views/ruby_llm/agents/shared/_tenant_filter.html.erb +6 -5
- data/app/views/ruby_llm/agents/system_config/show.html.erb +240 -351
- data/app/views/ruby_llm/agents/tenants/_form.html.erb +67 -77
- data/app/views/ruby_llm/agents/tenants/edit.html.erb +7 -9
- data/app/views/ruby_llm/agents/tenants/index.html.erb +100 -122
- data/app/views/ruby_llm/agents/tenants/show.html.erb +146 -336
- data/config/routes.rb +0 -13
- data/lib/generators/ruby_llm_agents/install_generator.rb +13 -17
- data/lib/generators/ruby_llm_agents/migrate_structure_generator.rb +2 -12
- data/lib/generators/ruby_llm_agents/restructure_generator.rb +0 -2
- data/lib/generators/ruby_llm_agents/templates/add_usage_counters_to_tenants_migration.rb.tt +37 -0
- data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +1 -2
- data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +1 -1
- data/lib/generators/ruby_llm_agents/templates/application_image_pipeline.rb.tt +0 -1
- data/lib/generators/ruby_llm_agents/templates/create_execution_details_migration.rb.tt +27 -0
- data/lib/generators/ruby_llm_agents/templates/create_tenants_migration.rb.tt +25 -0
- data/lib/generators/ruby_llm_agents/templates/image_pipeline.rb.tt +0 -1
- data/lib/generators/ruby_llm_agents/templates/initializer.rb.tt +33 -12
- data/lib/generators/ruby_llm_agents/templates/migration.rb.tt +40 -71
- data/lib/generators/ruby_llm_agents/templates/remove_agent_version_migration.rb.tt +13 -0
- data/lib/generators/ruby_llm_agents/templates/remove_workflow_columns_migration.rb.tt +19 -0
- data/lib/generators/ruby_llm_agents/templates/skills/AGENTS.md.tt +2 -4
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_PIPELINES.md.tt +0 -1
- data/lib/generators/ruby_llm_agents/templates/split_execution_details_migration.rb.tt +232 -0
- data/lib/generators/ruby_llm_agents/upgrade_generator.rb +77 -259
- data/lib/ruby_llm/agents/audio/speaker.rb +0 -1
- data/lib/ruby_llm/agents/audio/transcriber.rb +0 -1
- data/lib/ruby_llm/agents/base_agent.rb +54 -23
- data/lib/ruby_llm/agents/core/base/callbacks.rb +142 -0
- data/lib/ruby_llm/agents/core/base.rb +23 -55
- data/lib/ruby_llm/agents/core/configuration.rb +97 -117
- data/lib/ruby_llm/agents/core/errors.rb +0 -58
- data/lib/ruby_llm/agents/core/instrumentation.rb +157 -110
- data/lib/ruby_llm/agents/core/llm_tenant.rb +8 -7
- data/lib/ruby_llm/agents/core/version.rb +1 -1
- data/lib/ruby_llm/agents/dsl/base.rb +157 -17
- data/lib/ruby_llm/agents/dsl/caching.rb +33 -2
- data/lib/ruby_llm/agents/dsl/reliability.rb +148 -0
- data/lib/ruby_llm/agents/dsl.rb +1 -2
- data/lib/ruby_llm/agents/image/analyzer/execution.rb +1 -2
- data/lib/ruby_llm/agents/image/background_remover/execution.rb +1 -2
- data/lib/ruby_llm/agents/image/concerns/image_operation_dsl.rb +1 -13
- data/lib/ruby_llm/agents/image/concerns/image_operation_execution.rb +2 -2
- data/lib/ruby_llm/agents/image/editor/dsl.rb +0 -14
- data/lib/ruby_llm/agents/image/editor/execution.rb +1 -10
- data/lib/ruby_llm/agents/image/editor.rb +0 -1
- data/lib/ruby_llm/agents/image/generator.rb +0 -21
- data/lib/ruby_llm/agents/image/pipeline/dsl.rb +0 -13
- data/lib/ruby_llm/agents/image/pipeline/execution.rb +0 -1
- data/lib/ruby_llm/agents/image/transformer/dsl.rb +0 -13
- data/lib/ruby_llm/agents/image/transformer/execution.rb +1 -10
- data/lib/ruby_llm/agents/image/transformer.rb +0 -1
- data/lib/ruby_llm/agents/image/upscaler/execution.rb +1 -2
- data/lib/ruby_llm/agents/image/variator/execution.rb +1 -2
- data/lib/ruby_llm/agents/infrastructure/alert_manager.rb +78 -173
- data/lib/ruby_llm/agents/infrastructure/budget/budget_query.rb +66 -2
- data/lib/ruby_llm/agents/infrastructure/budget/spend_recorder.rb +0 -12
- data/lib/ruby_llm/agents/infrastructure/circuit_breaker.rb +10 -13
- data/lib/ruby_llm/agents/infrastructure/execution_logger_job.rb +8 -0
- data/lib/ruby_llm/agents/pipeline/context.rb +0 -1
- data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +28 -4
- data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +3 -10
- data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +88 -55
- data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +5 -41
- data/lib/ruby_llm/agents/rails/engine.rb +6 -6
- data/lib/ruby_llm/agents/results/base.rb +1 -49
- data/lib/ruby_llm/agents/text/embedder.rb +0 -1
- data/lib/ruby_llm/agents.rb +1 -9
- data/lib/tasks/ruby_llm_agents.rake +34 -0
- metadata +14 -83
- data/app/controllers/ruby_llm/agents/api_configurations_controller.rb +0 -214
- data/app/controllers/ruby_llm/agents/workflows_controller.rb +0 -544
- data/app/mailers/ruby_llm/agents/alert_mailer.rb +0 -84
- data/app/mailers/ruby_llm/agents/application_mailer.rb +0 -28
- data/app/models/ruby_llm/agents/api_configuration.rb +0 -386
- data/app/models/ruby_llm/agents/execution/workflow.rb +0 -170
- data/app/models/ruby_llm/agents/tenant/configurable.rb +0 -135
- data/app/views/ruby_llm/agents/agents/_agent.html.erb +0 -98
- data/app/views/ruby_llm/agents/agents/_version_comparison.html.erb +0 -186
- data/app/views/ruby_llm/agents/agents/_workflow.html.erb +0 -126
- data/app/views/ruby_llm/agents/alert_mailer/alert_notification.html.erb +0 -107
- data/app/views/ruby_llm/agents/alert_mailer/alert_notification.text.erb +0 -18
- data/app/views/ruby_llm/agents/api_configurations/_api_key_field.html.erb +0 -34
- data/app/views/ruby_llm/agents/api_configurations/_form.html.erb +0 -288
- data/app/views/ruby_llm/agents/api_configurations/edit.html.erb +0 -95
- data/app/views/ruby_llm/agents/api_configurations/edit_tenant.html.erb +0 -97
- data/app/views/ruby_llm/agents/api_configurations/show.html.erb +0 -214
- data/app/views/ruby_llm/agents/api_configurations/tenant.html.erb +0 -179
- data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +0 -73
- data/app/views/ruby_llm/agents/dashboard/_alerts_feed.html.erb +0 -62
- data/app/views/ruby_llm/agents/dashboard/_breaker_strip.html.erb +0 -47
- data/app/views/ruby_llm/agents/dashboard/_budgets_bar.html.erb +0 -75
- data/app/views/ruby_llm/agents/dashboard/_model_comparison.html.erb +0 -56
- data/app/views/ruby_llm/agents/dashboard/_model_cost_breakdown.html.erb +0 -115
- data/app/views/ruby_llm/agents/dashboard/_now_strip.html.erb +0 -59
- data/app/views/ruby_llm/agents/dashboard/_top_errors.html.erb +0 -60
- data/app/views/ruby_llm/agents/executions/_workflow_summary.html.erb +0 -86
- data/app/views/ruby_llm/agents/executions/dry_run.html.erb +0 -149
- data/app/views/ruby_llm/agents/shared/_breadcrumbs.html.erb +0 -48
- data/app/views/ruby_llm/agents/shared/_nav_link.html.erb +0 -27
- data/app/views/ruby_llm/agents/shared/_stat_card.html.erb +0 -14
- data/app/views/ruby_llm/agents/shared/_workflow_type_badge.html.erb +0 -35
- data/app/views/ruby_llm/agents/workflows/_empty_state.html.erb +0 -22
- data/app/views/ruby_llm/agents/workflows/_step_performance.html.erb +0 -228
- data/app/views/ruby_llm/agents/workflows/_structure_dsl.html.erb +0 -539
- data/app/views/ruby_llm/agents/workflows/_structure_parallel.html.erb +0 -76
- data/app/views/ruby_llm/agents/workflows/_structure_pipeline.html.erb +0 -74
- data/app/views/ruby_llm/agents/workflows/_structure_router.html.erb +0 -108
- data/app/views/ruby_llm/agents/workflows/_workflow_diagram.html.erb +0 -920
- data/app/views/ruby_llm/agents/workflows/index.html.erb +0 -179
- data/app/views/ruby_llm/agents/workflows/show.html.erb +0 -467
- data/lib/generators/ruby_llm_agents/api_configuration_generator.rb +0 -100
- data/lib/generators/ruby_llm_agents/templates/add_workflow_migration.rb.tt +0 -38
- data/lib/generators/ruby_llm_agents/templates/application_workflow.rb.tt +0 -48
- data/lib/generators/ruby_llm_agents/templates/create_api_configurations_migration.rb.tt +0 -90
- data/lib/generators/ruby_llm_agents/templates/skills/WORKFLOWS.md.tt +0 -551
- data/lib/ruby_llm/agents/core/base/moderation_dsl.rb +0 -181
- data/lib/ruby_llm/agents/core/base/moderation_execution.rb +0 -274
- data/lib/ruby_llm/agents/core/resolved_config.rb +0 -348
- data/lib/ruby_llm/agents/image/generator/content_policy.rb +0 -95
- data/lib/ruby_llm/agents/infrastructure/redactor.rb +0 -130
- data/lib/ruby_llm/agents/results/moderation_result.rb +0 -158
- data/lib/ruby_llm/agents/text/moderator.rb +0 -237
- data/lib/ruby_llm/agents/workflow/approval.rb +0 -205
- data/lib/ruby_llm/agents/workflow/approval_store.rb +0 -179
- data/lib/ruby_llm/agents/workflow/async.rb +0 -220
- data/lib/ruby_llm/agents/workflow/async_executor.rb +0 -156
- data/lib/ruby_llm/agents/workflow/dsl/executor.rb +0 -467
- data/lib/ruby_llm/agents/workflow/dsl/input_schema.rb +0 -244
- data/lib/ruby_llm/agents/workflow/dsl/iteration_executor.rb +0 -289
- data/lib/ruby_llm/agents/workflow/dsl/parallel_group.rb +0 -107
- data/lib/ruby_llm/agents/workflow/dsl/route_builder.rb +0 -150
- data/lib/ruby_llm/agents/workflow/dsl/schedule_helpers.rb +0 -187
- data/lib/ruby_llm/agents/workflow/dsl/step_config.rb +0 -352
- data/lib/ruby_llm/agents/workflow/dsl/step_executor.rb +0 -415
- data/lib/ruby_llm/agents/workflow/dsl/wait_config.rb +0 -257
- data/lib/ruby_llm/agents/workflow/dsl/wait_executor.rb +0 -317
- data/lib/ruby_llm/agents/workflow/dsl.rb +0 -576
- data/lib/ruby_llm/agents/workflow/instrumentation.rb +0 -249
- data/lib/ruby_llm/agents/workflow/notifiers/base.rb +0 -117
- data/lib/ruby_llm/agents/workflow/notifiers/email.rb +0 -117
- data/lib/ruby_llm/agents/workflow/notifiers/slack.rb +0 -180
- data/lib/ruby_llm/agents/workflow/notifiers/webhook.rb +0 -121
- data/lib/ruby_llm/agents/workflow/notifiers.rb +0 -70
- data/lib/ruby_llm/agents/workflow/orchestrator.rb +0 -416
- data/lib/ruby_llm/agents/workflow/result.rb +0 -592
- data/lib/ruby_llm/agents/workflow/thread_pool.rb +0 -185
- data/lib/ruby_llm/agents/workflow/throttle_manager.rb +0 -206
- data/lib/ruby_llm/agents/workflow/wait_result.rb +0 -213
|
@@ -1,672 +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
|
-
|
|
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 %>
|
|
411
122
|
|
|
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
|
-
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
|
460
|
-
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
|
|
461
|
-
</svg>
|
|
462
|
-
Success
|
|
463
|
-
</span>
|
|
464
|
-
<% end %>
|
|
465
|
-
</td>
|
|
466
|
-
<td class="px-3 py-2 text-sm text-gray-500 dark:text-gray-400">
|
|
467
|
-
<%= attempt['duration_ms'] ? "#{attempt['duration_ms']}ms" : '-' %>
|
|
468
|
-
</td>
|
|
469
|
-
<td class="px-3 py-2 text-sm text-gray-500 dark:text-gray-400">
|
|
470
|
-
<% if attempt['input_tokens'] || attempt['output_tokens'] %>
|
|
471
|
-
<span class="text-blue-600 dark:text-blue-400"><%= attempt['input_tokens'] || 0 %></span>
|
|
472
|
-
/
|
|
473
|
-
<span class="text-green-600 dark:text-green-400"><%= attempt['output_tokens'] || 0 %></span>
|
|
474
|
-
<% else %>
|
|
475
|
-
-
|
|
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>
|
|
476
170
|
<% end %>
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
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 %>
|
|
484
189
|
</div>
|
|
485
|
-
|
|
486
|
-
<%= attempt['error_message'].to_s.truncate(150) %>
|
|
487
|
-
</p>
|
|
488
|
-
<% if attempt['error_backtrace'].present? %>
|
|
489
|
-
<button onclick="var el = document.getElementById('backtrace-<%= index %>'); el.classList.toggle('hidden');"
|
|
490
|
-
class="text-xs text-gray-500 dark:text-gray-400 hover:text-red-500 dark:hover:text-red-400 mt-1 underline">
|
|
491
|
-
Stack trace
|
|
492
|
-
</button>
|
|
493
|
-
<% end %>
|
|
494
|
-
<% else %>
|
|
495
|
-
<span class="text-gray-400">-</span>
|
|
496
|
-
<% end %>
|
|
190
|
+
</div>
|
|
497
191
|
</td>
|
|
498
192
|
</tr>
|
|
499
|
-
<% if attempt['error_backtrace'].present? %>
|
|
500
|
-
<tr id="backtrace-<%= index %>" class="hidden">
|
|
501
|
-
<td colspan="6" class="px-3 py-2">
|
|
502
|
-
<div class="bg-gray-900 text-gray-100 text-xs font-mono p-3 rounded-lg max-h-64 overflow-auto whitespace-pre-wrap relative group/bt">
|
|
503
|
-
<button onclick="var el = document.getElementById('backtrace-text-<%= index %>'); navigator.clipboard.writeText(el.textContent.trim()).then(function() { var btn = event.currentTarget; btn.querySelector('span').textContent = 'Copied!'; setTimeout(function() { btn.querySelector('span').textContent = 'Copy'; }, 2000); });"
|
|
504
|
-
class="absolute top-2 right-2 inline-flex items-center gap-1 px-2 py-0.5 text-xs text-gray-400 hover:text-white bg-gray-800 hover:bg-gray-700 rounded transition-colors">
|
|
505
|
-
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"/></svg>
|
|
506
|
-
<span>Copy</span>
|
|
507
|
-
</button>
|
|
508
|
-
<div id="backtrace-text-<%= index %>">
|
|
509
|
-
<div class="font-semibold text-red-400 mb-1"><%= attempt['error_class'] %>: <%= attempt['error_message'].to_s.truncate(200) %></div>
|
|
510
|
-
<% attempt['error_backtrace'].each do |line| %>
|
|
511
|
-
<div class="text-gray-300 leading-relaxed"><%= line %></div>
|
|
512
|
-
<% end %>
|
|
513
|
-
</div>
|
|
514
|
-
</div>
|
|
515
|
-
</td>
|
|
516
|
-
</tr>
|
|
517
|
-
<% end %>
|
|
518
193
|
<% end %>
|
|
519
|
-
</tbody>
|
|
520
|
-
</table>
|
|
521
|
-
</div>
|
|
522
|
-
|
|
523
|
-
<% if @execution.fallback_chain.present? && @execution.fallback_chain.any? %>
|
|
524
|
-
<div class="mt-4 pt-4 border-t border-gray-100 dark:border-gray-700">
|
|
525
|
-
<p class="text-xs text-gray-500 dark:text-gray-400">
|
|
526
|
-
<span class="font-medium">Fallback chain:</span>
|
|
527
|
-
<%= @execution.fallback_chain.join(' → ') %>
|
|
528
|
-
</p>
|
|
529
|
-
<% if @execution.fallback_reason.present? %>
|
|
530
|
-
<p class="text-xs text-amber-600 dark:text-amber-400 mt-1">
|
|
531
|
-
Fallback reason: <%= @execution.fallback_reason %>
|
|
532
|
-
</p>
|
|
533
194
|
<% end %>
|
|
534
|
-
</
|
|
535
|
-
|
|
195
|
+
</tbody>
|
|
196
|
+
</table>
|
|
536
197
|
</div>
|
|
537
|
-
<% end %>
|
|
538
198
|
|
|
539
|
-
<% if @execution.
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
Rate Limited
|
|
548
|
-
</span>
|
|
549
|
-
<% end %>
|
|
550
|
-
<% if @execution.retryable %>
|
|
551
|
-
<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">
|
|
552
|
-
Retryable
|
|
553
|
-
</span>
|
|
554
|
-
<% end %>
|
|
555
|
-
<button
|
|
556
|
-
type="button"
|
|
557
|
-
data-copy-json="<%= Base64.strict_encode64({
|
|
558
|
-
error_class: @execution.error_class,
|
|
559
|
-
error_message: @execution.error_message
|
|
560
|
-
}.to_json) %>"
|
|
561
|
-
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"
|
|
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>
|
|
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 %>
|
|
572
207
|
</div>
|
|
573
|
-
|
|
574
|
-
<p class="font-mono text-sm text-red-700 dark:text-red-400 mb-2">
|
|
575
|
-
<%= @execution.error_class %>
|
|
576
|
-
</p>
|
|
577
|
-
|
|
578
|
-
<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>
|
|
579
|
-
</div>
|
|
208
|
+
<% end %>
|
|
580
209
|
<% end %>
|
|
581
210
|
|
|
582
|
-
<!--
|
|
583
|
-
|
|
584
|
-
<div class="flex items-center
|
|
585
|
-
<
|
|
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>
|
|
586
224
|
<button
|
|
587
225
|
type="button"
|
|
588
|
-
data-copy-json="<%= Base64.strict_encode64(
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
<svg class="w-4 h-4 check-icon hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
595
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
|
596
|
-
</svg>
|
|
597
|
-
<span>Copy</span>
|
|
598
|
-
</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>
|
|
599
232
|
</div>
|
|
600
|
-
|
|
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>
|
|
601
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>
|
|
602
249
|
|
|
603
|
-
<!--
|
|
250
|
+
<!-- ── response ──────────────────────── -->
|
|
604
251
|
<% if @execution.response.present? %>
|
|
605
|
-
<div class="
|
|
606
|
-
<
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
<svg class="w-4 h-4 copy-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
614
|
-
<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"/>
|
|
615
|
-
</svg>
|
|
616
|
-
<svg class="w-4 h-4 check-icon hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
617
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
|
618
|
-
</svg>
|
|
619
|
-
<span>Copy</span>
|
|
620
|
-
</button>
|
|
621
|
-
</div>
|
|
622
|
-
<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>
|
|
623
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>
|
|
624
262
|
<% end %>
|
|
625
263
|
|
|
626
|
-
<!--
|
|
264
|
+
<!-- ── tool calls ──────────────────── -->
|
|
627
265
|
<% tool_calls = @execution.tool_calls || [] %>
|
|
628
266
|
<% tool_call_count = tool_calls.size %>
|
|
629
|
-
<div
|
|
630
|
-
<div class="flex items-center
|
|
631
|
-
<
|
|
632
|
-
|
|
633
|
-
<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"/>
|
|
634
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|
635
|
-
</svg>
|
|
636
|
-
<h3 class="text-sm font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">Tool Calls</h3>
|
|
637
|
-
<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">
|
|
638
|
-
<%= tool_call_count %>
|
|
639
|
-
</span>
|
|
640
|
-
</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>
|
|
641
271
|
<% if tool_call_count > 0 %>
|
|
642
|
-
<
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
</svg>
|
|
651
|
-
<svg class="w-4 h-4 check-icon hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
652
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
|
653
|
-
</svg>
|
|
654
|
-
<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>
|
|
655
280
|
</button>
|
|
656
|
-
|
|
657
|
-
<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">
|
|
658
|
-
<span x-text="expanded ? 'Collapse' : 'Expand'">Expand</span>
|
|
659
|
-
</button>
|
|
660
|
-
<% end %>
|
|
661
|
-
</div>
|
|
281
|
+
<% end %>
|
|
662
282
|
<% end %>
|
|
663
283
|
</div>
|
|
664
284
|
|
|
665
285
|
<% if tool_call_count > 0 %>
|
|
666
|
-
<div class="space-y-
|
|
286
|
+
<div class="space-y-3" x-show="expanded" x-cloak>
|
|
667
287
|
<% tool_calls.each_with_index do |tool_call, index| %>
|
|
668
288
|
<%
|
|
669
|
-
# Handle both symbol and string keys for backward compatibility
|
|
670
289
|
tool_id = tool_call['id'] || tool_call[:id]
|
|
671
290
|
tool_name = tool_call['name'] || tool_call[:name]
|
|
672
291
|
tool_args = tool_call['arguments'] || tool_call[:arguments] || {}
|
|
@@ -676,480 +295,403 @@
|
|
|
676
295
|
tool_duration = tool_call['duration_ms'] || tool_call[:duration_ms]
|
|
677
296
|
tool_called_at = tool_call['called_at'] || tool_call[:called_at]
|
|
678
297
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
when '
|
|
682
|
-
|
|
683
|
-
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'
|
|
684
302
|
end
|
|
685
303
|
%>
|
|
686
|
-
<div class="border border-gray-
|
|
687
|
-
|
|
688
|
-
<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">
|
|
689
306
|
<div class="flex items-center justify-between">
|
|
690
|
-
<div class="flex items-center gap-
|
|
691
|
-
<span class="
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
<code class="text-sm font-semibold text-gray-900 dark:text-gray-100"><%= tool_name %></code>
|
|
695
|
-
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium <%= status_badge_class %>">
|
|
696
|
-
<%= tool_status %>
|
|
697
|
-
</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>
|
|
698
311
|
<% if tool_duration.present? %>
|
|
699
|
-
<span class="
|
|
700
|
-
<%= tool_duration %>ms
|
|
701
|
-
</span>
|
|
312
|
+
<span class="badge badge-sm badge-purple"><%= tool_duration %>ms</span>
|
|
702
313
|
<% end %>
|
|
703
314
|
</div>
|
|
704
|
-
<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">
|
|
705
316
|
<% if tool_called_at.present? %>
|
|
706
|
-
<span
|
|
317
|
+
<span title="Called at"><%= Time.parse(tool_called_at).strftime("%H:%M:%S.%L") rescue tool_called_at %></span>
|
|
707
318
|
<% end %>
|
|
708
319
|
<% if tool_id.present? %>
|
|
709
|
-
<span class="
|
|
710
|
-
<%= tool_id.to_s.truncate(16) %>
|
|
711
|
-
</span>
|
|
320
|
+
<span class="truncate max-w-[120px]" title="<%= tool_id %>"><%= tool_id.to_s.truncate(16) %></span>
|
|
712
321
|
<% end %>
|
|
713
322
|
</div>
|
|
714
323
|
</div>
|
|
715
324
|
</div>
|
|
716
325
|
|
|
717
|
-
<!-- Tool Call Arguments -->
|
|
718
326
|
<% if tool_args.present? && tool_args.any? %>
|
|
719
|
-
<div class="px-
|
|
720
|
-
<p class="text-
|
|
721
|
-
<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>
|
|
722
330
|
</div>
|
|
723
331
|
<% else %>
|
|
724
|
-
<div class="px-
|
|
725
|
-
<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>
|
|
726
334
|
</div>
|
|
727
335
|
<% end %>
|
|
728
336
|
|
|
729
|
-
<!-- Tool Call Result (NEW) -->
|
|
730
337
|
<% if tool_result.present? %>
|
|
731
|
-
<div class="px-
|
|
732
|
-
<p class="text-
|
|
733
|
-
<div class="bg-gray-50 dark:bg-gray-900 rounded
|
|
734
|
-
<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>
|
|
735
342
|
</div>
|
|
736
343
|
</div>
|
|
737
344
|
<% end %>
|
|
738
345
|
|
|
739
|
-
<!-- Tool Call Error (NEW) -->
|
|
740
346
|
<% if tool_status == 'error' && tool_error.present? %>
|
|
741
|
-
<div class="px-
|
|
742
|
-
<p class="text-
|
|
743
|
-
<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>
|
|
744
350
|
</div>
|
|
745
351
|
<% end %>
|
|
746
352
|
</div>
|
|
747
353
|
<% end %>
|
|
748
354
|
</div>
|
|
749
355
|
<% else %>
|
|
750
|
-
<p class="text-
|
|
356
|
+
<p class="text-xs text-gray-400 dark:text-gray-600 font-mono italic">no tool calls</p>
|
|
751
357
|
<% end %>
|
|
752
358
|
</div>
|
|
753
359
|
|
|
754
|
-
<!--
|
|
360
|
+
<!-- ── metadata ──────────────────────── -->
|
|
755
361
|
<% if @execution.metadata.present? && @execution.metadata.any? %>
|
|
756
|
-
<div
|
|
757
|
-
<div class="flex items-center
|
|
758
|
-
<
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
<svg class="w-4 h-4 copy-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
769
|
-
<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"/>
|
|
770
|
-
</svg>
|
|
771
|
-
<svg class="w-4 h-4 check-icon hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
772
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
|
773
|
-
</svg>
|
|
774
|
-
<span>Copy</span>
|
|
775
|
-
</button>
|
|
776
|
-
<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">
|
|
777
|
-
<span x-text="expanded ? 'Collapse' : 'Expand'">Expand</span>
|
|
778
|
-
</button>
|
|
779
|
-
</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>
|
|
780
374
|
</div>
|
|
781
|
-
<div x-show="expanded" x-cloak
|
|
782
|
-
<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>
|
|
783
377
|
</div>
|
|
784
378
|
</div>
|
|
785
379
|
<% end %>
|
|
786
380
|
|
|
787
|
-
<!--
|
|
381
|
+
<!-- ── hierarchy ──────────────────────── -->
|
|
788
382
|
<% if @execution.parent_execution_id.present? || (@execution.respond_to?(:child_executions) && @execution.child_executions.any?) %>
|
|
789
|
-
<div class="
|
|
790
|
-
<
|
|
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>
|
|
791
387
|
|
|
388
|
+
<div class="font-mono text-xs space-y-1.5">
|
|
792
389
|
<% if @execution.parent_execution_id.present? %>
|
|
793
|
-
<div class="
|
|
794
|
-
|
|
390
|
+
<div class="text-gray-400 dark:text-gray-500">
|
|
391
|
+
parent:
|
|
795
392
|
<%= link_to "##{@execution.parent_execution_id}",
|
|
796
393
|
ruby_llm_agents.execution_path(@execution.parent_execution_id),
|
|
797
|
-
class: "
|
|
394
|
+
class: "text-blue-600 dark:text-blue-400 hover:underline" %>
|
|
798
395
|
</div>
|
|
799
396
|
<% end %>
|
|
800
397
|
|
|
801
398
|
<% if @execution.respond_to?(:child_executions) && @execution.child_executions.any? %>
|
|
802
|
-
<div>
|
|
803
|
-
|
|
804
|
-
<
|
|
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">
|
|
805
402
|
<% @execution.child_executions.limit(10).each do |child| %>
|
|
806
403
|
<%= link_to "##{child.id}",
|
|
807
404
|
ruby_llm_agents.execution_path(child),
|
|
808
|
-
class: "
|
|
405
|
+
class: "text-blue-600 dark:text-blue-400 hover:underline" %>
|
|
809
406
|
<% end %>
|
|
810
407
|
<% if @execution.child_executions.count > 10 %>
|
|
811
|
-
<span
|
|
408
|
+
<span>+<%= @execution.child_executions.count - 10 %> more</span>
|
|
812
409
|
<% end %>
|
|
813
|
-
</
|
|
410
|
+
</span>
|
|
814
411
|
</div>
|
|
815
412
|
<% end %>
|
|
816
413
|
</div>
|
|
817
414
|
<% end %>
|
|
818
415
|
|
|
819
|
-
<!--
|
|
416
|
+
<!-- ── system prompt ──────────────────── -->
|
|
820
417
|
<% if @execution.system_prompt.present? %>
|
|
821
|
-
<div class="
|
|
822
|
-
<
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
</div>
|
|
828
|
-
<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>
|
|
829
|
-
<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>
|
|
830
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>
|
|
831
427
|
<% end %>
|
|
832
428
|
|
|
833
|
-
<!--
|
|
429
|
+
<!-- ── user prompt ──────────────────── -->
|
|
834
430
|
<% if @execution.user_prompt.present? %>
|
|
835
|
-
<div class="
|
|
836
|
-
<
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
</div>
|
|
842
|
-
<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>
|
|
843
|
-
<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>
|
|
844
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>
|
|
845
440
|
<% end %>
|
|
846
441
|
|
|
847
|
-
<!--
|
|
442
|
+
<!-- ── conversation ──────────────────── -->
|
|
848
443
|
<% if @execution.respond_to?(:messages_count) && @execution.messages_count.to_i > 0 %>
|
|
849
444
|
<%
|
|
850
445
|
messages_summary = @execution.messages_summary || {}
|
|
851
|
-
# Handle both string and symbol keys
|
|
852
446
|
first_message = messages_summary["first"] || messages_summary[:first]
|
|
853
447
|
last_message = messages_summary["last"] || messages_summary[:last]
|
|
854
448
|
max_len = RubyLLM::Agents.configuration.messages_summary_max_length || 500
|
|
855
449
|
%>
|
|
856
|
-
<div class="
|
|
857
|
-
<
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
</svg>
|
|
861
|
-
<h3 class="text-sm font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">Conversation Context</h3>
|
|
862
|
-
<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">
|
|
863
|
-
<%= @execution.messages_count %> message<%= @execution.messages_count == 1 ? '' : 's' %>
|
|
864
|
-
</span>
|
|
865
|
-
</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>
|
|
866
454
|
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
</span>
|
|
886
|
-
|
|
887
|
-
<span class="text-xs text-gray-400 dark:text-gray-500 italic">(truncated)</span>
|
|
888
|
-
<% end %>
|
|
889
|
-
</div>
|
|
890
|
-
<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 %>
|
|
891
475
|
</div>
|
|
892
|
-
|
|
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 %>
|
|
893
479
|
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
</span>
|
|
912
|
-
|
|
913
|
-
<span class="text-xs text-gray-400 dark:text-gray-500 italic">(truncated)</span>
|
|
914
|
-
<% end %>
|
|
915
|
-
</div>
|
|
916
|
-
<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 %>
|
|
917
499
|
</div>
|
|
918
|
-
|
|
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 %>
|
|
919
503
|
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
</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 %>
|
|
926
509
|
</div>
|
|
927
510
|
<% end %>
|
|
928
511
|
|
|
929
|
-
<!--
|
|
930
|
-
<div
|
|
931
|
-
<div class="flex items-center
|
|
932
|
-
<
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
<button
|
|
939
|
-
type="button"
|
|
940
|
-
id="toggle-diagnostics-btn"
|
|
941
|
-
onclick="toggleDiagnostics()"
|
|
942
|
-
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"
|
|
943
|
-
>
|
|
944
|
-
<svg id="diagnostics-expand-icon" class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
945
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
|
946
|
-
</svg>
|
|
947
|
-
<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>
|
|
948
521
|
</button>
|
|
949
522
|
</div>
|
|
950
523
|
|
|
951
|
-
<!-- Quick
|
|
952
|
-
<div class="
|
|
953
|
-
<
|
|
954
|
-
|
|
955
|
-
<
|
|
956
|
-
|
|
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 -->
|
|
957
536
|
<div>
|
|
958
|
-
<
|
|
959
|
-
<
|
|
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>
|
|
960
562
|
</div>
|
|
563
|
+
|
|
564
|
+
<!-- Performance -->
|
|
961
565
|
<div>
|
|
962
|
-
<
|
|
963
|
-
<
|
|
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>
|
|
964
585
|
</div>
|
|
586
|
+
|
|
587
|
+
<!-- Cost -->
|
|
965
588
|
<div>
|
|
966
|
-
<
|
|
967
|
-
<
|
|
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>
|
|
968
604
|
</div>
|
|
969
|
-
</div>
|
|
970
605
|
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
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 %>
|
|
982
619
|
<div class="flex justify-between">
|
|
983
|
-
<dt class="text-gray-
|
|
984
|
-
<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>
|
|
985
622
|
</div>
|
|
623
|
+
<% end %>
|
|
624
|
+
<% if @execution.respond_to?(:attempts_count) && @execution.attempts_count && @execution.attempts_count > 1 %>
|
|
986
625
|
<div class="flex justify-between">
|
|
987
|
-
<dt class="text-gray-
|
|
988
|
-
<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>
|
|
989
628
|
</div>
|
|
990
|
-
|
|
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? %>
|
|
991
639
|
<div class="flex justify-between">
|
|
992
|
-
<dt class="text-gray-
|
|
993
|
-
<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>
|
|
994
654
|
</div>
|
|
995
655
|
<% end %>
|
|
996
|
-
<div class="flex justify-between">
|
|
997
|
-
<dt class="text-gray-500 dark:text-gray-400">Created At</dt>
|
|
998
|
-
<dd class="font-mono text-gray-900 dark:text-gray-100"><%= @execution.created_at.strftime("%Y-%m-%d %H:%M:%S") %></dd>
|
|
999
|
-
</div>
|
|
1000
|
-
</dl>
|
|
1001
|
-
</div>
|
|
1002
|
-
|
|
1003
|
-
<!-- Performance Metrics -->
|
|
1004
|
-
<div class="bg-gray-50 dark:bg-gray-900/50 rounded-lg p-4">
|
|
1005
|
-
<h4 class="text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wide mb-3">Performance</h4>
|
|
1006
|
-
<dl class="space-y-2 text-sm">
|
|
1007
|
-
<div class="flex justify-between">
|
|
1008
|
-
<dt class="text-gray-500 dark:text-gray-400">Tokens/Second</dt>
|
|
1009
|
-
<dd class="font-mono text-gray-900 dark:text-gray-100"><%= @execution.tokens_per_second&.round(1) || 'N/A' %></dd>
|
|
1010
|
-
</div>
|
|
1011
|
-
<div class="flex justify-between">
|
|
1012
|
-
<dt class="text-gray-500 dark:text-gray-400">Input Tokens</dt>
|
|
1013
|
-
<dd class="font-mono text-gray-900 dark:text-gray-100"><%= @execution.input_tokens || 0 %></dd>
|
|
1014
|
-
</div>
|
|
1015
|
-
<div class="flex justify-between">
|
|
1016
|
-
<dt class="text-gray-500 dark:text-gray-400">Output Tokens</dt>
|
|
1017
|
-
<dd class="font-mono text-gray-900 dark:text-gray-100"><%= @execution.output_tokens || 0 %></dd>
|
|
1018
|
-
</div>
|
|
1019
|
-
<div class="flex justify-between">
|
|
1020
|
-
<dt class="text-gray-500 dark:text-gray-400">Cached Tokens</dt>
|
|
1021
|
-
<dd class="font-mono text-gray-900 dark:text-gray-100"><%= @execution.cached_tokens || 0 %></dd>
|
|
1022
|
-
</div>
|
|
1023
|
-
</dl>
|
|
1024
|
-
</div>
|
|
1025
|
-
|
|
1026
|
-
<!-- Cost Breakdown -->
|
|
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">Cost</h4>
|
|
1029
|
-
<dl class="space-y-2 text-sm">
|
|
1030
|
-
<div class="flex justify-between">
|
|
1031
|
-
<dt class="text-gray-500 dark:text-gray-400">Input Cost</dt>
|
|
1032
|
-
<dd class="font-mono text-gray-900 dark:text-gray-100">$<%= number_with_precision(@execution.input_cost || 0, precision: 6) %></dd>
|
|
1033
|
-
</div>
|
|
1034
|
-
<div class="flex justify-between">
|
|
1035
|
-
<dt class="text-gray-500 dark:text-gray-400">Output Cost</dt>
|
|
1036
|
-
<dd class="font-mono text-gray-900 dark:text-gray-100">$<%= number_with_precision(@execution.output_cost || 0, precision: 6) %></dd>
|
|
1037
|
-
</div>
|
|
1038
|
-
<div class="flex justify-between font-semibold">
|
|
1039
|
-
<dt class="text-gray-600 dark:text-gray-300">Total Cost</dt>
|
|
1040
|
-
<dd class="font-mono text-gray-900 dark:text-gray-100">$<%= number_with_precision(@execution.total_cost || 0, precision: 6) %></dd>
|
|
1041
|
-
</div>
|
|
1042
656
|
</dl>
|
|
1043
657
|
</div>
|
|
658
|
+
<% end %>
|
|
1044
659
|
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
<
|
|
1049
|
-
|
|
1050
|
-
<dt class="text-gray-500 dark:text-gray-400">Agent Type</dt>
|
|
1051
|
-
<dd class="font-mono text-gray-900 dark:text-gray-100"><%= @execution.agent_type %></dd>
|
|
1052
|
-
</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">
|
|
1053
665
|
<div class="flex justify-between">
|
|
1054
|
-
<dt class="text-gray-
|
|
1055
|
-
<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>
|
|
1056
668
|
</div>
|
|
1057
|
-
<% if @execution.
|
|
669
|
+
<% if @execution.response_cache_key.present? %>
|
|
1058
670
|
<div class="flex justify-between">
|
|
1059
|
-
<dt class="text-gray-
|
|
1060
|
-
<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>
|
|
1061
673
|
</div>
|
|
1062
674
|
<% end %>
|
|
1063
|
-
<% if @execution.
|
|
675
|
+
<% if @execution.cached_at.present? %>
|
|
1064
676
|
<div class="flex justify-between">
|
|
1065
|
-
<dt class="text-gray-
|
|
1066
|
-
<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>
|
|
1067
679
|
</div>
|
|
1068
680
|
<% end %>
|
|
1069
681
|
</dl>
|
|
1070
682
|
</div>
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
<!-- Second Row: Tracing and Caching -->
|
|
1074
|
-
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mt-6">
|
|
1075
|
-
<!-- Tracing Information -->
|
|
1076
|
-
<% if @execution.trace_id.present? || @execution.request_id.present? %>
|
|
1077
|
-
<div class="bg-gray-50 dark:bg-gray-900/50 rounded-lg p-4">
|
|
1078
|
-
<h4 class="text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wide mb-3">Tracing</h4>
|
|
1079
|
-
<dl class="space-y-2 text-sm">
|
|
1080
|
-
<% if @execution.request_id.present? %>
|
|
1081
|
-
<div class="flex justify-between">
|
|
1082
|
-
<dt class="text-gray-500 dark:text-gray-400">Request ID</dt>
|
|
1083
|
-
<dd class="font-mono text-gray-900 dark:text-gray-100 text-xs"><%= @execution.request_id %></dd>
|
|
1084
|
-
</div>
|
|
1085
|
-
<% end %>
|
|
1086
|
-
<% if @execution.trace_id.present? %>
|
|
1087
|
-
<div class="flex justify-between">
|
|
1088
|
-
<dt class="text-gray-500 dark:text-gray-400">Trace ID</dt>
|
|
1089
|
-
<dd class="font-mono text-gray-900 dark:text-gray-100 text-xs"><%= @execution.trace_id %></dd>
|
|
1090
|
-
</div>
|
|
1091
|
-
<% end %>
|
|
1092
|
-
<% if @execution.span_id.present? %>
|
|
1093
|
-
<div class="flex justify-between">
|
|
1094
|
-
<dt class="text-gray-500 dark:text-gray-400">Span ID</dt>
|
|
1095
|
-
<dd class="font-mono text-gray-900 dark:text-gray-100 text-xs"><%= @execution.span_id %></dd>
|
|
1096
|
-
</div>
|
|
1097
|
-
<% end %>
|
|
1098
|
-
</dl>
|
|
1099
|
-
</div>
|
|
1100
|
-
<% end %>
|
|
1101
|
-
|
|
1102
|
-
<!-- Caching Information -->
|
|
1103
|
-
<% if @execution.cache_hit || @execution.response_cache_key.present? %>
|
|
1104
|
-
<div class="bg-gray-50 dark:bg-gray-900/50 rounded-lg p-4">
|
|
1105
|
-
<h4 class="text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wide mb-3">Caching</h4>
|
|
1106
|
-
<dl class="space-y-2 text-sm">
|
|
1107
|
-
<div class="flex justify-between">
|
|
1108
|
-
<dt class="text-gray-500 dark:text-gray-400">Cache Hit</dt>
|
|
1109
|
-
<dd class="font-mono text-gray-900 dark:text-gray-100">
|
|
1110
|
-
<% if @execution.cache_hit %>
|
|
1111
|
-
<span class="text-green-600 dark:text-green-400">Yes</span>
|
|
1112
|
-
<% else %>
|
|
1113
|
-
<span class="text-gray-400">No</span>
|
|
1114
|
-
<% end %>
|
|
1115
|
-
</dd>
|
|
1116
|
-
</div>
|
|
1117
|
-
<% if @execution.response_cache_key.present? %>
|
|
1118
|
-
<div class="flex justify-between">
|
|
1119
|
-
<dt class="text-gray-500 dark:text-gray-400">Cache Key</dt>
|
|
1120
|
-
<dd class="font-mono text-gray-900 dark:text-gray-100 text-xs truncate max-w-xs" title="<%= @execution.response_cache_key %>">
|
|
1121
|
-
<%= @execution.response_cache_key.truncate(30) %>
|
|
1122
|
-
</dd>
|
|
1123
|
-
</div>
|
|
1124
|
-
<% end %>
|
|
1125
|
-
<% if @execution.cached_at.present? %>
|
|
1126
|
-
<div class="flex justify-between">
|
|
1127
|
-
<dt class="text-gray-500 dark:text-gray-400">Cached At</dt>
|
|
1128
|
-
<dd class="font-mono text-gray-900 dark:text-gray-100"><%= @execution.cached_at.strftime("%Y-%m-%d %H:%M:%S") %></dd>
|
|
1129
|
-
</div>
|
|
1130
|
-
<% end %>
|
|
1131
|
-
</dl>
|
|
1132
|
-
</div>
|
|
1133
|
-
<% end %>
|
|
1134
|
-
</div>
|
|
683
|
+
<% end %>
|
|
1135
684
|
|
|
1136
|
-
<!--
|
|
1137
|
-
<div class="
|
|
1138
|
-
<
|
|
1139
|
-
<span class="text-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
>
|
|
1147
|
-
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
1148
|
-
<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"/>
|
|
1149
|
-
</svg>
|
|
1150
|
-
Copy All
|
|
1151
|
-
</button>
|
|
1152
|
-
</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>
|
|
1153
695
|
</div>
|
|
1154
696
|
</div>
|
|
1155
697
|
</div>
|
|
@@ -1163,88 +705,36 @@
|
|
|
1163
705
|
const isHidden = content.classList.contains('hidden');
|
|
1164
706
|
content.classList.toggle('hidden', !isHidden);
|
|
1165
707
|
if (preview) preview.classList.toggle('hidden', isHidden);
|
|
1166
|
-
toggle.textContent = isHidden ? '
|
|
708
|
+
toggle.textContent = isHidden ? 'collapse' : 'expand';
|
|
1167
709
|
}
|
|
1168
710
|
|
|
1169
711
|
document.addEventListener('DOMContentLoaded', function() {
|
|
1170
712
|
document.querySelectorAll('.copy-json-btn').forEach(function(button) {
|
|
1171
713
|
button.addEventListener('click', function() {
|
|
1172
714
|
const jsonText = atob(this.getAttribute('data-copy-json'));
|
|
1173
|
-
const
|
|
1174
|
-
const
|
|
1175
|
-
const checkIcon = this.querySelector('.check-icon');
|
|
715
|
+
const btn = this;
|
|
716
|
+
const originalText = btn.textContent;
|
|
1176
717
|
|
|
1177
718
|
navigator.clipboard.writeText(jsonText).then(function() {
|
|
1178
|
-
|
|
1179
|
-
copyIcon.classList.add('hidden');
|
|
1180
|
-
checkIcon.classList.remove('hidden');
|
|
1181
|
-
button.classList.add('text-green-600');
|
|
1182
|
-
|
|
719
|
+
btn.textContent = 'copied!';
|
|
1183
720
|
setTimeout(function() {
|
|
1184
|
-
|
|
1185
|
-
copyIcon.classList.remove('hidden');
|
|
1186
|
-
checkIcon.classList.add('hidden');
|
|
1187
|
-
button.classList.remove('text-green-600');
|
|
721
|
+
btn.textContent = originalText;
|
|
1188
722
|
}, 2000);
|
|
1189
723
|
}).catch(function(err) {
|
|
1190
724
|
console.error('Failed to copy:', err);
|
|
1191
|
-
|
|
725
|
+
btn.textContent = 'failed';
|
|
1192
726
|
setTimeout(function() {
|
|
1193
|
-
|
|
727
|
+
btn.textContent = originalText;
|
|
1194
728
|
}, 2000);
|
|
1195
729
|
});
|
|
1196
730
|
});
|
|
1197
731
|
});
|
|
1198
732
|
});
|
|
1199
733
|
|
|
1200
|
-
// Rerun modal functions
|
|
1201
|
-
function confirmRerun() {
|
|
1202
|
-
document.getElementById('rerun-modal').classList.remove('hidden');
|
|
1203
|
-
document.body.classList.add('overflow-hidden');
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
|
-
function closeRerunModal() {
|
|
1207
|
-
document.getElementById('rerun-modal').classList.add('hidden');
|
|
1208
|
-
document.body.classList.remove('overflow-hidden');
|
|
1209
|
-
}
|
|
1210
|
-
|
|
1211
|
-
// Close modal on Escape key
|
|
1212
|
-
document.addEventListener('keydown', function(e) {
|
|
1213
|
-
if (e.key === 'Escape') {
|
|
1214
|
-
closeRerunModal();
|
|
1215
|
-
}
|
|
1216
|
-
});
|
|
1217
|
-
|
|
1218
|
-
// Diagnostics panel toggle (default to expanded)
|
|
1219
|
-
let diagnosticsExpanded = localStorage.getItem('ruby_llm_agents_diagnostics_expanded') !== 'false';
|
|
1220
|
-
|
|
1221
|
-
function toggleDiagnostics() {
|
|
1222
|
-
diagnosticsExpanded = !diagnosticsExpanded;
|
|
1223
|
-
localStorage.setItem('ruby_llm_agents_diagnostics_expanded', diagnosticsExpanded);
|
|
1224
|
-
updateDiagnosticsUI();
|
|
1225
|
-
}
|
|
1226
|
-
|
|
1227
|
-
function updateDiagnosticsUI() {
|
|
1228
|
-
const details = document.getElementById('diagnostics-details');
|
|
1229
|
-
const toggleText = document.getElementById('diagnostics-toggle-text');
|
|
1230
|
-
const expandIcon = document.getElementById('diagnostics-expand-icon');
|
|
1231
|
-
|
|
1232
|
-
if (diagnosticsExpanded) {
|
|
1233
|
-
details.classList.remove('hidden');
|
|
1234
|
-
toggleText.textContent = 'Collapse';
|
|
1235
|
-
expandIcon.style.transform = 'rotate(180deg)';
|
|
1236
|
-
} else {
|
|
1237
|
-
details.classList.add('hidden');
|
|
1238
|
-
toggleText.textContent = 'Expand';
|
|
1239
|
-
expandIcon.style.transform = 'rotate(0deg)';
|
|
1240
|
-
}
|
|
1241
|
-
}
|
|
1242
|
-
|
|
1243
734
|
function copyDiagnostics() {
|
|
1244
735
|
const diagnostics = {
|
|
1245
736
|
execution_id: <%= @execution.id %>,
|
|
1246
737
|
agent_type: "<%= @execution.agent_type %>",
|
|
1247
|
-
agent_version: "<%= @execution.agent_version || '1.0' %>",
|
|
1248
738
|
model_id: "<%= @execution.model_id %>",
|
|
1249
739
|
status: "<%= @execution.status %>",
|
|
1250
740
|
temperature: <%= @execution.temperature || 'null' %>,
|
|
@@ -1261,16 +751,15 @@
|
|
|
1261
751
|
created_at: "<%= @execution.created_at.iso8601 %>"
|
|
1262
752
|
};
|
|
1263
753
|
|
|
754
|
+
const btn = event.currentTarget;
|
|
1264
755
|
navigator.clipboard.writeText(JSON.stringify(diagnostics, null, 2)).then(function() {
|
|
1265
|
-
|
|
756
|
+
btn.textContent = 'copied!';
|
|
757
|
+
setTimeout(function() {
|
|
758
|
+
btn.textContent = 'copy all';
|
|
759
|
+
}, 2000);
|
|
1266
760
|
}).catch(function(err) {
|
|
1267
761
|
console.error('Failed to copy diagnostics:', err);
|
|
1268
762
|
});
|
|
1269
763
|
}
|
|
1270
|
-
|
|
1271
|
-
// Initialize diagnostics panel on page load
|
|
1272
|
-
document.addEventListener('DOMContentLoaded', function() {
|
|
1273
|
-
updateDiagnosticsUI();
|
|
1274
|
-
});
|
|
1275
764
|
</script>
|
|
1276
765
|
</div>
|