Remove recommendation score and reason from UI and simplify logic

This commit is contained in:
Yusuf İpek
2025-11-23 13:10:34 +03:00
parent 17bbe54cb1
commit 5b66d4700a
3 changed files with 25 additions and 113 deletions
-25
View File
@@ -41,32 +41,7 @@ export function RecommendationCard({ pkg, isSelected, onToggle }: Recommendation
{pkg.description} {pkg.description}
</p> </p>
{/* Recommendation score and reason */}
<div className="space-y-2">
<div className="flex items-center justify-between">
<span className="text-xs text-muted-foreground">
{t('recommendations.score')}
</span>
<div className="flex items-center gap-1">
<div className="h-2 w-16 bg-secondary rounded-full overflow-hidden">
<div
className="h-full bg-primary rounded-full transition-all"
style={{ width: `${pkg.recommendationScore}%` }}
/>
</div>
<span className="text-xs font-semibold">{pkg.recommendationScore}%</span>
</div>
</div>
{pkg.recommendationReason && (
<div className="pt-2 border-t">
<p className="text-xs text-muted-foreground">
<span className="font-semibold">{t('recommendations.reason')}</span>{' '}
{pkg.recommendationReason}
</p>
</div>
)}
</div>
<Button <Button
variant={isSelected ? 'default' : 'outline'} variant={isSelected ? 'default' : 'outline'}
+15 -33
View File
@@ -12,8 +12,8 @@ export function RecommendationListItem({ pkg, isSelected, onToggle }: Recommenda
return ( return (
<div <div
className={`flex items-center gap-3 p-3 rounded-lg border-2 transition-all cursor-pointer hover:shadow-md ${isSelected className={`flex items-center gap-3 p-3 rounded-lg border-2 transition-all cursor-pointer hover:shadow-md ${isSelected
? 'border-primary bg-primary/5' ? 'border-primary bg-primary/5'
: 'border-border hover:border-primary/50' : 'border-border hover:border-primary/50'
}`} }`}
onClick={() => onToggle(pkg)} onClick={() => onToggle(pkg)}
> >
@@ -35,39 +35,21 @@ export function RecommendationListItem({ pkg, isSelected, onToggle }: Recommenda
<p className="text-xs text-muted-foreground truncate">{pkg.description}</p> <p className="text-xs text-muted-foreground truncate">{pkg.description}</p>
</div> </div>
{/* Score Badge */}
<div className="flex items-center gap-2 flex-shrink-0">
<div className="text-right">
<div className="text-xs font-semibold text-primary">
{pkg.recommendationScore}%
</div>
<div className="text-xs text-muted-foreground">match</div>
</div>
{/* Info Tooltip */}
{pkg.recommendationReason && (
<div className="relative group">
<Info className="h-4 w-4 text-muted-foreground cursor-help" />
<div className="absolute right-0 top-full mt-2 w-64 p-2 bg-popover text-popover-foreground text-xs rounded-md shadow-lg border opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-10">
<p className="font-semibold mb-1">Why recommended?</p>
<p>{pkg.recommendationReason}</p>
</div>
</div>
)}
{/* Select Button */} {/* Select Button */}
<Button <Button
variant={isSelected ? 'default' : 'outline'} variant={isSelected ? 'default' : 'outline'}
size="sm" size="sm"
className="ml-2" className="ml-2"
onClick={(e) => { onClick={(e) => {
e.stopPropagation() e.stopPropagation()
onToggle(pkg) onToggle(pkg)
}} }}
> >
{isSelected ? '✓' : '+'} {isSelected ? '✓' : '+'}
</Button> </Button>
</div>
</div> </div>
) )
} }
+10 -55
View File
@@ -8,20 +8,10 @@ import {
} from "@/types/recommendations"; } from "@/types/recommendations";
import { import {
getPresetPackageNames, getPresetPackageNames,
getPresetPriority,
getRecommendationReason,
RECOMMENDATION_PRESETS, RECOMMENDATION_PRESETS,
} from "@/data/recommendationPresets"; } from "@/data/recommendationPresets";
/**
* Recommendation scoring weights
*/
const SCORING_WEIGHTS = {
CATEGORY_MATCH: 0.4,
POPULARITY: 0.3,
OS_COMPATIBILITY: 0.2,
PRESET_BOOST: 0.1,
};
export class RecommendationService { export class RecommendationService {
/** /**
@@ -295,6 +285,9 @@ export class RecommendationService {
/** /**
* Score a package based on multiple factors * Score a package based on multiple factors
*/ */
/**
* Score a package based on simplified factors (popularity & preset)
*/
private static scorePackage( private static scorePackage(
pkg: Package, pkg: Package,
categories: UserCategory[], categories: UserCategory[],
@@ -303,53 +296,15 @@ export class RecommendationService {
experienceLevel?: ExperienceLevel, experienceLevel?: ExperienceLevel,
matchedCategory?: UserCategory matchedCategory?: UserCategory
): RecommendedPackage { ): RecommendedPackage {
let score = 0;
let reason = "";
const isPresetMatch = presetPackageNames.includes(pkg.name); const isPresetMatch = presetPackageNames.includes(pkg.name);
// 1. Category Match Score (40%) // Simplified score: just use popularity score (0-100)
// For preset packages, this is always high // Give a boost to preset packages so they appear first
const categoryScore = isPresetMatch ? 1.0 : 0.5; let finalScore = pkg.popularity_score || 0;
score += categoryScore * SCORING_WEIGHTS.CATEGORY_MATCH;
// 2. Popularity Score (30%)
// Normalize popularity_score (0-100) to 0-1
const popularityScore = (pkg.popularity_score || 0) / 100;
score += popularityScore * SCORING_WEIGHTS.POPULARITY;
// 3. OS Compatibility Score (20%)
// All packages from DB should be compatible, so this is always 1.0
const osScore = 1.0;
score += osScore * SCORING_WEIGHTS.OS_COMPATIBILITY;
// 4. Preset Boost (10%)
// Extra boost for preset packages based on priority
let presetBoost = 0;
if (isPresetMatch) { if (isPresetMatch) {
const priority = getPresetPriority(pkg.name, categories, platformId); finalScore += 100; // Ensure presets are always on top
if (priority !== null) {
presetBoost = priority / 10; // Normalize 1-10 to 0.1-1.0
// Get recommendation reason from preset
const presetReason = getRecommendationReason(pkg.name, categories);
if (presetReason) {
reason = presetReason;
}
}
} }
score += presetBoost * SCORING_WEIGHTS.PRESET_BOOST;
// Default reason if not from preset
if (!reason) {
if (pkg.popularity_score && pkg.popularity_score > 70) {
reason = "Popular choice in the community";
} else {
reason = "Recommended for your selected categories";
}
}
// Normalize final score to 0-100
const finalScore = Math.round(score * 100);
return { return {
id: pkg.id, id: pkg.id,
@@ -361,7 +316,7 @@ export class RecommendationService {
license: license:
typeof pkg.license === "string" ? pkg.license : pkg.license?.name, typeof pkg.license === "string" ? pkg.license : pkg.license?.name,
type: pkg.type || "cli", type: pkg.type || "cli",
platform: pkg.platform, platform: pkg.platform as any,
platform_id: pkg.platform_id, platform_id: pkg.platform_id,
repository: pkg.repository || "official", repository: pkg.repository || "official",
download_url: pkg.download_url, download_url: pkg.download_url,
@@ -371,7 +326,7 @@ export class RecommendationService {
popularity_score: pkg.popularity_score, popularity_score: pkg.popularity_score,
tags: pkg.tags, tags: pkg.tags,
recommendationScore: finalScore, recommendationScore: finalScore,
recommendationReason: reason, recommendationReason: "", // Removed as requested
presetMatch: isPresetMatch, presetMatch: isPresetMatch,
matchedCategory: matchedCategory, matchedCategory: matchedCategory,
}; };