Class: Collavre::User
- Inherits:
-
ApplicationRecord
- Object
- ApplicationRecord
- ApplicationRecord
- Collavre::User
- Includes:
- HasInboxCreative
- Defined in:
- app/models/collavre/user.rb
Constant Summary collapse
- AGENT_CONF_DEFAULTS =
Default context settings for AI agents
{ "context" => { "chat_history" => 50, "chat_history_size" => 100_000, "creative_children_level" => nil }, "session" => { "enabled" => nil } }.freeze
- DEFAULT_CREATIVE_CHILDREN_LEVEL =
Default creative children level when agent_conf doesn’t specify one
6- LLM_VENDOR_OPTIONS =
[ [ "Google (Gemini)", "google" ], [ "OpenAI", "openai" ], [ "Anthropic", "anthropic" ], [ "OpenClaw", "openclaw" ] ].freeze
- SUPPORTED_LLM_MODELS =
[ "gemini-3-flash-preview", "gemini-1.5-flash", "gemini-1.5-pro" ].freeze
Class Method Summary collapse
Instance Method Summary collapse
- #ai_user? ⇒ Boolean
-
#chat_history_limit ⇒ Object
Convenience accessors for context settings.
- #chat_history_size_limit ⇒ Object
-
#creative_children_level ⇒ Object
Returns the creative children depth level for AI context.
-
#destroy_creatives_leaf_first ⇒ Object
Destroy creatives deepest-first so closure_tree always finds its parent.
- #display_name ⇒ Object
- #email_verified? ⇒ Boolean
- #lock_account! ⇒ Object
-
#locked? ⇒ Boolean
Account lockout methods.
-
#parsed_agent_conf ⇒ Object
Returns parsed agent_conf merged with defaults.
- #password_meets_minimum_length ⇒ Object
- #record_failed_login! ⇒ Object
- #remaining_lockout_time ⇒ Object
- #reset_failed_login_attempts! ⇒ Object
-
#supports_session? ⇒ Boolean
Whether this agent uses stateful sessions (incremental messaging).
- #theme_accessibility ⇒ Object
- #unlock_account! ⇒ Object
Methods included from HasInboxCreative
Class Method Details
.accessible_ai_agents_for(user) ⇒ Object
162 163 164 165 166 |
# File 'app/models/collavre/user.rb', line 162 def self.accessible_ai_agents_for(user) owned = ai_agents.where(created_by_id: user.id) searchable = ai_agents.where(searchable: true) owned.or(searchable).distinct.order(:name) end |
.mentionable_for(creative) ⇒ Object
168 169 170 171 172 173 174 175 176 |
# File 'app/models/collavre/user.rb', line 168 def self.mentionable_for(creative) scope = where(searchable: true) return scope unless creative origin = creative.effective_origin permitted_users = [ origin.user ].compact + origin.all_shared_users(:feedback).map(&:user) permitted_ids = permitted_users.compact.map(&:id) permitted_ids.any? ? scope.or(where(id: permitted_ids)) : scope end |
Instance Method Details
#ai_user? ⇒ Boolean
156 157 158 |
# File 'app/models/collavre/user.rb', line 156 def ai_user? llm_vendor.present? end |
#chat_history_limit ⇒ Object
Convenience accessors for context settings
100 101 102 |
# File 'app/models/collavre/user.rb', line 100 def chat_history_limit parsed_agent_conf.dig("context", "chat_history") || 50 end |
#chat_history_size_limit ⇒ Object
104 105 106 |
# File 'app/models/collavre/user.rb', line 104 def chat_history_size_limit parsed_agent_conf.dig("context", "chat_history_size") || 100_000 end |
#creative_children_level ⇒ Object
Returns the creative children depth level for AI context. nil in agent_conf means use the default (6). 0 = no children, 1 = direct children only, 2 = grandchildren, etc.
111 112 113 114 |
# File 'app/models/collavre/user.rb', line 111 def creative_children_level level = parsed_agent_conf.dig("context", "creative_children_level") level.nil? ? DEFAULT_CREATIVE_CHILDREN_LEVEL : level.to_i end |
#destroy_creatives_leaf_first ⇒ Object
Destroy creatives deepest-first so closure_tree always finds its parent
255 256 257 258 259 260 |
# File 'app/models/collavre/user.rb', line 255 def destroy_creatives_leaf_first all_creatives = creatives.flat_map { |c| c.self_and_descendants.to_a }.uniq all_creatives.sort_by { |c| -c.self_and_ancestors.count }.each do |c| c.reload.destroy! if Creative.exists?(c.id) end end |
#display_name ⇒ Object
202 203 204 |
# File 'app/models/collavre/user.rb', line 202 def display_name name.presence || email end |
#email_verified? ⇒ Boolean
198 199 200 |
# File 'app/models/collavre/user.rb', line 198 def email_verified? email_verified_at.present? end |
#lock_account! ⇒ Object
211 212 213 |
# File 'app/models/collavre/user.rb', line 211 def lock_account! update_columns(locked_at: Time.current) end |
#locked? ⇒ Boolean
Account lockout methods
207 208 209 |
# File 'app/models/collavre/user.rb', line 207 def locked? locked_at.present? && locked_at > Collavre::SystemSetting.lockout_duration.ago end |
#parsed_agent_conf ⇒ Object
Returns parsed agent_conf merged with defaults
88 89 90 91 92 93 94 95 96 97 |
# File 'app/models/collavre/user.rb', line 88 def parsed_agent_conf defaults = AGENT_CONF_DEFAULTS.deep_dup return defaults if agent_conf.blank? user_conf = YAML.safe_load(agent_conf, permitted_classes: [ Symbol ]) || {} defaults.deep_merge(user_conf) rescue Psych::SyntaxError => e Rails.logger.warn("[User#parsed_agent_conf] Invalid YAML for user #{id}: #{e.}") AGENT_CONF_DEFAULTS.deep_dup end |
#password_meets_minimum_length ⇒ Object
237 238 239 240 241 242 243 244 |
# File 'app/models/collavre/user.rb', line 237 def password_meets_minimum_length return if password.blank? min_length = Collavre::SystemSetting.password_min_length if password.length < min_length errors.add(:password, :too_short, count: min_length) end end |
#record_failed_login! ⇒ Object
219 220 221 222 223 224 225 226 |
# File 'app/models/collavre/user.rb', line 219 def record_failed_login! new_count = (failed_login_attempts || 0) + 1 if new_count >= Collavre::SystemSetting.max_login_attempts update_columns(failed_login_attempts: new_count, locked_at: Time.current) else update_column(:failed_login_attempts, new_count) end end |
#remaining_lockout_time ⇒ Object
232 233 234 235 |
# File 'app/models/collavre/user.rb', line 232 def remaining_lockout_time return 0 unless locked? ((locked_at + Collavre::SystemSetting.lockout_duration) - Time.current).to_i end |
#reset_failed_login_attempts! ⇒ Object
228 229 230 |
# File 'app/models/collavre/user.rb', line 228 def reset_failed_login_attempts! update_column(:failed_login_attempts, 0) if failed_login_attempts.to_i > 0 end |
#supports_session? ⇒ Boolean
Whether this agent uses stateful sessions (incremental messaging). nil in agent_conf = auto-detect by vendor (openclaw → true).
118 119 120 121 122 123 |
# File 'app/models/collavre/user.rb', line 118 def supports_session? explicit = parsed_agent_conf.dig("session", "enabled") return ActiveModel::Type::Boolean.new.cast(explicit) unless explicit.nil? llm_vendor&.downcase == "openclaw" end |
#theme_accessibility ⇒ Object
246 247 248 249 250 251 252 |
# File 'app/models/collavre/user.rb', line 246 def theme_accessibility return if theme.blank? || %w[light dark].include?(theme) unless user_themes.exists?(id: theme) errors.add(:theme, "is invalid") end end |
#unlock_account! ⇒ Object
215 216 217 |
# File 'app/models/collavre/user.rb', line 215 def unlock_account! update_columns(locked_at: nil, failed_login_attempts: 0) end |