feat: монорепо миграция, Discover/SearxNG улучшения
- Миграция на монорепозиторий (apps/frontend, apps/chat-service, etc.) - Discover: проверка SearxNG, понятное empty state при ненастроенном поиске - searxng.ts: валидация URL, проверка JSON-ответа, авто-добавление http:// - docker/searxng-config: настройки для JSON API SearxNG Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
113
apps/frontend/src/app/api/discover/route.ts
Normal file
113
apps/frontend/src/app/api/discover/route.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import { searchSearxng } from '@/lib/searxng';
|
||||
import { getSearxngURL } from '@/lib/config/serverRegistry';
|
||||
|
||||
const websitesForTopic = {
|
||||
tech: {
|
||||
query: ['technology news', 'latest tech', 'AI', 'science and innovation'],
|
||||
links: ['techcrunch.com', 'wired.com', 'theverge.com'],
|
||||
},
|
||||
finance: {
|
||||
query: ['finance news', 'economy', 'stock market', 'investing'],
|
||||
links: ['bloomberg.com', 'cnbc.com', 'marketwatch.com'],
|
||||
},
|
||||
art: {
|
||||
query: ['art news', 'culture', 'modern art', 'cultural events'],
|
||||
links: ['artnews.com', 'hyperallergic.com', 'theartnewspaper.com'],
|
||||
},
|
||||
sports: {
|
||||
query: ['sports news', 'latest sports', 'cricket football tennis'],
|
||||
links: ['espn.com', 'bbc.com/sport', 'skysports.com'],
|
||||
},
|
||||
entertainment: {
|
||||
query: ['entertainment news', 'movies', 'TV shows', 'celebrities'],
|
||||
links: ['hollywoodreporter.com', 'variety.com', 'deadline.com'],
|
||||
},
|
||||
};
|
||||
|
||||
type Topic = keyof typeof websitesForTopic;
|
||||
|
||||
export const GET = async (req: Request) => {
|
||||
try {
|
||||
const searxngURL = getSearxngURL();
|
||||
if (!searxngURL?.trim()) {
|
||||
return Response.json(
|
||||
{
|
||||
message:
|
||||
'SearxNG is not configured. Please set the SearxNG URL in Settings → Search.',
|
||||
},
|
||||
{ status: 503 },
|
||||
);
|
||||
}
|
||||
|
||||
const params = new URL(req.url).searchParams;
|
||||
|
||||
const mode: 'normal' | 'preview' =
|
||||
(params.get('mode') as 'normal' | 'preview') || 'normal';
|
||||
const topic: Topic = (params.get('topic') as Topic) || 'tech';
|
||||
|
||||
const selectedTopic = websitesForTopic[topic];
|
||||
|
||||
let data = [];
|
||||
|
||||
if (mode === 'normal') {
|
||||
const seenUrls = new Set();
|
||||
|
||||
data = (
|
||||
await Promise.all(
|
||||
selectedTopic.links.flatMap((link) =>
|
||||
selectedTopic.query.map(async (query) => {
|
||||
return (
|
||||
await searchSearxng(`site:${link} ${query}`, {
|
||||
engines: ['bing news'],
|
||||
pageno: 1,
|
||||
language: 'en',
|
||||
})
|
||||
).results;
|
||||
}),
|
||||
),
|
||||
)
|
||||
)
|
||||
.flat()
|
||||
.filter((item) => {
|
||||
const url = item.url?.toLowerCase().trim();
|
||||
if (seenUrls.has(url)) return false;
|
||||
seenUrls.add(url);
|
||||
return true;
|
||||
})
|
||||
.sort(() => Math.random() - 0.5);
|
||||
} else {
|
||||
data = (
|
||||
await searchSearxng(
|
||||
`site:${selectedTopic.links[Math.floor(Math.random() * selectedTopic.links.length)]} ${selectedTopic.query[Math.floor(Math.random() * selectedTopic.query.length)]}`,
|
||||
{
|
||||
engines: ['bing news'],
|
||||
pageno: 1,
|
||||
language: 'en',
|
||||
},
|
||||
)
|
||||
).results;
|
||||
}
|
||||
|
||||
return Response.json(
|
||||
{
|
||||
blogs: data,
|
||||
},
|
||||
{
|
||||
status: 200,
|
||||
},
|
||||
);
|
||||
} catch (err) {
|
||||
const message =
|
||||
err instanceof Error ? err.message : 'An error has occurred';
|
||||
console.error(`Discover route error:`, err);
|
||||
return Response.json(
|
||||
{
|
||||
message:
|
||||
message.includes('fetch') || message.includes('ECONNREFUSED')
|
||||
? 'Cannot connect to SearxNG. Check that it is running and the URL is correct in Settings.'
|
||||
: message,
|
||||
},
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user