Files
Perplexica/src/app/api/discover/search.ts

174 lines
5.8 KiB
TypeScript

import { searchSearxng } from '@/lib/searxng';
import { LANGUAGE_SPECIFIC_ENGINES } from './languages';
// Define the search options interface to match the one in lib/searxng.ts
interface SearxngSearchOptions {
categories?: string[];
engines?: string[];
language?: string;
pageno?: number;
}
/**
* Default search engines to use, in priority order
*/
export const DEFAULT_ENGINES = ['bing news', 'brave news', 'duckduckgo news'];
/**
* Search with multiple engines as fallbacks
* Tries each engine in sequence until results are found or engines exhausted
*/
export async function searchWithMultipleEngines(
query: string,
language: string,
engines: string[] = DEFAULT_ENGINES
): Promise<any[]> {
let allResults: any[] = [];
let hasResults = false;
// Try each engine in sequence until we get results or run out of engines
for (const engine of engines) {
try {
console.log(`[Discover] Trying engine "${engine}" for query "${query}" in language "${language || 'default'}"`);
const searchOptions: SearxngSearchOptions = {
engines: [engine],
pageno: 1,
};
if (language) {
searchOptions.language = language;
}
const result = await searchSearxng(query, searchOptions);
if (result.results && result.results.length > 0) {
console.log(`[Discover] Found ${result.results.length} results from engine "${engine}"`);
allResults.push(...result.results);
hasResults = true;
// If we've found enough results, stop trying more engines
if (allResults.length >= 20) {
break;
}
} else {
console.log(`[Discover] No results from engine "${engine}", trying next engine if available`);
}
} catch (err) {
console.error(`[Discover] Error searching with engine "${engine}": ${err instanceof Error ? err.message : String(err)}`);
}
}
return allResults;
}
/**
* Search for a specific category across multiple languages and engines
*/
export async function searchCategory(
category: string,
languages: string[] = [],
getQueries: (cat: string, lang?: string) => { site: string; keyword: string }[]
): Promise<any[]> {
console.log(`[Discover] Searching category "${category}" in languages: ${JSON.stringify(languages)}`);
// If no languages specified or empty array, search in English
if (!languages || languages.length === 0) {
const queries = getQueries(category);
const searchPromises = queries.map(query =>
searchWithMultipleEngines(`site:${query.site} ${query.keyword}`, '')
);
const results = await Promise.all(searchPromises);
return results.flat();
}
// If languages specified, search each language and combine results
const allResults = [];
for (const language of languages) {
console.log(`[Discover] Searching in language: ${language}`);
// Get language-specific engines if available, otherwise use defaults
const engines = LANGUAGE_SPECIFIC_ENGINES[language] || DEFAULT_ENGINES;
// Get language-specific queries
const queries = getQueries(category, language);
const searchPromises = queries.map(query => {
// For Chinese languages, don't use the site: operator
const isChinese = language.startsWith('zh');
const queryString = isChinese ? query.keyword : `site:${query.site} ${query.keyword}`;
return searchWithMultipleEngines(queryString, language, engines);
});
const results = await Promise.all(searchPromises);
allResults.push(...results.flat());
}
return allResults;
}
/**
* Helper function for default search behavior that supports multiple languages
*/
export async function getDefaultResults(
languages: string[] = [],
defaultSources: { site: string; keyword: string }[]
): Promise<any[]> {
console.log(`[Discover] Getting default results for languages: ${JSON.stringify(languages)}`);
// If no languages specified, search with no language filter
if (languages.length === 0) {
const searchPromises = defaultSources.map(query =>
searchWithMultipleEngines(`site:${query.site} ${query.keyword}`, '')
);
const results = await Promise.all(searchPromises);
return results.flat();
}
// Otherwise, search each language separately and combine results
let allResults: any[] = [];
for (const language of languages) {
console.log(`[Discover] Default search in language: ${language}`);
// Get language-specific engines if available, otherwise use defaults
const engines = LANGUAGE_SPECIFIC_ENGINES[language] || DEFAULT_ENGINES;
const searchPromises = defaultSources.map(query => {
// For Chinese languages, don't use the site: operator
const isChinese = language.startsWith('zh');
const queryString = isChinese ? query.keyword : `site:${query.site} ${query.keyword}`;
return searchWithMultipleEngines(queryString, language, engines);
});
const results = await Promise.all(searchPromises);
allResults.push(...results.flat());
}
return allResults;
}
/**
* Process results to filter and prepare for display
*/
export function processResults(results: any[]): any[] {
// Filter out items without thumbnails
const resultsWithThumbnails = results.filter((item) => item.thumbnail);
// If there are no results with thumbnails but we have results without thumbnails,
// use some of the results without thumbnails rather than showing nothing
let finalResults = resultsWithThumbnails;
if (resultsWithThumbnails.length === 0 && results.length > 0) {
console.log(`[Discover] No results with thumbnails found, using up to 10 results without thumbnails`);
finalResults = results.slice(0, 10); // Limit to 10 results without thumbnails
} else {
finalResults = resultsWithThumbnails;
}
// Shuffle the results
return finalResults.sort(() => Math.random() - 0.5);
}