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
|
@@ -4,32 +4,50 @@ module RubyLLM
|
|
|
4
4
|
module Agents
|
|
5
5
|
# Service for discovering and listing available agents
|
|
6
6
|
#
|
|
7
|
-
# Combines two sources:
|
|
7
|
+
# Combines two sources to ensure complete agent discovery:
|
|
8
8
|
# 1. File system - Classes inheriting from ApplicationAgent in app/agents/
|
|
9
9
|
# 2. Execution history - Agent types that have execution records
|
|
10
10
|
#
|
|
11
|
-
# This ensures
|
|
12
|
-
#
|
|
13
|
-
# - Deleted agents that still have execution history
|
|
11
|
+
# This ensures visibility of both current agents and deleted agents
|
|
12
|
+
# that still have execution history.
|
|
14
13
|
#
|
|
14
|
+
# @example Getting all agent names
|
|
15
|
+
# AgentRegistry.all #=> ["SearchAgent", "SummaryAgent"]
|
|
16
|
+
#
|
|
17
|
+
# @example Getting detailed info
|
|
18
|
+
# AgentRegistry.all_with_details.each do |agent|
|
|
19
|
+
# puts "#{agent[:name]}: #{agent[:execution_count]} executions"
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# @api public
|
|
15
23
|
class AgentRegistry
|
|
16
24
|
class << self
|
|
17
|
-
# Returns all unique agent type names
|
|
25
|
+
# Returns all unique agent type names
|
|
26
|
+
#
|
|
27
|
+
# @return [Array<String>] Sorted list of agent class names
|
|
18
28
|
def all
|
|
19
29
|
(file_system_agents + execution_agents).uniq.sort
|
|
20
30
|
end
|
|
21
31
|
|
|
22
|
-
#
|
|
32
|
+
# Finds an agent class by type name
|
|
33
|
+
#
|
|
34
|
+
# @param agent_type [String] The agent class name
|
|
35
|
+
# @return [Class, nil] The agent class, or nil if not found
|
|
23
36
|
def find(agent_type)
|
|
24
37
|
agent_type.safe_constantize
|
|
25
38
|
end
|
|
26
39
|
|
|
27
|
-
#
|
|
40
|
+
# Checks if an agent class is currently defined
|
|
41
|
+
#
|
|
42
|
+
# @param agent_type [String] The agent class name
|
|
43
|
+
# @return [Boolean] true if the class exists
|
|
28
44
|
def exists?(agent_type)
|
|
29
45
|
find(agent_type).present?
|
|
30
46
|
end
|
|
31
47
|
|
|
32
|
-
#
|
|
48
|
+
# Returns detailed info about all agents
|
|
49
|
+
#
|
|
50
|
+
# @return [Array<Hash>] Agent info hashes with configuration and stats
|
|
33
51
|
def all_with_details
|
|
34
52
|
all.map do |agent_type|
|
|
35
53
|
build_agent_info(agent_type)
|
|
@@ -38,7 +56,9 @@ module RubyLLM
|
|
|
38
56
|
|
|
39
57
|
private
|
|
40
58
|
|
|
41
|
-
#
|
|
59
|
+
# Finds agent classes from the file system
|
|
60
|
+
#
|
|
61
|
+
# @return [Array<String>] Agent class names
|
|
42
62
|
def file_system_agents
|
|
43
63
|
# Ensure all agent classes are loaded
|
|
44
64
|
eager_load_agents!
|
|
@@ -51,7 +71,9 @@ module RubyLLM
|
|
|
51
71
|
[]
|
|
52
72
|
end
|
|
53
73
|
|
|
54
|
-
#
|
|
74
|
+
# Finds agent types from execution history
|
|
75
|
+
#
|
|
76
|
+
# @return [Array<String>] Agent class names with execution records
|
|
55
77
|
def execution_agents
|
|
56
78
|
Execution.distinct.pluck(:agent_type).compact
|
|
57
79
|
rescue StandardError => e
|
|
@@ -59,17 +81,24 @@ module RubyLLM
|
|
|
59
81
|
[]
|
|
60
82
|
end
|
|
61
83
|
|
|
62
|
-
# Eager
|
|
84
|
+
# Eager loads all agent files to register descendants
|
|
85
|
+
#
|
|
86
|
+
# @return [void]
|
|
63
87
|
def eager_load_agents!
|
|
64
88
|
agents_path = Rails.root.join("app", "agents")
|
|
65
89
|
return unless agents_path.exist?
|
|
66
90
|
|
|
67
91
|
Dir.glob(agents_path.join("**", "*.rb")).each do |file|
|
|
68
92
|
require_dependency file
|
|
93
|
+
rescue LoadError, StandardError => e
|
|
94
|
+
Rails.logger.error("[RubyLLM::Agents] Failed to load agent file #{file}: #{e.message}")
|
|
69
95
|
end
|
|
70
96
|
end
|
|
71
97
|
|
|
72
|
-
#
|
|
98
|
+
# Builds detailed info hash for an agent
|
|
99
|
+
#
|
|
100
|
+
# @param agent_type [String] The agent class name
|
|
101
|
+
# @return [Hash] Agent info including config and stats
|
|
73
102
|
def build_agent_info(agent_type)
|
|
74
103
|
agent_class = find(agent_type)
|
|
75
104
|
stats = fetch_stats(agent_type)
|
|
@@ -95,12 +124,20 @@ module RubyLLM
|
|
|
95
124
|
}
|
|
96
125
|
end
|
|
97
126
|
|
|
127
|
+
# Fetches statistics for an agent
|
|
128
|
+
#
|
|
129
|
+
# @param agent_type [String] The agent class name
|
|
130
|
+
# @return [Hash] Statistics hash
|
|
98
131
|
def fetch_stats(agent_type)
|
|
99
132
|
Execution.stats_for(agent_type, period: :all_time)
|
|
100
133
|
rescue StandardError
|
|
101
134
|
{ count: 0, total_cost: 0, total_tokens: 0, avg_duration_ms: 0, success_rate: 0, error_rate: 0 }
|
|
102
135
|
end
|
|
103
136
|
|
|
137
|
+
# Gets the timestamp of the last execution for an agent
|
|
138
|
+
#
|
|
139
|
+
# @param agent_type [String] The agent class name
|
|
140
|
+
# @return [Time, nil] Last execution time or nil
|
|
104
141
|
def last_execution_time(agent_type)
|
|
105
142
|
Execution.by_agent(agent_type).order(created_at: :desc).first&.created_at
|
|
106
143
|
rescue StandardError
|
|
@@ -21,29 +21,169 @@
|
|
|
21
21
|
|
|
22
22
|
<!-- Tailwind CSS via CDN -->
|
|
23
23
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
24
|
+
|
|
24
25
|
<script>
|
|
25
26
|
tailwind.config = {
|
|
26
27
|
darkMode: 'class'
|
|
27
28
|
}
|
|
28
29
|
</script>
|
|
29
30
|
|
|
30
|
-
<!--
|
|
31
|
-
<script src="https://
|
|
32
|
-
|
|
33
|
-
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@3.0.0"></script>
|
|
34
|
-
|
|
31
|
+
<!-- Highcharts for charts -->
|
|
32
|
+
<script src="https://code.highcharts.com/highcharts.js"></script>
|
|
35
33
|
<script src="https://cdn.jsdelivr.net/npm/chartkick@5.0.1"></script>
|
|
36
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
|
+
|
|
37
65
|
<!-- Stimulus -->
|
|
38
66
|
<script
|
|
39
67
|
src="https://unpkg.com/@hotwired/stimulus@3.2.2/dist/stimulus.umd.js"
|
|
40
68
|
></script>
|
|
41
69
|
|
|
42
|
-
<!--
|
|
43
|
-
|
|
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>
|
|
44
77
|
|
|
45
|
-
<!--
|
|
46
|
-
|
|
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>
|
|
47
187
|
|
|
48
188
|
<!-- Live Clock Script -->
|
|
49
189
|
<script type="module">
|
|
@@ -65,10 +205,19 @@
|
|
|
65
205
|
if (window.rubyLLMAgentsClock) clearInterval(window.rubyLLMAgentsClock);
|
|
66
206
|
updateClock();
|
|
67
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
|
+
});
|
|
68
217
|
});
|
|
69
218
|
</script>
|
|
70
219
|
|
|
71
|
-
<!-- Stimulus
|
|
220
|
+
<!-- Stimulus Controllers -->
|
|
72
221
|
<script>
|
|
73
222
|
(function() {
|
|
74
223
|
// Initialize Stimulus immediately (works with Turbo)
|
|
@@ -82,63 +231,6 @@
|
|
|
82
231
|
if (!Stimulus.application) {
|
|
83
232
|
Stimulus.application = Stimulus.Application.start();
|
|
84
233
|
|
|
85
|
-
// Polling Controller - simple auto-refresh
|
|
86
|
-
Stimulus.application.register("connection", class extends Stimulus.Controller {
|
|
87
|
-
static targets = ["indicator", "dashboardFrame"]
|
|
88
|
-
|
|
89
|
-
static values = {
|
|
90
|
-
pollInterval: { type: Number, default: 4000 }
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
connect() {
|
|
94
|
-
this.pollTimer = null;
|
|
95
|
-
this.startPolling();
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
disconnect() {
|
|
99
|
-
this.stopPolling();
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
startPolling() {
|
|
103
|
-
if (this.pollTimer) return;
|
|
104
|
-
|
|
105
|
-
this.updateIndicator('polling');
|
|
106
|
-
|
|
107
|
-
this.pollTimer = setInterval(() => {
|
|
108
|
-
this.poll();
|
|
109
|
-
}, this.pollIntervalValue);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
stopPolling() {
|
|
113
|
-
if (this.pollTimer) {
|
|
114
|
-
clearInterval(this.pollTimer);
|
|
115
|
-
this.pollTimer = null;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
poll() {
|
|
120
|
-
if (this.hasDashboardFrameTarget) {
|
|
121
|
-
const frame = this.dashboardFrameTarget;
|
|
122
|
-
if (frame.src) {
|
|
123
|
-
frame.reload();
|
|
124
|
-
} else {
|
|
125
|
-
frame.src = window.location.href;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
updateIndicator(status) {
|
|
131
|
-
if (!this.hasIndicatorTarget) return;
|
|
132
|
-
|
|
133
|
-
const indicator = this.indicatorTarget;
|
|
134
|
-
indicator.innerHTML = `
|
|
135
|
-
<span class="w-1.5 h-1.5 bg-blue-500 rounded-full mr-1"></span>
|
|
136
|
-
<span class="hidden sm:inline">Polling</span>
|
|
137
|
-
`;
|
|
138
|
-
indicator.className = 'flex items-center text-blue-600';
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
|
|
142
234
|
// Theme Controller - handles dark mode switching
|
|
143
235
|
Stimulus.application.register("theme", class extends Stimulus.Controller {
|
|
144
236
|
static targets = ["select"]
|
|
@@ -233,21 +325,28 @@
|
|
|
233
325
|
</style>
|
|
234
326
|
</head>
|
|
235
327
|
|
|
236
|
-
<body
|
|
237
|
-
class="bg-gray-50 dark:bg-gray-900 min-h-screen flex flex-col"
|
|
238
|
-
data-controller="connection"
|
|
239
|
-
>
|
|
328
|
+
<body class="bg-gray-50 dark:bg-gray-900 min-h-screen flex flex-col">
|
|
240
329
|
<!-- Header -->
|
|
241
|
-
<header
|
|
242
|
-
|
|
243
|
-
|
|
330
|
+
<header
|
|
331
|
+
class="
|
|
332
|
+
bg-white dark:bg-gray-800 border-b border-gray-200
|
|
333
|
+
dark:border-gray-700
|
|
334
|
+
"
|
|
335
|
+
x-data="{ mobileMenuOpen: false }"
|
|
336
|
+
>
|
|
337
|
+
<div class="max-w-7xl w-full mx-auto px-4 sm:px-6 lg:px-8 h-16 flex items-center">
|
|
338
|
+
<div class="flex justify-between items-center w-full">
|
|
244
339
|
<div class="flex items-center space-x-8">
|
|
245
340
|
<%= link_to ruby_llm_agents.root_path, class: "flex items-center space-x-2" do %>
|
|
246
341
|
<span class="text-lg">🤖</span>
|
|
247
|
-
|
|
342
|
+
|
|
343
|
+
<span class="font-semibold text-gray-900 dark:text-gray-100">
|
|
344
|
+
RubyLLM Agents
|
|
345
|
+
</span>
|
|
248
346
|
<% end %>
|
|
249
347
|
|
|
250
|
-
|
|
348
|
+
<!-- Desktop Navigation -->
|
|
349
|
+
<nav class="hidden md:flex items-center space-x-1">
|
|
251
350
|
<%= 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 %>
|
|
252
351
|
<svg
|
|
253
352
|
class="w-4 h-4 mr-1.5"
|
|
@@ -298,24 +397,177 @@
|
|
|
298
397
|
</svg>
|
|
299
398
|
Executions
|
|
300
399
|
<% end %>
|
|
400
|
+
|
|
401
|
+
<%= 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 %>
|
|
402
|
+
<svg
|
|
403
|
+
class="w-4 h-4 mr-1.5"
|
|
404
|
+
fill="none"
|
|
405
|
+
stroke="currentColor"
|
|
406
|
+
viewBox="0 0 24 24"
|
|
407
|
+
>
|
|
408
|
+
<path
|
|
409
|
+
stroke-linecap="round"
|
|
410
|
+
stroke-linejoin="round"
|
|
411
|
+
stroke-width="2"
|
|
412
|
+
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"
|
|
413
|
+
/>
|
|
414
|
+
|
|
415
|
+
<path
|
|
416
|
+
stroke-linecap="round"
|
|
417
|
+
stroke-linejoin="round"
|
|
418
|
+
stroke-width="2"
|
|
419
|
+
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
|
420
|
+
/>
|
|
421
|
+
</svg>
|
|
422
|
+
Settings
|
|
423
|
+
<% end %>
|
|
301
424
|
</nav>
|
|
302
425
|
</div>
|
|
303
426
|
|
|
304
|
-
<div
|
|
427
|
+
<div
|
|
428
|
+
class="
|
|
429
|
+
flex items-center space-x-2 text-xs text-gray-500
|
|
430
|
+
dark:text-gray-400
|
|
431
|
+
"
|
|
432
|
+
>
|
|
305
433
|
<span id="live-clock" class="tabular-nums"></span>
|
|
306
|
-
<span class="text-gray-300 dark:text-gray-600">•</span>
|
|
307
434
|
|
|
308
435
|
<span
|
|
309
436
|
id="live-indicator"
|
|
310
|
-
|
|
311
|
-
class="flex items-center text-gray-400 dark:text-gray-500"
|
|
437
|
+
class="hidden sm:flex items-center text-blue-600 dark:text-blue-400"
|
|
312
438
|
>
|
|
313
|
-
<span class="w-1.5 h-1.5 bg-
|
|
314
|
-
<span class="hidden sm:inline">
|
|
439
|
+
<span class="w-1.5 h-1.5 bg-blue-500 rounded-full mr-1 animate-pulse"></span>
|
|
440
|
+
<span class="hidden sm:inline">Auto</span>
|
|
315
441
|
</span>
|
|
442
|
+
|
|
443
|
+
<!-- Mobile menu button -->
|
|
444
|
+
<button
|
|
445
|
+
type="button"
|
|
446
|
+
@click="mobileMenuOpen = !mobileMenuOpen"
|
|
447
|
+
:aria-expanded="mobileMenuOpen"
|
|
448
|
+
aria-controls="mobile-menu"
|
|
449
|
+
class="
|
|
450
|
+
md:hidden inline-flex items-center justify-center p-2
|
|
451
|
+
rounded-md text-gray-500 dark:text-gray-400
|
|
452
|
+
hover:text-gray-900 dark:hover:text-gray-100
|
|
453
|
+
hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none
|
|
454
|
+
focus:ring-2 focus:ring-inset focus:ring-blue-500
|
|
455
|
+
"
|
|
456
|
+
>
|
|
457
|
+
<span class="sr-only">Open main menu</span>
|
|
458
|
+
|
|
459
|
+
<svg
|
|
460
|
+
class="w-5 h-5"
|
|
461
|
+
fill="none"
|
|
462
|
+
stroke="currentColor"
|
|
463
|
+
viewBox="0 0 24 24"
|
|
464
|
+
>
|
|
465
|
+
<path
|
|
466
|
+
stroke-linecap="round"
|
|
467
|
+
stroke-linejoin="round"
|
|
468
|
+
stroke-width="2"
|
|
469
|
+
d="M4 6h16M4 12h16M4 18h16"
|
|
470
|
+
/>
|
|
471
|
+
</svg>
|
|
472
|
+
</button>
|
|
316
473
|
</div>
|
|
317
474
|
</div>
|
|
318
475
|
</div>
|
|
476
|
+
|
|
477
|
+
<!-- Mobile Navigation Menu -->
|
|
478
|
+
<div
|
|
479
|
+
id="mobile-menu"
|
|
480
|
+
x-show="mobileMenuOpen"
|
|
481
|
+
x-cloak
|
|
482
|
+
x-transition:enter="transition ease-out duration-200"
|
|
483
|
+
x-transition:enter-start="opacity-0 -translate-y-1"
|
|
484
|
+
x-transition:enter-end="opacity-100 translate-y-0"
|
|
485
|
+
x-transition:leave="transition ease-in duration-150"
|
|
486
|
+
x-transition:leave-start="opacity-100 translate-y-0"
|
|
487
|
+
x-transition:leave-end="opacity-0 -translate-y-1"
|
|
488
|
+
@click.outside="mobileMenuOpen = false"
|
|
489
|
+
class="
|
|
490
|
+
md:hidden border-t border-gray-200 dark:border-gray-700 bg-white
|
|
491
|
+
dark:bg-gray-800
|
|
492
|
+
"
|
|
493
|
+
>
|
|
494
|
+
<nav class="max-w-7xl mx-auto px-4 py-3 space-y-1">
|
|
495
|
+
<%= 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 %>
|
|
496
|
+
<svg
|
|
497
|
+
class="w-5 h-5 mr-3"
|
|
498
|
+
fill="none"
|
|
499
|
+
stroke="currentColor"
|
|
500
|
+
viewBox="0 0 24 24"
|
|
501
|
+
>
|
|
502
|
+
<path
|
|
503
|
+
stroke-linecap="round"
|
|
504
|
+
stroke-linejoin="round"
|
|
505
|
+
stroke-width="2"
|
|
506
|
+
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"
|
|
507
|
+
/>
|
|
508
|
+
</svg>
|
|
509
|
+
Dashboard
|
|
510
|
+
<% end %>
|
|
511
|
+
|
|
512
|
+
<%= 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 %>
|
|
513
|
+
<svg
|
|
514
|
+
class="w-5 h-5 mr-3"
|
|
515
|
+
fill="none"
|
|
516
|
+
stroke="currentColor"
|
|
517
|
+
viewBox="0 0 24 24"
|
|
518
|
+
>
|
|
519
|
+
<path
|
|
520
|
+
stroke-linecap="round"
|
|
521
|
+
stroke-linejoin="round"
|
|
522
|
+
stroke-width="2"
|
|
523
|
+
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"
|
|
524
|
+
/>
|
|
525
|
+
</svg>
|
|
526
|
+
Agents
|
|
527
|
+
<% end %>
|
|
528
|
+
|
|
529
|
+
<%= 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 %>
|
|
530
|
+
<svg
|
|
531
|
+
class="w-5 h-5 mr-3"
|
|
532
|
+
fill="none"
|
|
533
|
+
stroke="currentColor"
|
|
534
|
+
viewBox="0 0 24 24"
|
|
535
|
+
>
|
|
536
|
+
<path
|
|
537
|
+
stroke-linecap="round"
|
|
538
|
+
stroke-linejoin="round"
|
|
539
|
+
stroke-width="2"
|
|
540
|
+
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"
|
|
541
|
+
/>
|
|
542
|
+
</svg>
|
|
543
|
+
Executions
|
|
544
|
+
<% end %>
|
|
545
|
+
|
|
546
|
+
<%= 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 %>
|
|
547
|
+
<svg
|
|
548
|
+
class="w-5 h-5 mr-3"
|
|
549
|
+
fill="none"
|
|
550
|
+
stroke="currentColor"
|
|
551
|
+
viewBox="0 0 24 24"
|
|
552
|
+
>
|
|
553
|
+
<path
|
|
554
|
+
stroke-linecap="round"
|
|
555
|
+
stroke-linejoin="round"
|
|
556
|
+
stroke-width="2"
|
|
557
|
+
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"
|
|
558
|
+
/>
|
|
559
|
+
|
|
560
|
+
<path
|
|
561
|
+
stroke-linecap="round"
|
|
562
|
+
stroke-linejoin="round"
|
|
563
|
+
stroke-width="2"
|
|
564
|
+
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
|
565
|
+
/>
|
|
566
|
+
</svg>
|
|
567
|
+
Settings
|
|
568
|
+
<% end %>
|
|
569
|
+
</nav>
|
|
570
|
+
</div>
|
|
319
571
|
</header>
|
|
320
572
|
|
|
321
573
|
<!-- Main content -->
|
|
@@ -324,22 +576,36 @@
|
|
|
324
576
|
</main>
|
|
325
577
|
|
|
326
578
|
<!-- Footer -->
|
|
327
|
-
<footer
|
|
579
|
+
<footer
|
|
580
|
+
class="border-t bg-white dark:bg-gray-800 dark:border-gray-700 mt-auto"
|
|
581
|
+
data-controller="theme"
|
|
582
|
+
>
|
|
328
583
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
|
|
329
584
|
<div class="flex items-center justify-between">
|
|
330
585
|
<div class="flex items-center space-x-2">
|
|
331
|
-
<label
|
|
586
|
+
<label
|
|
587
|
+
for="theme-select"
|
|
588
|
+
class="text-sm text-gray-500 dark:text-gray-400"
|
|
589
|
+
>
|
|
590
|
+
Theme:
|
|
591
|
+
</label>
|
|
592
|
+
|
|
332
593
|
<select
|
|
333
594
|
id="theme-select"
|
|
334
595
|
data-theme-target="select"
|
|
335
596
|
data-action="change->theme#change"
|
|
336
|
-
class="
|
|
597
|
+
class="
|
|
598
|
+
text-sm border border-gray-300 dark:border-gray-600
|
|
599
|
+
dark:bg-gray-700 dark:text-gray-200 rounded-md shadow-sm
|
|
600
|
+
focus:ring-blue-500 focus:border-blue-500 py-1 px-2
|
|
601
|
+
"
|
|
337
602
|
>
|
|
338
603
|
<option value="light">Light</option>
|
|
339
604
|
<option value="dark">Dark</option>
|
|
340
605
|
<option value="auto">Auto</option>
|
|
341
606
|
</select>
|
|
342
607
|
</div>
|
|
608
|
+
|
|
343
609
|
<p class="text-sm text-gray-500 dark:text-gray-400">
|
|
344
610
|
Powered by
|
|
345
611
|
<a href="https://github.com/adham90/ruby_llm-agents" class="text-blue-600 dark:text-blue-400 hover:underline">ruby_llm-agents</a>
|