deep-research / src /hooks /useWebSearch.ts
Leon4gr45's picture
Deploy app
c16e487 verified
import { useSettingStore } from "@/store/setting";
import {
createSearchProvider,
type SearchProviderOptions,
} from "@/utils/deep-research/search";
import { multiApiKeyPolling } from "@/utils/model";
import { generateSignature } from "@/utils/signature";
function normalizeDomain(input: string) {
return input
.trim()
.toLowerCase()
.replace(/^https?:\/\//, "")
.replace(/^www\./, "")
.replace(/^\*\./, "")
.replace(/\/.*$/, "")
.replace(/:\d+$/, "");
}
function parseDomainList(value: string) {
return value
.split(/[\s,\n]+/g)
.map((item) => normalizeDomain(item))
.filter((item) => item.length > 0);
}
function matchDomain(hostname: string, domain: string) {
return hostname === domain || hostname.endsWith(`.${domain}`);
}
function isUrlAllowed(
url: string,
includeDomains: string[],
excludeDomains: string[]
) {
try {
const hostname = normalizeDomain(new URL(url).hostname);
if (excludeDomains.some((domain) => matchDomain(hostname, domain))) {
return false;
}
if (includeDomains.length === 0) {
return true;
}
return includeDomains.some((domain) => matchDomain(hostname, domain));
} catch {
return includeDomains.length === 0;
}
}
function applyDomainFilters(
result: { sources: Source[]; images: ImageSource[] },
includeDomains: string[],
excludeDomains: string[]
) {
if (includeDomains.length === 0 && excludeDomains.length === 0) {
return result;
}
return {
sources: result.sources.filter((source) =>
isUrlAllowed(source.url, includeDomains, excludeDomains)
),
images: result.images.filter((image) =>
isUrlAllowed(image.url, includeDomains, excludeDomains)
),
};
}
function useWebSearch() {
async function search(query: string) {
const {
mode,
searchProvider,
searchMaxResult,
accessPassword,
searchIncludeDomains,
searchExcludeDomains,
} = useSettingStore.getState();
const options: SearchProviderOptions = {
provider: searchProvider,
maxResult: searchMaxResult,
query,
};
const includeDomains = parseDomainList(searchIncludeDomains);
const excludeDomains = parseDomainList(searchExcludeDomains);
switch (searchProvider) {
case "tavily":
const { tavilyApiKey, tavilyApiProxy, tavilyScope } =
useSettingStore.getState();
if (mode === "local") {
options.baseURL = tavilyApiProxy;
options.apiKey = multiApiKeyPolling(tavilyApiKey);
} else {
options.baseURL = typeof window !== "undefined" ? `${window.location.origin}/api/search/tavily` : "/api/search/tavily";
}
options.scope = tavilyScope;
break;
case "firecrawl":
const { firecrawlApiKey, firecrawlApiProxy } =
useSettingStore.getState();
if (mode === "local") {
options.baseURL = firecrawlApiProxy;
options.apiKey = multiApiKeyPolling(firecrawlApiKey);
} else {
options.baseURL = typeof window !== "undefined" ? `${window.location.origin}/api/search/firecrawl` : "/api/search/firecrawl";
}
break;
case "exa":
const { exaApiKey, exaApiProxy, exaScope } = useSettingStore.getState();
if (mode === "local") {
options.baseURL = exaApiProxy;
options.apiKey = multiApiKeyPolling(exaApiKey);
} else {
options.baseURL = typeof window !== "undefined" ? `${window.location.origin}/api/search/exa` : "/api/search/exa";
}
options.scope = exaScope;
break;
case "bocha":
const { bochaApiKey, bochaApiProxy } = useSettingStore.getState();
if (mode === "local") {
options.baseURL = bochaApiProxy;
options.apiKey = multiApiKeyPolling(bochaApiKey);
} else {
options.baseURL = typeof window !== "undefined" ? `${window.location.origin}/api/search/bocha` : "/api/search/bocha";
}
break;
case "brave":
const { braveApiKey, braveApiProxy } = useSettingStore.getState();
if (mode === "local") {
options.baseURL = braveApiProxy;
options.apiKey = multiApiKeyPolling(braveApiKey);
} else {
options.baseURL = typeof window !== "undefined" ? `${window.location.origin}/api/search/brave` : "/api/search/brave";
}
break;
case "searxng":
const { searxngApiProxy, searxngScope } = useSettingStore.getState();
if (mode === "local") {
options.baseURL = searxngApiProxy;
} else {
options.baseURL = typeof window !== "undefined" ? `${window.location.origin}/api/search/searxng` : "/api/search/searxng";
}
options.scope = searxngScope;
break;
default:
break;
}
if (mode === "proxy") {
options.apiKey = generateSignature(accessPassword, Date.now());
}
const result = await createSearchProvider(options);
return applyDomainFilters(result, includeDomains, excludeDomains);
}
return { search };
}
export default useWebSearch;