import { llmJsonRequest } from "../llm-client.js"; import type { TopicCluster } from "../types.js"; export interface ExistingPage { url: string; title: string; } export interface ContentGap { recommendedTitle: string; targetKeyword: string; relatedCluster: string; priority: "high" | "medium" | "low"; rationale: string; } const CONTENT_GAP_SYSTEM_PROMPT = ` You are a senior SEO Content Strategist. Your job is to compare a set of TOPIC CLUSTERS (keywords the company should rank for) against the EXISTING PAGES on their website. ### OBJECTIVE: Identify content gaps — topics/keywords that have NO corresponding page yet. For each gap, recommend a page title, the primary target keyword, which cluster it belongs to, and a priority (high/medium/low) based on commercial intent and relevance. ### RULES: - Only recommend gaps for topics that are genuinely MISSING from the existing pages. - Do NOT recommend pages that already exist (even if the title is slightly different — use semantic matching). - Priority "high" = commercial/transactional intent, directly drives revenue. - Priority "medium" = informational with strong industry relevance. - Priority "low" = broad, top-of-funnel topics. - LANGUAGE: Match the language of the project context (if German context, recommend German titles). ### OUTPUT FORMAT: { "contentGaps": [ { "recommendedTitle": "string", "targetKeyword": "string", "relatedCluster": "string", "priority": "high" | "medium" | "low", "rationale": "string" } ] } `; export async function analyzeContentGaps( topicClusters: TopicCluster[], existingPages: ExistingPage[], config: { openRouterApiKey: string; model?: string }, ): Promise { if (topicClusters.length === 0) return []; if (existingPages.length === 0) { console.log( "[Content Gap] No existing pages provided, skipping gap analysis.", ); return []; } const userPrompt = ` TOPIC CLUSTERS (what the company SHOULD rank for): ${JSON.stringify(topicClusters, null, 2)} EXISTING PAGES ON THE WEBSITE: ${existingPages.map((p, i) => `${i + 1}. "${p.title}" — ${p.url}`).join("\n")} Identify ALL content gaps. Be thorough but precise. `; try { const { data } = await llmJsonRequest<{ contentGaps: ContentGap[] }>({ model: config.model || "google/gemini-2.5-pro", apiKey: config.openRouterApiKey, systemPrompt: CONTENT_GAP_SYSTEM_PROMPT, userPrompt, }); return data.contentGaps || []; } catch (err) { console.error("[Content Gap] Analysis failed:", (err as Error).message); return []; } }