126 lines
3.2 KiB
TypeScript
126 lines
3.2 KiB
TypeScript
|
|
/**
|
|||
|
|
* 巡樓網路搜尋 — 委派至 lib/search(Brave Search API)。
|
|||
|
|
*
|
|||
|
|
* 允許的搜尋來源(全專案):
|
|||
|
|
* 1. Threads API(lib/threads-api / lib/search)
|
|||
|
|
* 2. Brave Search API(lib/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;
|
|||
|
|
}
|