Spaces:
Build error
Build error
| /** | |
| * Section 1: Backend Core - Search Integration | |
| * | |
| * Integrates DuckDuckGo search API for "Search Online" mode | |
| * Provides web search results to augment LLM responses | |
| */ | |
| /** | |
| * Search result interface | |
| */ | |
| export interface SearchResult { | |
| title: string; | |
| url: string; | |
| snippet: string; | |
| source?: string; | |
| } | |
| /** | |
| * Search the web using DuckDuckGo API | |
| * | |
| * @param query - Search query | |
| * @param maxResults - Maximum number of results to return | |
| * @returns Array of search results | |
| */ | |
| export async function searchOnline( | |
| query: string, | |
| maxResults: number = 5 | |
| ): Promise<SearchResult[]> { | |
| try { | |
| if (!query || query.trim().length === 0) { | |
| return []; | |
| } | |
| console.log(`Searching for: "${query}"`); | |
| // Use DuckDuckGo search API (no authentication required for basic searches) | |
| // We'll use the instant answer API which is free and doesn't require authentication | |
| const searchUrl = new URL("https://api.duckduckgo.com/"); | |
| searchUrl.searchParams.set("q", query); | |
| searchUrl.searchParams.set("format", "json"); | |
| searchUrl.searchParams.set("no_html", "1"); | |
| searchUrl.searchParams.set("skip_disambig", "1"); | |
| const response = await fetch(searchUrl.toString(), { | |
| headers: { | |
| "User-Agent": | |
| "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", | |
| }, | |
| }); | |
| if (!response.ok) { | |
| console.error(`DuckDuckGo API error: ${response.status}`); | |
| return []; | |
| } | |
| const data = (await response.json()) as any; | |
| // Parse DuckDuckGo response | |
| const results: SearchResult[] = []; | |
| // Add instant answer if available | |
| if (data.AbstractText) { | |
| results.push({ | |
| title: data.AbstractTitle || "Answer", | |
| url: data.AbstractURL || "", | |
| snippet: data.AbstractText, | |
| source: "DuckDuckGo Instant Answer", | |
| }); | |
| } | |
| // Add related topics | |
| if (data.RelatedTopics && Array.isArray(data.RelatedTopics)) { | |
| for (const topic of data.RelatedTopics.slice(0, maxResults - results.length)) { | |
| if (topic.FirstURL && topic.Text) { | |
| results.push({ | |
| title: topic.FirstURL.split("/")[2] || "Result", | |
| url: topic.FirstURL, | |
| snippet: topic.Text.substring(0, 200), | |
| source: "DuckDuckGo", | |
| }); | |
| } | |
| } | |
| } | |
| // If no results from instant answer, try fetching from search results | |
| if (results.length === 0) { | |
| return await searchDuckDuckGoWeb(query, maxResults); | |
| } | |
| return results.slice(0, maxResults); | |
| } catch (error) { | |
| console.error("Search error:", error); | |
| return []; | |
| } | |
| } | |
| /** | |
| * Fallback search using DuckDuckGo HTML scraping | |
| * (Limited by rate limiting and reliability) | |
| */ | |
| async function searchDuckDuckGoWeb( | |
| query: string, | |
| maxResults: number | |
| ): Promise<SearchResult[]> { | |
| try { | |
| // This is a simplified fallback - in production, consider using a dedicated search API | |
| const searchUrl = `https://html.duckduckgo.com/?q=${encodeURIComponent(query)}&t=h_&ia=web`; | |
| const response = await fetch(searchUrl, { | |
| headers: { | |
| "User-Agent": | |
| "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", | |
| }, | |
| }); | |
| if (!response.ok) { | |
| return []; | |
| } | |
| // Basic HTML parsing (would need cheerio or similar in production) | |
| // For now, return empty to avoid complexity | |
| return []; | |
| } catch (error) { | |
| console.error("DuckDuckGo web search error:", error); | |
| return []; | |
| } | |
| } | |
| /** | |
| * Format search results into a context string for the LLM | |
| * | |
| * @param results - Array of search results | |
| * @returns Formatted string for LLM context | |
| */ | |
| export function formatSearchResults(results: SearchResult[]): string { | |
| if (results.length === 0) { | |
| return ""; | |
| } | |
| let formatted = "## Search Results\n\n"; | |
| results.forEach((result, index) => { | |
| formatted += `${index + 1}. **${result.title}**\n`; | |
| formatted += ` URL: ${result.url}\n`; | |
| formatted += ` ${result.snippet}\n\n`; | |
| }); | |
| formatted += "---\n"; | |
| return formatted; | |
| } | |
| /** | |
| * Validate and sanitize search query | |
| * | |
| * @param query - Raw search query | |
| * @returns Sanitized query | |
| */ | |
| export function sanitizeSearchQuery(query: string): string { | |
| // Remove potentially harmful characters | |
| return query | |
| .replace(/[<>]/g, "") | |
| .trim() | |
| .substring(0, 500); // Limit query length | |
| } | |