aboutsummaryrefslogtreecommitdiff
path: root/modules/core/module-searxng.js
blob: 24db0f5e1ce13c94ba7fa733476d3de0552a798c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
class SearXNGModule {
    constructor() {
        this.commands = {};
        this.terminal = null;
    }
    getHelpInfo() {
        return {
            'search': 'search <query> [--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 <query> [--summarize] [--fetch] [--images] [--videos] — Alias for search',
            'images': 'images <query> — Search for images',
            'videos': 'videos <query> — 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 <query> [--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 <query> or videos <query>", "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');

        let params = 'q=' + encodeURIComponent(query) + '&categories=' + category;
        if (summarize) params += '&summarize=true';
        if (fetchContent) params += '&fetch_content=true';

        try {
            const resp = await fetch('/api/search?' + params);
            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(`
                        <div class="search-result search-result-image">
                            <a href="${r.url}" target="_blank"><img src="${imgSrc}" style="max-width:200px;max-height:150px;border-radius:4px;object-fit:cover;" alt="${r.title || ''}" loading="lazy"></a>
                            <br/><strong>${i + 1}. <a href="${r.url}" target="_blank" style="color: #00aaff;">${r.title || 'No Title'}</a></strong>
                        </div>
                    `);
                } else if (category === 'videos' && videoThumb) {
                    this.terminal.printHTML(`
                        <div class="search-result search-result-video">
                            <a href="${r.url}" target="_blank"><img src="${videoThumb}" style="max-width:200px;max-height:150px;border-radius:4px;object-fit:cover;" alt="${r.title || ''}" loading="lazy"></a>
                            <br/><strong>${i + 1}. <a href="${r.url}" target="_blank" style="color: #00aaff;">${r.title || 'No Title'}</a></strong><br/>
                            <span style="color: #888888; font-size: 0.85em;">${r.url || ''}</span>
                        </div>
                    `);
                } else {
                    this.terminal.printHTML(`
                        <div class="search-result">
                            <strong>${i + 1}. <a href="${r.url}" target="_blank" style="color: #00aaff;">${r.title || 'No Title'}</a></strong><br/>
                            <span style="color: #888888; font-size: 0.85em;">${r.url || 'No URL'}</span><br/>
                            <span style="color: #00ff00; font-size: 0.85em;">${(snippet || '').substring(0, 200)}...</span>
                            ${contentSnippet ? `<br/><span style="color: #aaaaaa; font-size: 0.8em;">📄 ${contentSnippet.substring(0, 150)}...</span>` : ''}
                            ${imgSrc && category === 'general' ? `<br/><a href="${imgSrc}" target="_blank" style="color: #ff8800; font-size: 0.8em;">🖼️ View image</a>` : ''}
                        </div>
                    `);
                }
            });
        } catch (e) {
            this.terminal.print('Search failed: ' + e.message, 'error');
        }
    }
}