class SearXNGModule { constructor() { this.commands = {}; this.terminal = null; } getHelpInfo() { return { 'search': 'search [--summarize/-s] [--fetch/-f] [--images/-i] [--videos/-v] — Search using SearXNG; --summarize/-s adds AI overview, --fetch/-f fetches page content, --images/-i searches for images, --videos/-v searches for videos', 's': 's [--summarize] [--fetch] [--images] [--videos] — Alias for search', 'images': 'images — Search for images', 'videos': 'videos — Search for videos', }; } registerCommands() { this.commands['search'] = (args) => this.search(args); this.commands['s'] = (args) => this.search(args); this.commands['images'] = (args) => this.search(args, 'images'); this.commands['videos'] = (args) => this.search(args, 'videos'); } async search(args, forceCategory) { if (!args[0] && !forceCategory) { this.terminal.print("Usage: search [--summarize] [--fetch] [--images] [--videos]", "warning"); this.terminal.print("Example: search quantum computing --summarize", "info"); this.terminal.print("Example: search cats --images", "info"); this.terminal.print("Example: search cats --videos", "info"); this.terminal.print("Shortcuts: images or videos ", "info"); return; } const argsList = args.slice(); const summarize = argsList.includes('--summarize') || argsList.includes('-s'); const fetchContent = argsList.includes('--fetch') || argsList.includes('-f'); const wantImages = forceCategory === 'images' || argsList.includes('--images') || argsList.includes('-i'); const wantVideos = forceCategory === 'videos' || argsList.includes('--videos') || argsList.includes('-v'); const query = argsList.filter(a => !a.startsWith('-')).join(' '); if (!query) { this.terminal.print('Search query is required', 'warning'); return; } const category = wantImages ? 'images' : (wantVideos ? 'videos' : 'general'); const categoryLabel = category === 'images' ? '🖼️' : (category === 'videos' ? '🎬' : '🔍'); this.terminal.print(`${categoryLabel} Searching ${category}: ${query}`, 'info'); if (summarize) this.terminal.print('⏳ AI overview will be generated (1-3s)...', 'info'); let params = 'q=' + encodeURIComponent(query) + '&categories=' + category; if (summarize) params += '&summarize=true'; if (fetchContent) params += '&fetch_content=true'; try { // 30s client-side timeout (matches module-fetcher) const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), 30000); const resp = await fetch('/api/search?' + params, { signal: controller.signal }); clearTimeout(timer); if (!resp.ok) { const err = await resp.json().catch(() => ({})); this.terminal.print('Search failed: ' + (err.error || 'HTTP ' + resp.status), 'error'); return; } const data = await resp.json(); const results = data.results || []; if (results.length === 0) { this.terminal.print('No results found.', 'info'); return; } this.terminal.print(`\n=== ${results.length} results (${category}, via ${data.source || 'unknown'}) ===\n`, 'success'); if (data.ai_overview) { this.terminal.print('🤖 AI Overview:', 'info'); this.terminal.print(data.ai_overview, 'ollama'); this.terminal.print(''); } results.forEach((r, i) => { const snippet = r.content || ''; const contentSnippet = r.content_snippet || ''; const imgSrc = r.img_src || r.thumbnail_src || ''; const videoThumb = r.thumbnail || ''; if (category === 'images' && imgSrc) { this.terminal.printHTML(` `); } else if (category === 'videos' && videoThumb) { this.terminal.printHTML(`
${r.title || ''}
${i + 1}. ${r.title || 'No Title'}
${r.url || ''}
`); } else { this.terminal.printHTML(`
${i + 1}. ${r.title || 'No Title'}
${r.url || 'No URL'}
${(snippet || '').substring(0, 200)}... ${contentSnippet ? `
📄 ${contentSnippet.substring(0, 150)}...` : ''} ${imgSrc && category === 'general' ? `
🖼️ View image` : ''}
`); } }); } catch (e) { if (e.name === 'AbortError') { this.terminal.print('⏱ Search timed out (30s). Try removing --summarize or --fetch flags.', 'error'); } else { this.terminal.print('Search failed: ' + e.message, 'error'); } } } }