wip: snapshot das alterações não commitadas (2026-10-20)
Origem: D:/Documents/Github/LocalAiApp Arquivos omitidos (segredos, bancos de dados, logs, >50 MB): 0
This commit is contained in:
1 parent
ac215b4229
commit
e08189f640
17 files changed
+2914
-13236
No files matched your search
@@ -0,0 +1,190 @@
|
||||
/**
|
||||
* webSearchLogger.ts
|
||||
* Centralized structured logger for the web search pipeline on the client side.
|
||||
* Outputs consistently formatted, emoji-tagged logs to the React Native / Metro console.
|
||||
*/
|
||||
|
||||
type LogLevel = 'INFO' | 'WARN' | 'ERROR' | 'DEBUG';
|
||||
|
||||
const ICONS: Record<LogLevel, string> = {
|
||||
INFO: 'ℹ️ ',
|
||||
WARN: '⚠️ ',
|
||||
ERROR: '❌',
|
||||
DEBUG: '🔍',
|
||||
};
|
||||
|
||||
function formatMeta(meta?: Record<string, any>): string {
|
||||
if (!meta) return '';
|
||||
try {
|
||||
return '\n ' + JSON.stringify(meta, null, 2).replace(/\n/g, '\n ');
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function log(level: LogLevel, phase: string, message: string, meta?: Record<string, any>) {
|
||||
const ts = new Date().toISOString();
|
||||
const metaStr = formatMeta(meta);
|
||||
const prefix = `[${ts}] ${ICONS[level]} [WebSearch/${phase}]`;
|
||||
const fullMsg = `${prefix} ${message}${metaStr}`;
|
||||
|
||||
switch (level) {
|
||||
case 'ERROR':
|
||||
console.error(fullMsg);
|
||||
break;
|
||||
case 'WARN':
|
||||
console.warn(fullMsg);
|
||||
break;
|
||||
default:
|
||||
console.log(fullMsg);
|
||||
}
|
||||
}
|
||||
|
||||
function logSection(title: string) {
|
||||
const border = '─'.repeat(55);
|
||||
console.log(`\n${border}`);
|
||||
console.log(` ${title}`);
|
||||
console.log(`${border}`);
|
||||
}
|
||||
|
||||
/** Rough tokens estimate (~4 chars per token) */
|
||||
function estimateTokens(text: string): number {
|
||||
return Math.ceil(text.length / 4);
|
||||
}
|
||||
|
||||
// ─── Public API ────────────────────────────────────────────────────────────────
|
||||
|
||||
export const WebSearchLogger = {
|
||||
pipelineStart(query: string, searchEndpoint: string) {
|
||||
logSection(`🚀 Web Search Pipeline Started`);
|
||||
log('INFO', 'Init', `New search initiated`, {
|
||||
query,
|
||||
searchEndpoint,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
},
|
||||
|
||||
endpointResolved(host: string, port: number, searchEndpoint: string) {
|
||||
log('INFO', 'Endpoint', `Search service URL resolved`, {
|
||||
lmStudioHost: host,
|
||||
lmStudioPort: port,
|
||||
searchServiceEndpoint: searchEndpoint,
|
||||
});
|
||||
},
|
||||
|
||||
requestSent(query: string, maxContextLength: number, timeoutMs = 30000) {
|
||||
log('INFO', 'Request', `Sending search request to local service`, {
|
||||
query,
|
||||
maxContextLength,
|
||||
estimatedTokenBudget: estimateTokens('x'.repeat(maxContextLength)),
|
||||
timeoutMs,
|
||||
});
|
||||
},
|
||||
|
||||
requestFailed(status: number, statusText: string) {
|
||||
log('ERROR', 'Request', `Search service returned error`, {
|
||||
httpStatus: status,
|
||||
statusText,
|
||||
});
|
||||
},
|
||||
|
||||
serviceUnreachable(error: unknown, isTimeout = false) {
|
||||
const err = error instanceof Error ? error : new Error(String(error));
|
||||
const isConnectionRefused =
|
||||
err.message.includes('Network request failed') ||
|
||||
err.message.includes('ECONNREFUSED') ||
|
||||
err.message.includes('Connection refused');
|
||||
|
||||
const message = isTimeout
|
||||
? `Search service timeout (30s) — service may be overloaded or unreachable`
|
||||
: isConnectionRefused
|
||||
? `Search service is NOT running — connection refused`
|
||||
: `Cannot reach local search service`;
|
||||
|
||||
log('ERROR', 'Request', message, {
|
||||
error: err.message,
|
||||
isTimeout,
|
||||
isConnectionRefused,
|
||||
diagnosis: isConnectionRefused
|
||||
? 'The local-search-service Node.js process is not running on this machine'
|
||||
: isTimeout
|
||||
? 'Service is running but not responding within 30s (SearXNG may be slow)'
|
||||
: 'Unknown network error',
|
||||
hint: 'Run: cd local-search-service && npx ts-node server.ts',
|
||||
});
|
||||
},
|
||||
|
||||
contextReceived(
|
||||
context: string,
|
||||
sources: Array<{ title: string; url: string }>,
|
||||
meta?: {
|
||||
requestId?: string;
|
||||
totalLatencyMs?: number;
|
||||
finalContextChars?: number;
|
||||
finalContextEstimatedTokens?: number;
|
||||
sourcesKept?: number;
|
||||
sourcesDropped?: number;
|
||||
pruned?: boolean;
|
||||
},
|
||||
) {
|
||||
logSection(`✅ Context Received from Search Service`);
|
||||
log('INFO', 'Context', `Web search context ready`, {
|
||||
contextChars: context.length,
|
||||
estimatedTokens: estimateTokens(context),
|
||||
sourcesReceived: sources.length,
|
||||
sourceTitles: sources.map((s) => s.title.substring(0, 60)),
|
||||
sourceUrls: sources.map((s) => s.url),
|
||||
...(meta ?? {}),
|
||||
});
|
||||
},
|
||||
|
||||
contextAnalysis(systemPromptChars: number, historyChars: number, searchContextChars: number) {
|
||||
logSection(`🧠 Context Window Analysis`);
|
||||
const totalChars = systemPromptChars + historyChars + searchContextChars;
|
||||
const totalTokens = estimateTokens('x'.repeat(totalChars));
|
||||
log('INFO', 'Analysis', `Total context before sending to LLM`, {
|
||||
systemPromptChars,
|
||||
systemPromptTokens: estimateTokens('x'.repeat(systemPromptChars)),
|
||||
chatHistoryChars: historyChars,
|
||||
chatHistoryTokens: estimateTokens('x'.repeat(historyChars)),
|
||||
searchContextChars,
|
||||
searchContextTokens: estimateTokens('x'.repeat(searchContextChars)),
|
||||
totalChars,
|
||||
estimatedTotalTokens: totalTokens,
|
||||
note: 'Token estimates are approximate (~4 chars/token). Actual tokenization varies by model.',
|
||||
});
|
||||
},
|
||||
|
||||
llmRequestStart(model: string, messageCount: number) {
|
||||
logSection(`🤖 LLM Request`);
|
||||
log('INFO', 'LLM', `Sending messages to LM Studio`, {
|
||||
model,
|
||||
totalMessages: messageCount,
|
||||
streamingEnabled: true,
|
||||
});
|
||||
},
|
||||
|
||||
llmStreamComplete(assistantMessageId: string, chunkCount?: number) {
|
||||
log('INFO', 'LLM', `Stream complete — response generated`, {
|
||||
assistantMessageId,
|
||||
...(chunkCount !== undefined ? { chunkCount } : {}),
|
||||
});
|
||||
},
|
||||
|
||||
llmError(error: Error) {
|
||||
log('ERROR', 'LLM', `LLM stream error`, {
|
||||
error: error.message,
|
||||
stack: error.stack?.split('\n').slice(0, 3).join(' | '),
|
||||
});
|
||||
},
|
||||
|
||||
pipelineComplete(query: string, totalLatencyMs: number, withSearch: boolean) {
|
||||
logSection(`🏁 Pipeline Complete`);
|
||||
log('INFO', 'Done', `Full request cycle finished`, {
|
||||
query,
|
||||
totalLatencyMs,
|
||||
webSearchUsed: withSearch,
|
||||
});
|
||||
console.log('─'.repeat(55) + '\n');
|
||||
},
|
||||
};
|
||||
Reference in new issue
Block a user