Show related packages based on description embeddings

Package pages get a "Related packages" section: a nearest neighbor
query against the package's own stored embedding (no external service
call), excluding the package itself. Packages without an embedding
render no section.
This commit is contained in:
Christoph Haas 2026-08-22 23:47:42 +02:00
parent fdf3289f54
commit 4d07f43a2e
5 changed files with 46 additions and 0 deletions

View file

@ -30,6 +30,9 @@ class PackagesController < ApplicationController
@screenshots = @package.screenshots.accessible_by(current_ability, :view).paginate(
page: @page, per_page: 6
)
# Semantic recommendations based on the package's own description
# embedding - a plain nearest neighbor query, no service call.
@related_packages = @package.related_packages
end
end

View file

@ -112,6 +112,16 @@ class Package < ApplicationRecord
text.gsub(/[\\%_]/) { |char| "\\#{char}" }
end
# Packages semantically similar to this one, based on the stored
# description embedding. Returns an empty relation for packages that
# have no embedding (yet).
def related_packages(limit: 6)
return Package.none unless embedding
Package.nearest_to_vector(embedding, limit: limit + 1)
.where.not(id: id).limit(limit)
end
# Check whether the given text shares at least one lexeme with any
# package name/description in the database. Queries without such an
# overlap (gibberish, keyboard mash, unsupported languages) can
@ -209,3 +219,4 @@ class Package < ApplicationRecord
end
end
end
# rubocop:enable Metrics/ClassLength

View file

@ -70,6 +70,19 @@
/ #metadata is the anchor for this sticky container.
= render(partial: 'details_rightbox', locals: {pkg: @package})
/ Semantically similar packages, based on the package's description embedding
- if @related_packages.any?
#related
h3 Related packages
.grid-x.grid-margin-x.medium-up-2.large-up-3 data-equalizer=true
- @related_packages.each do |pkg|
.cell.pkgcard data-equalizer-watch=true
a.black href=package_path(name: pkg.name)
.text.pkgname
= pkg.name
.text.small
= pkg.description
javascript:
// Where to send POST requests for uploads
let ajax_upload_url = '#{{upload_receive_json_path}}';