feat(web-search): implement two-phase smart search pipeline

Add metadata-first search approach followed by targeted scraping of top URLs. Includes:
- New `smartSearchRag` method in web search service
- Metadata-only search endpoint in Tauri backend
- Bulk URL scraping capability
- Payload content override in chat messages
This commit is contained in:
Evandro Fonseca Junior committed 2025-11-20 05:59:09 -03:00
1 parent 5c1a859925
commit c06e7ae5cf
6 files changed
+380 -10

No files matched your search

+17 -4
View File
@@ -202,6 +202,9 @@ export function useChat() {
systemPrompt?: string,
options?: {
onBeforeModelRequest?: (pendingMessages: Message[]) => Promise<void>;
// Quando presente, substitui SOMENTE o conteúdo da última mensagem do usuário
// no payload enviado ao LLM, mantendo a UI com o conteúdo original.
payloadContentOverride?: string;
}
) => {
setIsLoading(true);
@@ -257,12 +260,22 @@ export function useChat() {
selectedMessages.push(lastUserMessage);
totalTokens += estimateTokens(lastUserMessage.content);
const apiMessages = (systemPrompt
// Constrói mensagens para payload, aplicando override apenas à última mensagem do usuário
const baseMessages = systemPrompt
? [{ role: 'system', content: systemPrompt }, ...selectedMessages]
: selectedMessages).map(m => ({
: selectedMessages;
const apiMessages = baseMessages.map((m, idx) => {
// Detecta última mensagem do usuário
const isLastUser = m.role === 'user' && idx === baseMessages.length - 1;
const payloadContent = isLastUser && options?.payloadContentOverride
? options.payloadContentOverride
: m.content;
return {
role: m.role,
content: m.content // Send only content, not metadata (unless we want to inject it back)
}));
content: payloadContent,
};
});
// [DEBUG INJECTION START]
const systemMsg = apiMessages.find(m => m.role === 'system');
+57
View File
@@ -119,6 +119,62 @@ export function useWebSearch() {
}
}, []);
/**
* Pipeline em duas etapas: metadados → scraping das top URLs
*/
const smartSearchRag = useCallback(async (query: string, limit: number = 3): Promise<ScrapedContent[]> => {
if (!query || !query.trim()) {
return [];
}
setState(prev => ({
...prev,
status: 'searching',
currentQuery: query,
error: null,
scrapedSources: [],
}));
try {
const settings = useSettingsStore.getState();
const searchConfig: SearchConfig = {
maxConcurrentTabs: settings.webSearch.maxConcurrentTabs,
totalSourcesLimit: settings.webSearch.totalSourcesLimit,
categories: settings.webSearch.categories,
userCustomSites: settings.webSearch.userCustomSites,
excludedDomains: settings.webSearch.excludedDomains,
};
// Etapa 1: metadados
setState(prev => ({ ...prev, status: 'searching' }));
const { metadata, contents } = await webSearchService.smartSearchRag(query, limit, searchConfig);
// Etapa 2: scraping
setState(prev => ({ ...prev, status: 'scraping' }));
// [DEBUG] metadados coletados
console.log('🔎 Metadados coletados:', metadata);
setState(prev => ({
...prev,
status: 'completed',
scrapedSources: contents,
error: null,
}));
return contents;
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Erro desconhecido no smartSearchRag';
setState(prev => ({
...prev,
status: 'error',
error: errorMessage,
scrapedSources: [],
}));
throw error;
}
}, []);
/**
* Extrai conteúdo de uma URL
*/
@@ -169,6 +225,7 @@ export function useWebSearch() {
...state,
setEnabled,
search,
smartSearchRag,
extractUrl,
reset,
clearCache,