ruby_llm-agents 1.0.0 → 1.2.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.
- checksums.yaml +4 -4
- data/app/controllers/concerns/ruby_llm/agents/paginatable.rb +9 -3
- data/app/controllers/concerns/ruby_llm/agents/sortable.rb +58 -0
- data/app/controllers/ruby_llm/agents/agents_controller.rb +59 -16
- data/app/controllers/ruby_llm/agents/dashboard_controller.rb +144 -20
- data/app/controllers/ruby_llm/agents/executions_controller.rb +13 -16
- data/app/controllers/ruby_llm/agents/workflows_controller.rb +279 -90
- data/app/helpers/ruby_llm/agents/application_helper.rb +100 -0
- data/app/mailers/ruby_llm/agents/alert_mailer.rb +84 -0
- data/app/mailers/ruby_llm/agents/application_mailer.rb +28 -0
- data/app/models/ruby_llm/agents/execution/analytics.rb +170 -20
- data/app/models/ruby_llm/agents/execution/scopes.rb +0 -31
- data/app/models/ruby_llm/agents/execution/workflow.rb +0 -129
- data/app/models/ruby_llm/agents/execution.rb +50 -14
- data/app/models/ruby_llm/agents/tenant/budgetable.rb +277 -0
- data/app/models/ruby_llm/agents/tenant/configurable.rb +135 -0
- data/app/models/ruby_llm/agents/tenant/trackable.rb +310 -0
- data/app/models/ruby_llm/agents/tenant.rb +146 -0
- data/app/models/ruby_llm/agents/tenant_budget.rb +12 -253
- data/app/services/ruby_llm/agents/agent_registry.rb +18 -12
- data/app/views/layouts/ruby_llm/agents/application.html.erb +72 -76
- data/app/views/ruby_llm/agents/agents/_agent.html.erb +0 -12
- data/app/views/ruby_llm/agents/agents/_sortable_header.html.erb +56 -0
- data/app/views/ruby_llm/agents/agents/_workflow.html.erb +5 -15
- data/app/views/ruby_llm/agents/agents/index.html.erb +271 -100
- data/app/views/ruby_llm/agents/agents/show.html.erb +1 -0
- data/app/views/ruby_llm/agents/alert_mailer/alert_notification.html.erb +107 -0
- data/app/views/ruby_llm/agents/alert_mailer/alert_notification.text.erb +18 -0
- data/app/views/ruby_llm/agents/api_configurations/show.html.erb +4 -1
- data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +66 -359
- data/app/views/ruby_llm/agents/dashboard/_model_comparison.html.erb +56 -0
- data/app/views/ruby_llm/agents/dashboard/_model_cost_breakdown.html.erb +115 -0
- data/app/views/ruby_llm/agents/dashboard/_now_strip.html.erb +35 -60
- data/app/views/ruby_llm/agents/dashboard/_top_errors.html.erb +17 -6
- data/app/views/ruby_llm/agents/dashboard/index.html.erb +373 -72
- data/app/views/ruby_llm/agents/executions/_execution.html.erb +0 -1
- data/app/views/ruby_llm/agents/executions/_filters.html.erb +51 -39
- data/app/views/ruby_llm/agents/executions/_list.html.erb +53 -195
- data/app/views/ruby_llm/agents/executions/_workflow_summary.html.erb +5 -20
- data/app/views/ruby_llm/agents/executions/index.html.erb +7 -83
- data/app/views/ruby_llm/agents/executions/show.html.erb +10 -20
- data/app/views/ruby_llm/agents/shared/_agent_type_badge.html.erb +2 -1
- data/app/views/ruby_llm/agents/shared/_doc_link.html.erb +12 -0
- data/app/views/ruby_llm/agents/shared/_executions_table.html.erb +3 -15
- data/app/views/ruby_llm/agents/shared/_filter_dropdown.html.erb +1 -1
- data/app/views/ruby_llm/agents/shared/_select_dropdown.html.erb +1 -1
- data/app/views/ruby_llm/agents/shared/_sortable_header.html.erb +53 -0
- data/app/views/ruby_llm/agents/shared/_status_badge.html.erb +7 -0
- data/app/views/ruby_llm/agents/shared/_status_dot.html.erb +1 -1
- data/app/views/ruby_llm/agents/shared/_workflow_type_badge.html.erb +9 -35
- data/app/views/ruby_llm/agents/system_config/show.html.erb +4 -1
- data/app/views/ruby_llm/agents/tenants/index.html.erb +4 -1
- data/app/views/ruby_llm/agents/workflows/_step_performance.html.erb +7 -15
- data/app/views/ruby_llm/agents/workflows/_structure_dsl.html.erb +539 -0
- data/app/views/ruby_llm/agents/workflows/_workflow_diagram.html.erb +920 -0
- data/app/views/ruby_llm/agents/workflows/index.html.erb +179 -0
- data/app/views/ruby_llm/agents/workflows/show.html.erb +164 -139
- data/config/routes.rb +1 -1
- data/lib/generators/ruby_llm_agents/agent_generator.rb +6 -36
- data/lib/generators/ruby_llm_agents/background_remover_generator.rb +7 -37
- data/lib/generators/ruby_llm_agents/embedder_generator.rb +5 -38
- data/lib/generators/ruby_llm_agents/image_analyzer_generator.rb +7 -37
- data/lib/generators/ruby_llm_agents/image_editor_generator.rb +7 -37
- data/lib/generators/ruby_llm_agents/image_generator_generator.rb +8 -41
- data/lib/generators/ruby_llm_agents/image_pipeline_generator.rb +18 -46
- data/lib/generators/ruby_llm_agents/image_transformer_generator.rb +7 -37
- data/lib/generators/ruby_llm_agents/image_upscaler_generator.rb +7 -37
- data/lib/generators/ruby_llm_agents/image_variator_generator.rb +7 -37
- data/lib/generators/ruby_llm_agents/install_generator.rb +33 -56
- data/lib/generators/ruby_llm_agents/migrate_structure_generator.rb +480 -0
- data/lib/generators/ruby_llm_agents/multi_tenancy_generator.rb +42 -22
- data/lib/generators/ruby_llm_agents/restructure_generator.rb +2 -2
- data/lib/generators/ruby_llm_agents/speaker_generator.rb +8 -39
- data/lib/generators/ruby_llm_agents/templates/add_tenant_to_executions_migration.rb.tt +13 -2
- data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +5 -8
- data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +40 -42
- data/lib/generators/ruby_llm_agents/templates/application_background_remover.rb.tt +20 -22
- data/lib/generators/ruby_llm_agents/templates/application_embedder.rb.tt +24 -26
- data/lib/generators/ruby_llm_agents/templates/application_image_analyzer.rb.tt +20 -22
- data/lib/generators/ruby_llm_agents/templates/application_image_editor.rb.tt +19 -17
- data/lib/generators/ruby_llm_agents/templates/application_image_generator.rb.tt +31 -33
- data/lib/generators/ruby_llm_agents/templates/application_image_pipeline.rb.tt +125 -127
- data/lib/generators/ruby_llm_agents/templates/application_image_transformer.rb.tt +20 -18
- data/lib/generators/ruby_llm_agents/templates/application_image_upscaler.rb.tt +19 -17
- data/lib/generators/ruby_llm_agents/templates/application_image_variator.rb.tt +19 -17
- data/lib/generators/ruby_llm_agents/templates/application_speaker.rb.tt +38 -40
- data/lib/generators/ruby_llm_agents/templates/application_transcriber.rb.tt +42 -44
- data/lib/generators/ruby_llm_agents/templates/application_workflow.rb.tt +48 -0
- data/lib/generators/ruby_llm_agents/templates/background_remover.rb.tt +19 -21
- data/lib/generators/ruby_llm_agents/templates/create_tenant_budgets_migration.rb.tt +11 -0
- data/lib/generators/ruby_llm_agents/templates/create_tenants_migration.rb.tt +72 -0
- data/lib/generators/ruby_llm_agents/templates/embedder.rb.tt +19 -21
- data/lib/generators/ruby_llm_agents/templates/image_analyzer.rb.tt +20 -22
- data/lib/generators/ruby_llm_agents/templates/image_editor.rb.tt +15 -17
- data/lib/generators/ruby_llm_agents/templates/image_generator.rb.tt +25 -27
- data/lib/generators/ruby_llm_agents/templates/image_pipeline.rb.tt +19 -21
- data/lib/generators/ruby_llm_agents/templates/image_transformer.rb.tt +20 -22
- data/lib/generators/ruby_llm_agents/templates/image_upscaler.rb.tt +17 -19
- data/lib/generators/ruby_llm_agents/templates/image_variator.rb.tt +15 -17
- data/lib/generators/ruby_llm_agents/templates/rename_tenant_budgets_to_tenants_migration.rb.tt +34 -0
- data/lib/generators/ruby_llm_agents/templates/skills/AGENTS.md.tt +87 -24
- data/lib/generators/ruby_llm_agents/templates/skills/BACKGROUND_REMOVERS.md.tt +21 -27
- data/lib/generators/ruby_llm_agents/templates/skills/EMBEDDERS.md.tt +46 -54
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_ANALYZERS.md.tt +31 -39
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_EDITORS.md.tt +22 -28
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_GENERATORS.md.tt +53 -63
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_PIPELINES.md.tt +46 -56
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_TRANSFORMERS.md.tt +23 -31
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_UPSCALERS.md.tt +22 -30
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_VARIATORS.md.tt +23 -31
- data/lib/generators/ruby_llm_agents/templates/skills/SPEAKERS.md.tt +38 -46
- data/lib/generators/ruby_llm_agents/templates/skills/TOOLS.md.tt +7 -7
- data/lib/generators/ruby_llm_agents/templates/skills/TRANSCRIBERS.md.tt +59 -71
- data/lib/generators/ruby_llm_agents/templates/skills/WORKFLOWS.md.tt +274 -23
- data/lib/generators/ruby_llm_agents/templates/speaker.rb.tt +29 -31
- data/lib/generators/ruby_llm_agents/templates/transcriber.rb.tt +28 -30
- data/lib/generators/ruby_llm_agents/transcriber_generator.rb +10 -43
- data/lib/generators/ruby_llm_agents/upgrade_generator.rb +26 -0
- data/lib/ruby_llm/agents/core/configuration.rb +55 -43
- data/lib/ruby_llm/agents/core/llm_tenant.rb +60 -60
- data/lib/ruby_llm/agents/core/version.rb +1 -1
- data/lib/ruby_llm/agents/infrastructure/alert_manager.rb +26 -0
- data/lib/ruby_llm/agents/infrastructure/budget/config_resolver.rb +4 -2
- data/lib/ruby_llm/agents/pipeline.rb +69 -0
- data/lib/ruby_llm/agents/workflow/approval.rb +205 -0
- data/lib/ruby_llm/agents/workflow/approval_store.rb +179 -0
- data/lib/ruby_llm/agents/workflow/dsl/executor.rb +467 -0
- data/lib/ruby_llm/agents/workflow/dsl/input_schema.rb +244 -0
- data/lib/ruby_llm/agents/workflow/dsl/iteration_executor.rb +289 -0
- data/lib/ruby_llm/agents/workflow/dsl/parallel_group.rb +107 -0
- data/lib/ruby_llm/agents/workflow/dsl/route_builder.rb +150 -0
- data/lib/ruby_llm/agents/workflow/dsl/schedule_helpers.rb +187 -0
- data/lib/ruby_llm/agents/workflow/dsl/step_config.rb +352 -0
- data/lib/ruby_llm/agents/workflow/dsl/step_executor.rb +415 -0
- data/lib/ruby_llm/agents/workflow/dsl/wait_config.rb +257 -0
- data/lib/ruby_llm/agents/workflow/dsl/wait_executor.rb +317 -0
- data/lib/ruby_llm/agents/workflow/dsl.rb +576 -0
- data/lib/ruby_llm/agents/workflow/instrumentation.rb +2 -7
- data/lib/ruby_llm/agents/workflow/notifiers/base.rb +117 -0
- data/lib/ruby_llm/agents/workflow/notifiers/email.rb +117 -0
- data/lib/ruby_llm/agents/workflow/notifiers/slack.rb +180 -0
- data/lib/ruby_llm/agents/workflow/notifiers/webhook.rb +121 -0
- data/lib/ruby_llm/agents/workflow/notifiers.rb +70 -0
- data/lib/ruby_llm/agents/workflow/orchestrator.rb +190 -23
- data/lib/ruby_llm/agents/workflow/result.rb +202 -0
- data/lib/ruby_llm/agents/workflow/throttle_manager.rb +206 -0
- data/lib/ruby_llm/agents/workflow/wait_result.rb +213 -0
- metadata +43 -6
- data/app/views/ruby_llm/agents/dashboard/_execution_item.html.erb +0 -66
- data/lib/ruby_llm/agents/workflow/parallel.rb +0 -299
- data/lib/ruby_llm/agents/workflow/pipeline.rb +0 -306
- data/lib/ruby_llm/agents/workflow/router.rb +0 -429
|
@@ -2,262 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
module RubyLLM
|
|
4
4
|
module Agents
|
|
5
|
-
#
|
|
5
|
+
# @deprecated Use {Tenant} instead. This class will be removed in a future major version.
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# Supports cost-based (USD), token-based, and execution-based limits.
|
|
7
|
+
# TenantBudget is now an alias to Tenant for backward compatibility.
|
|
8
|
+
# All functionality has been moved to the Tenant model with organized concerns.
|
|
10
9
|
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
# @!attribute [rw] daily_limit
|
|
16
|
-
# @return [BigDecimal, nil] Daily budget limit in USD
|
|
17
|
-
# @!attribute [rw] monthly_limit
|
|
18
|
-
# @return [BigDecimal, nil] Monthly budget limit in USD
|
|
19
|
-
# @!attribute [rw] daily_token_limit
|
|
20
|
-
# @return [Integer, nil] Daily token limit (across all models)
|
|
21
|
-
# @!attribute [rw] monthly_token_limit
|
|
22
|
-
# @return [Integer, nil] Monthly token limit (across all models)
|
|
23
|
-
# @!attribute [rw] daily_execution_limit
|
|
24
|
-
# @return [Integer, nil] Daily execution/call limit
|
|
25
|
-
# @!attribute [rw] monthly_execution_limit
|
|
26
|
-
# @return [Integer, nil] Monthly execution/call limit
|
|
27
|
-
# @!attribute [rw] per_agent_daily
|
|
28
|
-
# @return [Hash] Per-agent daily cost limits: { "AgentName" => limit }
|
|
29
|
-
# @!attribute [rw] per_agent_monthly
|
|
30
|
-
# @return [Hash] Per-agent monthly cost limits: { "AgentName" => limit }
|
|
31
|
-
# @!attribute [rw] enforcement
|
|
32
|
-
# @return [String] Enforcement mode: "none", "soft", or "hard"
|
|
33
|
-
# @!attribute [rw] inherit_global_defaults
|
|
34
|
-
# @return [Boolean] Whether to fall back to global config for unset limits
|
|
35
|
-
# @!attribute [rw] tenant_record
|
|
36
|
-
# @return [ActiveRecord::Base, nil] Polymorphic association to tenant model
|
|
10
|
+
# @example Migration path
|
|
11
|
+
# # Old usage (still works)
|
|
12
|
+
# TenantBudget.for_tenant("acme_corp")
|
|
13
|
+
# TenantBudget.create!(tenant_id: "acme", daily_limit: 100)
|
|
37
14
|
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
# name: "Acme Corporation",
|
|
42
|
-
# daily_limit: 50.0, # USD
|
|
43
|
-
# monthly_limit: 500.0, # USD
|
|
44
|
-
# daily_token_limit: 1_000_000,
|
|
45
|
-
# monthly_token_limit: 10_000_000,
|
|
46
|
-
# daily_execution_limit: 500,
|
|
47
|
-
# monthly_execution_limit: 10_000,
|
|
48
|
-
# enforcement: "hard"
|
|
49
|
-
# )
|
|
15
|
+
# # New usage (preferred)
|
|
16
|
+
# Tenant.for("acme_corp")
|
|
17
|
+
# Tenant.create!(tenant_id: "acme", daily_limit: 100)
|
|
50
18
|
#
|
|
51
|
-
# @
|
|
52
|
-
|
|
53
|
-
# budget.effective_daily_limit # => 50.0 (cost)
|
|
54
|
-
# budget.effective_daily_token_limit # => 1_000_000 (tokens)
|
|
55
|
-
# budget.effective_daily_execution_limit # => 500 (executions)
|
|
56
|
-
#
|
|
57
|
-
# @see RubyLLM::Agents::BudgetTracker
|
|
58
|
-
# @see RubyLLM::Agents::LLMTenant
|
|
59
|
-
# @api public
|
|
60
|
-
class TenantBudget < ::ActiveRecord::Base
|
|
61
|
-
self.table_name = "ruby_llm_agents_tenant_budgets"
|
|
62
|
-
|
|
63
|
-
# Valid enforcement modes
|
|
64
|
-
ENFORCEMENT_MODES = %w[none soft hard].freeze
|
|
65
|
-
|
|
66
|
-
# Polymorphic association to the tenant model (e.g., Organization, Account)
|
|
67
|
-
belongs_to :tenant_record, polymorphic: true, optional: true
|
|
68
|
-
|
|
69
|
-
# Validations
|
|
70
|
-
validates :tenant_id, presence: true, uniqueness: true
|
|
71
|
-
validates :enforcement, inclusion: { in: ENFORCEMENT_MODES }, allow_nil: true
|
|
72
|
-
validates :daily_limit, :monthly_limit,
|
|
73
|
-
numericality: { greater_than_or_equal_to: 0 }, allow_nil: true
|
|
74
|
-
validates :daily_token_limit, :monthly_token_limit,
|
|
75
|
-
numericality: { greater_than_or_equal_to: 0, only_integer: true }, allow_nil: true
|
|
76
|
-
validates :daily_execution_limit, :monthly_execution_limit,
|
|
77
|
-
numericality: { greater_than_or_equal_to: 0, only_integer: true }, allow_nil: true
|
|
78
|
-
|
|
79
|
-
# Finds a budget for the given tenant
|
|
80
|
-
#
|
|
81
|
-
# @param tenant [String, Object] The tenant identifier string or object with llm_tenant_id
|
|
82
|
-
# @return [TenantBudget, nil] The budget record or nil if not found
|
|
83
|
-
def self.for_tenant(tenant)
|
|
84
|
-
return nil if tenant.blank?
|
|
85
|
-
|
|
86
|
-
if tenant.respond_to?(:llm_tenant_id)
|
|
87
|
-
# Object with llm_tenant DSL - try polymorphic first, then tenant_id
|
|
88
|
-
find_by(tenant_record: tenant) || find_by(tenant_id: tenant.llm_tenant_id)
|
|
89
|
-
else
|
|
90
|
-
# String tenant_id
|
|
91
|
-
find_by(tenant_id: tenant.to_s)
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
# Finds or creates a budget for the given tenant
|
|
96
|
-
#
|
|
97
|
-
# @param tenant_id [String] The tenant identifier
|
|
98
|
-
# @param name [String, nil] Optional human-readable name
|
|
99
|
-
# @return [TenantBudget] The budget record
|
|
100
|
-
def self.for_tenant!(tenant_id, name: nil)
|
|
101
|
-
find_or_create_by!(tenant_id: tenant_id) do |budget|
|
|
102
|
-
budget.name = name
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
# Returns the display name (name or tenant_id fallback)
|
|
107
|
-
#
|
|
108
|
-
# @return [String] The name to display
|
|
109
|
-
def display_name
|
|
110
|
-
name.presence || tenant_id
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
# Returns the effective daily limit, considering inheritance
|
|
114
|
-
#
|
|
115
|
-
# @return [Float, nil] The daily limit or nil if not set
|
|
116
|
-
def effective_daily_limit
|
|
117
|
-
return daily_limit if daily_limit.present?
|
|
118
|
-
return nil unless inherit_global_defaults
|
|
119
|
-
|
|
120
|
-
global_config&.dig(:global_daily)
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
# Returns the effective monthly limit, considering inheritance
|
|
124
|
-
#
|
|
125
|
-
# @return [Float, nil] The monthly limit or nil if not set
|
|
126
|
-
def effective_monthly_limit
|
|
127
|
-
return monthly_limit if monthly_limit.present?
|
|
128
|
-
return nil unless inherit_global_defaults
|
|
129
|
-
|
|
130
|
-
global_config&.dig(:global_monthly)
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
# Returns the effective per-agent daily limit
|
|
134
|
-
#
|
|
135
|
-
# @param agent_type [String] The agent class name
|
|
136
|
-
# @return [Float, nil] The limit or nil if not set
|
|
137
|
-
def effective_per_agent_daily(agent_type)
|
|
138
|
-
limit = per_agent_daily&.dig(agent_type)
|
|
139
|
-
return limit if limit.present?
|
|
140
|
-
return nil unless inherit_global_defaults
|
|
141
|
-
|
|
142
|
-
global_config&.dig(:per_agent_daily, agent_type)
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
# Returns the effective per-agent monthly limit
|
|
146
|
-
#
|
|
147
|
-
# @param agent_type [String] The agent class name
|
|
148
|
-
# @return [Float, nil] The limit or nil if not set
|
|
149
|
-
def effective_per_agent_monthly(agent_type)
|
|
150
|
-
limit = per_agent_monthly&.dig(agent_type)
|
|
151
|
-
return limit if limit.present?
|
|
152
|
-
return nil unless inherit_global_defaults
|
|
153
|
-
|
|
154
|
-
global_config&.dig(:per_agent_monthly, agent_type)
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
# Returns the effective daily token limit, considering inheritance
|
|
158
|
-
#
|
|
159
|
-
# @return [Integer, nil] The daily token limit or nil if not set
|
|
160
|
-
def effective_daily_token_limit
|
|
161
|
-
return daily_token_limit if daily_token_limit.present?
|
|
162
|
-
return nil unless inherit_global_defaults
|
|
163
|
-
|
|
164
|
-
global_config&.dig(:global_daily_tokens)
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
# Returns the effective monthly token limit, considering inheritance
|
|
168
|
-
#
|
|
169
|
-
# @return [Integer, nil] The monthly token limit or nil if not set
|
|
170
|
-
def effective_monthly_token_limit
|
|
171
|
-
return monthly_token_limit if monthly_token_limit.present?
|
|
172
|
-
return nil unless inherit_global_defaults
|
|
173
|
-
|
|
174
|
-
global_config&.dig(:global_monthly_tokens)
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
# Returns the effective daily execution limit, considering inheritance
|
|
178
|
-
#
|
|
179
|
-
# @return [Integer, nil] The daily execution limit or nil if not set
|
|
180
|
-
def effective_daily_execution_limit
|
|
181
|
-
return daily_execution_limit if daily_execution_limit.present?
|
|
182
|
-
return nil unless inherit_global_defaults
|
|
183
|
-
|
|
184
|
-
global_config&.dig(:global_daily_executions)
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
# Returns the effective monthly execution limit, considering inheritance
|
|
188
|
-
#
|
|
189
|
-
# @return [Integer, nil] The monthly execution limit or nil if not set
|
|
190
|
-
def effective_monthly_execution_limit
|
|
191
|
-
return monthly_execution_limit if monthly_execution_limit.present?
|
|
192
|
-
return nil unless inherit_global_defaults
|
|
193
|
-
|
|
194
|
-
global_config&.dig(:global_monthly_executions)
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
# Returns the effective enforcement mode
|
|
198
|
-
#
|
|
199
|
-
# @return [Symbol] :none, :soft, or :hard
|
|
200
|
-
def effective_enforcement
|
|
201
|
-
return enforcement.to_sym if enforcement.present?
|
|
202
|
-
return :soft unless inherit_global_defaults
|
|
203
|
-
|
|
204
|
-
RubyLLM::Agents.configuration.budget_enforcement
|
|
205
|
-
end
|
|
206
|
-
|
|
207
|
-
# Checks if budget enforcement is enabled for this tenant
|
|
208
|
-
#
|
|
209
|
-
# @return [Boolean] true if enforcement is :soft or :hard
|
|
210
|
-
def budgets_enabled?
|
|
211
|
-
effective_enforcement != :none
|
|
212
|
-
end
|
|
213
|
-
|
|
214
|
-
# Returns a hash suitable for BudgetTracker
|
|
215
|
-
#
|
|
216
|
-
# @return [Hash] Budget configuration hash
|
|
217
|
-
def to_budget_config
|
|
218
|
-
{
|
|
219
|
-
enabled: budgets_enabled?,
|
|
220
|
-
enforcement: effective_enforcement,
|
|
221
|
-
# Cost limits
|
|
222
|
-
global_daily: effective_daily_limit,
|
|
223
|
-
global_monthly: effective_monthly_limit,
|
|
224
|
-
per_agent_daily: merged_per_agent_daily,
|
|
225
|
-
per_agent_monthly: merged_per_agent_monthly,
|
|
226
|
-
# Token limits
|
|
227
|
-
global_daily_tokens: effective_daily_token_limit,
|
|
228
|
-
global_monthly_tokens: effective_monthly_token_limit,
|
|
229
|
-
# Execution limits
|
|
230
|
-
global_daily_executions: effective_daily_execution_limit,
|
|
231
|
-
global_monthly_executions: effective_monthly_execution_limit
|
|
232
|
-
}
|
|
233
|
-
end
|
|
234
|
-
|
|
235
|
-
private
|
|
236
|
-
|
|
237
|
-
# Returns the global budgets configuration
|
|
238
|
-
#
|
|
239
|
-
# @return [Hash, nil] Global budget config
|
|
240
|
-
def global_config
|
|
241
|
-
RubyLLM::Agents.configuration.budgets
|
|
242
|
-
end
|
|
243
|
-
|
|
244
|
-
# Merges per-agent daily limits with global defaults
|
|
245
|
-
#
|
|
246
|
-
# @return [Hash] Merged per-agent daily limits
|
|
247
|
-
def merged_per_agent_daily
|
|
248
|
-
return per_agent_daily || {} unless inherit_global_defaults
|
|
249
|
-
|
|
250
|
-
(global_config&.dig(:per_agent_daily) || {}).merge(per_agent_daily || {})
|
|
251
|
-
end
|
|
252
|
-
|
|
253
|
-
# Merges per-agent monthly limits with global defaults
|
|
254
|
-
#
|
|
255
|
-
# @return [Hash] Merged per-agent monthly limits
|
|
256
|
-
def merged_per_agent_monthly
|
|
257
|
-
return per_agent_monthly || {} unless inherit_global_defaults
|
|
258
|
-
|
|
259
|
-
(global_config&.dig(:per_agent_monthly) || {}).merge(per_agent_monthly || {})
|
|
260
|
-
end
|
|
261
|
-
end
|
|
19
|
+
# @see Tenant
|
|
20
|
+
TenantBudget = Tenant
|
|
262
21
|
end
|
|
263
22
|
end
|
|
@@ -21,6 +21,12 @@ module RubyLLM
|
|
|
21
21
|
#
|
|
22
22
|
# @api public
|
|
23
23
|
class AgentRegistry
|
|
24
|
+
# Base workflow classes to exclude from listings
|
|
25
|
+
# These are abstract parent classes, not concrete workflows
|
|
26
|
+
BASE_WORKFLOW_CLASSES = [
|
|
27
|
+
"RubyLLM::Agents::Workflow"
|
|
28
|
+
].freeze
|
|
29
|
+
|
|
24
30
|
class << self
|
|
25
31
|
# Returns all unique agent type names
|
|
26
32
|
#
|
|
@@ -72,7 +78,10 @@ module RubyLLM
|
|
|
72
78
|
transcribers = RubyLLM::Agents::Transcriber.descendants.map(&:name).compact
|
|
73
79
|
image_generators = RubyLLM::Agents::ImageGenerator.descendants.map(&:name).compact
|
|
74
80
|
|
|
75
|
-
(agents + workflows + embedders + moderators + speakers + transcribers + image_generators).uniq
|
|
81
|
+
all_agents = (agents + workflows + embedders + moderators + speakers + transcribers + image_generators).uniq
|
|
82
|
+
|
|
83
|
+
# Filter out base workflow classes
|
|
84
|
+
all_agents.reject { |name| BASE_WORKFLOW_CLASSES.include?(name) }
|
|
76
85
|
rescue StandardError => e
|
|
77
86
|
Rails.logger.error("[RubyLLM::Agents] Error loading agents from file system: #{e.message}")
|
|
78
87
|
[]
|
|
@@ -90,10 +99,13 @@ module RubyLLM
|
|
|
90
99
|
|
|
91
100
|
# Eager loads all agent and workflow files to register descendants
|
|
92
101
|
#
|
|
102
|
+
# Uses the configured autoload paths from RubyLLM::Agents.configuration
|
|
103
|
+
# to ensure agents are discovered in the correct directories.
|
|
104
|
+
#
|
|
93
105
|
# @return [void]
|
|
94
106
|
def eager_load_agents!
|
|
95
|
-
|
|
96
|
-
path = Rails.root.join(
|
|
107
|
+
RubyLLM::Agents.configuration.all_autoload_paths.each do |relative_path|
|
|
108
|
+
path = Rails.root.join(relative_path)
|
|
97
109
|
next unless path.exist?
|
|
98
110
|
|
|
99
111
|
Dir.glob(path.join("**", "*.rb")).each do |file|
|
|
@@ -207,18 +219,12 @@ module RubyLLM
|
|
|
207
219
|
# Detects the specific workflow type from class hierarchy
|
|
208
220
|
#
|
|
209
221
|
# @param agent_class [Class, nil] The agent class
|
|
210
|
-
# @return [String, nil] "
|
|
222
|
+
# @return [String, nil] "workflow" for DSL workflows, or nil
|
|
211
223
|
def detect_workflow_type(agent_class)
|
|
212
224
|
return nil unless agent_class
|
|
213
225
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
if ancestors.include?("RubyLLM::Agents::Workflow::Pipeline")
|
|
217
|
-
"pipeline"
|
|
218
|
-
elsif ancestors.include?("RubyLLM::Agents::Workflow::Parallel")
|
|
219
|
-
"parallel"
|
|
220
|
-
elsif ancestors.include?("RubyLLM::Agents::Workflow::Router")
|
|
221
|
-
"router"
|
|
226
|
+
if agent_class.respond_to?(:step_configs) && agent_class.step_configs.any?
|
|
227
|
+
"workflow"
|
|
222
228
|
end
|
|
223
229
|
end
|
|
224
230
|
|
|
@@ -215,10 +215,9 @@
|
|
|
215
215
|
nav_items = [
|
|
216
216
|
{ path: ruby_llm_agents.root_path, label: "Dashboard", icon: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 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" />' },
|
|
217
217
|
{ path: ruby_llm_agents.agents_path, label: "Agents", icon: '<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" />' },
|
|
218
|
+
{ path: ruby_llm_agents.workflows_path, label: "Workflows", icon: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zm0 8a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zm12 0a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z"/>' },
|
|
218
219
|
{ path: ruby_llm_agents.executions_path, label: "Executions", icon: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 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" />' },
|
|
219
|
-
{ path: ruby_llm_agents.tenants_path, label: "Tenants", icon: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />' }
|
|
220
|
-
{ path: ruby_llm_agents.system_config_path, label: "System Config", icon: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 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" /><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />' },
|
|
221
|
-
{ path: ruby_llm_agents.api_configuration_path, label: "API Keys", icon: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" />' }
|
|
220
|
+
{ path: ruby_llm_agents.tenants_path, label: "Tenants", icon: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />' }
|
|
222
221
|
]
|
|
223
222
|
%>
|
|
224
223
|
<nav class="hidden md:flex items-center space-x-1">
|
|
@@ -234,56 +233,55 @@
|
|
|
234
233
|
dark:text-gray-400
|
|
235
234
|
"
|
|
236
235
|
>
|
|
237
|
-
<%#
|
|
238
|
-
|
|
239
|
-
<
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
236
|
+
<%# Settings Dropdown %>
|
|
237
|
+
<div x-data="{ settingsOpen: false }" class="relative hidden md:block">
|
|
238
|
+
<button
|
|
239
|
+
@click="settingsOpen = !settingsOpen"
|
|
240
|
+
@click.outside="settingsOpen = false"
|
|
241
|
+
type="button"
|
|
242
|
+
class="p-2 rounded-md text-gray-500 hover:text-gray-700 hover:bg-gray-100 dark:text-gray-400 dark:hover:text-gray-200 dark:hover:bg-gray-700 transition-colors"
|
|
243
|
+
aria-label="Settings"
|
|
244
|
+
>
|
|
245
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
246
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 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"/>
|
|
247
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|
248
|
+
</svg>
|
|
249
|
+
</button>
|
|
250
|
+
|
|
251
|
+
<div
|
|
252
|
+
x-show="settingsOpen"
|
|
253
|
+
x-cloak
|
|
254
|
+
x-transition:enter="transition ease-out duration-100"
|
|
255
|
+
x-transition:enter-start="opacity-0 scale-95"
|
|
256
|
+
x-transition:enter-end="opacity-100 scale-100"
|
|
257
|
+
x-transition:leave="transition ease-in duration-75"
|
|
258
|
+
x-transition:leave-start="opacity-100 scale-100"
|
|
259
|
+
x-transition:leave-end="opacity-0 scale-95"
|
|
260
|
+
class="absolute right-0 mt-2 w-48 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-50 py-1"
|
|
261
|
+
>
|
|
262
|
+
<a href="<%= ruby_llm_agents.system_config_path %>"
|
|
263
|
+
class="flex items-center gap-2 px-4 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 <%= 'bg-gray-100 dark:bg-gray-700' if request.path == ruby_llm_agents.system_config_path %>">
|
|
264
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
265
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4"/>
|
|
251
266
|
</svg>
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
267
|
+
System Config
|
|
268
|
+
</a>
|
|
269
|
+
<a href="<%= ruby_llm_agents.api_configuration_path %>"
|
|
270
|
+
class="flex items-center gap-2 px-4 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 <%= 'bg-gray-100 dark:bg-gray-700' if request.path == ruby_llm_agents.api_configuration_path %>">
|
|
271
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
272
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"/>
|
|
255
273
|
</svg>
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
<div
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
x-transition:leave-start="opacity-100 scale-100"
|
|
266
|
-
x-transition:leave-end="opacity-0 scale-95"
|
|
267
|
-
class="absolute right-0 mt-1 w-48 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-50 py-1"
|
|
268
|
-
>
|
|
269
|
-
<a
|
|
270
|
-
href="<%= all_tenants_url %>"
|
|
271
|
-
class="block px-3 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-700 <%= 'bg-blue-50 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300' if current_tenant_id.blank? %>"
|
|
272
|
-
>
|
|
273
|
-
All Tenants
|
|
274
|
-
</a>
|
|
275
|
-
<div class="border-t border-gray-100 dark:border-gray-700 my-1"></div>
|
|
276
|
-
<% available_tenants.each do |tenant| %>
|
|
277
|
-
<a
|
|
278
|
-
href="<%= url_for(request.query_parameters.merge(tenant_id: tenant)) %>"
|
|
279
|
-
class="block px-3 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-700 <%= 'bg-blue-50 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300' if tenant == current_tenant_id %>"
|
|
280
|
-
>
|
|
281
|
-
<%= tenant %>
|
|
282
|
-
</a>
|
|
283
|
-
<% end %>
|
|
284
|
-
</div>
|
|
274
|
+
API Keys
|
|
275
|
+
</a>
|
|
276
|
+
<div class="border-t border-gray-100 dark:border-gray-700 my-1"></div>
|
|
277
|
+
<a href="#" class="flex items-center gap-2 px-4 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700">
|
|
278
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
279
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/>
|
|
280
|
+
</svg>
|
|
281
|
+
Logout
|
|
282
|
+
</a>
|
|
285
283
|
</div>
|
|
286
|
-
|
|
284
|
+
</div>
|
|
287
285
|
|
|
288
286
|
<!-- Mobile menu button -->
|
|
289
287
|
<button
|
|
@@ -340,35 +338,33 @@
|
|
|
340
338
|
<% nav_items.each do |item| %>
|
|
341
339
|
<%= render "ruby_llm/agents/shared/nav_link", path: item[:path], label: item[:label], icon: item[:icon], mobile: true %>
|
|
342
340
|
<% end %>
|
|
343
|
-
</nav>
|
|
344
|
-
</div>
|
|
345
|
-
</header>
|
|
346
341
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-2">
|
|
351
|
-
<div class="flex items-center justify-between">
|
|
352
|
-
<div class="flex items-center gap-2 text-sm text-blue-700 dark:text-blue-300">
|
|
353
|
-
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
354
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
|
|
355
|
-
</svg>
|
|
356
|
-
<span>Viewing tenant:</span>
|
|
357
|
-
<span class="font-semibold"><%= current_tenant_id %></span>
|
|
358
|
-
</div>
|
|
359
|
-
<a
|
|
360
|
-
href="<%= url_for(request.query_parameters.except('tenant_id')) %>"
|
|
361
|
-
class="flex items-center gap-1 text-sm text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-200"
|
|
362
|
-
>
|
|
363
|
-
<span>Clear filter</span>
|
|
364
|
-
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
365
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
366
|
-
</svg>
|
|
367
|
-
</a>
|
|
342
|
+
<%# Settings section divider %>
|
|
343
|
+
<div class="border-t border-gray-200 dark:border-gray-700 my-2 pt-2">
|
|
344
|
+
<span class="px-3 text-xs font-semibold text-gray-400 dark:text-gray-500 uppercase tracking-wider">Settings</span>
|
|
368
345
|
</div>
|
|
369
|
-
|
|
346
|
+
|
|
347
|
+
<%= render "ruby_llm/agents/shared/nav_link",
|
|
348
|
+
path: ruby_llm_agents.system_config_path,
|
|
349
|
+
label: "System Config",
|
|
350
|
+
icon: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4"/>',
|
|
351
|
+
mobile: true %>
|
|
352
|
+
|
|
353
|
+
<%= render "ruby_llm/agents/shared/nav_link",
|
|
354
|
+
path: ruby_llm_agents.api_configuration_path,
|
|
355
|
+
label: "API Keys",
|
|
356
|
+
icon: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"/>',
|
|
357
|
+
mobile: true %>
|
|
358
|
+
|
|
359
|
+
<a href="#" class="flex items-center px-3 py-2 text-sm font-medium 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">
|
|
360
|
+
<svg class="w-5 h-5 mr-3 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
361
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/>
|
|
362
|
+
</svg>
|
|
363
|
+
Logout
|
|
364
|
+
</a>
|
|
365
|
+
</nav>
|
|
370
366
|
</div>
|
|
371
|
-
|
|
367
|
+
</header>
|
|
372
368
|
|
|
373
369
|
<!-- Main content -->
|
|
374
370
|
<main class="max-w-7xl w-full mx-auto px-4 sm:px-6 lg:px-8 py-8 h-full">
|
|
@@ -16,18 +16,6 @@
|
|
|
16
16
|
<% else %>
|
|
17
17
|
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400">Deleted</span>
|
|
18
18
|
<% end %>
|
|
19
|
-
<% if agent[:agent_type] && agent[:agent_type] != "agent" %>
|
|
20
|
-
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium
|
|
21
|
-
<%= case agent[:agent_type]
|
|
22
|
-
when 'embedder' then 'bg-purple-100 dark:bg-purple-900/50 text-purple-800 dark:text-purple-300'
|
|
23
|
-
when 'moderator' then 'bg-orange-100 dark:bg-orange-900/50 text-orange-800 dark:text-orange-300'
|
|
24
|
-
when 'speaker' then 'bg-green-100 dark:bg-green-900/50 text-green-800 dark:text-green-300'
|
|
25
|
-
when 'transcriber' then 'bg-pink-100 dark:bg-pink-900/50 text-pink-800 dark:text-pink-300'
|
|
26
|
-
else ''
|
|
27
|
-
end %>">
|
|
28
|
-
<%= agent[:agent_type].capitalize %>
|
|
29
|
-
</span>
|
|
30
|
-
<% end %>
|
|
31
19
|
</div>
|
|
32
20
|
<!-- Desktop: model -->
|
|
33
21
|
<span class="hidden sm:block text-sm text-gray-500 dark:text-gray-400"><%= agent[:model] %></span>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<%#
|
|
2
|
+
Sortable column header component for agents table
|
|
3
|
+
|
|
4
|
+
Creates a clickable table header that toggles sort direction and
|
|
5
|
+
displays visual feedback for the current sort state.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
render "ruby_llm/agents/agents/sortable_header",
|
|
9
|
+
column: "name",
|
|
10
|
+
label: "Name",
|
|
11
|
+
current_sort: @sort_params[:column],
|
|
12
|
+
current_direction: @sort_params[:direction],
|
|
13
|
+
align: "right",
|
|
14
|
+
th_class: "hidden lg:table-cell"
|
|
15
|
+
|
|
16
|
+
Parameters:
|
|
17
|
+
column - (String) The column name used in sort URL parameter
|
|
18
|
+
label - (String) Display text for the header
|
|
19
|
+
current_sort - (String) Currently active sort column
|
|
20
|
+
current_direction - (String) Current sort direction ('asc' or 'desc')
|
|
21
|
+
align - (String) Text alignment: 'left' (default), 'center', or 'right'
|
|
22
|
+
th_class - (String) Additional CSS classes for the th element (e.g., responsive hiding)
|
|
23
|
+
%>
|
|
24
|
+
<%
|
|
25
|
+
align = local_assigns[:align] || "left"
|
|
26
|
+
th_class = local_assigns[:th_class] || ""
|
|
27
|
+
is_active = column == current_sort
|
|
28
|
+
next_direction = is_active && current_direction == "asc" ? "desc" : "asc"
|
|
29
|
+
sort_url = url_for(request.query_parameters.merge(sort: column, direction: next_direction))
|
|
30
|
+
|
|
31
|
+
align_class = case align
|
|
32
|
+
when "right" then "text-right justify-end"
|
|
33
|
+
when "center" then "text-center justify-center"
|
|
34
|
+
else "text-left justify-start"
|
|
35
|
+
end
|
|
36
|
+
%>
|
|
37
|
+
<th scope="col" class="px-4 py-3 <%= th_class %>">
|
|
38
|
+
<a href="<%= sort_url %>"
|
|
39
|
+
class="group inline-flex items-center gap-1 <%= align_class %> w-full
|
|
40
|
+
text-xs font-semibold uppercase tracking-wider
|
|
41
|
+
<%= is_active ? 'text-blue-600 dark:text-blue-400' : 'text-gray-500 dark:text-gray-400' %>
|
|
42
|
+
hover:text-blue-600 dark:hover:text-blue-400 transition-colors">
|
|
43
|
+
<span><%= label %></span>
|
|
44
|
+
<span class="<%= is_active ? 'opacity-100' : 'opacity-0 group-hover:opacity-50' %> transition-opacity">
|
|
45
|
+
<% if is_active && current_direction == "asc" %>
|
|
46
|
+
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
47
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15l7-7 7 7"/>
|
|
48
|
+
</svg>
|
|
49
|
+
<% else %>
|
|
50
|
+
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
51
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
|
52
|
+
</svg>
|
|
53
|
+
<% end %>
|
|
54
|
+
</span>
|
|
55
|
+
</a>
|
|
56
|
+
</th>
|
|
@@ -1,27 +1,17 @@
|
|
|
1
1
|
<%
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
"router" => { border: "border-l-amber-500", icon_bg: "bg-amber-100 dark:bg-amber-900/50", icon_text: "text-amber-600 dark:text-amber-300" }
|
|
6
|
-
}
|
|
7
|
-
colors = workflow_colors[workflow[:workflow_type]] || { border: "border-l-gray-400", icon_bg: "bg-gray-100", icon_text: "text-gray-600" }
|
|
8
|
-
|
|
9
|
-
child_label = case workflow[:workflow_type]
|
|
10
|
-
when "pipeline" then "steps"
|
|
11
|
-
when "parallel" then "branches"
|
|
12
|
-
when "router" then "routes"
|
|
13
|
-
else "children"
|
|
14
|
-
end
|
|
2
|
+
# All workflows use unified emerald color scheme
|
|
3
|
+
colors = { border: "border-l-emerald-500", icon_bg: "bg-emerald-100 dark:bg-emerald-900/50", icon_text: "text-emerald-600 dark:text-emerald-300" }
|
|
4
|
+
child_label = "steps"
|
|
15
5
|
%>
|
|
16
6
|
|
|
17
7
|
<div x-data="{ expanded: false }" class="block bg-white dark:bg-gray-800 rounded-lg shadow hover:shadow-md transition-shadow border-l-4 <%= colors[:border] %>">
|
|
18
|
-
<%= link_to ruby_llm_agents.workflow_path(ERB::Util.url_encode(workflow[:name]), tab: local_assigns[:current_tab]
|
|
8
|
+
<%= link_to ruby_llm_agents.workflow_path(ERB::Util.url_encode(workflow[:name]), tab: local_assigns[:current_tab]), class: "block p-4 sm:p-5 relative z-10", data: { turbo: false }, style: "pointer-events: auto;" do %>
|
|
19
9
|
<!-- Header Row -->
|
|
20
10
|
<div class="flex items-center justify-between gap-2">
|
|
21
11
|
<div class="flex items-center gap-2 min-w-0">
|
|
22
12
|
<%= render "ruby_llm/agents/shared/workflow_type_badge", workflow_type: workflow[:workflow_type], size: :sm %>
|
|
23
13
|
<h3 class="font-semibold text-gray-900 dark:text-gray-100 truncate">
|
|
24
|
-
<% name_parts = workflow[:name].
|
|
14
|
+
<% name_parts = workflow[:name].split('::') %>
|
|
25
15
|
<% if name_parts.length > 1 %>
|
|
26
16
|
<span class="text-gray-400 dark:text-gray-500 font-normal"><%= name_parts[0..-2].join('::') %>::</span>
|
|
27
17
|
<% end %>
|