ruby_llm-agents 0.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.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +898 -0
  4. data/app/channels/ruby_llm/agents/executions_channel.rb +23 -0
  5. data/app/controllers/ruby_llm/agents/agents_controller.rb +100 -0
  6. data/app/controllers/ruby_llm/agents/application_controller.rb +20 -0
  7. data/app/controllers/ruby_llm/agents/dashboard_controller.rb +34 -0
  8. data/app/controllers/ruby_llm/agents/executions_controller.rb +93 -0
  9. data/app/helpers/ruby_llm/agents/application_helper.rb +149 -0
  10. data/app/javascript/ruby_llm/agents/controllers/filter_controller.js +56 -0
  11. data/app/javascript/ruby_llm/agents/controllers/index.js +12 -0
  12. data/app/javascript/ruby_llm/agents/controllers/refresh_controller.js +83 -0
  13. data/app/models/ruby_llm/agents/execution/analytics.rb +166 -0
  14. data/app/models/ruby_llm/agents/execution/metrics.rb +89 -0
  15. data/app/models/ruby_llm/agents/execution/scopes.rb +81 -0
  16. data/app/models/ruby_llm/agents/execution.rb +81 -0
  17. data/app/services/ruby_llm/agents/agent_registry.rb +112 -0
  18. data/app/views/layouts/rubyllm/agents/application.html.erb +276 -0
  19. data/app/views/rubyllm/agents/agents/index.html.erb +89 -0
  20. data/app/views/rubyllm/agents/agents/show.html.erb +562 -0
  21. data/app/views/rubyllm/agents/dashboard/_execution_item.html.erb +48 -0
  22. data/app/views/rubyllm/agents/dashboard/index.html.erb +121 -0
  23. data/app/views/rubyllm/agents/executions/_execution.html.erb +64 -0
  24. data/app/views/rubyllm/agents/executions/_filters.html.erb +172 -0
  25. data/app/views/rubyllm/agents/executions/_list.html.erb +229 -0
  26. data/app/views/rubyllm/agents/executions/index.html.erb +83 -0
  27. data/app/views/rubyllm/agents/executions/index.turbo_stream.erb +4 -0
  28. data/app/views/rubyllm/agents/executions/show.html.erb +240 -0
  29. data/app/views/rubyllm/agents/shared/_executions_table.html.erb +193 -0
  30. data/app/views/rubyllm/agents/shared/_stat_card.html.erb +14 -0
  31. data/app/views/rubyllm/agents/shared/_status_badge.html.erb +65 -0
  32. data/app/views/rubyllm/agents/shared/_status_dot.html.erb +18 -0
  33. data/config/routes.rb +13 -0
  34. data/lib/generators/ruby_llm_agents/agent_generator.rb +79 -0
  35. data/lib/generators/ruby_llm_agents/install_generator.rb +89 -0
  36. data/lib/generators/ruby_llm_agents/templates/add_prompts_migration.rb.tt +12 -0
  37. data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +46 -0
  38. data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +22 -0
  39. data/lib/generators/ruby_llm_agents/templates/initializer.rb.tt +36 -0
  40. data/lib/generators/ruby_llm_agents/templates/migration.rb.tt +66 -0
  41. data/lib/generators/ruby_llm_agents/upgrade_generator.rb +59 -0
  42. data/lib/ruby_llm/agents/base.rb +271 -0
  43. data/lib/ruby_llm/agents/configuration.rb +36 -0
  44. data/lib/ruby_llm/agents/engine.rb +32 -0
  45. data/lib/ruby_llm/agents/execution_logger_job.rb +59 -0
  46. data/lib/ruby_llm/agents/inflections.rb +13 -0
  47. data/lib/ruby_llm/agents/instrumentation.rb +245 -0
  48. data/lib/ruby_llm/agents/version.rb +7 -0
  49. data/lib/ruby_llm/agents.rb +26 -0
  50. data/lib/ruby_llm-agents.rb +3 -0
  51. metadata +164 -0
@@ -0,0 +1,276 @@
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
+ <!-- Tailwind CSS via CDN -->
11
+ <script src="https://cdn.tailwindcss.com"></script>
12
+
13
+ <!-- Chartkick for charts -->
14
+ <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0"></script>
15
+
16
+ <script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@3.0.0"></script>
17
+
18
+ <script src="https://cdn.jsdelivr.net/npm/chartkick@5.0.1"></script>
19
+
20
+ <!-- Stimulus -->
21
+ <script
22
+ src="https://unpkg.com/@hotwired/stimulus@3.2.2/dist/stimulus.umd.js"
23
+ ></script>
24
+
25
+ <!-- Turbo -->
26
+ <%= javascript_include_tag "turbo", type: "module" rescue nil %>
27
+
28
+ <!-- ActionCable (if available) -->
29
+ <%= action_cable_meta_tag rescue nil %>
30
+
31
+ <!-- Live Clock Script -->
32
+ <script type="module">
33
+ function updateClock() {
34
+ const clock = document.getElementById('live-clock');
35
+ if (clock) {
36
+ const now = new Date();
37
+ clock.textContent = now.toLocaleTimeString('en-US', {
38
+ hour: '2-digit',
39
+ minute: '2-digit',
40
+ second: '2-digit',
41
+ hour12: false
42
+ });
43
+ }
44
+ }
45
+
46
+ // Start clock on page load
47
+ document.addEventListener('turbo:load', function() {
48
+ if (window.rubyLLMAgentsClock) clearInterval(window.rubyLLMAgentsClock);
49
+ updateClock();
50
+ window.rubyLLMAgentsClock = setInterval(updateClock, 1000);
51
+ });
52
+ </script>
53
+
54
+ <!-- Stimulus Connection Controller -->
55
+ <script>
56
+ (function() {
57
+ // Initialize Stimulus immediately (works with Turbo)
58
+ if (!window.Stimulus) {
59
+ window.Stimulus = {};
60
+ }
61
+
62
+ const Stimulus = window.Stimulus;
63
+
64
+ // Only initialize once
65
+ if (!Stimulus.application) {
66
+ Stimulus.application = Stimulus.Application.start();
67
+
68
+ // Polling Controller - simple auto-refresh
69
+ Stimulus.application.register("connection", class extends Stimulus.Controller {
70
+ static targets = ["indicator", "dashboardFrame"]
71
+
72
+ static values = {
73
+ pollInterval: { type: Number, default: 4000 }
74
+ }
75
+
76
+ connect() {
77
+ this.pollTimer = null;
78
+ this.startPolling();
79
+ }
80
+
81
+ disconnect() {
82
+ this.stopPolling();
83
+ }
84
+
85
+ startPolling() {
86
+ if (this.pollTimer) return;
87
+
88
+ this.updateIndicator('polling');
89
+
90
+ this.pollTimer = setInterval(() => {
91
+ this.poll();
92
+ }, this.pollIntervalValue);
93
+ }
94
+
95
+ stopPolling() {
96
+ if (this.pollTimer) {
97
+ clearInterval(this.pollTimer);
98
+ this.pollTimer = null;
99
+ }
100
+ }
101
+
102
+ poll() {
103
+ if (this.hasDashboardFrameTarget) {
104
+ const frame = this.dashboardFrameTarget;
105
+ if (frame.src) {
106
+ frame.reload();
107
+ } else {
108
+ frame.src = window.location.href;
109
+ }
110
+ }
111
+ }
112
+
113
+ updateIndicator(status) {
114
+ if (!this.hasIndicatorTarget) return;
115
+
116
+ const indicator = this.indicatorTarget;
117
+ indicator.innerHTML = `
118
+ <span class="w-1.5 h-1.5 bg-blue-500 rounded-full mr-1"></span>
119
+ <span class="hidden sm:inline">Polling</span>
120
+ `;
121
+ indicator.className = 'flex items-center text-blue-600';
122
+ }
123
+ });
124
+
125
+ console.log('[RubyLLM::Agents] Stimulus initialized');
126
+ }
127
+ })();
128
+ </script>
129
+
130
+ <style>
131
+ /* Custom styles for the dashboard */
132
+ .stat-card {
133
+ @apply bg-white rounded-xl shadow-sm border border-gray-100 p-4 transition-shadow;
134
+ }
135
+ .stat-card:hover {
136
+ @apply shadow-md;
137
+ }
138
+ .stat-value {
139
+ @apply text-2xl font-bold text-gray-900 mt-2;
140
+ }
141
+ @media (min-width: 1024px) {
142
+ .stat-value {
143
+ font-size: 1.5rem;
144
+ }
145
+ }
146
+ .stat-label {
147
+ @apply text-xs text-gray-500 uppercase tracking-wide font-medium;
148
+ }
149
+ .chart-card {
150
+ @apply bg-white rounded-xl shadow-sm border border-gray-100 p-6;
151
+ }
152
+ .nav-link {
153
+ @apply px-4 py-2 text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-md transition-colors;
154
+ }
155
+ .nav-link.active {
156
+ @apply bg-blue-50 text-blue-700;
157
+ }
158
+ .badge {
159
+ @apply inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium;
160
+ }
161
+ .badge-running {
162
+ @apply bg-blue-100 text-blue-800;
163
+ }
164
+ .badge-success {
165
+ @apply bg-green-100 text-green-800;
166
+ }
167
+ .badge-error {
168
+ @apply bg-red-100 text-red-800;
169
+ }
170
+ .badge-timeout {
171
+ @apply bg-yellow-100 text-yellow-800;
172
+ }
173
+ </style>
174
+ </head>
175
+
176
+ <body
177
+ class="bg-gray-50 min-h-screen flex flex-col"
178
+ data-controller="connection"
179
+ >
180
+ <!-- Header -->
181
+ <header class="bg-white border-b border-gray-200 h-16 flex items-center">
182
+ <div class="max-w-7xl w-full mx-auto px-4 sm:px-6 lg:px-8">
183
+ <div class="flex justify-between items-center">
184
+ <div class="flex items-center space-x-8">
185
+ <%= link_to ruby_llm_agents.root_path, class: "flex items-center space-x-2" do %>
186
+ <span class="text-lg">🤖</span>
187
+ <span class="font-semibold text-gray-900">RubyLLM Agents</span>
188
+ <% end %>
189
+
190
+ <nav class="flex items-center space-x-1">
191
+ <%= 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' : 'text-gray-600 hover:text-gray-900 hover:bg-gray-100'}" do %>
192
+ <svg
193
+ class="w-4 h-4 mr-1.5"
194
+ fill="none"
195
+ stroke="currentColor"
196
+ viewBox="0 0 24 24"
197
+ >
198
+ <path
199
+ stroke-linecap="round"
200
+ stroke-linejoin="round"
201
+ stroke-width="2"
202
+ 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"
203
+ />
204
+ </svg>
205
+ Dashboard
206
+ <% end %>
207
+
208
+ <%= 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' : 'text-gray-600 hover:text-gray-900 hover:bg-gray-100'}" do %>
209
+ <svg
210
+ class="w-4 h-4 mr-1.5"
211
+ fill="none"
212
+ stroke="currentColor"
213
+ viewBox="0 0 24 24"
214
+ >
215
+ <path
216
+ stroke-linecap="round"
217
+ stroke-linejoin="round"
218
+ stroke-width="2"
219
+ 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"
220
+ />
221
+ </svg>
222
+ Agents
223
+ <% end %>
224
+
225
+ <%= 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' : 'text-gray-600 hover:text-gray-900 hover:bg-gray-100'}" do %>
226
+ <svg
227
+ class="w-4 h-4 mr-1.5"
228
+ fill="none"
229
+ stroke="currentColor"
230
+ viewBox="0 0 24 24"
231
+ >
232
+ <path
233
+ stroke-linecap="round"
234
+ stroke-linejoin="round"
235
+ stroke-width="2"
236
+ 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"
237
+ />
238
+ </svg>
239
+ Executions
240
+ <% end %>
241
+ </nav>
242
+ </div>
243
+
244
+ <div class="flex items-center space-x-2 text-xs text-gray-500">
245
+ <span id="live-clock" class="tabular-nums"></span>
246
+ <span class="text-gray-300">•</span>
247
+
248
+ <span
249
+ id="live-indicator"
250
+ data-connection-target="indicator"
251
+ class="flex items-center text-gray-400"
252
+ >
253
+ <span class="w-1.5 h-1.5 bg-gray-300 rounded-full mr-1"></span>
254
+ <span class="hidden sm:inline">Connecting</span>
255
+ </span>
256
+ </div>
257
+ </div>
258
+ </div>
259
+ </header>
260
+
261
+ <!-- Main content -->
262
+ <main class="max-w-7xl w-full mx-auto px-4 sm:px-6 lg:px-8 py-8 h-full">
263
+ <%= yield %>
264
+ </main>
265
+
266
+ <!-- Footer -->
267
+ <footer class="border-t bg-white mt-auto">
268
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
269
+ <p class="text-center text-sm text-gray-500">
270
+ Powered by
271
+ <a href="https://github.com/adham90/ruby_llm-agents" class="text-blue-600 hover:underline">ruby_llm-agents</a>
272
+ </p>
273
+ </div>
274
+ </footer>
275
+ </body>
276
+ </html>
@@ -0,0 +1,89 @@
1
+ <div class="mb-6">
2
+ <h1 class="text-2xl font-bold text-gray-900">Agents</h1>
3
+ <p class="text-gray-500 mt-1">All available agents and their execution statistics</p>
4
+ </div>
5
+
6
+ <% if @agents.empty? %>
7
+ <div class="bg-white rounded-lg shadow p-8 text-center">
8
+ <svg class="mx-auto h-12 w-12 text-gray-400" 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">No agents found</h3>
12
+ <p class="mt-1 text-sm text-gray-500">Create an agent by running <code class="bg-gray-100 px-1 rounded">rails g ruby_llm_agents:agent YourAgentName</code></p>
13
+ </div>
14
+ <% else %>
15
+ <div class="space-y-4">
16
+ <% @agents.each do |agent| %>
17
+ <%= link_to ruby_llm_agents.agent_path(agent[:name]), class: "block bg-white rounded-lg shadow hover:shadow-md transition-shadow" do %>
18
+ <div class="p-5">
19
+ <!-- Header Row -->
20
+ <div class="flex items-start justify-between mb-3">
21
+ <div class="flex items-center space-x-3">
22
+ <h3 class="text-lg font-semibold text-gray-900">
23
+ <%= agent[:name].gsub(/Agent$/, '') %>
24
+ </h3>
25
+ <span class="text-sm text-gray-500">v<%= agent[:version] %></span>
26
+ <% if agent[:active] %>
27
+ <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800">
28
+ Active
29
+ </span>
30
+ <% else %>
31
+ <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-600">
32
+ Deleted
33
+ </span>
34
+ <% end %>
35
+ </div>
36
+ <div class="text-right">
37
+ <p class="text-sm text-gray-500"><%= agent[:model] %></p>
38
+ </div>
39
+ </div>
40
+
41
+ <!-- Stats Row -->
42
+ <div class="flex items-center justify-between text-sm border-t border-gray-100 pt-3">
43
+ <div class="flex items-center space-x-6">
44
+ <!-- Executions -->
45
+ <div class="flex items-center text-gray-600">
46
+ <svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
47
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
48
+ </svg>
49
+ <span><%= number_with_delimiter(agent[:execution_count]) %> executions</span>
50
+ </div>
51
+
52
+ <!-- Cost -->
53
+ <div class="flex items-center text-gray-600">
54
+ <svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
55
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="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" />
56
+ </svg>
57
+ <span>$<%= number_with_precision(agent[:total_cost] || 0, precision: 4) %></span>
58
+ </div>
59
+
60
+ <!-- Success Rate -->
61
+ <div class="flex items-center">
62
+ <% success_rate = agent[:success_rate] || 0 %>
63
+ <% if success_rate >= 95 %>
64
+ <span class="w-2 h-2 rounded-full bg-green-500 mr-1.5"></span>
65
+ <span class="text-green-600"><%= success_rate %>%</span>
66
+ <% elsif success_rate >= 80 %>
67
+ <span class="w-2 h-2 rounded-full bg-yellow-500 mr-1.5"></span>
68
+ <span class="text-yellow-600"><%= success_rate %>%</span>
69
+ <% else %>
70
+ <span class="w-2 h-2 rounded-full bg-red-500 mr-1.5"></span>
71
+ <span class="text-red-600"><%= success_rate %>%</span>
72
+ <% end %>
73
+ </div>
74
+ </div>
75
+
76
+ <!-- Last Executed -->
77
+ <div class="text-gray-400">
78
+ <% if agent[:last_executed] %>
79
+ <%= time_ago_in_words(agent[:last_executed]) %> ago
80
+ <% else %>
81
+ Never executed
82
+ <% end %>
83
+ </div>
84
+ </div>
85
+ </div>
86
+ <% end %>
87
+ <% end %>
88
+ </div>
89
+ <% end %>