fix(news): relax SearXNG fallback when prefetch images are missing
Allow no-image candidates and create missing section shells so news002 image gate can pass after source-page enrichment on thin agent output. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -223,6 +223,32 @@ function pickSearchCandidates(groupMap, poolIds, seenTitles, { requireImage = tr
|
||||
return picked;
|
||||
}
|
||||
|
||||
/** 先取带图候选,不足时再放宽为无图(后续 enrichment 会从来源页补 og:image)。 */
|
||||
function pickSearchCandidatesWithFallback(groupMap, poolIds, seenTitles, needCount) {
|
||||
if (needCount <= 0) return [];
|
||||
const workingSeen = [...seenTitles];
|
||||
const withImage = pickSearchCandidates(groupMap, poolIds, workingSeen, { requireImage: true });
|
||||
let merged = withImage;
|
||||
if (merged.length < needCount) {
|
||||
const withoutImage = pickSearchCandidates(groupMap, poolIds, workingSeen, { requireImage: false });
|
||||
const usedUrls = new Set(merged.map((item) => item.url));
|
||||
for (const item of withoutImage) {
|
||||
if (merged.length >= needCount) break;
|
||||
if (usedUrls.has(item.url)) continue;
|
||||
merged.push(item);
|
||||
usedUrls.add(item.url);
|
||||
}
|
||||
}
|
||||
const picked = merged.slice(0, needCount);
|
||||
for (const item of picked) {
|
||||
seenTitles.push({
|
||||
urlKey: normalizeNewsMorningUrl(item.url),
|
||||
title: normalizeNewsMorningTitle(item.title),
|
||||
});
|
||||
}
|
||||
return picked;
|
||||
}
|
||||
|
||||
function buildLeadCardHtml(item, eventKey) {
|
||||
const title = escapeHtml(item.title);
|
||||
const snippet = escapeHtml(truncateText(item.snippet || item.title, 120));
|
||||
@@ -340,17 +366,15 @@ export function supplementNews002HtmlFromSearxngGroups(
|
||||
|| countLeadCardsInHeadlines(output) < 3;
|
||||
let remainingBudget = Math.max(0, maxAdds - existingCards.length);
|
||||
|
||||
const leadPool = pickSearchCandidates(
|
||||
groupMap,
|
||||
SECTION_SEARCH_POOLS.headlines,
|
||||
[...seenTitles],
|
||||
{ requireImage: true },
|
||||
);
|
||||
|
||||
if (needLeads && remainingBudget > 0) {
|
||||
const currentLeads = countLeadCardsInHeadlines(output);
|
||||
const needCount = Math.max(0, 3 - currentLeads);
|
||||
const toAdd = leadPool.splice(0, Math.min(needCount, remainingBudget));
|
||||
const toAdd = pickSearchCandidatesWithFallback(
|
||||
groupMap,
|
||||
SECTION_SEARCH_POOLS.headlines,
|
||||
seenTitles,
|
||||
Math.min(needCount, remainingBudget),
|
||||
);
|
||||
if (toAdd.length) {
|
||||
const cardsHtml = toAdd.map((item) => (
|
||||
buildLeadCardHtml(item, buildNewsMorningEventKey(item.title, usedEventKeys))
|
||||
@@ -394,9 +418,17 @@ export function supplementNews002HtmlFromSearxngGroups(
|
||||
if (!needCount && hasNews002Section(output, sectionId)) continue;
|
||||
|
||||
const pool = SECTION_SEARCH_POOLS[sectionId] ?? [SEARXNG_GROUP_TO_NEWS002_SECTION[sectionId]].filter(Boolean);
|
||||
const candidates = pickSearchCandidates(groupMap, pool, [...seenTitles], { requireImage: true });
|
||||
const toAdd = candidates.slice(0, Math.min(needCount || meta.min, remainingBudget));
|
||||
if (!toAdd.length) continue;
|
||||
const targetCount = Math.min(needCount || meta.min, remainingBudget);
|
||||
const toAdd = pickSearchCandidatesWithFallback(groupMap, pool, seenTitles, targetCount);
|
||||
const sectionMissing = missingSections.includes(sectionId) || !hasNews002Section(output, sectionId);
|
||||
|
||||
if (!toAdd.length) {
|
||||
if (sectionMissing) {
|
||||
output = createAndInsertSection(output, sectionId, '');
|
||||
stats.addedSections.push(sectionId);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const cardsHtml = toAdd.map((item) => (
|
||||
buildStoryCardHtml(item, buildNewsMorningEventKey(item.title, usedEventKeys))
|
||||
|
||||
@@ -75,6 +75,37 @@ test('buildNewsMorningEventKey deduplicates keys', () => {
|
||||
assert.equal(buildNewsMorningEventKey('Same Title', used), 'same-title-2');
|
||||
});
|
||||
|
||||
test('supplementNews002HtmlFromSearxngGroups fills sections without prefetch images', () => {
|
||||
const groupsNoImage = [
|
||||
{
|
||||
id: 'world',
|
||||
title: '国际',
|
||||
results: [
|
||||
{ title: '国际无图一', url: 'https://news.example/w-no1', snippet: '国际一' },
|
||||
{ title: '国际无图二', url: 'https://news.example/w-no2', snippet: '国际二' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'tech',
|
||||
title: '科技',
|
||||
results: [
|
||||
{ title: '科技无图一', url: 'https://news.example/t-no1', snippet: '科技一' },
|
||||
{ title: '科技无图二', url: 'https://news.example/t-no2', snippet: '科技二' },
|
||||
],
|
||||
},
|
||||
];
|
||||
const result = supplementNews002HtmlFromSearxngGroups(
|
||||
THIN_AGENT_HTML,
|
||||
groupsNoImage,
|
||||
{ violations: ['missing-sections:world|tech'] },
|
||||
);
|
||||
assert.equal(result.changed, true);
|
||||
assert.match(result.html, /id="world"/u);
|
||||
assert.match(result.html, /id="tech"/u);
|
||||
assert.match(result.html, /国际无图一/u);
|
||||
assert.match(result.html, /科技无图一/u);
|
||||
});
|
||||
|
||||
test('supplementNews002HtmlFromSearxngGroups fills missing core sections and leads', () => {
|
||||
const beforeAudit = auditNews002ImageCoverage(THIN_AGENT_HTML, { requireTemplateStructure: true });
|
||||
assert.equal(beforeAudit.ok, false);
|
||||
|
||||
Reference in New Issue
Block a user