devoxx / DevoxxGenieIDEAPlugin

DevoxxGenie is a plugin for IntelliJ IDEA that uses local LLM's (Ollama, LMStudio, GPT4All, Llama.cpp and Exo) and Cloud based LLMs to help review, test, explain your project code.
https://devoxx.com
MIT License
137 stars 22 forks source link

Feature Request: Add support for SearXNG #197

Closed bannert1337 closed 1 day ago

bannert1337 commented 1 month ago

It would be a great addition to the already implemented search providers offering a free and self-hostable provider.

stephanj commented 1 month ago

This is what needs to happen + we accept PR's :)

To support SearXNG as another search engine in the DevoxxGenieIDEAPlugin, we'll need to make several modifications to the existing codebase. I'll guide you through the process step by step, providing code examples for each modification.

  1. Add SearXNG to the ModelProvider enum:

First, we need to add SearXNG to the ModelProvider enum. Open the ModelProvider.java file and add a new entry:

public enum ModelProvider {
    // ... existing entries ...
    SearXNG("SearXNG");

    // ... rest of the enum implementation
}
  1. Update the DevoxxGenieStateService:

We need to add a new field to store the SearXNG URL. Open DevoxxGenieStateService.java and add the following:

@Getter
@Setter
public final class DevoxxGenieStateService implements PersistentStateComponent<DevoxxGenieStateService>, DevoxxGenieSettingsService {
    // ... existing fields ...

    private String searxngUrl = "https://your-default-searxng-instance.com";

    // ... rest of the class implementation
}
  1. Update the LLMProvidersComponent:

We need to add a new field for the SearXNG URL in the settings UI. Open LLMProvidersComponent.java and add:

public class LLMProvidersComponent extends AbstractSettingsComponent {
    // ... existing fields ...

    @Getter
    private final JTextField searxngUrlField = new JTextField(stateService.getSearxngUrl());

    // ... existing methods ...

    @Override
    public JPanel createPanel() {
        // ... existing code ...

        addSection(panel, gbc, "Search Providers");
        // ... existing search provider fields ...
        addSettingRow(panel, gbc, "SearXNG URL", searxngUrlField);

        // ... rest of the method
    }
}
  1. Update the LLMProvidersConfigurable:

We need to handle the new SearXNG URL field in the settings configuration. Open LLMProvidersConfigurable.java and update the following methods:

public class LLMProvidersConfigurable implements Configurable {
    // ... existing code ...

    @Override
    public boolean isModified() {
        // ... existing checks ...
        isModified |= isFieldModified(llmSettingsComponent.getSearxngUrlField(), settings.getSearxngUrl());
        return isModified;
    }

    @Override
    public void apply() {
        // ... existing code ...
        settings.setSearxngUrl(llmSettingsComponent.getSearxngUrlField().getText());
    }

    @Override
    public void reset() {
        // ... existing code ...
        llmSettingsComponent.getSearxngUrlField().setText(settings.getSearxngUrl());
    }
}
  1. Create a SearXNG search engine implementation:

Create a new file called SearXNGWebSearchEngine.java in the appropriate package:

package com.devoxx.genie.service.search;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.retriever.Retriever;
import dev.langchain4j.web.search.WebSearchEngine;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;

public class SearXNGWebSearchEngine implements WebSearchEngine {

    private final String baseUrl;
    private final OkHttpClient client;
    private final ObjectMapper objectMapper;

    public SearXNGWebSearchEngine(String baseUrl) {
        this.baseUrl = baseUrl;
        this.client = new OkHttpClient();
        this.objectMapper = new ObjectMapper();
    }

    @Override
    public List<TextSegment> search(String query, int maxResults) {
        String url = baseUrl + "/search?q=" + query + "&format=json&engines=general&language=en";
        Request request = new Request.Builder().url(url).build();

        try (Response response = client.newCall(request).execute()) {
            if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

            SearXNGResponse searxngResponse = objectMapper.readValue(response.body().string(), SearXNGResponse.class);

            return searxngResponse.results.stream()
                .limit(maxResults)
                .map(result -> TextSegment.from(result.content, Document.from(result.content, result.url)))
                .collect(Collectors.toList());
        } catch (IOException e) {
            throw new RuntimeException("Error executing SearXNG search", e);
        }
    }

    private static class SearXNGResponse {
        @JsonProperty("results")
        List<SearXNGResult> results;
    }

    private static class SearXNGResult {
        @JsonProperty("url")
        String url;

        @JsonProperty("content")
        String content;
    }
}
  1. Update the WebSearchService:

Modify the WebSearchService.java file to include SearXNG:

public class WebSearchService {
    // ... existing code ...

    private @Nullable WebSearchEngine createWebSearchEngine(@NotNull String searchType) {
        DevoxxGenieSettingsService settings = DevoxxGenieSettingsServiceProvider.getInstance();

        if (searchType.equals(TAVILY_SEARCH_ACTION) && settings.getTavilySearchKey() != null) {
            return TavilyWebSearchEngine.builder()
                .apiKey(settings.getTavilySearchKey())
                .build();
        } else if (searchType.equals(GOOGLE_SEARCH_ACTION) &&
            settings.getGoogleSearchKey() != null &&
            settings.getGoogleCSIKey() != null) {
            return GoogleCustomWebSearchEngine.builder()
                .apiKey(settings.getGoogleSearchKey())
                .csi(settings.getGoogleCSIKey())
                .build();
        } else if (searchType.equals(SEARXNG_SEARCH_ACTION) &&
            settings.getSearxngUrl() != null) {
            return new SearXNGWebSearchEngine(settings.getSearxngUrl());
        }
        return null;
    }

    // ... rest of the class
}
  1. Update the Constant class:

Add a new constant for the SearXNG search action in the Constant.java file:

public class Constant {
    // ... existing constants ...

    public static final String SEARXNG_SEARCH_ACTION = "searxngSearch";

    // ... rest of the class
}
  1. Update the ActionButtonsPanel:

Modify the ActionButtonsPanel.java file to include a new button for SearXNG search:

public class ActionButtonsPanel extends JPanel implements SettingsChangeListener {
    // ... existing code ...

    private final JButton searxngSearchBtn = new JHoverButton(WebSearchIcon, true);

    // ... existing methods ...

    public void setupUI() {
        // ... existing code ...

        JPanel searchPanel = new JPanel(new FlowLayout());
        createSearchButton(searchPanel, tavilySearchBtn, TAVILY_SEARCH_ACTION, SEARCH_THE_WEB_WITH_TAVILY_FOR_AN_ANSWER);
        createSearchButton(searchPanel, googleSearchBtn, GOOGLE_SEARCH_ACTION, SEARCH_GOOGLE_FOR_AN_ANSWER);
        createSearchButton(searchPanel, searxngSearchBtn, SEARXNG_SEARCH_ACTION, SEARCH_SEARXNG_FOR_AN_ANSWER);
        add(searchPanel, BorderLayout.CENTER);

        // ... rest of the method
    }

    public void configureSearchButtonsVisibility() {
        if (DevoxxGenieSettingsServiceProvider.getInstance().getHideSearchButtonsFlag()) {
            tavilySearchBtn.setVisible(false);
            googleSearchBtn.setVisible(false);
            searxngSearchBtn.setVisible(false);
        } else {
            // ... existing visibility checks ...
            searxngSearchBtn.setVisible(!DevoxxGenieSettingsServiceProvider.getInstance().getSearxngUrl().isEmpty());
        }
    }

    // ... rest of the class
}
  1. Update the ChatMessageContextUtil:

Modify the ChatMessageContextUtil.java file to include SearXNG in the web search request check:

public class ChatMessageContextUtil {
    // ... existing code ...

    public static @NotNull ChatMessageContext createContext(/* ... existing parameters ... */) {
        ChatMessageContext context = ChatMessageContext.builder()
            // ... existing builder code ...
            .webSearchRequested(actionCommand.equals(Constant.TAVILY_SEARCH_ACTION) ||
                                actionCommand.equals(Constant.GOOGLE_SEARCH_ACTION) ||
                                actionCommand.equals(Constant.SEARXNG_SEARCH_ACTION))
            .build();

        // ... rest of the method
    }

    // ... rest of the class
}

These modifications will add support for SearXNG as a new search engine in the DevoxxGenieIDEAPlugin. Remember to update any relevant documentation or user guides to reflect this new feature. Also, ensure that you handle any potential errors or edge cases that may arise from using SearXNG, such as network issues or API changes.

bannert1337 commented 1 month ago

Great, that is comprehensive. I will have a look at it.

stephanj commented 1 day ago

Closed because has been open for too long