import { LLMClient } from './client.js';
import { buildAnalysisPrompt } from './prompts.js';
import { logger } from '@/utils/logger.js';
import { extractDomain } from '@/utils/text.js';
import type {
ContentAnalysis,
AnalysisRequest,
LinkAnalysis,
CodeExample
} from './types.js';
import { AnalysisType, ContentType, LinkCategory, CodeExampleCategory } from './types.js';
export class ContentAnalyzer {
private llmClient: LLMClient;
constructor(llmClient: LLMClient) {
this.llmClient = llmClient;
}
async analyzeContent(request: AnalysisRequest): Promise<ContentAnalysis | null> {
if (!this.llmClient.isAvailable()) {
logger.debug('LLM not available, skipping content analysis');
return null;
}
try {
switch (request.analysisType) {
case AnalysisType.FULL:
return await this.performFullAnalysis(request);
case AnalysisType.SUMMARY_ONLY:
return await this.performSummaryAnalysis(request);
case AnalysisType.LINKS_ONLY:
return await this.performLinksAnalysis(request);
case AnalysisType.CLASSIFICATION_ONLY:
return await this.performClassificationAnalysis(request);
case AnalysisType.CODE_EXAMPLES_ONLY:
return await this.performCodeExamplesAnalysis(request);
default:
logger.warn(`Unknown analysis type: ${request.analysisType}`);
return null;
}
} catch (error) {
logger.error(`Content analysis failed for ${request.url}:`, error);
return null;
}
}
private async performFullAnalysis(request: AnalysisRequest): Promise<ContentAnalysis | null> {
const domain = extractDomain(request.url);
const prompt = buildAnalysisPrompt('full', {
url: request.url,
title: request.title,
domain,
});
const response = await this.llmClient.analyze(request.content, prompt);
if (!response) return null;
try {
const parsed = JSON.parse(response);
return this.validateAndCleanAnalysis(parsed, request);
} catch (error) {
logger.error('Failed to parse full analysis JSON:', error);
return await this.fallbackAnalysis(request);
}
}
private async performSummaryAnalysis(request: AnalysisRequest): Promise<ContentAnalysis | null> {
const prompt = buildAnalysisPrompt('summary', {
url: request.url,
title: request.title,
});
const response = await this.llmClient.analyze(request.content, prompt);
if (!response) return null;
return {
summary: response.trim(),
keyPoints: [],
contentType: ContentType.OTHER,
relevantLinks: [],
codeExamples: [],
confidence: 0.7,
};
}
private async performLinksAnalysis(request: AnalysisRequest): Promise<ContentAnalysis | null> {
if (request.links.length === 0) {
return {
summary: '',
keyPoints: [],
contentType: ContentType.OTHER,
relevantLinks: [],
codeExamples: [],
confidence: 1.0,
};
}
const domain = extractDomain(request.url);
const linksContext = request.links.join('\n');
const contentWithLinks = `${request.content}\n\nAvailable links:\n${linksContext}`;
const prompt = buildAnalysisPrompt('links', { domain });
const response = await this.llmClient.analyze(contentWithLinks, prompt);
if (!response) return null;
try {
const parsed = JSON.parse(response);
const relevantLinks = Array.isArray(parsed) ? parsed : [];
return {
summary: '',
keyPoints: [],
contentType: ContentType.OTHER,
relevantLinks: relevantLinks.map(this.validateLinkAnalysis),
codeExamples: [],
confidence: 0.8,
};
} catch (error) {
logger.error('Failed to parse links analysis JSON:', error);
return null;
}
}
private async performClassificationAnalysis(request: AnalysisRequest): Promise<ContentAnalysis | null> {
const prompt = buildAnalysisPrompt('classification');
const response = await this.llmClient.analyze(request.content, prompt);
if (!response) return null;
const contentType = this.parseContentType(response.trim().toLowerCase());
return {
summary: '',
keyPoints: [],
contentType,
relevantLinks: [],
codeExamples: [],
confidence: 0.9,
};
}
private async fallbackAnalysis(request: AnalysisRequest): Promise<ContentAnalysis | null> {
logger.info('Attempting fallback analysis with individual components');
const summaryAnalysis = await this.performSummaryAnalysis(request);
const linksAnalysis = await this.performLinksAnalysis(request);
const classificationAnalysis = await this.performClassificationAnalysis(request);
return {
summary: summaryAnalysis?.summary || 'Summary not available',
keyPoints: [],
contentType: classificationAnalysis?.contentType || ContentType.OTHER,
relevantLinks: linksAnalysis?.relevantLinks || [],
codeExamples: [],
confidence: 0.6,
};
}
private validateAndCleanAnalysis(parsed: any, request: AnalysisRequest): ContentAnalysis {
return {
summary: typeof parsed.summary === 'string' ? parsed.summary : 'Summary not available',
keyPoints: Array.isArray(parsed.keyPoints) ? parsed.keyPoints.filter(kp => typeof kp === 'string') : [],
contentType: this.parseContentType(parsed.contentType) || ContentType.OTHER,
relevantLinks: Array.isArray(parsed.relevantLinks)
? parsed.relevantLinks.map(this.validateLinkAnalysis).filter(link => link.url)
: [],
codeExamples: Array.isArray(parsed.codeExamples)
? parsed.codeExamples.map(this.validateCodeExample).filter(example => example.code)
: [],
confidence: typeof parsed.confidence === 'number' ? Math.max(0, Math.min(1, parsed.confidence)) : 0.5,
};
}
private validateLinkAnalysis = (link: any): LinkAnalysis => {
return {
url: typeof link.url === 'string' ? link.url : '',
title: typeof link.title === 'string' ? link.title : link.url || 'Untitled',
relevance: typeof link.relevance === 'number' ? Math.max(0, Math.min(1, link.relevance)) : 0.5,
reason: typeof link.reason === 'string' ? link.reason : 'No reason provided',
category: this.parseLinkCategory(link.category) || LinkCategory.RELATED_TOPIC,
};
};
private parseContentType(type: string): ContentType {
const normalizedType = type.toLowerCase().replace(/[_-]/g, '_');
return Object.values(ContentType).find(ct => ct === normalizedType) || ContentType.OTHER;
}
private parseLinkCategory(category: string): LinkCategory | null {
if (!category) return null;
const normalizedCategory = category.toLowerCase().replace(/[_-]/g, '_');
return Object.values(LinkCategory).find(lc => lc === normalizedCategory) || null;
}
private async performCodeExamplesAnalysis(request: AnalysisRequest): Promise<ContentAnalysis | null> {
const prompt = buildAnalysisPrompt('code_examples');
const response = await this.llmClient.analyze(request.content, prompt);
if (!response) return null;
try {
const parsed = JSON.parse(response);
const codeExamples = Array.isArray(parsed) ? parsed : [];
return {
summary: '',
keyPoints: [],
contentType: ContentType.OTHER,
relevantLinks: [],
codeExamples: codeExamples.map(this.validateCodeExample).filter(example => example.code),
confidence: 0.9,
};
} catch (error) {
logger.error('Failed to parse code examples analysis JSON:', error);
return null;
}
}
private validateCodeExample = (example: any): CodeExample => {
return {
language: typeof example.language === 'string' ? example.language : 'plaintext',
code: typeof example.code === 'string' ? example.code : '',
description: typeof example.description === 'string' ? example.description : 'Code example',
category: this.parseCodeExampleCategory(example.category) || CodeExampleCategory.OTHER,
};
};
private parseCodeExampleCategory(category: string): CodeExampleCategory | null {
if (!category) return null;
const normalizedCategory = category.toLowerCase().replace(/[_-]/g, '_');
return Object.values(CodeExampleCategory).find(cec => cec === normalizedCategory) || null;
}
isAvailable(): boolean {
return this.llmClient.isAvailable();
}
getProviderInfo(): string {
return this.llmClient.getProviderName() || 'none';
}
}