diff --git a/frontend/src/pages/RecommendationPage.test.tsx b/frontend/src/pages/RecommendationPage.test.tsx index bd12a28..a2f839b 100644 --- a/frontend/src/pages/RecommendationPage.test.tsx +++ b/frontend/src/pages/RecommendationPage.test.tsx @@ -23,10 +23,12 @@ describe("RecommendationPage", () => { if (url.endsWith("/admin/api/settings") && init?.method === "PUT") { const body = init.body ? JSON.parse(String(init.body)) : {}; expect(body.settings.random.strategy).toBe("quality"); - expect(body.settings.random.quality_samples).toBe(5); + expect(body.settings.random.quality_samples).toBe(12); expect(body.settings.random.recommendation.pick_mode).toBe("weighted"); expect(body.settings.random.recommendation.temperature).toBe(1); expect(body.settings.random.recommendation.score_weights.bookmark).toBe(4); + expect(body.settings.random.recommendation.score_weights.freshness).toBe(1); + expect(body.settings.random.recommendation.score_weights.bookmark_velocity).toBe(1.2); expect(body.settings.random.recommendation.multipliers.ai).toBe(0.5); expect(body.settings.random.recommendation.multipliers.manga).toBe(0); return new Response(JSON.stringify({ ok: true, updated: 3, request_id: "req_save" }), { @@ -41,11 +43,11 @@ describe("RecommendationPage", () => { settings: { random: { strategy: "quality", - quality_samples: 5, + quality_samples: 12, recommendation: { pick_mode: "weighted", temperature: 1, - score_weights: { bookmark: 4, view: 0.5, comment: 2, pixels: 1, bookmark_rate: 3 }, + score_weights: { bookmark: 4, view: 0.5, comment: 2, pixels: 1, bookmark_rate: 3, freshness: 1, bookmark_velocity: 1.2 }, multipliers: { ai: 1, non_ai: 1, diff --git a/frontend/src/pages/RecommendationPage.tsx b/frontend/src/pages/RecommendationPage.tsx index 17d3dab..0c31a55 100644 --- a/frontend/src/pages/RecommendationPage.tsx +++ b/frontend/src/pages/RecommendationPage.tsx @@ -33,6 +33,8 @@ type FormValues = { w_comment: number; w_pixels: number; w_bookmark_rate: number; + w_freshness: number; + w_bookmark_velocity: number; m_ai: number; m_non_ai: number; @@ -50,7 +52,7 @@ const PICK_MODE_VALUES = new Set(["weighted", "best"]); const DEFAULTS: Omit = { random_strategy: "quality", - random_quality_samples: 5, + random_quality_samples: 12, pick_mode: "weighted", temperature: 1.0, w_bookmark: 4.0, @@ -58,6 +60,8 @@ const DEFAULTS: Omit = { w_comment: 2.0, w_pixels: 1.0, w_bookmark_rate: 3.0, + w_freshness: 1.0, + w_bookmark_velocity: 1.2, m_ai: 1.0, m_non_ai: 1.0, m_unknown_ai: 1.0, @@ -147,6 +151,8 @@ export function RecommendationPage() { w_comment: asFloat(scoreWeights.comment, DEFAULTS.w_comment), w_pixels: asFloat(scoreWeights.pixels, DEFAULTS.w_pixels), w_bookmark_rate: asFloat(scoreWeights.bookmark_rate, DEFAULTS.w_bookmark_rate), + w_freshness: asFloat(scoreWeights.freshness, DEFAULTS.w_freshness), + w_bookmark_velocity: asFloat(scoreWeights.bookmark_velocity, DEFAULTS.w_bookmark_velocity), m_ai: asFloat(multipliers.ai, DEFAULTS.m_ai), m_non_ai: asFloat(multipliers.non_ai, DEFAULTS.m_non_ai), m_unknown_ai: asFloat(multipliers.unknown_ai, DEFAULTS.m_unknown_ai), @@ -166,7 +172,10 @@ export function RecommendationPage() { "+ w_comment * ln(1 + comment_count)", "+ w_pixels * ln(1 + (width*height)/1_000_000)", "+ w_bookmark_rate * ln(1 + (bookmark_count/view_count)*1000)", + "+ w_freshness * exp(-age_days / 21)", + "+ w_bookmark_velocity * ln(1 + bookmark_count / (age_days + 2))", "", + "说明:有 seed 时,为保证可复现,会自动关闭 freshness/bookmark_velocity 两项。", "最终得分会再乘以“类别倍率”(AI/插画/漫画/动图等)。", "倍率=0 表示彻底不返回该类别。", ].join("\n"); @@ -190,6 +199,8 @@ export function RecommendationPage() { comment: values.w_comment, pixels: values.w_pixels, bookmark_rate: values.w_bookmark_rate, + freshness: values.w_freshness, + bookmark_velocity: values.w_bookmark_velocity, }, multipliers: { ai: values.m_ai, @@ -385,6 +396,16 @@ export function RecommendationPage() { + + + + + +