haixunMaster/lib/services/web-search.ts

126 lines
3.2 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 巡樓網路搜尋 — 委派至 lib/searchBrave Search API
*
* 允許的搜尋來源(全專案):
* 1. Threads APIlib/threads-api / lib/search
* 2. Brave Search APIlib/search
* 3. 瀏覽器爬蟲lib/threads-browser
*
* 以下 provider 已移除,不得再接入:
* SerpAPI, Google Search API, Google Custom Search, Bing Search API,
* Serper, Tavily, Exa, DuckDuckGo wrapper, SearXNG, 其他第三方 SERP。
*/
import {
detectLegacySearchEnvKeys,
executeBraveSearch,
getSearchConfig,
LEGACY_SEARCH_ENV_KEYS,
} from "@/lib/search";
export type SearchProvider = "brave";
export { detectLegacySearchEnvKeys, LEGACY_SEARCH_ENV_KEYS };
export interface WebSearchResult {
title: string;
snippet: string;
link: string;
provider: SearchProvider;
}
const PROVIDER_LABELS: Record<SearchProvider, string> = {
brave: "Brave Search",
};
export function getProviderLabel(provider: SearchProvider): string {
return PROVIDER_LABELS[provider];
}
function toWebResults(
results: Array<{ title: string; snippet: string; url: string }>
): WebSearchResult[] {
return results.map((item) => ({
title: item.title,
snippet: item.snippet,
link: item.url,
provider: "brave" as const,
}));
}
export interface BraveWebSearchOptions {
limit?: number;
/** 巡邏模式:檢查 high priority + 每日額度(預設依 context */
patrolMode?: boolean;
priority?: "high" | "medium" | "low";
/** 僅保留 threads.com 結果 */
threadsOnly?: boolean;
}
/** Brave Search API需 BRAVE_SEARCH_API_KEY */
export async function searchBrave(
query: string,
limit = 5,
options?: BraveWebSearchOptions
): Promise<WebSearchResult[]> {
const cfg = getSearchConfig().brave;
if (!cfg.enabled || !cfg.apiKey) return [];
const patrolMode = options?.patrolMode ?? true;
const executed = await executeBraveSearch({
keyword: query,
query,
limit,
priority: options?.priority ?? (patrolMode ? "high" : undefined),
patrolMode,
threadsOnly: options?.threadsOnly ?? true,
});
if (executed.status === "skipped" && executed.skipReason !== "cache_hit") {
return [];
}
return toWebResults(executed.results);
}
/** 單一查詢Brave */
export async function searchWeb(
query: string,
limit = 5,
options?: BraveWebSearchOptions
): Promise<{
results: WebSearchResult[];
provider: SearchProvider;
providerLabel: string;
}> {
const results = await searchBrave(query, limit, options);
return {
results,
provider: "brave",
providerLabel: results.length > 0 ? PROVIDER_LABELS.brave : "無搜尋結果",
};
}
/** 網路搜尋入口(僅 Brave */
export async function searchWebThorough(
query: string,
limit = 6,
options?: BraveWebSearchOptions
): Promise<{
results: WebSearchResult[];
provider: SearchProvider;
providerLabel: string;
sources: SearchProvider[];
}> {
const results = await searchBrave(query, limit, options);
return {
results,
provider: "brave",
sources: results.length > 0 ? ["brave"] : [],
providerLabel: results.length > 0 ? PROVIDER_LABELS.brave : "無搜尋結果",
};
}
export function isBraveSearchConfigured(): boolean {
const cfg = getSearchConfig().brave;
return cfg.enabled && !!cfg.apiKey;
}