class Chat < ApplicationRecord include Debuggable belongs_to :user has_one :viewer, class_name: "User", foreign_key: :last_viewed_chat_id, dependent: :nullify # "Last chat user has viewed" has_many :messages, dependent: :destroy validates :title, presence: true scope :ordered, -> { order(created_at: :desc) } class << self def start!(prompt, model:) # Ensure we have a valid model by using the default if none provided effective_model = model.presence || default_model create!( title: generate_title(prompt), messages: [ UserMessage.new(content: prompt, ai_model: effective_model) ] ) end def generate_title(prompt) prompt.first(80) end # Returns the default AI model to use for chats # Priority: ENV variable > Setting > OpenAI default def default_model ENV["OPENAI_MODEL"].presence || Setting.openai_model.presence || Provider::Openai::DEFAULT_MODEL end end def needs_assistant_response? conversation_messages.ordered.last.role != "assistant" end def retry_last_message! update!(error: nil) last_message = conversation_messages.ordered.last if last_message.present? && last_message.role == "user" ask_assistant_later(last_message) end end def update_latest_response!(provider_response_id) update!(latest_assistant_response_id: provider_response_id) end def add_error(e) update! error: e.to_json broadcast_append target: "messages", partial: "chats/error", locals: { chat: self } end def clear_error update! error: nil broadcast_remove target: "chat-error" end def assistant @assistant ||= Assistant.for_chat(self) end def ask_assistant_later(message) clear_error AssistantResponseJob.perform_later(message) end def ask_assistant(message) assistant.respond_to(message) end def conversation_messages if debug_mode? messages else messages.where(type: [ "UserMessage", "AssistantMessage" ]) end end end