Origem: D:/Documents/Github/OllaHub Arquivos omitidos (segredos, bancos de dados, logs, >50 MB): 0
This commit is contained in:
1 parent
a7cbd1a3b4
commit
861af0bb9f
217 files changed
+39421
-1340
No files matched your search
+75
-73
@@ -1,9 +1,9 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { SearchCategory, useSettingsStore } from '@/store/settings-store';
|
||||
import {
|
||||
retryWithBackoff,
|
||||
withTimeout,
|
||||
isRetryableError,
|
||||
import {
|
||||
retryWithBackoff,
|
||||
withTimeout,
|
||||
isRetryableError,
|
||||
calculateEngineTimeout,
|
||||
TIMEOUT_CONFIG
|
||||
} from '@/lib/retry-utils';
|
||||
@@ -15,6 +15,7 @@ export interface ScrapedContent {
|
||||
content: string;
|
||||
markdown: string;
|
||||
cached: boolean;
|
||||
snippet?: string;
|
||||
}
|
||||
|
||||
export interface SearchConfig {
|
||||
@@ -29,6 +30,7 @@ export interface SearchResultMetadata {
|
||||
title: string;
|
||||
url: string;
|
||||
snippet: string;
|
||||
engine?: string;
|
||||
}
|
||||
|
||||
interface CachedResult {
|
||||
@@ -71,13 +73,13 @@ class WebSearchService {
|
||||
if (typeof window === 'undefined' || typeof localStorage === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const stored = localStorage.getItem('webSearchCache');
|
||||
if (stored) {
|
||||
const parsed = JSON.parse(stored) as Array<[string, CachedResult]>;
|
||||
const now = Date.now();
|
||||
|
||||
|
||||
// Filtrar entradas expiradas
|
||||
for (const [key, value] of parsed) {
|
||||
if (now - value.timestamp < this.CACHE_TTL) {
|
||||
@@ -98,7 +100,7 @@ class WebSearchService {
|
||||
if (typeof window === 'undefined' || typeof localStorage === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const entries = Array.from(this.cache.entries());
|
||||
localStorage.setItem('webSearchCache', JSON.stringify(entries));
|
||||
@@ -121,9 +123,9 @@ class WebSearchService {
|
||||
private getCachedResult(query: string, limit?: number): ScrapedContent[] | null {
|
||||
const key = this.getCacheKey(query, limit);
|
||||
const cached = this.cache.get(key);
|
||||
|
||||
|
||||
if (!cached) return null;
|
||||
|
||||
|
||||
const now = Date.now();
|
||||
if (now - cached.timestamp > this.CACHE_TTL) {
|
||||
// Cache expirado, remover
|
||||
@@ -131,7 +133,7 @@ class WebSearchService {
|
||||
this.saveCacheToStorage();
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
return cached.results;
|
||||
}
|
||||
|
||||
@@ -153,25 +155,25 @@ class WebSearchService {
|
||||
*/
|
||||
private async processRateLimitQueue(): Promise<void> {
|
||||
if (this.rateLimitQueue.length === 0) return;
|
||||
|
||||
|
||||
const now = Date.now();
|
||||
const timeSinceLastSearch = now - this.lastSearchTime;
|
||||
|
||||
|
||||
if (timeSinceLastSearch < this.RATE_LIMIT_DELAY) {
|
||||
// Aguardar antes de processar próximo
|
||||
const waitTime = this.RATE_LIMIT_DELAY - timeSinceLastSearch;
|
||||
await new Promise(resolve => setTimeout(resolve, waitTime));
|
||||
}
|
||||
|
||||
|
||||
const nextRequest = this.rateLimitQueue.shift();
|
||||
if (nextRequest) {
|
||||
this.lastSearchTime = Date.now();
|
||||
this.pendingRequest = nextRequest;
|
||||
|
||||
|
||||
try {
|
||||
const results = await this.executeSearch(
|
||||
nextRequest.query,
|
||||
nextRequest.limit,
|
||||
nextRequest.query,
|
||||
nextRequest.limit,
|
||||
nextRequest.excludedDomains,
|
||||
nextRequest.searchConfig
|
||||
);
|
||||
@@ -190,7 +192,7 @@ class WebSearchService {
|
||||
* Executa busca no backend (sem cache/rate limit)
|
||||
*/
|
||||
private async executeSearch(
|
||||
query: string,
|
||||
query: string,
|
||||
limit: number = 3,
|
||||
excludedDomains: string[] = [],
|
||||
searchConfig?: SearchConfig
|
||||
@@ -247,41 +249,41 @@ class WebSearchService {
|
||||
const failureEntry = this.failureCache.get(query);
|
||||
if (failureEntry) {
|
||||
console.warn(`[WebSearch] Query falhou recentemente (há ${Math.round((Date.now() - failureEntry.timestamp) / 1000)}s), retornando resultados parciais ou vazio`);
|
||||
|
||||
|
||||
// Retornar resultados parciais se disponíveis
|
||||
if (failureEntry.partialResults && failureEntry.partialResults.length > 0) {
|
||||
console.log(`[WebSearch] Retornando ${failureEntry.partialResults.length} resultados parciais do cache de falhas`);
|
||||
return failureEntry.partialResults as SearchResultMetadata[];
|
||||
}
|
||||
|
||||
|
||||
// Se não há resultados parciais, retornar vazio imediatamente (sem retry)
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
let rustConfig: Record<string, unknown> | undefined = undefined;
|
||||
if (searchConfig) {
|
||||
const settings = useSettingsStore.getState();
|
||||
rustConfig = {
|
||||
max_concurrent_tabs: searchConfig.maxConcurrentTabs,
|
||||
total_sources_limit: searchConfig.totalSourcesLimit,
|
||||
categories: searchConfig.categories.map(cat => ({
|
||||
id: cat.id,
|
||||
name: cat.name,
|
||||
base_sites: cat.baseSites,
|
||||
enabled: cat.enabled,
|
||||
})),
|
||||
user_custom_sites: searchConfig.userCustomSites,
|
||||
excluded_domains: searchConfig.excludedDomains,
|
||||
searxng: {
|
||||
enabled: settings.webSearch.searxng.enabled,
|
||||
url: settings.webSearch.searxng.url,
|
||||
},
|
||||
};
|
||||
}
|
||||
if (searchConfig) {
|
||||
const settings = useSettingsStore.getState();
|
||||
rustConfig = {
|
||||
max_concurrent_tabs: searchConfig.maxConcurrentTabs,
|
||||
total_sources_limit: searchConfig.totalSourcesLimit,
|
||||
categories: searchConfig.categories.map(cat => ({
|
||||
id: cat.id,
|
||||
name: cat.name,
|
||||
base_sites: cat.baseSites,
|
||||
enabled: cat.enabled,
|
||||
})),
|
||||
user_custom_sites: searchConfig.userCustomSites,
|
||||
excluded_domains: searchConfig.excludedDomains,
|
||||
searxng: {
|
||||
enabled: settings.webSearch.searxng.enabled,
|
||||
url: settings.webSearch.searxng.url,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Calcular timeout adaptativo baseado na tentativa
|
||||
const adaptiveTimeout = calculateEngineTimeout(attempt, timeoutMs);
|
||||
|
||||
|
||||
// Log detalhado da tentativa
|
||||
const searchStartTime = Date.now();
|
||||
console.log(`\n[WebSearch] ========== METADATA SEARCH START ==========`);
|
||||
@@ -304,7 +306,7 @@ class WebSearchService {
|
||||
const startTime = Date.now();
|
||||
// Timeout ainda mais reduzido em retries internos
|
||||
const retryTimeout = calculateEngineTimeout(retryAttempt, adaptiveTimeout);
|
||||
|
||||
|
||||
try {
|
||||
const promise = invoke<SearchResultMetadata[]>('search_web_metadata', {
|
||||
query: query.trim(),
|
||||
@@ -312,19 +314,19 @@ class WebSearchService {
|
||||
search_config: rustConfig,
|
||||
engine_order: undefined, // Não usado mais, mantido para compatibilidade
|
||||
});
|
||||
|
||||
|
||||
const result = await withTimeout(
|
||||
promise,
|
||||
retryTimeout,
|
||||
`Timeout ao buscar metadados para "${query}" (tentativa ${retryAttempt})`
|
||||
);
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
|
||||
// Log detalhado dos resultados
|
||||
console.log(`\n[WebSearch] ========== METADATA SEARCH RESULTS ==========`);
|
||||
console.log(`[WebSearch] Duration: ${duration}ms`);
|
||||
console.log(`[WebSearch] Results count: ${result?.length ?? 0}`);
|
||||
|
||||
|
||||
if (result && result.length > 0) {
|
||||
console.log(`[WebSearch] Top ${Math.min(5, result.length)} results:`);
|
||||
result.slice(0, 5).forEach((meta, idx) => {
|
||||
@@ -337,13 +339,13 @@ class WebSearchService {
|
||||
console.warn(`[WebSearch] ⚠️ No metadata results returned`);
|
||||
}
|
||||
console.log(`[WebSearch] ============================================\n`);
|
||||
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
const duration = Date.now() - startTime;
|
||||
const errorMsg = error instanceof Error ? error.message : String(error);
|
||||
console.error(`[WebSearch] Erro após ${duration}ms (tentativa ${retryAttempt}):`, errorMsg);
|
||||
|
||||
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
@@ -367,7 +369,7 @@ class WebSearchService {
|
||||
if (retryResult.lastError) {
|
||||
const errorMsg = retryResult.lastError.message;
|
||||
const isRetryable = isRetryableError(retryResult.lastError);
|
||||
|
||||
|
||||
// Registrar falha no cache (mesmo que seja recuperável, para evitar retentar imediatamente)
|
||||
this.failureCache.recordFailure(
|
||||
query,
|
||||
@@ -375,7 +377,7 @@ class WebSearchService {
|
||||
[], // Sem resultados parciais neste ponto
|
||||
retryResult.attempts
|
||||
);
|
||||
|
||||
|
||||
if (isRetryable) {
|
||||
console.warn(
|
||||
`[WebSearch] Falha após ${retryResult.attempts} tentativas (erro recuperável):`,
|
||||
@@ -403,8 +405,8 @@ class WebSearchService {
|
||||
* Busca conteúdo na web com cache e rate limiting
|
||||
*/
|
||||
async search(
|
||||
query: string,
|
||||
limit: number = 3,
|
||||
query: string,
|
||||
limit: number = 3,
|
||||
excludedDomains: string[] = [],
|
||||
searchConfig?: SearchConfig
|
||||
): Promise<ScrapedContent[]> {
|
||||
@@ -462,8 +464,8 @@ class WebSearchService {
|
||||
|
||||
// Executar imediatamente
|
||||
this.lastSearchTime = now;
|
||||
this.pendingRequest = { query, limit, excludedDomains, searchConfig, resolve: () => {}, reject: () => {} };
|
||||
|
||||
this.pendingRequest = { query, limit, excludedDomains, searchConfig, resolve: () => { }, reject: () => { } };
|
||||
|
||||
try {
|
||||
const results = await this.executeSearch(query, limit, excludedDomains, searchConfig);
|
||||
this.setCachedResult(query, results, limit);
|
||||
@@ -496,60 +498,60 @@ class WebSearchService {
|
||||
// Calcular timeout escalonado por round
|
||||
const { calculateAdaptiveTimeout } = await import('@/lib/retry-utils');
|
||||
const roundTimeout = calculateAdaptiveTimeout(round, timeoutMs);
|
||||
|
||||
|
||||
const smartSearchStart = Date.now();
|
||||
console.log(`\n[WebSearch] ========== SMART SEARCH RAG START ==========`);
|
||||
console.log(`[WebSearch] Query: "${query}"`);
|
||||
console.log(`[WebSearch] Limit: ${limit}`);
|
||||
console.log(`[WebSearch] Round: ${round}`);
|
||||
console.log(`[WebSearch] Timeout: ${roundTimeout}ms`);
|
||||
|
||||
|
||||
// Buscar metadados com retry e timeout adaptativo
|
||||
const metas = await this.executeMetadataSearch(
|
||||
query,
|
||||
Math.max(limit * 2, 5),
|
||||
query,
|
||||
Math.max(limit * 2, 5),
|
||||
searchConfig,
|
||||
roundTimeout,
|
||||
1 // Primeira tentativa
|
||||
);
|
||||
|
||||
|
||||
// Continuar mesmo se metadados estiverem vazios (pode ter falhado parcialmente)
|
||||
const topUrls = metas.map(m => m.url).slice(0, limit);
|
||||
|
||||
|
||||
console.log(`[WebSearch] Selected ${topUrls.length} URLs for scraping:`);
|
||||
topUrls.forEach((url, idx) => {
|
||||
console.log(` [${idx + 1}] ${url}`);
|
||||
});
|
||||
|
||||
|
||||
if (topUrls.length === 0) {
|
||||
console.warn(`[WebSearch] ⚠️ No URLs to scrape, returning metadata only`);
|
||||
console.log(`[WebSearch] ============================================\n`);
|
||||
// Retornar resultados parciais (metadados vazios mas sem erro)
|
||||
return { metadata: metas, contents: [] };
|
||||
}
|
||||
|
||||
|
||||
// Tentar fazer scraping mesmo se metadados foram parciais
|
||||
// Usar timeout reduzido para scraping (50% do timeout de busca)
|
||||
const scrapingTimeout = Math.max(roundTimeout * 0.5, 5000);
|
||||
console.log(`[WebSearch] Starting scraping with timeout: ${scrapingTimeout}ms`);
|
||||
const scrapingStart = Date.now();
|
||||
|
||||
|
||||
try {
|
||||
const contents = await withTimeout(
|
||||
invoke<ScrapedContent[]>('scrape_urls', { urls: topUrls }),
|
||||
scrapingTimeout,
|
||||
`Timeout ao fazer scraping para "${query}"`
|
||||
);
|
||||
|
||||
|
||||
const scrapingDuration = Date.now() - scrapingStart;
|
||||
const totalDuration = Date.now() - smartSearchStart;
|
||||
|
||||
|
||||
// Log detalhado do scraping
|
||||
console.log(`\n[WebSearch] ========== SCRAPING RESULTS ==========`);
|
||||
console.log(`[WebSearch] Scraping duration: ${scrapingDuration}ms`);
|
||||
console.log(`[WebSearch] Total duration: ${totalDuration}ms`);
|
||||
console.log(`[WebSearch] Contents extracted: ${contents?.length ?? 0}`);
|
||||
|
||||
|
||||
if (contents && contents.length > 0) {
|
||||
contents.forEach((content, idx) => {
|
||||
console.log(`\n [${idx + 1}] ${content.title}`);
|
||||
@@ -567,7 +569,7 @@ class WebSearchService {
|
||||
console.warn(`[WebSearch] ⚠️ No content extracted from URLs`);
|
||||
}
|
||||
console.log(`[WebSearch] ============================================\n`);
|
||||
|
||||
|
||||
return { metadata: metas, contents: contents || [] };
|
||||
} catch (error) {
|
||||
// Logar erro mas retornar metadados disponíveis (resultado parcial)
|
||||
@@ -575,12 +577,12 @@ class WebSearchService {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
console.warn(`[WebSearch] Erro ao fazer scraping (continuando com metadados): ${errorMessage}`);
|
||||
console.warn(`[WebSearch] Retornando ${metas.length} metadados sem conteúdo scraped (resultado parcial)`);
|
||||
|
||||
|
||||
// Retornar metadados mesmo se scraping falhou - resultado parcial é melhor que nada
|
||||
return { metadata: metas, contents: [] };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Versão simplificada que aceita resultados parciais mais rapidamente
|
||||
* Retorna assim que qualquer motor retornar resultados, sem esperar todos
|
||||
@@ -598,22 +600,22 @@ class WebSearchService {
|
||||
|
||||
// Timeout ainda mais agressivo para resultados parciais
|
||||
const partialTimeout = Math.max(timeoutMs * 0.7, 5000); // 70% do timeout normal
|
||||
|
||||
|
||||
try {
|
||||
// Buscar metadados com timeout reduzido
|
||||
const metas = await this.executeMetadataSearch(
|
||||
query,
|
||||
Math.max(limit * 2, 5),
|
||||
query,
|
||||
Math.max(limit * 2, 5),
|
||||
searchConfig,
|
||||
partialTimeout,
|
||||
1
|
||||
);
|
||||
|
||||
|
||||
// Se temos metadados, tentar scraping rápido (timeout ainda menor)
|
||||
if (metas.length > 0) {
|
||||
const topUrls = metas.map(m => m.url).slice(0, limit);
|
||||
const scrapingTimeout = Math.max(partialTimeout * 0.4, 3000); // 40% do timeout parcial
|
||||
|
||||
|
||||
try {
|
||||
const contents = await withTimeout(
|
||||
invoke<ScrapedContent[]>('scrape_urls', { urls: topUrls }),
|
||||
@@ -627,7 +629,7 @@ class WebSearchService {
|
||||
return { metadata: metas, contents: [] };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return { metadata: metas, contents: [] };
|
||||
} catch (error) {
|
||||
// Em caso de erro total, retornar vazio mas não quebrar
|
||||
|
||||
Reference in new issue
Block a user