improve similar post lists, fallback creator models

This commit is contained in:
Dylan Knutson
2025-07-23 02:06:01 +00:00
parent 931e736bbf
commit 1470a21bbe
10 changed files with 102 additions and 65 deletions

View File

@@ -80,13 +80,15 @@ module Domain
fingerprint_detail_value: String,
limit: Integer,
oversearch: Integer,
includes: T.untyped,
).returns(T::Array[SimilarFingerprintResult])
end
def find_similar_fingerprints(
fingerprint_value:,
fingerprint_detail_value:,
limit: 32,
oversearch: 2
oversearch: 2,
includes: {}
)
ActiveRecord::Base.connection.execute("SET ivfflat.probes = 10")
@@ -95,6 +97,7 @@ module Domain
Arel.sql "(fingerprint_value <~> '#{ActiveRecord::Base.connection.quote_string(fingerprint_value)}')"
)
.limit(limit * oversearch)
.includes(includes)
.to_a
.uniq(&:post_file_id)
.map do |other_fingerprint|

View File

@@ -43,6 +43,11 @@ export const PostHoverPreviewWrapper: React.FC<
href={postPath}
className={anchorClassNamesForVisualStyle(visualStyle, true)}
>
<img
src={postDomainIcon}
alt={postThumbnailAlt}
className={iconClassNamesForSize('small')}
/>
{visualStyle === 'description-section-link' && (
<img
src={postDomainIcon}
@@ -50,7 +55,7 @@ export const PostHoverPreviewWrapper: React.FC<
className={iconClassNamesForSize('small')}
/>
)}
{linkText}
<span className="truncate">{linkText}</span>
</a>
</PostHoverPreview>
);

View File

@@ -17,7 +17,7 @@ export function iconClassNamesForSize(size: IconSize) {
case 'large':
return 'h-8 w-8 flex-shrink-0 rounded-md';
case 'small':
return 'h-5 w-5 flex-shrink-0 rounded-sm';
return 'h-6 w-6 flex-shrink-0 rounded-sm';
}
}
@@ -227,7 +227,8 @@ export function anchorClassNamesForVisualStyle(
visualStyle: string,
hasIcon: boolean = false,
) {
let classNames = ['truncate', 'gap-1'];
// let classNames = ['truncate', 'gap-1'];
let classNames = ['gap-1', 'min-w-0'];
if (hasIcon) {
classNames.push('flex items-center');
}

View File

@@ -216,6 +216,11 @@ class Domain::Post < ReduxApplicationRecord
nil
end
sig { overridable.returns(T.nilable(Domain::User)) }
def primary_fallback_creator_for_view
nil
end
class TagForView < T::Struct
const :category, Symbol
const :value, String

View File

@@ -114,26 +114,16 @@ class Domain::Post::E621Post < Domain::Post
sig { override.returns(T.nilable(String)) }
def primary_creator_name_fallback_for_view
self
.sources_array
.lazy
.filter_map do |source|
model =
T.cast(
T.unsafe(ApplicationController.helpers).link_for_source(source),
T.nilable(Domain::PostsHelper::LinkForSource),
)&.model
if model && model.is_a?(Domain::Post) && model.class.has_creators?
return T.unsafe(model).creator&.name_for_view
elsif model && model.is_a?(Domain::User)
return model.name_for_view
end
end
.first ||
guess_creator_from_sources&.name_for_view ||
self.class.first_valid_artist_tag_in(self.tags_array&.dig("artist")) ||
self.class.first_valid_artist_tag_in(self.artists_array)
end
sig { override.returns(T.nilable(Domain::User)) }
def primary_fallback_creator_for_view
guess_creator_from_sources
end
sig { override.returns(T.nilable(String)) }
def description_html_for_view
self.description
@@ -192,4 +182,28 @@ class Domain::Post::E621Post < Domain::Post
"Unknown (#{self.rating})"
end
end
private
sig { returns(T.nilable(Domain::User)) }
def guess_creator_from_sources
self
.sources_array
.lazy
.filter_map do |source|
model =
T.cast(
T.unsafe(ApplicationController.helpers).link_for_source(source),
T.nilable(Domain::PostsHelper::LinkForSource),
)&.model
if model && model.is_a?(Domain::Post) && model.class.has_creators?
T.unsafe(model).creator
elsif model && model.is_a?(Domain::User)
model
else
nil
end
end
.first
end
end

View File

@@ -0,0 +1,4 @@
<% source_url = local_assigns[:source_url] %>
<% source_url = source_url && Addressable::URI.parse(source_url).host %>
<% icon_path = source_url && icon_path_for_domain(source_url) %>
<%= image_tag icon_path, class: "w-6 h-6" if icon_path %>

View File

@@ -12,7 +12,7 @@
<i class="fa-solid fa-arrow-up-right-from-square text-slate-400"></i>
</div>
<div class="flex items-center gap-2 whitespace-nowrap text-slate-600 float-right">
<% if creator = post.primary_creator_for_view %>
<% if creator = post.primary_creator_for_view || post.primary_fallback_creator_for_view %>
<span class="flex gap-1 items-center text-lg">
<span class="text-slate-500 italic text-sm">by</span>
<%= render(
@@ -42,7 +42,7 @@
<i class="fa-solid fa-heart mr-1"></i>
Favorites: <%= num_favorites %>
<% if policy(post).view_faved_by? %>
(<%= link_to "#{pluralize(post.faving_users.count, "fav")} known",
(<%= link_to "#{pluralize(post.user_post_favs.count, "fav")} known",
faved_by_domain_post_users_path(post),
class: "text-blue-600 hover:underline" %>)
<% end %>

View File

@@ -1,26 +1,34 @@
<section class="sky-section">
<div class="section-header">Similar Posts</div>
<div class="grid grid-cols-[1fr,auto] bg-slate-100">
<div class="grid grid-cols-[minmax(10px,1fr),auto] bg-slate-100 divide-y divide-slate-300">
<% factors = Domain::Factors::UserPostFavPostFactors.find_by(post: post) %>
<% if factors %>
<% neighbors = Domain::NeighborFinder.find_neighbors(factors).includes(:post).limit(10) %>
<% neighbors = Domain::NeighborFinder.find_neighbors(factors) %>
<% if post.class.has_creators? %>
<% neighbors = neighbors.includes(post: :creator) %>
<% else %>
<% neighbors = neighbors.includes(:post) %>
<% end %>
<% neighbors = neighbors.limit(10) %>
<% num_neighbors = neighbors.size %>
<% neighbors.each_with_index do |neighbor, index| %>
<% border_classes = index < num_neighbors - 1 ? "border-b border-slate-300" : "" %>
<% border_classes = "" %>
<% post = neighbor.post %>
<% creator = post.class.has_creators? ? post.creator : nil %>
<div class="text-md flex items-center px-4 py-1 <%= border_classes %>">
<%= render "domain/has_description_html/inline_link_domain_post", post: post, visual_style: "sky-link" %>
<% creator = post.class.has_creators? ? post.creator : post.primary_fallback_creator_for_view %>
<div class="grid grid-cols-subgrid col-span-full max-w-max py-1 px-2 gap-1 min-h-10">
<div class="text-md flex items-center">
<%= render "domain/has_description_html/inline_link_domain_post", post: post, visual_style: "sky-link" %>
</div>
<% if creator %>
<div class="text-md items-center">
<%= render "domain/has_description_html/inline_link_domain_user", user: creator, visual_style: "sky-link", icon_size: "large" %>
</div>
<% else %>
<div class="text-md self-center truncate">
<%= post.primary_creator_name_fallback_for_view %>
</div>
<% end %>
</div>
<% if creator %>
<div class="text-md items-center px-4 py-1 <%= border_classes %>">
<%= render "domain/has_description_html/inline_link_domain_user", user: creator, visual_style: "sky-link", icon_size: "large" %>
</div>
<% else %>
<div class="text-md truncate px-4 py-1 <%= border_classes %>">
<%= post.primary_creator_name_fallback_for_view %>
</div>
<% end %>
<% end %>
<% else %>
<div class="col-span-full p-4 text-center text-slate-500">No similar posts found</div>

View File

@@ -1,9 +1,9 @@
<% post_file = post.primary_file_for_view || return%>
<% post_file = post.primary_file_for_view || return %>
<% return unless (content_type = post_file.content_type) && is_thumbable_content_type?(content_type) %>
<section class="sky-section">
<div class="section-header">Visually Similar Posts</div>
<div class="grid grid-cols-[auto,auto,1fr,auto] bg-slate-100">
<% fprint = post.primary_file_for_view&.bit_fingerprints&.first %>
<div class="grid grid-cols-[5rem,minmax(10px,1fr),auto] bg-slate-100 divide-y divide-slate-300">
<% fprint = post_file.bit_fingerprints&.first %>
<% fprint_value = fprint&.fingerprint_value %>
<% fprint_detail_value = fprint&.fingerprint_detail_value %>
<% fprints = fprint && fprint_value && fprint_detail_value && find_similar_fingerprints(
@@ -11,8 +11,10 @@
fingerprint_detail_value: fprint_detail_value,
limit: 5,
oversearch: 5,
includes: {post_file: :post},
)
.reject { |f| f.fingerprint.id == fprint.id }
.reject { |f| f.fingerprint.post_file.post_id == post.id}
.reject { |f| f.similarity_percentage < 70 } || []
%>
<% if fprint.nil? %>
@@ -22,32 +24,27 @@
<% elsif fprints.any? %>
<% num_neighbors = fprints.size %>
<% fprints.each_with_index do |similar_fingerprint, index| %>
<% border_classes = index < num_neighbors - 1 ? "border-b border-slate-300" : "" %>
<% post = similar_fingerprint.fingerprint.post_file.post %>
<% creator = post.class.has_creators? ? post.creator : nil %>
<div class="text-md items-center pl-4 pr-2 py-1 flex justify-end <%= border_classes %>">
<div class="w-full text-center font-medium <%= match_badge_classes(similar_fingerprint.similarity_percentage) %>">
<%= similar_fingerprint.similarity_percentage %>%
<div class="grid grid-cols-subgrid col-span-full max-w-max py-1 px-2 gap-2">
<% post = similar_fingerprint.fingerprint.post_file.post %>
<% creator = post.class.has_creators? ? post.creator : post.primary_fallback_creator_for_view %>
<div class="text-md items-center flex justify-end">
<div class="w-full text-center font-medium <%= match_badge_classes(similar_fingerprint.similarity_percentage) %>">
<%= similar_fingerprint.similarity_percentage %>%
</div>
</div>
</div>
<div class="flex items-center <%= border_classes %>">
<% source_url = post.external_url_for_view&.to_s %>
<% source_url = source_url && Addressable::URI.parse(source_url).host %>
<% icon_path = source_url && icon_path_for_domain(source_url) %>
<%= image_tag icon_path, class: "w-6 h-6 mr-2" if icon_path %>
</div>
<div class="text-md flex items-center pr-2 py-1 <%= border_classes %>">
<%= render "domain/has_description_html/inline_link_domain_post", post: post, visual_style: "sky-link" %>
</div>
<% if creator %>
<div class="text-md items-center px-4 py-1 <%= border_classes %>">
<%= render "domain/has_description_html/inline_link_domain_user", user: creator, visual_style: "sky-link", icon_size: "large" %>
<div class="text-md self-center">
<%= render "domain/has_description_html/inline_link_domain_post", post: post, visual_style: "sky-link" %>
</div>
<% else %>
<div class="text-md truncate px-4 py-1 <%= border_classes %>">
<%= post.primary_creator_name_fallback_for_view %>
</div>
<% end %>
<% if creator %>
<div class="text-md items-center">
<%= render "domain/has_description_html/inline_link_domain_user", user: creator, visual_style: "sky-link", icon_size: "large" %>
</div>
<% else %>
<div class="text-md truncate">
<%= post.primary_creator_name_fallback_for_view %>
</div>
<% end %>
</div>
<% end %>
<% else %>
<div class="col-span-full p-4 text-center text-slate-500">No visually similar posts found</div>

View File

@@ -1,7 +1,7 @@
<% if post.sources_array.any? %>
<section class="sky-section">
<div class="section-header">Sources</div>
<div class="bg-slate-100 p-4">
<div class="bg-slate-100 p-2">
<div class="divide-y divide-slate-200">
<% post.sources_array.each do |source| %>
<%= render partial: "domain/posts/e621/source_link", locals: { source: source } %>