| 1 | # frozen_string_literal: true |
| 2 | |
| 3 | # A siGit Code Cloud Session: a named, resumable conversation persisted so it |
| 4 | # follows the user across devices. Owns its messages in order; `model` is the |
| 5 | # neutral cloud tier wire id (onde-fast/balanced/large), never an upstream name. |
| 6 | class CloudSession < ApplicationRecord |
| 7 | belongs_to :user |
| 8 | has_many :cloud_messages, -> { order(:created_at, :id) }, dependent: :destroy |
| 9 | |
| 10 | validates :title, presence: true, length: { maximum: 200 } |
| 11 | validates :model, presence: true |
| 12 | |
| 13 | before_validation :default_title |
| 14 | |
| 15 | # Append a message and bump the session's activity timestamp so the list stays |
| 16 | # ordered by most-recent. Auto-titles the session from the first user message |
| 17 | # while it still has the default title. |
| 18 | def append_message!(role:, content:) |
| 19 | message = nil |
| 20 | transaction do |
| 21 | message = cloud_messages.create!(role: role, content: content.to_s) |
| 22 | retitle_from(content) if role == "user" && default_title? |
| 23 | update!(last_message_at: message.created_at) |
| 24 | end |
| 25 | message |
| 26 | end |
| 27 | |
| 28 | private |
| 29 | |
| 30 | def default_title |
| 31 | self.title = "New session" if title.blank? |
| 32 | end |
| 33 | |
| 34 | def default_title? |
| 35 | title.blank? || title == "New session" |
| 36 | end |
| 37 | |
| 38 | def retitle_from(content) |
| 39 | snippet = content.to_s.strip.split("\n").first.to_s.strip |
| 40 | return if snippet.blank? |
| 41 | |
| 42 | self.title = snippet.length > 80 ? "#{snippet[0, 80].rstrip}…" : snippet |
| 43 | end |
| 44 | end |