import { NextRequest } from 'next/server'; import { prisma } from '@/lib/db'; interface WikipediaSummary { title: string; extract: string; thumbnail?: { source: string }; originalimage?: { source: string }; } interface WikiParseResult { parse: { title: string; text: { '*': string }; images: string[]; }; } async function getWikipediaSummary(title: string): Promise { try { const apiTitle = title.replace(/%20/g, '_'); const response = await fetch( `https://en.wikipedia.org/api/rest_v1/page/summary/${apiTitle}`, { headers: { 'User-Agent': 'WikiPlus/1.0' }, } ); if (!response.ok) return null; return await response.json(); } catch { return null; } } async function getWikipediaContent(title: string): Promise<{ text: string; images: string[] } | null> { try { const apiTitle = title.replace(/%20/g, '_'); const response = await fetch( `https://en.wikipedia.org/w/api.php?action=parse&page=${encodeURIComponent(apiTitle)}&format=json&prop=text|images&disableeditsection=true&redirects=true`, { headers: { 'User-Agent': 'WikiPlus/1.0' }, } ); if (!response.ok) return null; const data: WikiParseResult = await response.json(); if (!data.parse) return null; // Extract plain text from HTML const html = data.parse.text?.['*'] || ''; const plainText = html .replace(/]*>[\s\S]*?<\/style>/gi, '') .replace(/]*>[\s\S]*?<\/script>/gi, '') .replace(/<[^>]+>/g, ' ') .replace(/ /g, ' ') .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"') .replace(/'/g, "'") .replace(/\s+/g, ' ') .trim(); // Get image URLs const images = (data.parse.images || []) .filter((img: string) => !img.includes('icon') && !img.includes('logo') && !img.includes('Commons-logo')) .slice(0, 5) .map((img: string) => `https://en.wikipedia.org/wiki/Special:FilePath/${encodeURIComponent(img)}`); return { text: plainText.slice(0, 15000), images }; } catch { return null; } } export async function GET(request: NextRequest) { const searchParams = request.nextUrl.searchParams; const title = searchParams.get('title'); if (!title) { return new Response('Missing title parameter', { status: 400 }); } // Normalize the slug for database lookup const slug = title.replace(/%20/g, '_').replace(/ /g, '_'); // Check if article exists in database const existingArticle = await prisma.article.findUnique({ where: { slug }, }); if (existingArticle) { // Return cached article wrapped in
tags const cachedContent = `
${existingArticle.content}
`; return new Response(cachedContent, { headers: { 'Content-Type': 'text/plain; charset=utf-8', 'X-Cache': 'HIT', }, }); } const apiKey = process.env.OPENROUTER_API_KEY; if (!apiKey) { return new Response('OpenRouter API key not configured', { status: 500 }); } // Fetch Wikipedia data const [summary, content] = await Promise.all([ getWikipediaSummary(title), getWikipediaContent(title), ]); if (!summary || !content) { return new Response('Article not found', { status: 404 }); } const imageUrl = summary.thumbnail?.source || summary.originalimage?.source; const imageContext = imageUrl ? `\n\nMain image available: ${imageUrl}\nAdditional images: ${content.images.join(', ')}` : content.images.length > 0 ? `\n\nImages available: ${content.images.join(', ')}` : ''; const systemPrompt = `You are an expert writer creating comprehensive, in-depth articles for an AI-powered Wikipedia alternative. Your goal is to transform encyclopedia content into rich, detailed, and engaging prose that thoroughly covers the topic. Guidelines: - Write a COMPREHENSIVE and DETAILED article - aim for depth and thoroughness - Cover ALL major aspects of the topic: history, significance, key details, related concepts, and impact - Use markdown formatting extensively: - Use ## for main sections and ### for subsections - Use **bold** for key terms and *italic* for emphasis - Use bullet lists and numbered lists where appropriate - Use > blockquotes for notable quotes or key facts - Include the main image at the top using markdown: ![Description](url) - Structure the article with multiple well-developed sections (5-8 sections minimum) - Each section should have multiple paragraphs with detailed explanations - Add context that helps readers understand why this topic matters - Include interesting facts, historical context, and connections to broader themes - Maintain factual accuracy - expand on the source material but don't invent facts - Write at least 1000-1500 words for a thorough treatment of the topic - Output ONLY the article content wrapped in
tags - Do not include any text outside the
tags`; const userPrompt = `Write a comprehensive, detailed article about "${summary.title}" based on this Wikipedia content: Summary: ${summary.extract} Full content: ${content.text}${imageContext} Requirements: - Write a THOROUGH article with 5-8 well-developed sections minimum - Each section should have multiple detailed paragraphs - Cover history, significance, key facts, and broader context - Use rich markdown formatting throughout - Aim for 1000-1500+ words total - Output in markdown wrapped in
tags`; // Call OpenRouter API with streaming const openRouterResponse = await fetch('https://openrouter.ai/api/v1/chat/completions', { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ model: 'minimax/minimax-m2.1', messages: [ { role: 'system', content: systemPrompt }, { role: 'user', content: userPrompt }, ], stream: true, }), }); if (!openRouterResponse.ok) { const error = await openRouterResponse.text(); console.error('OpenRouter error:', error); return new Response('Failed to generate article', { status: 500 }); } // Transform the OpenRouter SSE stream to extract content and save to DB const encoder = new TextEncoder(); const decoder = new TextDecoder(); let fullContent = ''; const transformStream = new TransformStream({ async transform(chunk, controller) { const text = decoder.decode(chunk); const lines = text.split('\n'); for (const line of lines) { if (line.startsWith('data: ')) { const data = line.slice(6); if (data === '[DONE]') { continue; } try { const parsed = JSON.parse(data); const contentChunk = parsed.choices?.[0]?.delta?.content; if (contentChunk) { fullContent += contentChunk; controller.enqueue(encoder.encode(contentChunk)); } } catch { // Skip invalid JSON } } } }, async flush() { // Extract content between
tags and save to database const articleMatch = fullContent.match(/
([\s\S]*?)<\/article>/); const articleContent = articleMatch ? articleMatch[1].trim() : fullContent; if (articleContent) { try { const newArticle = await prisma.article.create({ data: { slug, title: summary.title, content: articleContent, imageUrl: imageUrl || null, }, }); // Create initial history entry await prisma.articleHistory.create({ data: { articleId: newArticle.id, oldContent: '', newContent: articleContent, reason: 'Initial article', }, }); } catch (error) { console.error('Failed to save article to database:', error); } } }, }); const stream = openRouterResponse.body?.pipeThrough(transformStream); return new Response(stream, { headers: { 'Content-Type': 'text/plain; charset=utf-8', 'Transfer-Encoding': 'chunked', 'Cache-Control': 'no-cache', 'X-Cache': 'MISS', }, }); }