Merge pull request #392 from Aki-07/feature/add-searxng-connector

Add Searxng connector
This commit is contained in:
Rohan Verma 2025-10-13 13:41:37 -07:00 committed by GitHub
commit 08661e686c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 838 additions and 7 deletions

View file

@ -16,6 +16,38 @@ import {
useSearchSourceConnectors,
} from "@/hooks/use-search-source-connectors";
const normalizeListInput = (value: unknown): string[] => {
if (Array.isArray(value)) {
return value.map((item) => String(item).trim()).filter((item) => item.length > 0);
}
if (typeof value === "string") {
return value
.split(",")
.map((item) => item.trim())
.filter((item) => item.length > 0);
}
return [];
};
const arraysEqual = (a: string[], b: string[]): boolean => {
if (a.length !== b.length) return false;
return a.every((value, index) => value === b[index]);
};
const normalizeBoolean = (value: unknown): boolean | null => {
if (typeof value === "boolean") return value;
if (typeof value === "string") {
const lowered = value.trim().toLowerCase();
if (["true", "1", "yes", "on"].includes(lowered)) return true;
if (["false", "0", "no", "off"].includes(lowered)) return false;
}
if (typeof value === "number") {
if (value === 1) return true;
if (value === 0) return false;
}
return null;
};
export function useConnectorEditPage(connectorId: number, searchSpaceId: string) {
const router = useRouter();
const {
@ -48,6 +80,13 @@ export function useConnectorEditPage(connectorId: number, searchSpaceId: string)
NOTION_INTEGRATION_TOKEN: "",
SERPER_API_KEY: "",
TAVILY_API_KEY: "",
SEARXNG_HOST: "",
SEARXNG_API_KEY: "",
SEARXNG_ENGINES: "",
SEARXNG_CATEGORIES: "",
SEARXNG_LANGUAGE: "",
SEARXNG_SAFESEARCH: "",
SEARXNG_VERIFY_SSL: "",
LINEAR_API_KEY: "",
DISCORD_BOT_TOKEN: "",
CONFLUENCE_BASE_URL: "",
@ -74,6 +113,23 @@ export function useConnectorEditPage(connectorId: number, searchSpaceId: string)
NOTION_INTEGRATION_TOKEN: config.NOTION_INTEGRATION_TOKEN || "",
SERPER_API_KEY: config.SERPER_API_KEY || "",
TAVILY_API_KEY: config.TAVILY_API_KEY || "",
SEARXNG_HOST: config.SEARXNG_HOST || "",
SEARXNG_API_KEY: config.SEARXNG_API_KEY || "",
SEARXNG_ENGINES: Array.isArray(config.SEARXNG_ENGINES)
? config.SEARXNG_ENGINES.join(", ")
: config.SEARXNG_ENGINES || "",
SEARXNG_CATEGORIES: Array.isArray(config.SEARXNG_CATEGORIES)
? config.SEARXNG_CATEGORIES.join(", ")
: config.SEARXNG_CATEGORIES || "",
SEARXNG_LANGUAGE: config.SEARXNG_LANGUAGE || "",
SEARXNG_SAFESEARCH:
config.SEARXNG_SAFESEARCH !== undefined && config.SEARXNG_SAFESEARCH !== null
? String(config.SEARXNG_SAFESEARCH)
: "",
SEARXNG_VERIFY_SSL:
config.SEARXNG_VERIFY_SSL !== undefined && config.SEARXNG_VERIFY_SSL !== null
? String(config.SEARXNG_VERIFY_SSL)
: "",
LINEAR_API_KEY: config.LINEAR_API_KEY || "",
LINKUP_API_KEY: config.LINKUP_API_KEY || "",
DISCORD_BOT_TOKEN: config.DISCORD_BOT_TOKEN || "",
@ -238,6 +294,93 @@ export function useConnectorEditPage(connectorId: number, searchSpaceId: string)
newConfig = { TAVILY_API_KEY: formData.TAVILY_API_KEY };
}
break;
case "SEARXNG_API": {
const host = (formData.SEARXNG_HOST || "").trim();
if (!host) {
toast.error("SearxNG host is required.");
setIsSaving(false);
return;
}
const candidateConfig: Record<string, any> = { SEARXNG_HOST: host };
let hasChanges = host !== (originalConfig.SEARXNG_HOST || "").trim();
const apiKey = (formData.SEARXNG_API_KEY || "").trim();
const originalApiKey = (originalConfig.SEARXNG_API_KEY || "").trim();
if (apiKey !== originalApiKey) {
candidateConfig.SEARXNG_API_KEY = apiKey || null;
hasChanges = true;
}
const newEngines = normalizeListInput(formData.SEARXNG_ENGINES || "");
const originalEngines = normalizeListInput(originalConfig.SEARXNG_ENGINES);
if (!arraysEqual(newEngines, originalEngines)) {
candidateConfig.SEARXNG_ENGINES = newEngines;
hasChanges = true;
}
const newCategories = normalizeListInput(formData.SEARXNG_CATEGORIES || "");
const originalCategories = normalizeListInput(originalConfig.SEARXNG_CATEGORIES);
if (!arraysEqual(newCategories, originalCategories)) {
candidateConfig.SEARXNG_CATEGORIES = newCategories;
hasChanges = true;
}
const language = (formData.SEARXNG_LANGUAGE || "").trim();
const originalLanguage = (originalConfig.SEARXNG_LANGUAGE || "").trim();
if (language !== originalLanguage) {
candidateConfig.SEARXNG_LANGUAGE = language || null;
hasChanges = true;
}
const safesearchRaw = (formData.SEARXNG_SAFESEARCH || "").trim();
const originalSafesearch = originalConfig.SEARXNG_SAFESEARCH;
if (safesearchRaw) {
const parsed = Number(safesearchRaw);
if (
Number.isNaN(parsed) ||
!Number.isInteger(parsed) ||
parsed < 0 ||
parsed > 2
) {
toast.error("SearxNG SafeSearch must be 0, 1, or 2.");
setIsSaving(false);
return;
}
if (parsed !== Number(originalSafesearch)) {
candidateConfig.SEARXNG_SAFESEARCH = parsed;
hasChanges = true;
}
} else if (originalSafesearch !== undefined && originalSafesearch !== null) {
candidateConfig.SEARXNG_SAFESEARCH = null;
hasChanges = true;
}
const verifyRaw = (formData.SEARXNG_VERIFY_SSL || "").trim().toLowerCase();
const originalVerifyBool = normalizeBoolean(originalConfig.SEARXNG_VERIFY_SSL);
if (verifyRaw) {
let parsedBool: boolean | null = null;
if (["true", "1", "yes", "on"].includes(verifyRaw)) parsedBool = true;
else if (["false", "0", "no", "off"].includes(verifyRaw)) parsedBool = false;
if (parsedBool === null) {
toast.error("SearxNG SSL verification must be true or false.");
setIsSaving(false);
return;
}
if (parsedBool !== originalVerifyBool) {
candidateConfig.SEARXNG_VERIFY_SSL = parsedBool;
hasChanges = true;
}
} else if (originalVerifyBool !== null) {
candidateConfig.SEARXNG_VERIFY_SSL = null;
hasChanges = true;
}
if (hasChanges) {
newConfig = candidateConfig;
}
break;
}
case "LINEAR_CONNECTOR":
if (formData.LINEAR_API_KEY !== originalConfig.LINEAR_API_KEY) {
@ -367,6 +510,33 @@ export function useConnectorEditPage(connectorId: number, searchSpaceId: string)
editForm.setValue("SERPER_API_KEY", newlySavedConfig.SERPER_API_KEY || "");
} else if (connector.connector_type === "TAVILY_API") {
editForm.setValue("TAVILY_API_KEY", newlySavedConfig.TAVILY_API_KEY || "");
} else if (connector.connector_type === "SEARXNG_API") {
editForm.setValue("SEARXNG_HOST", newlySavedConfig.SEARXNG_HOST || "");
editForm.setValue("SEARXNG_API_KEY", newlySavedConfig.SEARXNG_API_KEY || "");
editForm.setValue(
"SEARXNG_ENGINES",
normalizeListInput(newlySavedConfig.SEARXNG_ENGINES).join(", ")
);
editForm.setValue(
"SEARXNG_CATEGORIES",
normalizeListInput(newlySavedConfig.SEARXNG_CATEGORIES).join(", ")
);
editForm.setValue(
"SEARXNG_LANGUAGE",
newlySavedConfig.SEARXNG_LANGUAGE || ""
);
editForm.setValue(
"SEARXNG_SAFESEARCH",
newlySavedConfig.SEARXNG_SAFESEARCH === null ||
newlySavedConfig.SEARXNG_SAFESEARCH === undefined
? ""
: String(newlySavedConfig.SEARXNG_SAFESEARCH)
);
const verifyValue = normalizeBoolean(newlySavedConfig.SEARXNG_VERIFY_SSL);
editForm.setValue(
"SEARXNG_VERIFY_SSL",
verifyValue === null ? "" : String(verifyValue)
);
} else if (connector.connector_type === "LINEAR_CONNECTOR") {
editForm.setValue("LINEAR_API_KEY", newlySavedConfig.LINEAR_API_KEY || "");
} else if (connector.connector_type === "LINKUP_API") {

View file

@ -23,7 +23,7 @@ export const getConnectorTypeDisplay = (type: string): string => {
const typeMap: Record<string, string> = {
SERPER_API: "Serper API",
TAVILY_API: "Tavily API",
// Add other connector types here as needed
SEARXNG_API: "SearxNG",
};
return typeMap[type] || type;
};