ruby_llm-agents 0.3.1 → 0.3.4

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +88 -0
  3. data/app/controllers/ruby_llm/agents/dashboard_controller.rb +68 -4
  4. data/app/models/ruby_llm/agents/execution/analytics.rb +114 -13
  5. data/app/models/ruby_llm/agents/execution/scopes.rb +10 -0
  6. data/app/models/ruby_llm/agents/execution.rb +26 -58
  7. data/app/views/layouts/rubyllm/agents/application.html.erb +103 -352
  8. data/app/views/rubyllm/agents/agents/_agent.html.erb +87 -0
  9. data/app/views/rubyllm/agents/agents/index.html.erb +2 -71
  10. data/app/views/rubyllm/agents/agents/show.html.erb +349 -416
  11. data/app/views/rubyllm/agents/dashboard/_action_center.html.erb +7 -7
  12. data/app/views/rubyllm/agents/dashboard/_agent_comparison.html.erb +46 -0
  13. data/app/views/rubyllm/agents/dashboard/_budgets_bar.html.erb +0 -90
  14. data/app/views/rubyllm/agents/dashboard/_execution_item.html.erb +54 -39
  15. data/app/views/rubyllm/agents/dashboard/_now_strip.html.erb +79 -5
  16. data/app/views/rubyllm/agents/dashboard/_top_errors.html.erb +49 -0
  17. data/app/views/rubyllm/agents/dashboard/index.html.erb +76 -151
  18. data/app/views/rubyllm/agents/executions/show.html.erb +256 -93
  19. data/app/views/rubyllm/agents/settings/show.html.erb +1 -1
  20. data/app/views/rubyllm/agents/shared/_breadcrumbs.html.erb +48 -0
  21. data/app/views/rubyllm/agents/shared/_nav_link.html.erb +27 -0
  22. data/config/routes.rb +2 -0
  23. data/lib/generators/ruby_llm_agents/templates/add_tool_calls_migration.rb.tt +28 -0
  24. data/lib/generators/ruby_llm_agents/templates/migration.rb.tt +7 -0
  25. data/lib/generators/ruby_llm_agents/upgrade_generator.rb +13 -0
  26. data/lib/ruby_llm/agents/base/caching.rb +43 -0
  27. data/lib/ruby_llm/agents/base/cost_calculation.rb +103 -0
  28. data/lib/ruby_llm/agents/base/dsl.rb +261 -0
  29. data/lib/ruby_llm/agents/base/execution.rb +206 -0
  30. data/lib/ruby_llm/agents/base/reliability_execution.rb +131 -0
  31. data/lib/ruby_llm/agents/base/response_building.rb +86 -0
  32. data/lib/ruby_llm/agents/base/tool_tracking.rb +57 -0
  33. data/lib/ruby_llm/agents/base.rb +19 -619
  34. data/lib/ruby_llm/agents/instrumentation.rb +36 -3
  35. data/lib/ruby_llm/agents/result.rb +235 -0
  36. data/lib/ruby_llm/agents/version.rb +1 -1
  37. data/lib/ruby_llm/agents.rb +1 -0
  38. metadata +15 -20
  39. data/app/channels/ruby_llm/agents/executions_channel.rb +0 -46
  40. data/app/javascript/ruby_llm/agents/controllers/filter_controller.js +0 -56
  41. data/app/javascript/ruby_llm/agents/controllers/index.js +0 -12
  42. data/app/javascript/ruby_llm/agents/controllers/refresh_controller.js +0 -83
  43. data/app/views/rubyllm/agents/dashboard/_now_strip_values.html.erb +0 -71
@@ -385,6 +385,12 @@ module RubyLLM
385
385
  update_data[:response] = redacted_response(@last_response)
386
386
  end
387
387
 
388
+ # Add tool calls from accumulated_tool_calls (captured from all responses)
389
+ if respond_to?(:accumulated_tool_calls) && accumulated_tool_calls.present?
390
+ update_data[:tool_calls] = accumulated_tool_calls
391
+ update_data[:tool_calls_count] = accumulated_tool_calls.size
392
+ end
393
+
388
394
  # Add error data if failed
389
395
  if error
390
396
  update_data.merge!(
@@ -566,7 +572,11 @@ module RubyLLM
566
572
  # @param response [RubyLLM::Message, nil] The LLM response
567
573
  # @return [Hash] Extracted response data (empty if response invalid)
568
574
  def safe_extract_response_data(response)
569
- return {} unless response.is_a?(RubyLLM::Message)
575
+ return {} unless response.respond_to?(:input_tokens)
576
+
577
+ # Use accumulated_tool_calls which captures tool calls from ALL responses
578
+ # during multi-turn conversations (when tools are used)
579
+ tool_calls_data = respond_to?(:accumulated_tool_calls) ? accumulated_tool_calls : []
570
580
 
571
581
  {
572
582
  input_tokens: safe_response_value(response, :input_tokens),
@@ -575,7 +585,9 @@ module RubyLLM
575
585
  cache_creation_tokens: safe_response_value(response, :cache_creation_tokens, 0),
576
586
  model_id: safe_response_value(response, :model_id),
577
587
  finish_reason: safe_extract_finish_reason(response),
578
- response: safe_serialize_response(response)
588
+ response: safe_serialize_response(response),
589
+ tool_calls: tool_calls_data || [],
590
+ tool_calls_count: tool_calls_data&.size || 0
579
591
  }.compact
580
592
  end
581
593
 
@@ -689,16 +701,37 @@ module RubyLLM
689
701
  # @param response [RubyLLM::Message] The LLM response
690
702
  # @return [Hash] Serialized response data
691
703
  def safe_serialize_response(response)
704
+ # Use accumulated_tool_calls which captures tool calls from ALL responses
705
+ tool_calls_data = respond_to?(:accumulated_tool_calls) ? accumulated_tool_calls : nil
706
+
692
707
  {
693
708
  content: safe_response_value(response, :content),
694
709
  model_id: safe_response_value(response, :model_id),
695
710
  input_tokens: safe_response_value(response, :input_tokens),
696
711
  output_tokens: safe_response_value(response, :output_tokens),
697
712
  cached_tokens: safe_response_value(response, :cached_tokens, 0),
698
- cache_creation_tokens: safe_response_value(response, :cache_creation_tokens, 0)
713
+ cache_creation_tokens: safe_response_value(response, :cache_creation_tokens, 0),
714
+ tool_calls: tool_calls_data.presence
699
715
  }.compact
700
716
  end
701
717
 
718
+ # Serializes tool calls to an array of hashes for storage
719
+ #
720
+ # @param response [RubyLLM::Message] The LLM response
721
+ # @return [Array<Hash>, nil] Serialized tool calls or nil if none
722
+ def serialize_tool_calls(response)
723
+ tool_calls = safe_response_value(response, :tool_calls)
724
+ return nil if tool_calls.nil? || tool_calls.empty?
725
+
726
+ tool_calls.map do |id, tool_call|
727
+ if tool_call.respond_to?(:to_h)
728
+ tool_call.to_h
729
+ else
730
+ { id: id, name: tool_call[:name], arguments: tool_call[:arguments] }
731
+ end
732
+ end
733
+ end
734
+
702
735
  # Emergency fallback to mark execution as failed
703
736
  #
704
737
  # Uses update_all to bypass ActiveRecord callbacks and validations,
@@ -0,0 +1,235 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module Agents
5
+ # Wrapper for agent execution results with full metadata
6
+ #
7
+ # Provides access to the response content along with execution details
8
+ # like token usage, cost, timing, and model information.
9
+ #
10
+ # @example Basic usage
11
+ # result = MyAgent.call(query: "test")
12
+ # result.content # => processed response
13
+ # result.input_tokens # => 150
14
+ # result.total_cost # => 0.00025
15
+ #
16
+ # @example Backward compatible hash access
17
+ # result[:key] # delegates to result.content[:key]
18
+ # result.dig(:nested, :key)
19
+ #
20
+ # @api public
21
+ class Result
22
+ extend ActiveSupport::Delegation
23
+
24
+ # @!attribute [r] content
25
+ # @return [Hash, String] The processed response content
26
+ attr_reader :content
27
+
28
+ # @!group Token Usage
29
+ # @!attribute [r] input_tokens
30
+ # @return [Integer, nil] Number of input tokens consumed
31
+ # @!attribute [r] output_tokens
32
+ # @return [Integer, nil] Number of output tokens generated
33
+ # @!attribute [r] cached_tokens
34
+ # @return [Integer] Number of tokens served from cache
35
+ # @!attribute [r] cache_creation_tokens
36
+ # @return [Integer] Number of tokens used to create cache
37
+ attr_reader :input_tokens, :output_tokens, :cached_tokens, :cache_creation_tokens
38
+
39
+ # @!group Cost
40
+ # @!attribute [r] input_cost
41
+ # @return [Float, nil] Cost of input tokens in USD
42
+ # @!attribute [r] output_cost
43
+ # @return [Float, nil] Cost of output tokens in USD
44
+ # @!attribute [r] total_cost
45
+ # @return [Float, nil] Total cost in USD
46
+ attr_reader :input_cost, :output_cost, :total_cost
47
+
48
+ # @!group Model Info
49
+ # @!attribute [r] model_id
50
+ # @return [String, nil] The model that was requested
51
+ # @!attribute [r] chosen_model_id
52
+ # @return [String, nil] The model that actually responded (may differ if fallback used)
53
+ # @!attribute [r] temperature
54
+ # @return [Float, nil] Temperature setting used
55
+ attr_reader :model_id, :chosen_model_id, :temperature
56
+
57
+ # @!group Timing
58
+ # @!attribute [r] started_at
59
+ # @return [Time, nil] When execution started
60
+ # @!attribute [r] completed_at
61
+ # @return [Time, nil] When execution completed
62
+ # @!attribute [r] duration_ms
63
+ # @return [Integer, nil] Execution duration in milliseconds
64
+ # @!attribute [r] time_to_first_token_ms
65
+ # @return [Integer, nil] Time to first token (streaming only)
66
+ attr_reader :started_at, :completed_at, :duration_ms, :time_to_first_token_ms
67
+
68
+ # @!group Status
69
+ # @!attribute [r] finish_reason
70
+ # @return [String, nil] Why generation stopped (stop, length, tool_calls, etc.)
71
+ # @!attribute [r] streaming
72
+ # @return [Boolean] Whether streaming was enabled
73
+ attr_reader :finish_reason, :streaming
74
+
75
+ # @!group Error Info
76
+ # @!attribute [r] error_class
77
+ # @return [String, nil] Exception class name if failed
78
+ # @!attribute [r] error_message
79
+ # @return [String, nil] Exception message if failed
80
+ attr_reader :error_class, :error_message
81
+
82
+ # @!group Reliability
83
+ # @!attribute [r] attempts
84
+ # @return [Array<Hash>] Details of each attempt (for retries/fallbacks)
85
+ # @!attribute [r] attempts_count
86
+ # @return [Integer] Number of attempts made
87
+ attr_reader :attempts, :attempts_count
88
+
89
+ # @!group Tool Calls
90
+ # @!attribute [r] tool_calls
91
+ # @return [Array<Hash>] Tool calls made during execution
92
+ # @!attribute [r] tool_calls_count
93
+ # @return [Integer] Number of tool calls made
94
+ attr_reader :tool_calls, :tool_calls_count
95
+
96
+ # Creates a new Result instance
97
+ #
98
+ # @param content [Hash, String] The processed response content
99
+ # @param options [Hash] Execution metadata
100
+ def initialize(content:, **options)
101
+ @content = content
102
+
103
+ # Token usage
104
+ @input_tokens = options[:input_tokens]
105
+ @output_tokens = options[:output_tokens]
106
+ @cached_tokens = options[:cached_tokens] || 0
107
+ @cache_creation_tokens = options[:cache_creation_tokens] || 0
108
+
109
+ # Cost
110
+ @input_cost = options[:input_cost]
111
+ @output_cost = options[:output_cost]
112
+ @total_cost = options[:total_cost]
113
+
114
+ # Model info
115
+ @model_id = options[:model_id]
116
+ @chosen_model_id = options[:chosen_model_id] || options[:model_id]
117
+ @temperature = options[:temperature]
118
+
119
+ # Timing
120
+ @started_at = options[:started_at]
121
+ @completed_at = options[:completed_at]
122
+ @duration_ms = options[:duration_ms]
123
+ @time_to_first_token_ms = options[:time_to_first_token_ms]
124
+
125
+ # Status
126
+ @finish_reason = options[:finish_reason]
127
+ @streaming = options[:streaming] || false
128
+
129
+ # Error
130
+ @error_class = options[:error_class]
131
+ @error_message = options[:error_message]
132
+
133
+ # Reliability
134
+ @attempts = options[:attempts] || []
135
+ @attempts_count = options[:attempts_count] || 1
136
+
137
+ # Tool calls
138
+ @tool_calls = options[:tool_calls] || []
139
+ @tool_calls_count = options[:tool_calls_count] || 0
140
+ end
141
+
142
+ # Returns total tokens (input + output)
143
+ #
144
+ # @return [Integer] Total token count
145
+ def total_tokens
146
+ (input_tokens || 0) + (output_tokens || 0)
147
+ end
148
+
149
+ # Returns whether streaming was enabled
150
+ #
151
+ # @return [Boolean] true if streaming was used
152
+ def streaming?
153
+ streaming == true
154
+ end
155
+
156
+ # Returns whether the execution succeeded
157
+ #
158
+ # @return [Boolean] true if no error occurred
159
+ def success?
160
+ error_class.nil?
161
+ end
162
+
163
+ # Returns whether the execution failed
164
+ #
165
+ # @return [Boolean] true if an error occurred
166
+ def error?
167
+ !success?
168
+ end
169
+
170
+ # Returns whether a fallback model was used
171
+ #
172
+ # @return [Boolean] true if chosen_model_id differs from model_id
173
+ def used_fallback?
174
+ chosen_model_id.present? && chosen_model_id != model_id
175
+ end
176
+
177
+ # Returns whether the response was truncated due to max tokens
178
+ #
179
+ # @return [Boolean] true if finish_reason is "length"
180
+ def truncated?
181
+ finish_reason == "length"
182
+ end
183
+
184
+ # Returns whether tool calls were made during execution
185
+ #
186
+ # @return [Boolean] true if tool_calls_count > 0
187
+ def has_tool_calls?
188
+ tool_calls_count.to_i > 0
189
+ end
190
+
191
+ # Converts the result to a hash
192
+ #
193
+ # @return [Hash] All result data as a hash
194
+ def to_h
195
+ {
196
+ content: content,
197
+ input_tokens: input_tokens,
198
+ output_tokens: output_tokens,
199
+ total_tokens: total_tokens,
200
+ cached_tokens: cached_tokens,
201
+ cache_creation_tokens: cache_creation_tokens,
202
+ input_cost: input_cost,
203
+ output_cost: output_cost,
204
+ total_cost: total_cost,
205
+ model_id: model_id,
206
+ chosen_model_id: chosen_model_id,
207
+ temperature: temperature,
208
+ started_at: started_at,
209
+ completed_at: completed_at,
210
+ duration_ms: duration_ms,
211
+ time_to_first_token_ms: time_to_first_token_ms,
212
+ finish_reason: finish_reason,
213
+ streaming: streaming,
214
+ error_class: error_class,
215
+ error_message: error_message,
216
+ attempts_count: attempts_count,
217
+ attempts: attempts,
218
+ tool_calls: tool_calls,
219
+ tool_calls_count: tool_calls_count
220
+ }
221
+ end
222
+
223
+ # Delegate hash methods to content for backward compatibility
224
+ delegate :[], :dig, :keys, :values, :each, :map, to: :content, allow_nil: true
225
+
226
+ # Custom to_json that returns content as JSON for backward compatibility
227
+ #
228
+ # @param args [Array] Arguments passed to to_json
229
+ # @return [String] JSON representation
230
+ def to_json(*args)
231
+ content.to_json(*args)
232
+ end
233
+ end
234
+ end
235
+ end
@@ -4,6 +4,6 @@ module RubyLLM
4
4
  module Agents
5
5
  # Current version of the RubyLLM::Agents gem
6
6
  # @return [String] Semantic version string
7
- VERSION = "0.3.1"
7
+ VERSION = "0.3.4"
8
8
  end
9
9
  end
@@ -10,6 +10,7 @@ require_relative "agents/circuit_breaker"
10
10
  require_relative "agents/budget_tracker"
11
11
  require_relative "agents/alert_manager"
12
12
  require_relative "agents/attempt_tracker"
13
+ require_relative "agents/result"
13
14
  require_relative "agents/inflections" if defined?(Rails)
14
15
  require_relative "agents/engine" if defined?(Rails::Engine)
15
16
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_llm-agents
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - adham90
@@ -65,20 +65,6 @@ dependencies:
65
65
  - - ">="
66
66
  - !ruby/object:Gem::Version
67
67
  version: '1.0'
68
- - !ruby/object:Gem::Dependency
69
- name: chartkick
70
- requirement: !ruby/object:Gem::Requirement
71
- requirements:
72
- - - ">="
73
- - !ruby/object:Gem::Version
74
- version: '5.0'
75
- type: :runtime
76
- prerelease: false
77
- version_requirements: !ruby/object:Gem::Requirement
78
- requirements:
79
- - - ">="
80
- - !ruby/object:Gem::Version
81
- version: '5.0'
82
68
  - !ruby/object:Gem::Dependency
83
69
  name: csv
84
70
  requirement: !ruby/object:Gem::Requirement
@@ -104,7 +90,6 @@ extra_rdoc_files: []
104
90
  files:
105
91
  - LICENSE.txt
106
92
  - README.md
107
- - app/channels/ruby_llm/agents/executions_channel.rb
108
93
  - app/controllers/concerns/ruby_llm/agents/filterable.rb
109
94
  - app/controllers/concerns/ruby_llm/agents/paginatable.rb
110
95
  - app/controllers/ruby_llm/agents/agents_controller.rb
@@ -112,25 +97,24 @@ files:
112
97
  - app/controllers/ruby_llm/agents/executions_controller.rb
113
98
  - app/controllers/ruby_llm/agents/settings_controller.rb
114
99
  - app/helpers/ruby_llm/agents/application_helper.rb
115
- - app/javascript/ruby_llm/agents/controllers/filter_controller.js
116
- - app/javascript/ruby_llm/agents/controllers/index.js
117
- - app/javascript/ruby_llm/agents/controllers/refresh_controller.js
118
100
  - app/models/ruby_llm/agents/execution.rb
119
101
  - app/models/ruby_llm/agents/execution/analytics.rb
120
102
  - app/models/ruby_llm/agents/execution/metrics.rb
121
103
  - app/models/ruby_llm/agents/execution/scopes.rb
122
104
  - app/services/ruby_llm/agents/agent_registry.rb
123
105
  - app/views/layouts/rubyllm/agents/application.html.erb
106
+ - app/views/rubyllm/agents/agents/_agent.html.erb
124
107
  - app/views/rubyllm/agents/agents/_version_comparison.html.erb
125
108
  - app/views/rubyllm/agents/agents/index.html.erb
126
109
  - app/views/rubyllm/agents/agents/show.html.erb
127
110
  - app/views/rubyllm/agents/dashboard/_action_center.html.erb
111
+ - app/views/rubyllm/agents/dashboard/_agent_comparison.html.erb
128
112
  - app/views/rubyllm/agents/dashboard/_alerts_feed.html.erb
129
113
  - app/views/rubyllm/agents/dashboard/_breaker_strip.html.erb
130
114
  - app/views/rubyllm/agents/dashboard/_budgets_bar.html.erb
131
115
  - app/views/rubyllm/agents/dashboard/_execution_item.html.erb
132
116
  - app/views/rubyllm/agents/dashboard/_now_strip.html.erb
133
- - app/views/rubyllm/agents/dashboard/_now_strip_values.html.erb
117
+ - app/views/rubyllm/agents/dashboard/_top_errors.html.erb
134
118
  - app/views/rubyllm/agents/dashboard/index.html.erb
135
119
  - app/views/rubyllm/agents/executions/_execution.html.erb
136
120
  - app/views/rubyllm/agents/executions/_filters.html.erb
@@ -140,8 +124,10 @@ files:
140
124
  - app/views/rubyllm/agents/executions/index.turbo_stream.erb
141
125
  - app/views/rubyllm/agents/executions/show.html.erb
142
126
  - app/views/rubyllm/agents/settings/show.html.erb
127
+ - app/views/rubyllm/agents/shared/_breadcrumbs.html.erb
143
128
  - app/views/rubyllm/agents/shared/_executions_table.html.erb
144
129
  - app/views/rubyllm/agents/shared/_filter_dropdown.html.erb
130
+ - app/views/rubyllm/agents/shared/_nav_link.html.erb
145
131
  - app/views/rubyllm/agents/shared/_select_dropdown.html.erb
146
132
  - app/views/rubyllm/agents/shared/_stat_card.html.erb
147
133
  - app/views/rubyllm/agents/shared/_status_badge.html.erb
@@ -155,6 +141,7 @@ files:
155
141
  - lib/generators/ruby_llm_agents/templates/add_prompts_migration.rb.tt
156
142
  - lib/generators/ruby_llm_agents/templates/add_routing_migration.rb.tt
157
143
  - lib/generators/ruby_llm_agents/templates/add_streaming_migration.rb.tt
144
+ - lib/generators/ruby_llm_agents/templates/add_tool_calls_migration.rb.tt
158
145
  - lib/generators/ruby_llm_agents/templates/add_tracing_migration.rb.tt
159
146
  - lib/generators/ruby_llm_agents/templates/agent.rb.tt
160
147
  - lib/generators/ruby_llm_agents/templates/application_agent.rb.tt
@@ -166,6 +153,13 @@ files:
166
153
  - lib/ruby_llm/agents/alert_manager.rb
167
154
  - lib/ruby_llm/agents/attempt_tracker.rb
168
155
  - lib/ruby_llm/agents/base.rb
156
+ - lib/ruby_llm/agents/base/caching.rb
157
+ - lib/ruby_llm/agents/base/cost_calculation.rb
158
+ - lib/ruby_llm/agents/base/dsl.rb
159
+ - lib/ruby_llm/agents/base/execution.rb
160
+ - lib/ruby_llm/agents/base/reliability_execution.rb
161
+ - lib/ruby_llm/agents/base/response_building.rb
162
+ - lib/ruby_llm/agents/base/tool_tracking.rb
169
163
  - lib/ruby_llm/agents/budget_tracker.rb
170
164
  - lib/ruby_llm/agents/circuit_breaker.rb
171
165
  - lib/ruby_llm/agents/configuration.rb
@@ -175,6 +169,7 @@ files:
175
169
  - lib/ruby_llm/agents/instrumentation.rb
176
170
  - lib/ruby_llm/agents/redactor.rb
177
171
  - lib/ruby_llm/agents/reliability.rb
172
+ - lib/ruby_llm/agents/result.rb
178
173
  - lib/ruby_llm/agents/version.rb
179
174
  homepage: https://github.com/adham90/ruby_llm-agents
180
175
  licenses:
@@ -1,46 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyLLM
4
- module Agents
5
- # ActionCable channel for real-time execution updates
6
- #
7
- # Broadcasts execution create/update events to subscribed clients.
8
- # Used by the dashboard to show live execution status changes.
9
- #
10
- # Inherits from the host app's ApplicationCable::Channel (note the :: prefix
11
- # to reference the root namespace, not the engine's namespace).
12
- #
13
- # @example JavaScript subscription
14
- # import { createConsumer } from "@rails/actioncable"
15
- # const consumer = createConsumer()
16
- # consumer.subscriptions.create("RubyLLM::Agents::ExecutionsChannel", {
17
- # received(data) {
18
- # console.log("Execution update:", data)
19
- # }
20
- # })
21
- #
22
- # @see Execution#broadcast_execution Broadcast trigger
23
- # @api private
24
- class ExecutionsChannel < ::ApplicationCable::Channel
25
- # Subscribes the client to the executions broadcast stream
26
- #
27
- # Called automatically when a client connects to this channel.
28
- # Streams from the "ruby_llm_agents:executions" channel name.
29
- #
30
- # @return [void]
31
- def subscribed
32
- stream_from "ruby_llm_agents:executions"
33
- logger.info "[RubyLLM::Agents] Client subscribed to executions channel"
34
- end
35
-
36
- # Cleans up when a client disconnects
37
- #
38
- # Called automatically when the WebSocket connection is closed.
39
- #
40
- # @return [void]
41
- def unsubscribed
42
- logger.info "[RubyLLM::Agents] Client unsubscribed from executions channel"
43
- end
44
- end
45
- end
46
- end
@@ -1,56 +0,0 @@
1
- import { Controller } from "@hotwired/stimulus"
2
-
3
- // Filter controller for live filtering of executions
4
- //
5
- // Usage:
6
- // <form data-controller="filter">
7
- // <select data-action="change->filter#submit">...</select>
8
- // </form>
9
- //
10
- export default class extends Controller {
11
- static targets = ["form"]
12
- static values = {
13
- debounce: { type: Number, default: 300 }
14
- }
15
-
16
- connect() {
17
- this.timeout = null
18
- }
19
-
20
- disconnect() {
21
- if (this.timeout) {
22
- clearTimeout(this.timeout)
23
- }
24
- }
25
-
26
- // Submit the form with debouncing
27
- submit(event) {
28
- if (this.timeout) {
29
- clearTimeout(this.timeout)
30
- }
31
-
32
- this.timeout = setTimeout(() => {
33
- this.element.requestSubmit()
34
- }, this.debounceValue)
35
- }
36
-
37
- // Submit immediately without debounce
38
- submitNow(event) {
39
- this.element.requestSubmit()
40
- }
41
-
42
- // Update URL with current filter state
43
- updateUrl() {
44
- const formData = new FormData(this.element)
45
- const params = new URLSearchParams()
46
-
47
- for (const [key, value] of formData) {
48
- if (value) {
49
- params.set(key, value)
50
- }
51
- }
52
-
53
- const newUrl = `${window.location.pathname}?${params.toString()}`
54
- window.history.pushState({}, "", newUrl)
55
- }
56
- }
@@ -1,12 +0,0 @@
1
- // Entry point for RubyLLM::Agents Stimulus controllers
2
- // Import and register controllers with your Stimulus application
3
-
4
- import FilterController from "./filter_controller"
5
- import RefreshController from "./refresh_controller"
6
-
7
- export { FilterController, RefreshController }
8
-
9
- export function registerControllers(application) {
10
- application.register("filter", FilterController)
11
- application.register("refresh", RefreshController)
12
- }
@@ -1,83 +0,0 @@
1
- import { Controller } from "@hotwired/stimulus"
2
-
3
- // Auto-refresh controller for dashboard with toggle support
4
- //
5
- // Usage:
6
- // <div data-controller="refresh"
7
- // data-refresh-interval-value="30000"
8
- // data-refresh-enabled-value="false">
9
- // <button data-action="refresh#toggle" data-refresh-target="button">
10
- // Live Poll: Off
11
- // </button>
12
- // </div>
13
- //
14
- export default class extends Controller {
15
- static values = {
16
- interval: { type: Number, default: 30000 },
17
- enabled: { type: Boolean, default: false }
18
- }
19
-
20
- static targets = ["button", "indicator"]
21
-
22
- connect() {
23
- this.updateUI()
24
- if (this.enabledValue) {
25
- this.startRefresh()
26
- }
27
- }
28
-
29
- disconnect() {
30
- this.stopRefresh()
31
- }
32
-
33
- toggle() {
34
- this.enabledValue = !this.enabledValue
35
-
36
- if (this.enabledValue) {
37
- this.startRefresh()
38
- this.refresh() // Immediate refresh when enabled
39
- } else {
40
- this.stopRefresh()
41
- }
42
-
43
- this.updateUI()
44
- }
45
-
46
- startRefresh() {
47
- if (this.intervalValue > 0 && !this.timer) {
48
- this.timer = setInterval(() => this.refresh(), this.intervalValue)
49
- }
50
- }
51
-
52
- stopRefresh() {
53
- if (this.timer) {
54
- clearInterval(this.timer)
55
- this.timer = null
56
- }
57
- }
58
-
59
- refresh() {
60
- const frame = this.element.closest("turbo-frame") ||
61
- this.element.querySelector("turbo-frame")
62
-
63
- if (frame && typeof frame.reload === "function") {
64
- frame.reload()
65
- }
66
- }
67
-
68
- updateUI() {
69
- if (this.hasButtonTarget) {
70
- if (this.enabledValue) {
71
- this.buttonTarget.classList.remove("bg-gray-100", "text-gray-600")
72
- this.buttonTarget.classList.add("bg-green-100", "text-green-700")
73
- } else {
74
- this.buttonTarget.classList.remove("bg-green-100", "text-green-700")
75
- this.buttonTarget.classList.add("bg-gray-100", "text-gray-600")
76
- }
77
- }
78
-
79
- if (this.hasIndicatorTarget) {
80
- this.indicatorTarget.textContent = this.enabledValue ? "On" : "Off"
81
- }
82
- }
83
- }