ruby_llm-agents 0.3.3 → 0.3.5

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.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +132 -1263
  3. data/app/controllers/concerns/ruby_llm/agents/filterable.rb +5 -1
  4. data/app/controllers/concerns/ruby_llm/agents/paginatable.rb +2 -1
  5. data/app/controllers/ruby_llm/agents/agents_controller.rb +21 -2
  6. data/app/controllers/ruby_llm/agents/dashboard_controller.rb +137 -9
  7. data/app/controllers/ruby_llm/agents/executions_controller.rb +83 -5
  8. data/app/models/ruby_llm/agents/execution/analytics.rb +103 -12
  9. data/app/models/ruby_llm/agents/execution/scopes.rb +25 -0
  10. data/app/models/ruby_llm/agents/execution/workflow.rb +299 -0
  11. data/app/models/ruby_llm/agents/execution.rb +28 -59
  12. data/app/models/ruby_llm/agents/tenant_budget.rb +165 -0
  13. data/app/services/ruby_llm/agents/agent_registry.rb +118 -7
  14. data/app/views/layouts/ruby_llm/agents/application.html.erb +430 -0
  15. data/app/views/ruby_llm/agents/agents/_empty_state.html.erb +23 -0
  16. data/app/views/ruby_llm/agents/agents/_workflow.html.erb +125 -0
  17. data/app/views/ruby_llm/agents/agents/index.html.erb +93 -0
  18. data/app/views/ruby_llm/agents/agents/show.html.erb +775 -0
  19. data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +112 -0
  20. data/app/views/ruby_llm/agents/dashboard/_budgets_bar.html.erb +75 -0
  21. data/app/views/{rubyllm → ruby_llm}/agents/dashboard/_execution_item.html.erb +7 -4
  22. data/app/views/ruby_llm/agents/dashboard/_now_strip.html.erb +84 -0
  23. data/app/views/ruby_llm/agents/dashboard/_tenant_budget.html.erb +115 -0
  24. data/app/views/ruby_llm/agents/dashboard/_top_errors.html.erb +49 -0
  25. data/app/views/ruby_llm/agents/dashboard/index.html.erb +155 -0
  26. data/app/views/{rubyllm → ruby_llm}/agents/executions/_execution.html.erb +1 -1
  27. data/app/views/{rubyllm → ruby_llm}/agents/executions/_filters.html.erb +39 -11
  28. data/app/views/{rubyllm → ruby_llm}/agents/executions/_list.html.erb +19 -9
  29. data/app/views/ruby_llm/agents/executions/_workflow_summary.html.erb +101 -0
  30. data/app/views/ruby_llm/agents/executions/index.html.erb +88 -0
  31. data/app/views/{rubyllm → ruby_llm}/agents/executions/show.html.erb +260 -200
  32. data/app/views/{rubyllm → ruby_llm}/agents/settings/show.html.erb +1 -1
  33. data/app/views/ruby_llm/agents/shared/_breadcrumbs.html.erb +48 -0
  34. data/app/views/ruby_llm/agents/shared/_executions_table.html.erb +251 -0
  35. data/app/views/{rubyllm → ruby_llm}/agents/shared/_filter_dropdown.html.erb +1 -1
  36. data/app/views/ruby_llm/agents/shared/_nav_link.html.erb +27 -0
  37. data/app/views/{rubyllm → ruby_llm}/agents/shared/_select_dropdown.html.erb +1 -1
  38. data/app/views/{rubyllm → ruby_llm}/agents/shared/_status_badge.html.erb +1 -1
  39. data/app/views/{rubyllm → ruby_llm}/agents/shared/_status_dot.html.erb +1 -1
  40. data/app/views/ruby_llm/agents/shared/_tenant_filter.html.erb +26 -0
  41. data/app/views/ruby_llm/agents/shared/_workflow_type_badge.html.erb +61 -0
  42. data/config/routes.rb +2 -0
  43. data/lib/generators/ruby_llm_agents/multi_tenancy_generator.rb +97 -0
  44. data/lib/generators/ruby_llm_agents/templates/add_attempts_migration.rb.tt +3 -3
  45. data/lib/generators/ruby_llm_agents/templates/add_tenant_to_executions_migration.rb.tt +23 -0
  46. data/lib/generators/ruby_llm_agents/templates/add_tool_calls_migration.rb.tt +2 -2
  47. data/lib/generators/ruby_llm_agents/templates/add_workflow_migration.rb.tt +38 -0
  48. data/lib/generators/ruby_llm_agents/templates/create_tenant_budgets_migration.rb.tt +45 -0
  49. data/lib/generators/ruby_llm_agents/templates/migration.rb.tt +17 -5
  50. data/lib/generators/ruby_llm_agents/upgrade_generator.rb +13 -0
  51. data/lib/ruby_llm/agents/alert_manager.rb +20 -16
  52. data/lib/ruby_llm/agents/base/caching.rb +40 -0
  53. data/lib/ruby_llm/agents/base/cost_calculation.rb +105 -0
  54. data/lib/ruby_llm/agents/base/dsl.rb +261 -0
  55. data/lib/ruby_llm/agents/base/execution.rb +258 -0
  56. data/lib/ruby_llm/agents/base/reliability_execution.rb +136 -0
  57. data/lib/ruby_llm/agents/base/response_building.rb +86 -0
  58. data/lib/ruby_llm/agents/base/tool_tracking.rb +57 -0
  59. data/lib/ruby_llm/agents/base.rb +37 -801
  60. data/lib/ruby_llm/agents/budget_tracker.rb +250 -139
  61. data/lib/ruby_llm/agents/cache_helper.rb +98 -0
  62. data/lib/ruby_llm/agents/circuit_breaker.rb +48 -30
  63. data/lib/ruby_llm/agents/configuration.rb +40 -1
  64. data/lib/ruby_llm/agents/engine.rb +65 -1
  65. data/lib/ruby_llm/agents/inflections.rb +14 -0
  66. data/lib/ruby_llm/agents/instrumentation.rb +66 -0
  67. data/lib/ruby_llm/agents/reliability.rb +8 -2
  68. data/lib/ruby_llm/agents/version.rb +1 -1
  69. data/lib/ruby_llm/agents/workflow/instrumentation.rb +254 -0
  70. data/lib/ruby_llm/agents/workflow/parallel.rb +282 -0
  71. data/lib/ruby_llm/agents/workflow/pipeline.rb +306 -0
  72. data/lib/ruby_llm/agents/workflow/result.rb +390 -0
  73. data/lib/ruby_llm/agents/workflow/router.rb +429 -0
  74. data/lib/ruby_llm/agents/workflow.rb +232 -0
  75. data/lib/ruby_llm/agents.rb +1 -0
  76. metadata +57 -75
  77. data/app/channels/ruby_llm/agents/executions_channel.rb +0 -46
  78. data/app/javascript/ruby_llm/agents/controllers/filter_controller.js +0 -56
  79. data/app/javascript/ruby_llm/agents/controllers/index.js +0 -12
  80. data/app/javascript/ruby_llm/agents/controllers/refresh_controller.js +0 -83
  81. data/app/views/layouts/rubyllm/agents/application.html.erb +0 -626
  82. data/app/views/rubyllm/agents/agents/index.html.erb +0 -20
  83. data/app/views/rubyllm/agents/agents/show.html.erb +0 -772
  84. data/app/views/rubyllm/agents/dashboard/_budgets_bar.html.erb +0 -165
  85. data/app/views/rubyllm/agents/dashboard/_now_strip.html.erb +0 -10
  86. data/app/views/rubyllm/agents/dashboard/_now_strip_values.html.erb +0 -71
  87. data/app/views/rubyllm/agents/dashboard/index.html.erb +0 -197
  88. data/app/views/rubyllm/agents/executions/index.html.erb +0 -28
  89. data/app/views/rubyllm/agents/executions/index.turbo_stream.erb +0 -18
  90. data/app/views/rubyllm/agents/shared/_executions_table.html.erb +0 -193
  91. /data/app/views/{rubyllm → ruby_llm}/agents/agents/_agent.html.erb +0 -0
  92. /data/app/views/{rubyllm → ruby_llm}/agents/agents/_version_comparison.html.erb +0 -0
  93. /data/app/views/{rubyllm → ruby_llm}/agents/dashboard/_action_center.html.erb +0 -0
  94. /data/app/views/{rubyllm → ruby_llm}/agents/dashboard/_alerts_feed.html.erb +0 -0
  95. /data/app/views/{rubyllm → ruby_llm}/agents/dashboard/_breaker_strip.html.erb +0 -0
  96. /data/app/views/{rubyllm → ruby_llm}/agents/executions/dry_run.html.erb +0 -0
  97. /data/app/views/{rubyllm → ruby_llm}/agents/shared/_stat_card.html.erb +0 -0
@@ -1,626 +0,0 @@
1
- <!DOCTYPE html>
2
-
3
- <html lang="en">
4
- <head>
5
- <meta charset="utf-8">
6
- <meta name="viewport" content="width=device-width, initial-scale=1">
7
-
8
- <title>RubyLLM Agents Dashboard</title>
9
-
10
- <!-- Prevent flash of wrong theme - must run before any rendering -->
11
- <script>
12
- (function() {
13
- const preference = localStorage.getItem('ruby_llm_agents_theme') || 'auto';
14
- const isDark = preference === 'dark' ||
15
- (preference === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches);
16
- if (isDark) {
17
- document.documentElement.classList.add('dark');
18
- }
19
- })();
20
- </script>
21
-
22
- <!-- Tailwind CSS via CDN -->
23
- <script src="https://cdn.tailwindcss.com"></script>
24
-
25
- <script>
26
- tailwind.config = {
27
- darkMode: 'class'
28
- }
29
- </script>
30
-
31
- <!-- Highcharts for charts -->
32
- <script src="https://code.highcharts.com/highcharts.js"></script>
33
- <script src="https://cdn.jsdelivr.net/npm/chartkick@5.0.1"></script>
34
-
35
- <!-- Configure Highcharts defaults -->
36
- <script>
37
- Highcharts.setOptions({
38
- credits: { enabled: false },
39
- chart: {
40
- backgroundColor: 'transparent',
41
- style: { fontFamily: 'inherit' }
42
- },
43
- title: { text: null },
44
- xAxis: {
45
- labels: { style: { color: '#9CA3AF' } },
46
- lineColor: 'rgba(156, 163, 175, 0.2)',
47
- tickColor: 'rgba(156, 163, 175, 0.2)'
48
- },
49
- yAxis: {
50
- labels: { style: { color: '#9CA3AF' } },
51
- gridLineColor: 'rgba(156, 163, 175, 0.2)'
52
- },
53
- legend: {
54
- itemStyle: { color: '#9CA3AF' },
55
- itemHoverStyle: { color: '#D1D5DB' }
56
- },
57
- tooltip: {
58
- backgroundColor: 'rgba(17, 24, 39, 0.9)',
59
- borderColor: 'rgba(75, 85, 99, 0.5)',
60
- style: { color: '#F3F4F6' }
61
- }
62
- });
63
- </script>
64
-
65
- <!-- Stimulus -->
66
- <script
67
- src="https://unpkg.com/@hotwired/stimulus@3.2.2/dist/stimulus.umd.js"
68
- ></script>
69
-
70
- <!-- Alpine.js -->
71
- <script
72
- defer
73
- src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.3/dist/cdn.min.js"
74
- ></script>
75
-
76
- <style>[x-cloak] { display: none !important; }</style>
77
-
78
- <!-- Auto-refresh for dashboard updates -->
79
- <script>
80
- (function() {
81
- // Simple polling for real-time updates (5 second interval)
82
- const POLL_INTERVAL = 5000
83
- let pollTimer = null
84
-
85
- function startPolling() {
86
- if (pollTimer) return
87
-
88
- // Update indicator to show polling is active
89
- const indicator = document.getElementById("live-indicator")
90
- if (indicator) {
91
- indicator.className = "hidden sm:flex items-center text-blue-600 dark:text-blue-400"
92
- indicator.innerHTML = '<span class="w-1.5 h-1.5 bg-blue-500 rounded-full mr-1 animate-pulse"></span><span class="hidden sm:inline">Auto</span>'
93
- }
94
-
95
- pollTimer = setInterval(() => {
96
- // Dashboard page - detected by presence of activity feed
97
- if (document.getElementById("activity-feed")) {
98
- fetch(window.location.href, {
99
- headers: { "Accept": "text/html" }
100
- })
101
- .then(response => response.text())
102
- .then(html => {
103
- const parser = new DOMParser()
104
- const doc = parser.parseFromString(html, "text/html")
105
-
106
- // Update activity feed
107
- const newFeed = doc.getElementById("activity-feed")
108
- const currentFeed = document.getElementById("activity-feed")
109
- if (newFeed && currentFeed) {
110
- currentFeed.innerHTML = newFeed.innerHTML
111
- }
112
-
113
- // Update now strip
114
- const newStrip = doc.getElementById("now-strip-values")
115
- const currentStrip = document.getElementById("now-strip-values")
116
- if (newStrip && currentStrip) {
117
- currentStrip.innerHTML = newStrip.innerHTML
118
- }
119
-
120
- // Note: Chart has its own 1-second live update built-in
121
-
122
- // Update action center
123
- const newActionCenter = doc.getElementById("action-center")
124
- const currentActionCenter = document.getElementById("action-center")
125
- if (newActionCenter && currentActionCenter) {
126
- currentActionCenter.outerHTML = newActionCenter.outerHTML
127
- } else if (newActionCenter && !currentActionCenter) {
128
- // Insert action center if it appeared
129
- const main = document.querySelector("main")
130
- if (main) main.insertAdjacentHTML("afterbegin", newActionCenter.outerHTML)
131
- } else if (!newActionCenter && currentActionCenter) {
132
- // Remove action center if it disappeared
133
- currentActionCenter.remove()
134
- }
135
- })
136
- .catch(err => console.log("[RubyLLM::Agents] Poll error:", err))
137
- }
138
-
139
- // Execution show page - detected by presence of execution-detail element
140
- const executionDetail = document.getElementById("execution-detail")
141
- if (executionDetail) {
142
- const currentStatus = executionDetail.dataset.status
143
-
144
- // Only poll if execution is still running
145
- if (currentStatus === "running") {
146
- fetch(window.location.href, {
147
- headers: { "Accept": "text/html" }
148
- })
149
- .then(response => response.text())
150
- .then(html => {
151
- const parser = new DOMParser()
152
- const doc = parser.parseFromString(html, "text/html")
153
-
154
- // Update the main content area when status changes
155
- const newContent = doc.getElementById("execution-detail")
156
- if (newContent && newContent.dataset.status !== "running") {
157
- // Execution completed - do a full content update
158
- executionDetail.outerHTML = newContent.outerHTML
159
- }
160
- })
161
- .catch(err => console.log("[RubyLLM::Agents] Poll error:", err))
162
- }
163
- }
164
- }, POLL_INTERVAL)
165
- }
166
-
167
- function stopPolling() {
168
- if (pollTimer) {
169
- clearInterval(pollTimer)
170
- pollTimer = null
171
- }
172
- }
173
-
174
- // Start polling on page load
175
- document.addEventListener("DOMContentLoaded", startPolling)
176
-
177
- // Handle visibility changes - pause when tab is hidden
178
- document.addEventListener("visibilitychange", () => {
179
- if (document.hidden) {
180
- stopPolling()
181
- } else {
182
- startPolling()
183
- }
184
- })
185
- })()
186
- </script>
187
-
188
- <!-- Live Clock Script -->
189
- <script type="module">
190
- function updateClock() {
191
- const clock = document.getElementById('live-clock');
192
- if (clock) {
193
- const now = new Date();
194
- clock.textContent = now.toLocaleTimeString('en-US', {
195
- hour: '2-digit',
196
- minute: '2-digit',
197
- second: '2-digit',
198
- hour12: false
199
- });
200
- }
201
- }
202
-
203
- // Start clock on page load
204
- document.addEventListener('turbo:load', function() {
205
- if (window.rubyLLMAgentsClock) clearInterval(window.rubyLLMAgentsClock);
206
- updateClock();
207
- window.rubyLLMAgentsClock = setInterval(updateClock, 1000);
208
-
209
- // Handle data-href clickable rows (semantic alternative to onclick)
210
- document.querySelectorAll('[data-href]').forEach(function(element) {
211
- element.addEventListener('click', function(e) {
212
- // Don't navigate if clicking on a link or button inside the row
213
- if (e.target.closest('a, button')) return;
214
- window.location = element.dataset.href;
215
- });
216
- });
217
- });
218
- </script>
219
-
220
- <!-- Stimulus Controllers -->
221
- <script>
222
- (function() {
223
- // Initialize Stimulus immediately (works with Turbo)
224
- if (!window.Stimulus) {
225
- window.Stimulus = {};
226
- }
227
-
228
- const Stimulus = window.Stimulus;
229
-
230
- // Only initialize once
231
- if (!Stimulus.application) {
232
- Stimulus.application = Stimulus.Application.start();
233
-
234
- // Theme Controller - handles dark mode switching
235
- Stimulus.application.register("theme", class extends Stimulus.Controller {
236
- static targets = ["select"]
237
-
238
- connect() {
239
- this.mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
240
- this.applyTheme(this.getPreference())
241
- this.boundHandleSystemChange = this.handleSystemChange.bind(this)
242
- this.mediaQuery.addEventListener('change', this.boundHandleSystemChange)
243
-
244
- // Set the select value to match stored preference
245
- if (this.hasSelectTarget) {
246
- this.selectTarget.value = this.getPreference()
247
- }
248
- }
249
-
250
- disconnect() {
251
- this.mediaQuery.removeEventListener('change', this.boundHandleSystemChange)
252
- }
253
-
254
- change(event) {
255
- const preference = event.target.value
256
- localStorage.setItem('ruby_llm_agents_theme', preference)
257
- this.applyTheme(preference)
258
- }
259
-
260
- getPreference() {
261
- return localStorage.getItem('ruby_llm_agents_theme') || 'auto'
262
- }
263
-
264
- applyTheme(preference) {
265
- const isDark = preference === 'dark' ||
266
- (preference === 'auto' && this.mediaQuery.matches)
267
- document.documentElement.classList.toggle('dark', isDark)
268
- }
269
-
270
- handleSystemChange() {
271
- if (this.getPreference() === 'auto') {
272
- this.applyTheme('auto')
273
- }
274
- }
275
- });
276
-
277
- console.log('[RubyLLM::Agents] Stimulus initialized');
278
- }
279
- })();
280
- </script>
281
-
282
- <style>
283
- /* Custom styles for the dashboard */
284
- .stat-card {
285
- @apply bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 p-4 transition-shadow;
286
- }
287
- .stat-card:hover {
288
- @apply shadow-md;
289
- }
290
- .stat-value {
291
- @apply text-2xl font-bold text-gray-900 dark:text-gray-100 mt-2;
292
- }
293
- @media (min-width: 1024px) {
294
- .stat-value {
295
- font-size: 1.5rem;
296
- }
297
- }
298
- .stat-label {
299
- @apply text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide font-medium;
300
- }
301
- .chart-card {
302
- @apply bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 p-6;
303
- }
304
- .nav-link {
305
- @apply px-4 py-2 text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md transition-colors;
306
- }
307
- .nav-link.active {
308
- @apply bg-blue-50 dark:bg-blue-900 text-blue-700 dark:text-blue-300;
309
- }
310
- .badge {
311
- @apply inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium;
312
- }
313
- .badge-running {
314
- @apply bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200;
315
- }
316
- .badge-success {
317
- @apply bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200;
318
- }
319
- .badge-error {
320
- @apply bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200;
321
- }
322
- .badge-timeout {
323
- @apply bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200;
324
- }
325
- .badge-cyan {
326
- @apply bg-cyan-100 dark:bg-cyan-900/50 text-cyan-700 dark:text-cyan-300;
327
- }
328
- .badge-purple {
329
- @apply bg-purple-100 dark:bg-purple-900/50 text-purple-700 dark:text-purple-300;
330
- }
331
- .badge-orange {
332
- @apply bg-orange-100 dark:bg-orange-900/50 text-orange-700 dark:text-orange-300;
333
- }
334
- </style>
335
- </head>
336
-
337
- <body class="bg-gray-50 dark:bg-gray-900 min-h-screen flex flex-col">
338
- <!-- Header -->
339
- <header
340
- class="
341
- bg-white dark:bg-gray-800 border-b border-gray-200
342
- dark:border-gray-700
343
- "
344
- x-data="{ mobileMenuOpen: false }"
345
- >
346
- <div class="max-w-7xl w-full mx-auto px-4 sm:px-6 lg:px-8 h-16 flex items-center">
347
- <div class="flex justify-between items-center w-full">
348
- <div class="flex items-center space-x-8">
349
- <%= link_to ruby_llm_agents.root_path, class: "flex items-center space-x-2" do %>
350
- <span class="text-lg">🤖</span>
351
-
352
- <span class="font-semibold text-gray-900 dark:text-gray-100">
353
- RubyLLM Agents
354
- </span>
355
- <% end %>
356
-
357
- <!-- Desktop Navigation -->
358
- <nav class="hidden md:flex items-center space-x-1">
359
- <%= link_to ruby_llm_agents.root_path, class: "inline-flex items-center px-3 py-1.5 text-sm font-medium rounded-md #{current_page?(ruby_llm_agents.root_path) ? 'bg-gray-200 dark:bg-gray-700 dark:text-gray-100' : 'text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-gray-100 dark:hover:bg-gray-700'}" do %>
360
- <svg
361
- class="w-4 h-4 mr-1.5"
362
- fill="none"
363
- stroke="currentColor"
364
- viewBox="0 0 24 24"
365
- >
366
- <path
367
- stroke-linecap="round"
368
- stroke-linejoin="round"
369
- stroke-width="2"
370
- d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
371
- />
372
- </svg>
373
- Dashboard
374
- <% end %>
375
-
376
- <%= link_to ruby_llm_agents.agents_path, class: "inline-flex items-center px-3 py-1.5 text-sm font-medium rounded-md #{request.path.start_with?(ruby_llm_agents.agents_path) ? 'bg-gray-200 dark:bg-gray-700 dark:text-gray-100' : 'text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-gray-100 dark:hover:bg-gray-700'}" do %>
377
- <svg
378
- class="w-4 h-4 mr-1.5"
379
- fill="none"
380
- stroke="currentColor"
381
- viewBox="0 0 24 24"
382
- >
383
- <path
384
- stroke-linecap="round"
385
- stroke-linejoin="round"
386
- stroke-width="2"
387
- d="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"
388
- />
389
- </svg>
390
- Agents
391
- <% end %>
392
-
393
- <%= link_to ruby_llm_agents.executions_path, class: "inline-flex items-center px-3 py-1.5 text-sm font-medium rounded-md #{current_page?(ruby_llm_agents.executions_path) ? 'bg-gray-200 dark:bg-gray-700 dark:text-gray-100' : 'text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-gray-100 dark:hover:bg-gray-700'}" do %>
394
- <svg
395
- class="w-4 h-4 mr-1.5"
396
- fill="none"
397
- stroke="currentColor"
398
- viewBox="0 0 24 24"
399
- >
400
- <path
401
- stroke-linecap="round"
402
- stroke-linejoin="round"
403
- stroke-width="2"
404
- d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01"
405
- />
406
- </svg>
407
- Executions
408
- <% end %>
409
-
410
- <%= link_to ruby_llm_agents.settings_path, class: "inline-flex items-center px-3 py-1.5 text-sm font-medium rounded-md #{current_page?(ruby_llm_agents.settings_path) ? 'bg-gray-200 dark:bg-gray-700 dark:text-gray-100' : 'text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-gray-100 dark:hover:bg-gray-700'}" do %>
411
- <svg
412
- class="w-4 h-4 mr-1.5"
413
- fill="none"
414
- stroke="currentColor"
415
- viewBox="0 0 24 24"
416
- >
417
- <path
418
- stroke-linecap="round"
419
- stroke-linejoin="round"
420
- stroke-width="2"
421
- 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"
422
- />
423
-
424
- <path
425
- stroke-linecap="round"
426
- stroke-linejoin="round"
427
- stroke-width="2"
428
- d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
429
- />
430
- </svg>
431
- Settings
432
- <% end %>
433
- </nav>
434
- </div>
435
-
436
- <div
437
- class="
438
- flex items-center space-x-2 text-xs text-gray-500
439
- dark:text-gray-400
440
- "
441
- >
442
- <span id="live-clock" class="tabular-nums"></span>
443
-
444
- <span
445
- id="live-indicator"
446
- class="hidden sm:flex items-center text-blue-600 dark:text-blue-400"
447
- >
448
- <span class="w-1.5 h-1.5 bg-blue-500 rounded-full mr-1 animate-pulse"></span>
449
- <span class="hidden sm:inline">Auto</span>
450
- </span>
451
-
452
- <!-- Mobile menu button -->
453
- <button
454
- type="button"
455
- @click="mobileMenuOpen = !mobileMenuOpen"
456
- :aria-expanded="mobileMenuOpen"
457
- aria-controls="mobile-menu"
458
- class="
459
- md:hidden inline-flex items-center justify-center p-2
460
- rounded-md text-gray-500 dark:text-gray-400
461
- hover:text-gray-900 dark:hover:text-gray-100
462
- hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none
463
- focus:ring-2 focus:ring-inset focus:ring-blue-500
464
- "
465
- >
466
- <span class="sr-only">Open main menu</span>
467
-
468
- <svg
469
- class="w-5 h-5"
470
- fill="none"
471
- stroke="currentColor"
472
- viewBox="0 0 24 24"
473
- >
474
- <path
475
- stroke-linecap="round"
476
- stroke-linejoin="round"
477
- stroke-width="2"
478
- d="M4 6h16M4 12h16M4 18h16"
479
- />
480
- </svg>
481
- </button>
482
- </div>
483
- </div>
484
- </div>
485
-
486
- <!-- Mobile Navigation Menu -->
487
- <div
488
- id="mobile-menu"
489
- x-show="mobileMenuOpen"
490
- x-cloak
491
- x-transition:enter="transition ease-out duration-200"
492
- x-transition:enter-start="opacity-0 -translate-y-1"
493
- x-transition:enter-end="opacity-100 translate-y-0"
494
- x-transition:leave="transition ease-in duration-150"
495
- x-transition:leave-start="opacity-100 translate-y-0"
496
- x-transition:leave-end="opacity-0 -translate-y-1"
497
- @click.outside="mobileMenuOpen = false"
498
- class="
499
- md:hidden border-t border-gray-200 dark:border-gray-700 bg-white
500
- dark:bg-gray-800
501
- "
502
- >
503
- <nav class="max-w-7xl mx-auto px-4 py-3 space-y-1">
504
- <%= link_to ruby_llm_agents.root_path, "x-on:click": "mobileMenuOpen = false", class: "flex items-center px-3 py-2 text-base font-medium rounded-md #{current_page?(ruby_llm_agents.root_path) ? 'bg-gray-200 dark:bg-gray-700 text-gray-900 dark:text-gray-100' : 'text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-gray-100 dark:hover:bg-gray-700'}" do %>
505
- <svg
506
- class="w-5 h-5 mr-3"
507
- fill="none"
508
- stroke="currentColor"
509
- viewBox="0 0 24 24"
510
- >
511
- <path
512
- stroke-linecap="round"
513
- stroke-linejoin="round"
514
- stroke-width="2"
515
- d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
516
- />
517
- </svg>
518
- Dashboard
519
- <% end %>
520
-
521
- <%= link_to ruby_llm_agents.agents_path, "x-on:click": "mobileMenuOpen = false", class: "flex items-center px-3 py-2 text-base font-medium rounded-md #{request.path.start_with?(ruby_llm_agents.agents_path) ? 'bg-gray-200 dark:bg-gray-700 text-gray-900 dark:text-gray-100' : 'text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-gray-100 dark:hover:bg-gray-700'}" do %>
522
- <svg
523
- class="w-5 h-5 mr-3"
524
- fill="none"
525
- stroke="currentColor"
526
- viewBox="0 0 24 24"
527
- >
528
- <path
529
- stroke-linecap="round"
530
- stroke-linejoin="round"
531
- stroke-width="2"
532
- d="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"
533
- />
534
- </svg>
535
- Agents
536
- <% end %>
537
-
538
- <%= link_to ruby_llm_agents.executions_path, "x-on:click": "mobileMenuOpen = false", class: "flex items-center px-3 py-2 text-base font-medium rounded-md #{current_page?(ruby_llm_agents.executions_path) ? 'bg-gray-200 dark:bg-gray-700 text-gray-900 dark:text-gray-100' : 'text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-gray-100 dark:hover:bg-gray-700'}" do %>
539
- <svg
540
- class="w-5 h-5 mr-3"
541
- fill="none"
542
- stroke="currentColor"
543
- viewBox="0 0 24 24"
544
- >
545
- <path
546
- stroke-linecap="round"
547
- stroke-linejoin="round"
548
- stroke-width="2"
549
- d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01"
550
- />
551
- </svg>
552
- Executions
553
- <% end %>
554
-
555
- <%= link_to ruby_llm_agents.settings_path, "x-on:click": "mobileMenuOpen = false", class: "flex items-center px-3 py-2 text-base font-medium rounded-md #{current_page?(ruby_llm_agents.settings_path) ? 'bg-gray-200 dark:bg-gray-700 text-gray-900 dark:text-gray-100' : 'text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-gray-100 dark:hover:bg-gray-700'}" do %>
556
- <svg
557
- class="w-5 h-5 mr-3"
558
- fill="none"
559
- stroke="currentColor"
560
- viewBox="0 0 24 24"
561
- >
562
- <path
563
- stroke-linecap="round"
564
- stroke-linejoin="round"
565
- stroke-width="2"
566
- 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"
567
- />
568
-
569
- <path
570
- stroke-linecap="round"
571
- stroke-linejoin="round"
572
- stroke-width="2"
573
- d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
574
- />
575
- </svg>
576
- Settings
577
- <% end %>
578
- </nav>
579
- </div>
580
- </header>
581
-
582
- <!-- Main content -->
583
- <main class="max-w-7xl w-full mx-auto px-4 sm:px-6 lg:px-8 py-8 h-full">
584
- <%= yield %>
585
- </main>
586
-
587
- <!-- Footer -->
588
- <footer
589
- class="border-t bg-white dark:bg-gray-800 dark:border-gray-700 mt-auto"
590
- data-controller="theme"
591
- >
592
- <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
593
- <div class="flex flex-col sm:flex-row items-center sm:justify-between gap-3 sm:gap-0">
594
- <div class="flex items-center space-x-2">
595
- <label
596
- for="theme-select"
597
- class="text-sm text-gray-500 dark:text-gray-400"
598
- >
599
- Theme:
600
- </label>
601
-
602
- <select
603
- id="theme-select"
604
- data-theme-target="select"
605
- data-action="change->theme#change"
606
- class="
607
- text-sm border border-gray-300 dark:border-gray-600
608
- dark:bg-gray-700 dark:text-gray-200 rounded-md shadow-sm
609
- focus:ring-blue-500 focus:border-blue-500 py-1 px-2
610
- "
611
- >
612
- <option value="light">Light</option>
613
- <option value="dark">Dark</option>
614
- <option value="auto">Auto</option>
615
- </select>
616
- </div>
617
-
618
- <p class="text-sm text-gray-500 dark:text-gray-400 text-center">
619
- Powered by
620
- <a href="https://github.com/adham90/ruby_llm-agents" class="text-blue-600 dark:text-blue-400 hover:underline">ruby_llm-agents</a>
621
- </p>
622
- </div>
623
- </div>
624
- </footer>
625
- </body>
626
- </html>
@@ -1,20 +0,0 @@
1
- <div class="mb-6">
2
- <h1 class="text-2xl font-bold text-gray-900 dark:text-gray-100">Agents</h1>
3
- <p class="text-gray-500 dark:text-gray-400 mt-1">All available agents and their execution statistics</p>
4
- </div>
5
-
6
- <% if @agents.empty? %>
7
- <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-8 text-center">
8
- <svg class="mx-auto h-12 w-12 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
9
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="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" />
10
- </svg>
11
- <h3 class="mt-2 text-sm font-medium text-gray-900 dark:text-gray-100">No agents found</h3>
12
- <p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Create an agent by running <code class="bg-gray-100 dark:bg-gray-700 dark:text-gray-200 px-1 rounded">rails g ruby_llm_agents:agent YourAgentName</code></p>
13
- </div>
14
- <% else %>
15
- <div class="space-y-3 sm:space-y-4">
16
- <% @agents.each do |agent| %>
17
- <%= render partial: "rubyllm/agents/agents/agent", locals: { agent: agent } %>
18
- <% end %>
19
- </div>
20
- <% end %>