ruby_llm-agents 0.2.4 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +413 -0
- data/app/channels/ruby_llm/agents/executions_channel.rb +24 -1
- data/app/controllers/concerns/ruby_llm/agents/filterable.rb +81 -0
- data/app/controllers/concerns/ruby_llm/agents/paginatable.rb +51 -0
- data/app/controllers/ruby_llm/agents/agents_controller.rb +228 -59
- data/app/controllers/ruby_llm/agents/dashboard_controller.rb +167 -12
- data/app/controllers/ruby_llm/agents/executions_controller.rb +189 -31
- data/app/controllers/ruby_llm/agents/settings_controller.rb +20 -0
- data/app/helpers/ruby_llm/agents/application_helper.rb +307 -7
- data/app/models/ruby_llm/agents/execution/analytics.rb +224 -20
- data/app/models/ruby_llm/agents/execution/metrics.rb +41 -25
- data/app/models/ruby_llm/agents/execution/scopes.rb +234 -14
- data/app/models/ruby_llm/agents/execution.rb +259 -16
- data/app/services/ruby_llm/agents/agent_registry.rb +49 -12
- data/app/views/layouts/rubyllm/agents/application.html.erb +351 -85
- data/app/views/rubyllm/agents/agents/_version_comparison.html.erb +186 -0
- data/app/views/rubyllm/agents/agents/show.html.erb +233 -10
- data/app/views/rubyllm/agents/dashboard/_action_center.html.erb +62 -0
- data/app/views/rubyllm/agents/dashboard/_alerts_feed.html.erb +62 -0
- data/app/views/rubyllm/agents/dashboard/_breaker_strip.html.erb +47 -0
- data/app/views/rubyllm/agents/dashboard/_budgets_bar.html.erb +165 -0
- data/app/views/rubyllm/agents/dashboard/_now_strip.html.erb +10 -0
- data/app/views/rubyllm/agents/dashboard/_now_strip_values.html.erb +71 -0
- data/app/views/rubyllm/agents/dashboard/index.html.erb +215 -109
- data/app/views/rubyllm/agents/executions/_filters.html.erb +152 -155
- data/app/views/rubyllm/agents/executions/_list.html.erb +103 -12
- data/app/views/rubyllm/agents/executions/dry_run.html.erb +149 -0
- data/app/views/rubyllm/agents/executions/index.html.erb +17 -72
- data/app/views/rubyllm/agents/executions/index.turbo_stream.erb +16 -2
- data/app/views/rubyllm/agents/executions/show.html.erb +693 -14
- data/app/views/rubyllm/agents/settings/show.html.erb +369 -0
- data/app/views/rubyllm/agents/shared/_filter_dropdown.html.erb +121 -0
- data/app/views/rubyllm/agents/shared/_select_dropdown.html.erb +85 -0
- data/config/routes.rb +7 -0
- data/lib/generators/ruby_llm_agents/templates/add_attempts_migration.rb.tt +27 -0
- data/lib/generators/ruby_llm_agents/templates/add_caching_migration.rb.tt +23 -0
- data/lib/generators/ruby_llm_agents/templates/add_finish_reason_migration.rb.tt +19 -0
- data/lib/generators/ruby_llm_agents/templates/add_routing_migration.rb.tt +19 -0
- data/lib/generators/ruby_llm_agents/templates/add_streaming_migration.rb.tt +8 -0
- data/lib/generators/ruby_llm_agents/templates/add_tracing_migration.rb.tt +34 -0
- data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +66 -4
- data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +53 -6
- data/lib/generators/ruby_llm_agents/templates/initializer.rb.tt +143 -8
- data/lib/generators/ruby_llm_agents/templates/migration.rb.tt +38 -1
- data/lib/generators/ruby_llm_agents/upgrade_generator.rb +78 -0
- data/lib/ruby_llm/agents/alert_manager.rb +207 -0
- data/lib/ruby_llm/agents/attempt_tracker.rb +295 -0
- data/lib/ruby_llm/agents/base.rb +597 -112
- data/lib/ruby_llm/agents/budget_tracker.rb +360 -0
- data/lib/ruby_llm/agents/circuit_breaker.rb +197 -0
- data/lib/ruby_llm/agents/configuration.rb +279 -1
- data/lib/ruby_llm/agents/engine.rb +58 -6
- data/lib/ruby_llm/agents/execution_logger_job.rb +17 -6
- data/lib/ruby_llm/agents/inflections.rb +13 -2
- data/lib/ruby_llm/agents/instrumentation.rb +538 -87
- data/lib/ruby_llm/agents/redactor.rb +130 -0
- data/lib/ruby_llm/agents/reliability.rb +185 -0
- data/lib/ruby_llm/agents/version.rb +3 -1
- data/lib/ruby_llm/agents.rb +52 -0
- metadata +41 -2
- data/app/controllers/ruby_llm/agents/application_controller.rb +0 -37
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
<div id="execution-detail" data-execution-id="<%= @execution.id %>" data-status="<%= @execution.status %>">
|
|
1
2
|
<div class="mb-6">
|
|
2
3
|
<%= link_to ruby_llm_agents.executions_path, class: "inline-flex items-center text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200" do %>
|
|
3
4
|
<svg
|
|
@@ -27,19 +28,113 @@
|
|
|
27
28
|
</h2>
|
|
28
29
|
|
|
29
30
|
<%= render "rubyllm/agents/shared/status_badge", status: @execution.status, size: :md %>
|
|
31
|
+
|
|
32
|
+
<% if @execution.streaming? %>
|
|
33
|
+
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-cyan-100 dark:bg-cyan-900/50 text-cyan-800 dark:text-cyan-300">
|
|
34
|
+
Streaming
|
|
35
|
+
</span>
|
|
36
|
+
<% end %>
|
|
37
|
+
|
|
38
|
+
<% if @execution.cache_hit %>
|
|
39
|
+
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 dark:bg-purple-900/50 text-purple-800 dark:text-purple-300">
|
|
40
|
+
Cached
|
|
41
|
+
</span>
|
|
42
|
+
<% end %>
|
|
43
|
+
|
|
44
|
+
<% if @execution.finish_reason.present? %>
|
|
45
|
+
<% finish_colors = {
|
|
46
|
+
'stop' => 'bg-green-100 text-green-800 dark:bg-green-900/50 dark:text-green-300',
|
|
47
|
+
'length' => 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/50 dark:text-yellow-300',
|
|
48
|
+
'content_filter' => 'bg-red-100 text-red-800 dark:bg-red-900/50 dark:text-red-300',
|
|
49
|
+
'tool_calls' => 'bg-blue-100 text-blue-800 dark:bg-blue-900/50 dark:text-blue-300'
|
|
50
|
+
} %>
|
|
51
|
+
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium <%= finish_colors[@execution.finish_reason] || 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300' %>">
|
|
52
|
+
<%= @execution.finish_reason %>
|
|
53
|
+
</span>
|
|
54
|
+
<% end %>
|
|
30
55
|
</div>
|
|
31
56
|
|
|
32
57
|
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">
|
|
33
58
|
Execution #<%= @execution.id %> · v<%= @execution.agent_version %>
|
|
59
|
+
<% if @execution.model_provider.present? %>
|
|
60
|
+
· <%= @execution.model_provider %>
|
|
61
|
+
<% end %>
|
|
34
62
|
</p>
|
|
35
63
|
</div>
|
|
36
64
|
|
|
37
|
-
<div class="
|
|
38
|
-
|
|
65
|
+
<div class="flex items-center gap-4">
|
|
66
|
+
<!-- Rerun Buttons -->
|
|
67
|
+
<div class="flex items-center gap-2">
|
|
68
|
+
<%= button_to rerun_execution_path(@execution, dry_run: true),
|
|
69
|
+
method: :post,
|
|
70
|
+
data: { turbo: false },
|
|
71
|
+
class: "inline-flex items-center gap-1.5 px-3 py-1.5 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",
|
|
72
|
+
title: "Preview what would be sent without making an API call" do %>
|
|
73
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
74
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|
75
|
+
<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"/>
|
|
76
|
+
</svg>
|
|
77
|
+
Dry Run
|
|
78
|
+
<% end %>
|
|
39
79
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
80
|
+
<button
|
|
81
|
+
type="button"
|
|
82
|
+
onclick="confirmRerun()"
|
|
83
|
+
class="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors"
|
|
84
|
+
title="Re-execute this agent with the same parameters"
|
|
85
|
+
>
|
|
86
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
87
|
+
<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"/>
|
|
88
|
+
</svg>
|
|
89
|
+
Rerun
|
|
90
|
+
</button>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<div class="text-right text-sm text-gray-500 dark:text-gray-400">
|
|
94
|
+
<p><%= @execution.created_at.strftime("%b %d, %Y at %H:%M") %></p>
|
|
95
|
+
|
|
96
|
+
<p class="text-xs text-gray-400 dark:text-gray-500">
|
|
97
|
+
<%= time_ago_in_words(@execution.created_at) %> ago
|
|
98
|
+
</p>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<!-- Rerun Confirmation Modal -->
|
|
105
|
+
<div id="rerun-modal" class="hidden fixed inset-0 z-50 overflow-y-auto">
|
|
106
|
+
<div class="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:p-0">
|
|
107
|
+
<div class="fixed inset-0 transition-opacity bg-gray-500 bg-opacity-75 dark:bg-gray-900 dark:bg-opacity-75" onclick="closeRerunModal()"></div>
|
|
108
|
+
|
|
109
|
+
<div class="relative inline-block px-4 pt-5 pb-4 overflow-hidden text-left align-bottom transition-all transform bg-white dark:bg-gray-800 rounded-lg shadow-xl sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:p-6">
|
|
110
|
+
<div class="sm:flex sm:items-start">
|
|
111
|
+
<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">
|
|
112
|
+
<svg class="w-6 h-6 text-blue-600 dark:text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
113
|
+
<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"/>
|
|
114
|
+
</svg>
|
|
115
|
+
</div>
|
|
116
|
+
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
|
117
|
+
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100">Confirm Rerun</h3>
|
|
118
|
+
<div class="mt-2">
|
|
119
|
+
<p class="text-sm text-gray-500 dark:text-gray-400">
|
|
120
|
+
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.
|
|
121
|
+
</p>
|
|
122
|
+
<p class="mt-2 text-sm text-amber-600 dark:text-amber-400">
|
|
123
|
+
This action may incur API costs.
|
|
124
|
+
</p>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse gap-3">
|
|
129
|
+
<%= button_to rerun_execution_path(@execution),
|
|
130
|
+
method: :post,
|
|
131
|
+
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 %>
|
|
132
|
+
Confirm Rerun
|
|
133
|
+
<% end %>
|
|
134
|
+
<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">
|
|
135
|
+
Cancel
|
|
136
|
+
</button>
|
|
137
|
+
</div>
|
|
43
138
|
</div>
|
|
44
139
|
</div>
|
|
45
140
|
</div>
|
|
@@ -125,10 +220,135 @@
|
|
|
125
220
|
</div>
|
|
126
221
|
</div>
|
|
127
222
|
|
|
223
|
+
<!-- Attempts Table (for reliability-enabled executions) -->
|
|
224
|
+
<% if @execution.respond_to?(:attempts) && @execution.attempts.present? %>
|
|
225
|
+
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-6 mb-6">
|
|
226
|
+
<div class="flex items-center justify-between mb-4">
|
|
227
|
+
<div>
|
|
228
|
+
<h3 class="text-sm font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">Attempts</h3>
|
|
229
|
+
<p class="text-xs text-gray-400 dark:text-gray-500 mt-1">
|
|
230
|
+
<%= @execution.attempts_count || @execution.attempts.size %> attempt(s)
|
|
231
|
+
<% if @execution.used_fallback? %>
|
|
232
|
+
· <span class="text-amber-500">Used fallback model</span>
|
|
233
|
+
<% end %>
|
|
234
|
+
<% if @execution.has_retries? %>
|
|
235
|
+
· <span class="text-blue-500">Retried</span>
|
|
236
|
+
<% end %>
|
|
237
|
+
</p>
|
|
238
|
+
</div>
|
|
239
|
+
<% if @execution.chosen_model_id.present? && @execution.chosen_model_id != @execution.model_id %>
|
|
240
|
+
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-amber-100 dark:bg-amber-900/30 text-amber-800 dark:text-amber-300">
|
|
241
|
+
Fallback: <%= @execution.chosen_model_id %>
|
|
242
|
+
</span>
|
|
243
|
+
<% end %>
|
|
244
|
+
</div>
|
|
245
|
+
|
|
246
|
+
<div class="overflow-x-auto">
|
|
247
|
+
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
|
248
|
+
<thead>
|
|
249
|
+
<tr>
|
|
250
|
+
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">#</th>
|
|
251
|
+
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Model</th>
|
|
252
|
+
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Status</th>
|
|
253
|
+
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Duration</th>
|
|
254
|
+
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Tokens</th>
|
|
255
|
+
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Error</th>
|
|
256
|
+
</tr>
|
|
257
|
+
</thead>
|
|
258
|
+
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
|
|
259
|
+
<% @execution.attempts.each_with_index do |attempt, index| %>
|
|
260
|
+
<tr class="<%= attempt['short_circuited'] ? 'bg-gray-50 dark:bg-gray-900/50' : '' %>">
|
|
261
|
+
<td class="px-3 py-2 text-sm text-gray-500 dark:text-gray-400">
|
|
262
|
+
<%= index + 1 %>
|
|
263
|
+
</td>
|
|
264
|
+
<td class="px-3 py-2 text-sm font-mono text-gray-900 dark:text-gray-100">
|
|
265
|
+
<%= attempt['model_id'] %>
|
|
266
|
+
</td>
|
|
267
|
+
<td class="px-3 py-2">
|
|
268
|
+
<% if attempt['short_circuited'] %>
|
|
269
|
+
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300">
|
|
270
|
+
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
|
271
|
+
<path fill-rule="evenodd" d="M13.477 14.89A6 6 0 015.11 6.524l8.367 8.368zm1.414-1.414L6.524 5.11a6 6 0 018.367 8.367zM18 10a8 8 0 11-16 0 8 8 0 0116 0z" clip-rule="evenodd"/>
|
|
272
|
+
</svg>
|
|
273
|
+
Blocked
|
|
274
|
+
</span>
|
|
275
|
+
<% elsif attempt['error_class'].present? %>
|
|
276
|
+
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300">
|
|
277
|
+
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
|
278
|
+
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
|
|
279
|
+
</svg>
|
|
280
|
+
Failed
|
|
281
|
+
</span>
|
|
282
|
+
<% else %>
|
|
283
|
+
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300">
|
|
284
|
+
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
|
285
|
+
<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"/>
|
|
286
|
+
</svg>
|
|
287
|
+
Success
|
|
288
|
+
</span>
|
|
289
|
+
<% end %>
|
|
290
|
+
</td>
|
|
291
|
+
<td class="px-3 py-2 text-sm text-gray-500 dark:text-gray-400">
|
|
292
|
+
<%= attempt['duration_ms'] ? "#{attempt['duration_ms']}ms" : '-' %>
|
|
293
|
+
</td>
|
|
294
|
+
<td class="px-3 py-2 text-sm text-gray-500 dark:text-gray-400">
|
|
295
|
+
<% if attempt['input_tokens'] || attempt['output_tokens'] %>
|
|
296
|
+
<span class="text-blue-600 dark:text-blue-400"><%= attempt['input_tokens'] || 0 %></span>
|
|
297
|
+
/
|
|
298
|
+
<span class="text-green-600 dark:text-green-400"><%= attempt['output_tokens'] || 0 %></span>
|
|
299
|
+
<% else %>
|
|
300
|
+
-
|
|
301
|
+
<% end %>
|
|
302
|
+
</td>
|
|
303
|
+
<td class="px-3 py-2 text-sm">
|
|
304
|
+
<% if attempt['error_class'].present? %>
|
|
305
|
+
<span class="text-red-600 dark:text-red-400 font-mono text-xs" title="<%= attempt['error_message'] %>">
|
|
306
|
+
<%= attempt['error_class'].split('::').last.truncate(30) %>
|
|
307
|
+
</span>
|
|
308
|
+
<% else %>
|
|
309
|
+
<span class="text-gray-400">-</span>
|
|
310
|
+
<% end %>
|
|
311
|
+
</td>
|
|
312
|
+
</tr>
|
|
313
|
+
<% end %>
|
|
314
|
+
</tbody>
|
|
315
|
+
</table>
|
|
316
|
+
</div>
|
|
317
|
+
|
|
318
|
+
<% if @execution.fallback_chain.present? && @execution.fallback_chain.any? %>
|
|
319
|
+
<div class="mt-4 pt-4 border-t border-gray-100 dark:border-gray-700">
|
|
320
|
+
<p class="text-xs text-gray-500 dark:text-gray-400">
|
|
321
|
+
<span class="font-medium">Fallback chain:</span>
|
|
322
|
+
<%= @execution.fallback_chain.join(' → ') %>
|
|
323
|
+
</p>
|
|
324
|
+
<% if @execution.fallback_reason.present? %>
|
|
325
|
+
<p class="text-xs text-amber-600 dark:text-amber-400 mt-1">
|
|
326
|
+
Fallback reason: <%= @execution.fallback_reason %>
|
|
327
|
+
</p>
|
|
328
|
+
<% end %>
|
|
329
|
+
</div>
|
|
330
|
+
<% end %>
|
|
331
|
+
</div>
|
|
332
|
+
<% end %>
|
|
333
|
+
|
|
128
334
|
<% if @execution.status_error? %>
|
|
129
335
|
<!-- Error Details -->
|
|
130
336
|
<div class="bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-800 rounded-lg p-6 mb-6">
|
|
131
|
-
<
|
|
337
|
+
<div class="flex items-center justify-between mb-2">
|
|
338
|
+
<h3 class="text-lg font-semibold text-red-800 dark:text-red-300">Error Details</h3>
|
|
339
|
+
<div class="flex items-center gap-2">
|
|
340
|
+
<% if @execution.rate_limited? %>
|
|
341
|
+
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-300">
|
|
342
|
+
Rate Limited
|
|
343
|
+
</span>
|
|
344
|
+
<% end %>
|
|
345
|
+
<% if @execution.retryable %>
|
|
346
|
+
<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">
|
|
347
|
+
Retryable
|
|
348
|
+
</span>
|
|
349
|
+
<% end %>
|
|
350
|
+
</div>
|
|
351
|
+
</div>
|
|
132
352
|
|
|
133
353
|
<p class="font-mono text-sm text-red-700 dark:text-red-400 mb-2">
|
|
134
354
|
<%= @execution.error_class %>
|
|
@@ -138,13 +358,46 @@
|
|
|
138
358
|
</div>
|
|
139
359
|
<% end %>
|
|
140
360
|
|
|
361
|
+
<!-- Masking Toggle -->
|
|
362
|
+
<div class="bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 rounded-xl p-4 mb-6">
|
|
363
|
+
<div class="flex items-center justify-between">
|
|
364
|
+
<div class="flex items-center gap-2">
|
|
365
|
+
<svg class="w-5 h-5 text-amber-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
366
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/>
|
|
367
|
+
</svg>
|
|
368
|
+
<span class="text-sm font-medium text-amber-800 dark:text-amber-200">Data Masking</span>
|
|
369
|
+
</div>
|
|
370
|
+
<div class="flex items-center gap-3">
|
|
371
|
+
<span id="masking-status" class="text-xs text-amber-600 dark:text-amber-400">
|
|
372
|
+
Sensitive data is hidden
|
|
373
|
+
</span>
|
|
374
|
+
<button
|
|
375
|
+
type="button"
|
|
376
|
+
id="toggle-masking-btn"
|
|
377
|
+
onclick="toggleMasking()"
|
|
378
|
+
class="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md transition-colors bg-amber-100 dark:bg-amber-900/50 text-amber-700 dark:text-amber-300 hover:bg-amber-200 dark:hover:bg-amber-800/50"
|
|
379
|
+
>
|
|
380
|
+
<svg id="eye-icon" class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
381
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|
382
|
+
<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"/>
|
|
383
|
+
</svg>
|
|
384
|
+
<svg id="eye-off-icon" class="w-4 h-4 hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
385
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"/>
|
|
386
|
+
</svg>
|
|
387
|
+
<span id="toggle-btn-text">Show Original</span>
|
|
388
|
+
</button>
|
|
389
|
+
</div>
|
|
390
|
+
</div>
|
|
391
|
+
</div>
|
|
392
|
+
|
|
141
393
|
<!-- Parameters -->
|
|
142
394
|
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-6 mb-6">
|
|
143
395
|
<div class="flex items-center justify-between mb-4">
|
|
144
396
|
<h3 class="text-sm font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">Parameters</h3>
|
|
145
397
|
<button
|
|
146
398
|
type="button"
|
|
147
|
-
data-copy-json="<%= Base64.strict_encode64(JSON.pretty_generate(@execution.parameters || {})) %>"
|
|
399
|
+
data-copy-json="<%= Base64.strict_encode64(JSON.pretty_generate(redact_for_display(@execution.parameters || {}))) %>"
|
|
400
|
+
data-copy-json-original="<%= Base64.strict_encode64(JSON.pretty_generate(@execution.parameters || {})) %>"
|
|
148
401
|
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"
|
|
149
402
|
>
|
|
150
403
|
<svg class="w-4 h-4 copy-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
@@ -156,7 +409,8 @@
|
|
|
156
409
|
<span>Copy</span>
|
|
157
410
|
</button>
|
|
158
411
|
</div>
|
|
159
|
-
<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 font-mono"><%=
|
|
412
|
+
<pre id="parameters-masked" class="maskable-content bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 rounded-lg p-4 text-sm overflow-x-auto font-mono"><%= highlight_json_redacted(@execution.parameters || {}) %></pre>
|
|
413
|
+
<pre id="parameters-original" class="maskable-content hidden bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 rounded-lg p-4 text-sm overflow-x-auto font-mono"><%= highlight_json(@execution.parameters || {}) %></pre>
|
|
160
414
|
</div>
|
|
161
415
|
|
|
162
416
|
<!-- Response -->
|
|
@@ -166,7 +420,8 @@
|
|
|
166
420
|
<h3 class="text-sm font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">Response</h3>
|
|
167
421
|
<button
|
|
168
422
|
type="button"
|
|
169
|
-
data-copy-json="<%= Base64.strict_encode64(JSON.pretty_generate(@execution.response)) %>"
|
|
423
|
+
data-copy-json="<%= Base64.strict_encode64(JSON.pretty_generate(redact_for_display(@execution.response))) %>"
|
|
424
|
+
data-copy-json-original="<%= Base64.strict_encode64(JSON.pretty_generate(@execution.response)) %>"
|
|
170
425
|
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"
|
|
171
426
|
>
|
|
172
427
|
<svg class="w-4 h-4 copy-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
@@ -178,7 +433,8 @@
|
|
|
178
433
|
<span>Copy</span>
|
|
179
434
|
</button>
|
|
180
435
|
</div>
|
|
181
|
-
<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"><%=
|
|
436
|
+
<pre id="response-masked" class="maskable-content 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_redacted(@execution.response) %></pre>
|
|
437
|
+
<pre id="response-original" class="maskable-content 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"><%= highlight_json(@execution.response) %></pre>
|
|
182
438
|
</div>
|
|
183
439
|
<% end %>
|
|
184
440
|
|
|
@@ -189,7 +445,8 @@
|
|
|
189
445
|
<h3 class="text-sm font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">Metadata</h3>
|
|
190
446
|
<button
|
|
191
447
|
type="button"
|
|
192
|
-
data-copy-json="<%= Base64.strict_encode64(JSON.pretty_generate(@execution.metadata)) %>"
|
|
448
|
+
data-copy-json="<%= Base64.strict_encode64(JSON.pretty_generate(redact_for_display(@execution.metadata))) %>"
|
|
449
|
+
data-copy-json-original="<%= Base64.strict_encode64(JSON.pretty_generate(@execution.metadata)) %>"
|
|
193
450
|
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"
|
|
194
451
|
>
|
|
195
452
|
<svg class="w-4 h-4 copy-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
@@ -201,16 +458,361 @@
|
|
|
201
458
|
<span>Copy</span>
|
|
202
459
|
</button>
|
|
203
460
|
</div>
|
|
204
|
-
<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 font-mono"><%=
|
|
461
|
+
<pre id="metadata-masked" class="maskable-content bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 rounded-lg p-4 text-sm overflow-x-auto font-mono"><%= highlight_json_redacted(@execution.metadata) %></pre>
|
|
462
|
+
<pre id="metadata-original" class="maskable-content hidden bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 rounded-lg p-4 text-sm overflow-x-auto font-mono"><%= highlight_json(@execution.metadata) %></pre>
|
|
463
|
+
</div>
|
|
464
|
+
<% end %>
|
|
465
|
+
|
|
466
|
+
<!-- Execution Hierarchy -->
|
|
467
|
+
<% if @execution.parent_execution_id.present? || (@execution.respond_to?(:child_executions) && @execution.child_executions.any?) %>
|
|
468
|
+
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-6 mb-6">
|
|
469
|
+
<h3 class="text-sm font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide mb-4">Execution Hierarchy</h3>
|
|
470
|
+
|
|
471
|
+
<% if @execution.parent_execution_id.present? %>
|
|
472
|
+
<div class="mb-4">
|
|
473
|
+
<span class="text-xs text-gray-500 dark:text-gray-400">Parent Execution:</span>
|
|
474
|
+
<%= link_to "##{@execution.parent_execution_id}",
|
|
475
|
+
ruby_llm_agents.execution_path(@execution.parent_execution_id),
|
|
476
|
+
class: "ml-2 text-blue-600 dark:text-blue-400 hover:underline font-mono text-sm" %>
|
|
477
|
+
</div>
|
|
478
|
+
<% end %>
|
|
479
|
+
|
|
480
|
+
<% if @execution.respond_to?(:child_executions) && @execution.child_executions.any? %>
|
|
481
|
+
<div>
|
|
482
|
+
<span class="text-xs text-gray-500 dark:text-gray-400">Child Executions (<%= @execution.child_executions.count %>):</span>
|
|
483
|
+
<div class="mt-2 flex flex-wrap gap-2">
|
|
484
|
+
<% @execution.child_executions.limit(10).each do |child| %>
|
|
485
|
+
<%= link_to "##{child.id}",
|
|
486
|
+
ruby_llm_agents.execution_path(child),
|
|
487
|
+
class: "inline-flex items-center px-2 py-1 rounded text-xs font-mono bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600" %>
|
|
488
|
+
<% end %>
|
|
489
|
+
<% if @execution.child_executions.count > 10 %>
|
|
490
|
+
<span class="text-xs text-gray-400">+<%= @execution.child_executions.count - 10 %> more</span>
|
|
491
|
+
<% end %>
|
|
492
|
+
</div>
|
|
493
|
+
</div>
|
|
494
|
+
<% end %>
|
|
495
|
+
</div>
|
|
496
|
+
<% end %>
|
|
497
|
+
|
|
498
|
+
<!-- System Prompt -->
|
|
499
|
+
<% if @execution.system_prompt.present? %>
|
|
500
|
+
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-6 mb-6">
|
|
501
|
+
<div class="flex items-center justify-between mb-4">
|
|
502
|
+
<h3 class="text-sm font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">System Prompt</h3>
|
|
503
|
+
<button type="button" onclick="togglePrompt('system')" class="text-xs text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">
|
|
504
|
+
<span id="system-prompt-toggle">Expand</span>
|
|
505
|
+
</button>
|
|
506
|
+
</div>
|
|
507
|
+
<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>
|
|
205
508
|
</div>
|
|
206
509
|
<% end %>
|
|
207
510
|
|
|
511
|
+
<!-- User Prompt -->
|
|
512
|
+
<% if @execution.user_prompt.present? %>
|
|
513
|
+
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-6 mb-6">
|
|
514
|
+
<div class="flex items-center justify-between mb-4">
|
|
515
|
+
<h3 class="text-sm font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">User Prompt</h3>
|
|
516
|
+
<button type="button" onclick="togglePrompt('user')" class="text-xs text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">
|
|
517
|
+
<span id="user-prompt-toggle">Expand</span>
|
|
518
|
+
</button>
|
|
519
|
+
</div>
|
|
520
|
+
<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>
|
|
521
|
+
</div>
|
|
522
|
+
<% end %>
|
|
523
|
+
|
|
524
|
+
<!-- Diagnostics Panel -->
|
|
525
|
+
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-6 mb-6">
|
|
526
|
+
<div class="flex items-center justify-between mb-4">
|
|
527
|
+
<div class="flex items-center gap-2">
|
|
528
|
+
<svg class="w-5 h-5 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
529
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z"/>
|
|
530
|
+
</svg>
|
|
531
|
+
<h3 class="text-sm font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">Diagnostics</h3>
|
|
532
|
+
</div>
|
|
533
|
+
<button
|
|
534
|
+
type="button"
|
|
535
|
+
id="toggle-diagnostics-btn"
|
|
536
|
+
onclick="toggleDiagnostics()"
|
|
537
|
+
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"
|
|
538
|
+
>
|
|
539
|
+
<svg id="diagnostics-expand-icon" class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
540
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
|
541
|
+
</svg>
|
|
542
|
+
<span id="diagnostics-toggle-text">Expand</span>
|
|
543
|
+
</button>
|
|
544
|
+
</div>
|
|
545
|
+
|
|
546
|
+
<!-- Quick Info (always visible) -->
|
|
547
|
+
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
|
|
548
|
+
<div>
|
|
549
|
+
<span class="text-gray-500 dark:text-gray-400 text-xs uppercase tracking-wide">Model</span>
|
|
550
|
+
<p class="font-mono text-gray-900 dark:text-gray-100"><%= @execution.model_id %></p>
|
|
551
|
+
</div>
|
|
552
|
+
<div>
|
|
553
|
+
<span class="text-gray-500 dark:text-gray-400 text-xs uppercase tracking-wide">Temperature</span>
|
|
554
|
+
<p class="font-mono text-gray-900 dark:text-gray-100"><%= @execution.temperature || 'N/A' %></p>
|
|
555
|
+
</div>
|
|
556
|
+
<div>
|
|
557
|
+
<span class="text-gray-500 dark:text-gray-400 text-xs uppercase tracking-wide">Version</span>
|
|
558
|
+
<p class="font-mono text-gray-900 dark:text-gray-100"><%= @execution.agent_version || '1.0' %></p>
|
|
559
|
+
</div>
|
|
560
|
+
<div>
|
|
561
|
+
<span class="text-gray-500 dark:text-gray-400 text-xs uppercase tracking-wide">Status</span>
|
|
562
|
+
<p class="font-mono text-gray-900 dark:text-gray-100"><%= @execution.status %></p>
|
|
563
|
+
</div>
|
|
564
|
+
</div>
|
|
565
|
+
|
|
566
|
+
<!-- Expanded Details (hidden by default) -->
|
|
567
|
+
<div id="diagnostics-details" class="hidden mt-6 pt-6 border-t border-gray-100 dark:border-gray-700">
|
|
568
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
569
|
+
<!-- Timing Information -->
|
|
570
|
+
<div class="bg-gray-50 dark:bg-gray-900/50 rounded-lg p-4">
|
|
571
|
+
<h4 class="text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wide mb-3">Timing</h4>
|
|
572
|
+
<dl class="space-y-2 text-sm">
|
|
573
|
+
<div class="flex justify-between">
|
|
574
|
+
<dt class="text-gray-500 dark:text-gray-400">Started</dt>
|
|
575
|
+
<dd class="font-mono text-gray-900 dark:text-gray-100"><%= @execution.started_at&.strftime("%Y-%m-%d %H:%M:%S.%L") || 'N/A' %></dd>
|
|
576
|
+
</div>
|
|
577
|
+
<div class="flex justify-between">
|
|
578
|
+
<dt class="text-gray-500 dark:text-gray-400">Completed</dt>
|
|
579
|
+
<dd class="font-mono text-gray-900 dark:text-gray-100"><%= @execution.completed_at&.strftime("%Y-%m-%d %H:%M:%S.%L") || 'N/A' %></dd>
|
|
580
|
+
</div>
|
|
581
|
+
<div class="flex justify-between">
|
|
582
|
+
<dt class="text-gray-500 dark:text-gray-400">Duration</dt>
|
|
583
|
+
<dd class="font-mono text-gray-900 dark:text-gray-100"><%= @execution.duration_ms ? "#{@execution.duration_ms}ms" : 'N/A' %></dd>
|
|
584
|
+
</div>
|
|
585
|
+
<% if @execution.streaming? && @execution.time_to_first_token_ms %>
|
|
586
|
+
<div class="flex justify-between">
|
|
587
|
+
<dt class="text-gray-500 dark:text-gray-400">Time to First Token</dt>
|
|
588
|
+
<dd class="font-mono text-gray-900 dark:text-gray-100"><%= @execution.time_to_first_token_ms %>ms</dd>
|
|
589
|
+
</div>
|
|
590
|
+
<% end %>
|
|
591
|
+
<div class="flex justify-between">
|
|
592
|
+
<dt class="text-gray-500 dark:text-gray-400">Created At</dt>
|
|
593
|
+
<dd class="font-mono text-gray-900 dark:text-gray-100"><%= @execution.created_at.strftime("%Y-%m-%d %H:%M:%S") %></dd>
|
|
594
|
+
</div>
|
|
595
|
+
</dl>
|
|
596
|
+
</div>
|
|
597
|
+
|
|
598
|
+
<!-- Performance Metrics -->
|
|
599
|
+
<div class="bg-gray-50 dark:bg-gray-900/50 rounded-lg p-4">
|
|
600
|
+
<h4 class="text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wide mb-3">Performance</h4>
|
|
601
|
+
<dl class="space-y-2 text-sm">
|
|
602
|
+
<div class="flex justify-between">
|
|
603
|
+
<dt class="text-gray-500 dark:text-gray-400">Tokens/Second</dt>
|
|
604
|
+
<dd class="font-mono text-gray-900 dark:text-gray-100"><%= @execution.tokens_per_second&.round(1) || 'N/A' %></dd>
|
|
605
|
+
</div>
|
|
606
|
+
<div class="flex justify-between">
|
|
607
|
+
<dt class="text-gray-500 dark:text-gray-400">Input Tokens</dt>
|
|
608
|
+
<dd class="font-mono text-gray-900 dark:text-gray-100"><%= @execution.input_tokens || 0 %></dd>
|
|
609
|
+
</div>
|
|
610
|
+
<div class="flex justify-between">
|
|
611
|
+
<dt class="text-gray-500 dark:text-gray-400">Output Tokens</dt>
|
|
612
|
+
<dd class="font-mono text-gray-900 dark:text-gray-100"><%= @execution.output_tokens || 0 %></dd>
|
|
613
|
+
</div>
|
|
614
|
+
<div class="flex justify-between">
|
|
615
|
+
<dt class="text-gray-500 dark:text-gray-400">Cached Tokens</dt>
|
|
616
|
+
<dd class="font-mono text-gray-900 dark:text-gray-100"><%= @execution.cached_tokens || 0 %></dd>
|
|
617
|
+
</div>
|
|
618
|
+
</dl>
|
|
619
|
+
</div>
|
|
620
|
+
|
|
621
|
+
<!-- Cost Breakdown -->
|
|
622
|
+
<div class="bg-gray-50 dark:bg-gray-900/50 rounded-lg p-4">
|
|
623
|
+
<h4 class="text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wide mb-3">Cost</h4>
|
|
624
|
+
<dl class="space-y-2 text-sm">
|
|
625
|
+
<div class="flex justify-between">
|
|
626
|
+
<dt class="text-gray-500 dark:text-gray-400">Input Cost</dt>
|
|
627
|
+
<dd class="font-mono text-gray-900 dark:text-gray-100">$<%= number_with_precision(@execution.input_cost || 0, precision: 6) %></dd>
|
|
628
|
+
</div>
|
|
629
|
+
<div class="flex justify-between">
|
|
630
|
+
<dt class="text-gray-500 dark:text-gray-400">Output Cost</dt>
|
|
631
|
+
<dd class="font-mono text-gray-900 dark:text-gray-100">$<%= number_with_precision(@execution.output_cost || 0, precision: 6) %></dd>
|
|
632
|
+
</div>
|
|
633
|
+
<div class="flex justify-between font-semibold">
|
|
634
|
+
<dt class="text-gray-600 dark:text-gray-300">Total Cost</dt>
|
|
635
|
+
<dd class="font-mono text-gray-900 dark:text-gray-100">$<%= number_with_precision(@execution.total_cost || 0, precision: 6) %></dd>
|
|
636
|
+
</div>
|
|
637
|
+
</dl>
|
|
638
|
+
</div>
|
|
639
|
+
|
|
640
|
+
<!-- Configuration -->
|
|
641
|
+
<div class="bg-gray-50 dark:bg-gray-900/50 rounded-lg p-4">
|
|
642
|
+
<h4 class="text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wide mb-3">Configuration</h4>
|
|
643
|
+
<dl class="space-y-2 text-sm">
|
|
644
|
+
<div class="flex justify-between">
|
|
645
|
+
<dt class="text-gray-500 dark:text-gray-400">Agent Type</dt>
|
|
646
|
+
<dd class="font-mono text-gray-900 dark:text-gray-100"><%= @execution.agent_type %></dd>
|
|
647
|
+
</div>
|
|
648
|
+
<div class="flex justify-between">
|
|
649
|
+
<dt class="text-gray-500 dark:text-gray-400">Temperature</dt>
|
|
650
|
+
<dd class="font-mono text-gray-900 dark:text-gray-100"><%= @execution.temperature || 'default' %></dd>
|
|
651
|
+
</div>
|
|
652
|
+
<% if @execution.respond_to?(:chosen_model_id) && @execution.chosen_model_id.present? && @execution.chosen_model_id != @execution.model_id %>
|
|
653
|
+
<div class="flex justify-between">
|
|
654
|
+
<dt class="text-gray-500 dark:text-gray-400">Chosen Model</dt>
|
|
655
|
+
<dd class="font-mono text-amber-600 dark:text-amber-400"><%= @execution.chosen_model_id %></dd>
|
|
656
|
+
</div>
|
|
657
|
+
<% end %>
|
|
658
|
+
<% if @execution.respond_to?(:attempts_count) && @execution.attempts_count && @execution.attempts_count > 1 %>
|
|
659
|
+
<div class="flex justify-between">
|
|
660
|
+
<dt class="text-gray-500 dark:text-gray-400">Retry Count</dt>
|
|
661
|
+
<dd class="font-mono text-blue-600 dark:text-blue-400"><%= @execution.attempts_count - 1 %></dd>
|
|
662
|
+
</div>
|
|
663
|
+
<% end %>
|
|
664
|
+
</dl>
|
|
665
|
+
</div>
|
|
666
|
+
</div>
|
|
667
|
+
|
|
668
|
+
<!-- Second Row: Tracing and Caching -->
|
|
669
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mt-6">
|
|
670
|
+
<!-- Tracing Information -->
|
|
671
|
+
<% if @execution.trace_id.present? || @execution.request_id.present? %>
|
|
672
|
+
<div class="bg-gray-50 dark:bg-gray-900/50 rounded-lg p-4">
|
|
673
|
+
<h4 class="text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wide mb-3">Tracing</h4>
|
|
674
|
+
<dl class="space-y-2 text-sm">
|
|
675
|
+
<% if @execution.request_id.present? %>
|
|
676
|
+
<div class="flex justify-between">
|
|
677
|
+
<dt class="text-gray-500 dark:text-gray-400">Request ID</dt>
|
|
678
|
+
<dd class="font-mono text-gray-900 dark:text-gray-100 text-xs"><%= @execution.request_id %></dd>
|
|
679
|
+
</div>
|
|
680
|
+
<% end %>
|
|
681
|
+
<% if @execution.trace_id.present? %>
|
|
682
|
+
<div class="flex justify-between">
|
|
683
|
+
<dt class="text-gray-500 dark:text-gray-400">Trace ID</dt>
|
|
684
|
+
<dd class="font-mono text-gray-900 dark:text-gray-100 text-xs"><%= @execution.trace_id %></dd>
|
|
685
|
+
</div>
|
|
686
|
+
<% end %>
|
|
687
|
+
<% if @execution.span_id.present? %>
|
|
688
|
+
<div class="flex justify-between">
|
|
689
|
+
<dt class="text-gray-500 dark:text-gray-400">Span ID</dt>
|
|
690
|
+
<dd class="font-mono text-gray-900 dark:text-gray-100 text-xs"><%= @execution.span_id %></dd>
|
|
691
|
+
</div>
|
|
692
|
+
<% end %>
|
|
693
|
+
</dl>
|
|
694
|
+
</div>
|
|
695
|
+
<% end %>
|
|
696
|
+
|
|
697
|
+
<!-- Caching Information -->
|
|
698
|
+
<% if @execution.cache_hit || @execution.response_cache_key.present? %>
|
|
699
|
+
<div class="bg-gray-50 dark:bg-gray-900/50 rounded-lg p-4">
|
|
700
|
+
<h4 class="text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wide mb-3">Caching</h4>
|
|
701
|
+
<dl class="space-y-2 text-sm">
|
|
702
|
+
<div class="flex justify-between">
|
|
703
|
+
<dt class="text-gray-500 dark:text-gray-400">Cache Hit</dt>
|
|
704
|
+
<dd class="font-mono text-gray-900 dark:text-gray-100">
|
|
705
|
+
<% if @execution.cache_hit %>
|
|
706
|
+
<span class="text-green-600 dark:text-green-400">Yes</span>
|
|
707
|
+
<% else %>
|
|
708
|
+
<span class="text-gray-400">No</span>
|
|
709
|
+
<% end %>
|
|
710
|
+
</dd>
|
|
711
|
+
</div>
|
|
712
|
+
<% if @execution.response_cache_key.present? %>
|
|
713
|
+
<div class="flex justify-between">
|
|
714
|
+
<dt class="text-gray-500 dark:text-gray-400">Cache Key</dt>
|
|
715
|
+
<dd class="font-mono text-gray-900 dark:text-gray-100 text-xs truncate max-w-xs" title="<%= @execution.response_cache_key %>">
|
|
716
|
+
<%= @execution.response_cache_key.truncate(30) %>
|
|
717
|
+
</dd>
|
|
718
|
+
</div>
|
|
719
|
+
<% end %>
|
|
720
|
+
<% if @execution.cached_at.present? %>
|
|
721
|
+
<div class="flex justify-between">
|
|
722
|
+
<dt class="text-gray-500 dark:text-gray-400">Cached At</dt>
|
|
723
|
+
<dd class="font-mono text-gray-900 dark:text-gray-100"><%= @execution.cached_at.strftime("%Y-%m-%d %H:%M:%S") %></dd>
|
|
724
|
+
</div>
|
|
725
|
+
<% end %>
|
|
726
|
+
</dl>
|
|
727
|
+
</div>
|
|
728
|
+
<% end %>
|
|
729
|
+
</div>
|
|
730
|
+
|
|
731
|
+
<!-- Execution ID -->
|
|
732
|
+
<div class="mt-4 pt-4 border-t border-gray-100 dark:border-gray-700">
|
|
733
|
+
<div class="flex items-center justify-between">
|
|
734
|
+
<span class="text-xs text-gray-500 dark:text-gray-400">
|
|
735
|
+
Execution ID: <code class="bg-gray-100 dark:bg-gray-700 px-1.5 py-0.5 rounded font-mono"><%= @execution.id %></code>
|
|
736
|
+
</span>
|
|
737
|
+
<button
|
|
738
|
+
type="button"
|
|
739
|
+
onclick="copyDiagnostics()"
|
|
740
|
+
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"
|
|
741
|
+
>
|
|
742
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
743
|
+
<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"/>
|
|
744
|
+
</svg>
|
|
745
|
+
Copy All
|
|
746
|
+
</button>
|
|
747
|
+
</div>
|
|
748
|
+
</div>
|
|
749
|
+
</div>
|
|
750
|
+
</div>
|
|
751
|
+
|
|
208
752
|
<script>
|
|
753
|
+
// Prompt toggle function
|
|
754
|
+
function togglePrompt(type) {
|
|
755
|
+
const content = document.getElementById(type + '-prompt-content');
|
|
756
|
+
const toggle = document.getElementById(type + '-prompt-toggle');
|
|
757
|
+
content.classList.toggle('hidden');
|
|
758
|
+
toggle.textContent = content.classList.contains('hidden') ? 'Expand' : 'Collapse';
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// Masking state (persisted in localStorage)
|
|
762
|
+
let isMasked = localStorage.getItem('ruby_llm_agents_masking') !== 'false';
|
|
763
|
+
|
|
764
|
+
function toggleMasking() {
|
|
765
|
+
isMasked = !isMasked;
|
|
766
|
+
localStorage.setItem('ruby_llm_agents_masking', isMasked);
|
|
767
|
+
updateMaskingUI();
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
function updateMaskingUI() {
|
|
771
|
+
const status = document.getElementById('masking-status');
|
|
772
|
+
const btnText = document.getElementById('toggle-btn-text');
|
|
773
|
+
const eyeIcon = document.getElementById('eye-icon');
|
|
774
|
+
const eyeOffIcon = document.getElementById('eye-off-icon');
|
|
775
|
+
|
|
776
|
+
// Update all maskable content sections
|
|
777
|
+
document.querySelectorAll('[id$="-masked"]').forEach(function(el) {
|
|
778
|
+
el.classList.toggle('hidden', !isMasked);
|
|
779
|
+
});
|
|
780
|
+
document.querySelectorAll('[id$="-original"]').forEach(function(el) {
|
|
781
|
+
el.classList.toggle('hidden', isMasked);
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
// Update status and button
|
|
785
|
+
if (isMasked) {
|
|
786
|
+
status.textContent = 'Sensitive data is hidden';
|
|
787
|
+
btnText.textContent = 'Show Original';
|
|
788
|
+
eyeIcon.classList.remove('hidden');
|
|
789
|
+
eyeOffIcon.classList.add('hidden');
|
|
790
|
+
} else {
|
|
791
|
+
status.textContent = 'Showing original data';
|
|
792
|
+
btnText.textContent = 'Hide Sensitive';
|
|
793
|
+
eyeIcon.classList.add('hidden');
|
|
794
|
+
eyeOffIcon.classList.remove('hidden');
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// Update copy buttons to use appropriate data
|
|
798
|
+
document.querySelectorAll('.copy-json-btn').forEach(function(button) {
|
|
799
|
+
const maskedData = button.getAttribute('data-copy-json');
|
|
800
|
+
const originalData = button.getAttribute('data-copy-json-original');
|
|
801
|
+
if (originalData) {
|
|
802
|
+
button.setAttribute('data-active-json', isMasked ? maskedData : originalData);
|
|
803
|
+
}
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
|
|
209
807
|
document.addEventListener('DOMContentLoaded', function() {
|
|
808
|
+
// Initialize masking UI on page load
|
|
809
|
+
updateMaskingUI();
|
|
810
|
+
|
|
210
811
|
document.querySelectorAll('.copy-json-btn').forEach(function(button) {
|
|
211
812
|
button.addEventListener('click', function() {
|
|
212
|
-
|
|
213
|
-
const
|
|
813
|
+
// Use active JSON (considers masking state) or fall back to default
|
|
814
|
+
const activeData = this.getAttribute('data-active-json') || this.getAttribute('data-copy-json');
|
|
815
|
+
const jsonText = atob(activeData);
|
|
214
816
|
const span = this.querySelector('span');
|
|
215
817
|
const copyIcon = this.querySelector('.copy-icon');
|
|
216
818
|
const checkIcon = this.querySelector('.check-icon');
|
|
@@ -237,4 +839,81 @@
|
|
|
237
839
|
});
|
|
238
840
|
});
|
|
239
841
|
});
|
|
842
|
+
|
|
843
|
+
// Rerun modal functions
|
|
844
|
+
function confirmRerun() {
|
|
845
|
+
document.getElementById('rerun-modal').classList.remove('hidden');
|
|
846
|
+
document.body.classList.add('overflow-hidden');
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
function closeRerunModal() {
|
|
850
|
+
document.getElementById('rerun-modal').classList.add('hidden');
|
|
851
|
+
document.body.classList.remove('overflow-hidden');
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// Close modal on Escape key
|
|
855
|
+
document.addEventListener('keydown', function(e) {
|
|
856
|
+
if (e.key === 'Escape') {
|
|
857
|
+
closeRerunModal();
|
|
858
|
+
}
|
|
859
|
+
});
|
|
860
|
+
|
|
861
|
+
// Diagnostics panel toggle
|
|
862
|
+
let diagnosticsExpanded = localStorage.getItem('ruby_llm_agents_diagnostics_expanded') === 'true';
|
|
863
|
+
|
|
864
|
+
function toggleDiagnostics() {
|
|
865
|
+
diagnosticsExpanded = !diagnosticsExpanded;
|
|
866
|
+
localStorage.setItem('ruby_llm_agents_diagnostics_expanded', diagnosticsExpanded);
|
|
867
|
+
updateDiagnosticsUI();
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
function updateDiagnosticsUI() {
|
|
871
|
+
const details = document.getElementById('diagnostics-details');
|
|
872
|
+
const toggleText = document.getElementById('diagnostics-toggle-text');
|
|
873
|
+
const expandIcon = document.getElementById('diagnostics-expand-icon');
|
|
874
|
+
|
|
875
|
+
if (diagnosticsExpanded) {
|
|
876
|
+
details.classList.remove('hidden');
|
|
877
|
+
toggleText.textContent = 'Collapse';
|
|
878
|
+
expandIcon.style.transform = 'rotate(180deg)';
|
|
879
|
+
} else {
|
|
880
|
+
details.classList.add('hidden');
|
|
881
|
+
toggleText.textContent = 'Expand';
|
|
882
|
+
expandIcon.style.transform = 'rotate(0deg)';
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
function copyDiagnostics() {
|
|
887
|
+
const diagnostics = {
|
|
888
|
+
execution_id: <%= @execution.id %>,
|
|
889
|
+
agent_type: "<%= @execution.agent_type %>",
|
|
890
|
+
agent_version: "<%= @execution.agent_version || '1.0' %>",
|
|
891
|
+
model_id: "<%= @execution.model_id %>",
|
|
892
|
+
status: "<%= @execution.status %>",
|
|
893
|
+
temperature: <%= @execution.temperature || 'null' %>,
|
|
894
|
+
duration_ms: <%= @execution.duration_ms || 'null' %>,
|
|
895
|
+
input_tokens: <%= @execution.input_tokens || 0 %>,
|
|
896
|
+
output_tokens: <%= @execution.output_tokens || 0 %>,
|
|
897
|
+
cached_tokens: <%= @execution.cached_tokens || 0 %>,
|
|
898
|
+
total_tokens: <%= @execution.total_tokens || 0 %>,
|
|
899
|
+
input_cost: <%= @execution.input_cost || 0 %>,
|
|
900
|
+
output_cost: <%= @execution.output_cost || 0 %>,
|
|
901
|
+
total_cost: <%= @execution.total_cost || 0 %>,
|
|
902
|
+
started_at: "<%= @execution.started_at&.iso8601 || '' %>",
|
|
903
|
+
completed_at: "<%= @execution.completed_at&.iso8601 || '' %>",
|
|
904
|
+
created_at: "<%= @execution.created_at.iso8601 %>"
|
|
905
|
+
};
|
|
906
|
+
|
|
907
|
+
navigator.clipboard.writeText(JSON.stringify(diagnostics, null, 2)).then(function() {
|
|
908
|
+
alert('Diagnostics copied to clipboard!');
|
|
909
|
+
}).catch(function(err) {
|
|
910
|
+
console.error('Failed to copy diagnostics:', err);
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// Initialize diagnostics panel on page load
|
|
915
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
916
|
+
updateDiagnosticsUI();
|
|
917
|
+
});
|
|
240
918
|
</script>
|
|
919
|
+
</div>
|