From 8d323bb0aa0393d47827b92b362b292dfd79cd43 Mon Sep 17 00:00:00 2001 From: "Stanko K.R." Date: Tue, 12 Aug 2025 15:40:53 +0200 Subject: [PATCH] Extract card filtering --- app/models/ai/tool.rb | 21 +++++ app/models/ai/tool/list_cards.rb | 101 +++++++++--------------- app/models/ai/tool/list_cards/filter.rb | 57 +++++++++++++ app/models/ai/tool/list_comments.rb | 2 +- 4 files changed, 116 insertions(+), 65 deletions(-) create mode 100644 app/models/ai/tool/list_cards/filter.rb diff --git a/app/models/ai/tool.rb b/app/models/ai/tool.rb index f2dc9de6e..dedaf34b1 100644 --- a/app/models/ai/tool.rb +++ b/app/models/ai/tool.rb @@ -2,6 +2,27 @@ class Ai::Tool < RubyLLM::Tool include Rails.application.routes.url_helpers private + def paginated_response(records, page:, ordered_by:, per_page: nil, &block) + page = GearedPagination::Recordset.new(records, ordered_by: ordered_by, per_page: per_page).page(page) + + response = [ "There are #{page.recordset.records_count} records in total." ] + + if page.only? || page.last? + response << "This is the last page of results." + else + response << "This is one page of results." + response << "To see more, use this cursor for the next page:" + response << page.next_param + end + + response << "Records:" + response << "```" + response << page.records.map(&block).to_json + response << "```" + + response.join("\n") + end + def default_url_options Rails.application.default_url_options .reverse_merge(host: "fizzy.localhost", port: 3006) diff --git a/app/models/ai/tool/list_cards.rb b/app/models/ai/tool/list_cards.rb index c5944b5ca..2abbdc3f4 100644 --- a/app/models/ai/tool/list_cards.rb +++ b/app/models/ai/tool/list_cards.rb @@ -32,39 +32,39 @@ class Ai::Tool::ListCards < Ai::Tool - name [String, not null] MD - param :ids, + param :page, type: :string, - desc: "If provided, will return only the cards with the given IDs (comma-separated)", + desc: "Which page to return. Leave balnk to get the first page", required: false param :query, type: :string, desc: "If provided, will perform a semantinc search by embeddings and return only matching cards", required: false - param :page, + param :ids, type: :string, - desc: "Which page to return. Leave balnk to get the first page", + desc: "If provided, will return only cards with the given IDs (comma-separated)", required: false - param :collection_id, + param :collection_ids, type: :integer, - desc: "If provided, will return only cards for the specified collection", + desc: "If provided, will return only cards for the specified collections (comma-separated)", required: false param :golden, type: :boolean, desc: "If provided, will return only golden cards", required: false - param :created_at_gte, + param :created_after, type: :string, - desc: "If provided, will return only card created on or after after the given ISO timestamp", + desc: "If provided, will return only cards created on or after the given ISO timestamp", required: false - param :created_at_lte, + param :created_before, type: :string, - desc: "If provided, will return only card created on or before the given ISO timestamp", + desc: "If provided, will return only cards created on or before the given ISO timestamp", required: false - param :last_active_at_gte, + param :last_active_after, type: :string, - desc: "If provided, will return only card that were last active on or after after the given ISO timestamp", + desc: "If provided, will return only card that were last active on or after the given ISO timestamp", required: false - param :last_active_at_lte, + param :last_active_before, type: :string, desc: "If provided, will return only card that were last active on or before the given ISO timestamp", required: false @@ -76,60 +76,33 @@ class Ai::Tool::ListCards < Ai::Tool end def execute(**params) - cards = Card.where(collection: user.collections).includes(:stage, :creator, :assignees, :goldness) + cards = Card + .where(collection: user.collections) + .with_rich_text_description + .includes(:stage, :creator, :assignees, :goldness, :collection) + cards = Filter.new(scope: cards, filters: params).filter cards = cards.search(params[:query]) if params[:query].present? - cards = cards.golden if params[:golden].present? - cards = cards.where(collection_id: params[:collection_id]) if params[:collection_id].present? - cards = cards.where(id: params[:ids]&.split(",")&.map(&:to_i)) if params[:ids].present? - if params[:last_active_at_gte].present? - timestamp = DateTime.parse(params[:last_active_at_gte]) - cards = cards.where(last_active_at: timestamp..) + paginated_response(cards, page: params[:page], ordered_by: { created_at: :asc, id: :desc }) do |card| + card_attributes(card) end - - if params[:last_active_at_lte].present? - timestamp = DateTime.parse(params[:last_active_at_lte]) - cards = cards.where(last_active_at: ..timestamp) - end - - if params[:created_at_gte].present? - timestamp = DateTime.parse(params[:created_at_gte]) - cards = cards.where(created_at: timestamp..) - end - - if params[:created_at_lte].present? - timestamp = DateTime.parse(params[:created_at_lte]) - cards = cards.where(created_at: ..timestamp) - end - - page = GearedPagination::Recordset.new(cards, ordered_by: { id: :desc }).page(params[:page]) - - puts "="*80 - puts "Account: #{Account.sole.id}" - puts "Tenant: #{ApplicationRecord.current_tenant}" - puts "URL options: #{default_url_options.inspect}" - puts "="*80 - - { - cards: page.records.map do |card| - { - id: card.id, - title: card.title, - status: card.status, - last_active_at: card.last_active_at, - collection_id: card.collection_id, - golden: card.golden?, - stage: card.stage.as_json(only: [ :id, :name ]), - creator: card.creator.as_json(only: [ :id, :name ]), - assignees: card.assignees.as_json(only: [ :id, :name ]), - description: card.description.to_plain_text.truncate(1000), - url: collection_card_url(card.collection, card) - } - end, - pagination: { - next_page: page.next_param - } - }.to_json end + + private + def card_attributes(card) + { + id: card.id, + title: card.title, + status: card.status, + last_active_at: card.last_active_at, + collection_id: card.collection_id, + golden: card.golden?, + stage: card.stage.as_json(only: [ :id, :name ]), + creator: card.creator.as_json(only: [ :id, :name ]), + assignees: card.assignees.as_json(only: [ :id, :name ]), + description: card.description.to_plain_text.truncate(1000), + url: collection_card_url(card.collection, card) + } + end end diff --git a/app/models/ai/tool/list_cards/filter.rb b/app/models/ai/tool/list_cards/filter.rb new file mode 100644 index 000000000..ba76569f8 --- /dev/null +++ b/app/models/ai/tool/list_cards/filter.rb @@ -0,0 +1,57 @@ +class Ai::Tool::ListCards::Filter + FILTERS = { + ids: :apply_ids_filter, + collection_ids: :apply_collection_ids_filter, + golden: :apply_golden_filter, + created_before: :apply_created_before_filter, + created_after: :apply_created_after_filter, + last_active_before: :apply_last_active_before_filter, + last_active_after: :apply_last_active_after_filter + }.freeze + + attr_reader :scope, :filters + + def initialize(scope:, filters:) + @scope = scope + @filters = filters + end + + def filter + FILTERS.reduce(scope) do |filtered_scope, (filter_name, method_name)| + if filters[filter_name].present? + send(method_name, filtered_scope) + else + filtered_scope + end + end + end + + private + def apply_ids_filter(scope) + scope.where(id: filters[:ids]) + end + + def apply_collection_ids_filter(scope) + scope.where(collection_id: filters[:collection_ids]) + end + + def apply_golden_filter(scope) + scope.golden + end + + def apply_created_before_filter(scope) + scope.where(created_at: ...filters[:created_before]) + end + + def apply_created_after_filter(scope) + scope.where(created_at: filters[:created_after]...) + end + + def apply_last_active_before_filter(scope) + scope.where(last_active_at: ...filters[:last_active_before]) + end + + def apply_last_active_after_filter(scope) + scope.where(last_active_at: filters[:last_active_after]...) + end +end diff --git a/app/models/ai/tool/list_comments.rb b/app/models/ai/tool/list_comments.rb index 47a7696c0..0a04c6bdf 100644 --- a/app/models/ai/tool/list_comments.rb +++ b/app/models/ai/tool/list_comments.rb @@ -68,7 +68,7 @@ class Ai::Tool::ListComments < Ai::Tool def execute(**params) cards = Card.where(collection: user.collections) - scope = Comment.where(card: cards).includes(:card, :creator, reactions: [ :reacter ]) + scope = Comment.where(card: cards).with_rich_text_description.includes(:card, :creator, reactions: [ :reacter ]) scope = scope.search(params[:query]) if params[:query].present? scope = scope.where(card_id: params[:card_id].to_i) if params[:card_id].present?