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- TYPO_CORRECTION_DEVICES =
%w[voice soft_keyboard physical_keyboard].freeze
- TYPO_CORRECTION_LOCATIONS =
%w[chat editor].freeze
- LLM_VENDOR_OPTIONS =
[ [ "Google (Gemini)", "google" ], [ "OpenAI", "openai" ], [ "Anthropic", "anthropic" ], [ "OpenClaw", "openclaw" ] ].freeze
- SUPPORTED_LLM_MODELS =
[ "gemini-3.1-flash-lite", "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
- #claude_channel_agent? ⇒ Boolean
-
#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
-
#preserve_durable_summary_comments ⇒ Object
Keep durable compress/merge summaries (snapshot result comments) alive when their author is deleted: nullify authorship instead of cascading destroy.
- #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
-
#typo_correction_active_for?(device:, location:) ⇒ Boolean
2D gating: typo correction runs only when the master switch is on AND the originating typing device AND the input location are both enabled.
- #unlock_account! ⇒ Object
Methods included from HasInboxCreative
Class Method Details
.accessible_ai_agents_for(user) ⇒ Object
209 210 211 212 213 |
# File 'app/models/collavre/user.rb', line 209 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
215 216 217 218 219 220 221 222 223 |
# File 'app/models/collavre/user.rb', line 215 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
199 200 201 |
# File 'app/models/collavre/user.rb', line 199 def ai_user? llm_vendor.present? end |
#chat_history_limit ⇒ Object
Convenience accessors for context settings
118 119 120 |
# File 'app/models/collavre/user.rb', line 118 def chat_history_limit parsed_agent_conf.dig("context", "chat_history") || 50 end |
#chat_history_size_limit ⇒ Object
122 123 124 |
# File 'app/models/collavre/user.rb', line 122 def chat_history_size_limit parsed_agent_conf.dig("context", "chat_history_size") || 100_000 end |
#claude_channel_agent? ⇒ Boolean
203 204 205 |
# File 'app/models/collavre/user.rb', line 203 def claude_channel_agent? llm_model == "claude-code" 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.
129 130 131 132 |
# File 'app/models/collavre/user.rb', line 129 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
315 316 317 318 319 320 |
# File 'app/models/collavre/user.rb', line 315 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
254 255 256 |
# File 'app/models/collavre/user.rb', line 254 def display_name name.presence || email end |
#email_verified? ⇒ Boolean
250 251 252 |
# File 'app/models/collavre/user.rb', line 250 def email_verified? email_verified_at.present? end |
#lock_account! ⇒ Object
263 264 265 |
# File 'app/models/collavre/user.rb', line 263 def lock_account! update_columns(locked_at: Time.current) end |
#locked? ⇒ Boolean
Account lockout methods
259 260 261 |
# File 'app/models/collavre/user.rb', line 259 def locked? locked_at.present? && locked_at > Collavre::SystemSetting.lockout_duration.ago end |
#parsed_agent_conf ⇒ Object
Returns parsed agent_conf merged with defaults
106 107 108 109 110 111 112 113 114 115 |
# File 'app/models/collavre/user.rb', line 106 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
289 290 291 292 293 294 295 296 |
# File 'app/models/collavre/user.rb', line 289 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 |
#preserve_durable_summary_comments ⇒ Object
Keep durable compress/merge summaries (snapshot result comments) alive when their author is deleted: nullify authorship instead of cascading destroy.
308 309 310 311 312 |
# File 'app/models/collavre/user.rb', line 308 def preserve_durable_summary_comments Collavre::Comment .where(id: Collavre::CommentSnapshot.where(result_comment_id: comments.select(:id)).select(:result_comment_id)) .update_all(user_id: nil) end |
#record_failed_login! ⇒ Object
271 272 273 274 275 276 277 278 |
# File 'app/models/collavre/user.rb', line 271 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
284 285 286 287 |
# File 'app/models/collavre/user.rb', line 284 def remaining_lockout_time return 0 unless locked? ((locked_at + Collavre::SystemSetting.lockout_duration) - Time.current).to_i end |
#reset_failed_login_attempts! ⇒ Object
280 281 282 |
# File 'app/models/collavre/user.rb', line 280 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).
136 137 138 139 140 141 |
# File 'app/models/collavre/user.rb', line 136 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
298 299 300 301 302 303 304 |
# File 'app/models/collavre/user.rb', line 298 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 |
#typo_correction_active_for?(device:, location:) ⇒ Boolean
2D gating: typo correction runs only when the master switch is on AND the originating typing device AND the input location are both enabled. Unknown device/location values are treated as disabled (fail closed).
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'app/models/collavre/user.rb', line 167 def typo_correction_active_for?(device:, location:) return false unless typo_correction_enabled device_on = case device.to_s when "voice" then typo_correction_on_voice when "soft_keyboard" then typo_correction_on_soft_keyboard when "physical_keyboard" then typo_correction_on_physical_keyboard else false end location_on = case location.to_s when "chat" then typo_correction_in_chat when "editor" then typo_correction_in_editor else false end device_on && location_on end |
#unlock_account! ⇒ Object
267 268 269 |
# File 'app/models/collavre/user.rb', line 267 def unlock_account! update_columns(locked_at: nil, failed_login_attempts: 0) end |