Class: Collavre::GeminiParentRecommender

Inherits:
Object
  • Object
show all
Defined in:
app/services/collavre/gemini_parent_recommender.rb

Instance Method Summary collapse

Constructor Details

#initialize(client: default_client) ⇒ GeminiParentRecommender

Returns a new instance of GeminiParentRecommender.



3
4
5
# File 'app/services/collavre/gemini_parent_recommender.rb', line 3

def initialize(client: default_client)
  @client = client
end

Instance Method Details

#recommend(creative) ⇒ Object



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'app/services/collavre/gemini_parent_recommender.rb', line 7

def recommend(creative)
  user = creative.user

  # 1. Gather all potential category candidates
  # Candidates: All creatives user can write to + creative's current parent (even if not writable?)
  # Original logic: Distinct creatives having children + parent.
  # Note: Using `joins(:children)` filters only those that have children.

  categories = Creative
                 .joins(:children)
                 .distinct
                 .where(origin_id: nil)
                 .select { |c| c.has_permission?(user, :write) }

  parent = creative.parent
  if parent&.has_permission?(user, :write)
    categories << parent
  end
  categories = categories.uniq

  # Rebuild tree in memory to avoid N+1 in TreeFormatter
  children_map = categories.group_by(&:parent_id)
  categories.each do |c|
    # Manually set the children association target
    c.association(:children).target = (children_map[c.id] || []).select { |child| child.has_permission?(user, :write) }
  end
  category_ids = categories.index_by(&:id)
  top_level_categories = categories.reject { |c| category_ids.key?(c.parent_id) }

  tree_text = Creatives::TreeFormatter.new(use_permissions: false).format(top_level_categories)

  prompt = build_prompt(tree_text, Creatives::TreeFormatter.plain_description(creative))
  Rails.logger.info("### prompt=#{prompt}")

  response = @client.chat([ { role: :user, parts: [ { text: prompt } ] } ])
  ids = parse_response(response)

  # Reconstruct paths for result
  ids.map do |id|
     c = Creative.find_by(id: id)
     next unless c
     path = c.ancestors.reverse.map { |a| Creatives::TreeFormatter.plain_description(a) } + [ Creatives::TreeFormatter.plain_description(c) ]
     { id: id, path: path.join(" > ") }
  end.compact
end