feat: add news source adapters and AI article analysis

- Implement adapters for NewsAPI, GNews, NewsData, Serper Web Search,
  and Brave Search.
- Add unified fetchNews aggregator with URL deduplication.
- Wire OpenAI-compatible Kimi client for bias classification, ranking,
  summarization, and topic extraction.
- Add AI analysis prompts, response parsing, and fallback logic.
- Build ranker combining AI score, source credibility, and recency.
- Create admin Discover page and API for manual news discovery.
This commit is contained in:
hermes
2026-06-17 04:32:25 +00:00
parent e086141620
commit 3e57802c6e
14 changed files with 897 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
import { FetchNewsOptions, RawArticle } from '../types';
import { getActiveKey } from '@/lib/api-keys';
export async function fetchWebSearch(options: FetchNewsOptions): Promise<RawArticle[]> {
const apiKey = await getActiveKey('WEBSEARCH');
if (!apiKey) {
console.warn('Web search key not configured');
return [];
}
const res = await fetch('https://google.serper.dev/search', {
method: 'POST',
headers: {
'X-API-KEY': apiKey,
'Content-Type': 'application/json',
},
body: JSON.stringify({
q: options.query,
num: Math.min(options.maxResults || 10, 20),
tbs: options.from ? `cdr:1,cd_min:${options.from.toISOString().split('T')[0]}` : undefined,
}),
});
if (!res.ok) {
const text = await res.text();
throw new Error(`Web search error ${res.status}: ${text}`);
}
const data = await res.json();
return (data.news || data.organic || []).map((item: Record<string, unknown>) => ({
title: String(item.title || ''),
url: String(item.link || item.url || ''),
sourceName: String(item.source || 'Web Search'),
sourceApiCode: 'websearch',
publishedAt: item.date ? new Date(String(item.date)) : undefined,
description: item.snippet ? String(item.snippet) : undefined,
content: undefined,
imageUrl: item.imageUrl ? String(item.imageUrl) : undefined,
raw: item,
}));
}