Closed bannert1337 closed 1 day 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.
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
}
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
}
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
}
}
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());
}
}
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;
}
}
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
}
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
}
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
}
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.
Great, that is comprehensive. I will have a look at it.
Closed because has been open for too long
It would be a great addition to the already implemented search providers offering a free and self-hostable provider.