Expand taste profile: show up to 60 tags with score bars
Top 10 shown as variable-size tag cloud, all tags below as a two-column bar chart. Backend limit raised from 20 to 60. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -114,7 +114,7 @@ def get_stats(
|
||||
SELECT tag, score FROM user_tag_affinity
|
||||
WHERE user_id = :uid AND score > 0
|
||||
ORDER BY score DESC
|
||||
LIMIT 20
|
||||
LIMIT 60
|
||||
"""),
|
||||
{"uid": uid},
|
||||
).mappings().all()
|
||||
|
||||
@@ -62,8 +62,8 @@ export default function Stats() {
|
||||
const maxDayCount = Math.max(...days.map(d => dailyMap[d]?.count || 0), 1);
|
||||
|
||||
const maxCatCount = Math.max(...(data.top_categories || []).map(c => c.watch_count || 0), 1);
|
||||
const topTags = (data.taste_profile || []).slice(0, 12);
|
||||
const maxTagScore = Math.max(...topTags.map(t => t.score || 0), 1);
|
||||
const allTags = data.taste_profile || [];
|
||||
const maxTagScore = Math.max(...allTags.map(t => t.score || 0), 1);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-8 max-w-4xl mx-auto">
|
||||
@@ -235,40 +235,76 @@ export default function Stats() {
|
||||
})()}
|
||||
|
||||
{/* Taste profile */}
|
||||
{topTags.length > 0 && (
|
||||
<div className="bg-zinc-900 rounded-2xl p-5 flex flex-col gap-3">
|
||||
{allTags.length > 0 && (
|
||||
<div className="bg-zinc-900 rounded-2xl p-5 flex flex-col gap-4">
|
||||
<div className="flex items-baseline gap-2">
|
||||
<h2 className="text-xs font-semibold text-zinc-500 uppercase tracking-wider">Taste profile</h2>
|
||||
<p className="text-[11px] text-zinc-600">built from your watches, likes and bookmarks</p>
|
||||
<p className="text-[11px] text-zinc-600">{allTags.length} interests · built from watches, likes & bookmarks</p>
|
||||
</div>
|
||||
|
||||
{/* Top tier tag cloud */}
|
||||
<div>
|
||||
<p className="text-[10px] text-zinc-600 uppercase tracking-wider mb-2">Top interests</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{topTags.map(t => {
|
||||
{allTags.slice(0, 10).map(t => {
|
||||
const intensity = t.score / maxTagScore;
|
||||
return (
|
||||
<span
|
||||
key={t.tag}
|
||||
title={`score: ${t.score.toFixed(1)}`}
|
||||
className="flex items-center gap-1 px-3 py-1 rounded-full text-xs font-medium"
|
||||
className="flex items-center gap-1 px-3 py-1 rounded-full font-semibold"
|
||||
style={{
|
||||
backgroundColor: `rgba(250,204,21,${0.08 + intensity * 0.18})`,
|
||||
backgroundColor: `rgba(250,204,21,${0.1 + intensity * 0.2})`,
|
||||
color: `hsl(50,95%,${55 + intensity * 20}%)`,
|
||||
fontSize: `${11 + intensity * 4}px`,
|
||||
fontSize: `${12 + intensity * 5}px`,
|
||||
}}
|
||||
>
|
||||
{t.tag}
|
||||
<button
|
||||
onClick={() => deleteTag.mutate(t.tag)}
|
||||
disabled={deleteTag.isPending}
|
||||
className="opacity-40 hover:opacity-100 transition-opacity leading-none ml-0.5"
|
||||
title="Remove from taste profile"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
className="opacity-30 hover:opacity-100 transition-opacity leading-none ml-0.5"
|
||||
title="Remove"
|
||||
>×</button>
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* All tags as score bars */}
|
||||
{allTags.length > 10 && (
|
||||
<div>
|
||||
<p className="text-[10px] text-zinc-600 uppercase tracking-wider mb-3">All interests</p>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-x-6 gap-y-2">
|
||||
{allTags.map(t => {
|
||||
const pct = (t.score / maxTagScore) * 100;
|
||||
const intensity = t.score / maxTagScore;
|
||||
return (
|
||||
<div key={t.tag} className="flex items-center gap-2 group/tag">
|
||||
<span className="text-[12px] text-zinc-300 w-32 shrink-0 truncate">{t.tag}</span>
|
||||
<div className="flex-1 h-1.5 bg-zinc-800 rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full rounded-full"
|
||||
style={{
|
||||
width: `${pct}%`,
|
||||
backgroundColor: `hsl(50,95%,${40 + intensity * 25}%)`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => deleteTag.mutate(t.tag)}
|
||||
disabled={deleteTag.isPending}
|
||||
className="opacity-0 group-hover/tag:opacity-40 hover:!opacity-100 transition-opacity text-zinc-400 text-xs leading-none shrink-0"
|
||||
title="Remove"
|
||||
>×</button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user