Class: Collavre::User

Inherits:
ApplicationRecord show all
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

Methods included from HasInboxCreative

#inbox_creative

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

Returns:

  • (Boolean)


156
157
158
# File 'app/models/collavre/user.rb', line 156

def ai_user?
  llm_vendor.present?
end

#chat_history_limitObject

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_limitObject



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_levelObject

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_firstObject

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_nameObject



202
203
204
# File 'app/models/collavre/user.rb', line 202

def display_name
  name.presence || email
end

#email_verified?Boolean

Returns:

  • (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

Returns:

  • (Boolean)


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_confObject

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.message}")
  AGENT_CONF_DEFAULTS.deep_dup
end

#password_meets_minimum_lengthObject



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 = ( || 0) + 1
  if new_count >= Collavre::SystemSetting.
    update_columns(failed_login_attempts: new_count, locked_at: Time.current)
  else
    update_column(:failed_login_attempts, new_count)
  end
end

#remaining_lockout_timeObject



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 
  update_column(:failed_login_attempts, 0) if .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).

Returns:

  • (Boolean)


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_accessibilityObject



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