Files
greywall-landing-page/app/api/analyze/route.ts
2026-03-16 17:02:44 -04:00

78 lines
3.0 KiB
TypeScript

import { NextRequest, NextResponse } from 'next/server'
import { readFileSync } from 'fs'
import { join } from 'path'
const promptTemplate = readFileSync(join(process.cwd(), 'app/api/analyze/prompt.txt'), 'utf-8')
// In-memory cache: persists for the lifetime of the server process
const cache = new Map<string, { data: any; timestamp: number }>()
const CACHE_TTL = 1000 * 60 * 60 * 24 // 24 hours
export async function POST(req: NextRequest) {
try {
const { owner, repo, files, stack, dependencies, sensitiveFiles, configFiles } = await req.json()
const baseUrl = process.env.SHARED_LLM_BASE_URL
const apiKey = process.env.SHARED_LLM_API_KEY
if (!baseUrl || !apiKey) {
return NextResponse.json({ error: 'LLM service not configured' }, { status: 500 })
}
// Check cache
const cacheKey = `${owner}/${repo}`.toLowerCase()
const cached = cache.get(cacheKey)
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
return NextResponse.json(cached.data)
}
const prompt = promptTemplate
.replace('{{owner}}', owner)
.replace('{{repo}}', repo)
.replace('{{files}}', files.slice(0, 80).join(', '))
.replace('{{stack}}', stack.join(', ') || 'Unknown')
.replace('{{dependencies}}', Object.keys(dependencies || {}).slice(0, 40).join(', ') || 'None detected')
.replace('{{sensitiveFiles}}', sensitiveFiles.join(', ') || 'None')
.replace('{{configFiles}}', configFiles.join(', ') || 'None')
let endpoint = baseUrl.replace(/\/+$/, '')
endpoint = endpoint.replace(/\/v1$/, '')
endpoint += '/v1/chat/completions'
const llmRes = await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },
body: JSON.stringify({
model: process.env.SHARED_LLM_MODEL || 'Kimi-K2.5-sandbox',
messages: [{ role: 'user', content: prompt }],
max_tokens: 8000,
temperature: 0.7,
}),
})
if (!llmRes.ok) {
console.error('LLM error:', await llmRes.text())
return NextResponse.json({ error: 'Failed to generate report' }, { status: 500 })
}
const data = await llmRes.json()
const msg = data.choices?.[0]?.message
const content = msg?.content || msg?.reasoning_content || ''
if (!content) {
console.error('LLM returned empty content:', JSON.stringify(data).slice(0, 500))
return NextResponse.json({ error: 'LLM returned empty response' }, { status: 500 })
}
const jsonMatch = content.match(/\{[\s\S]*\}/)
if (!jsonMatch) {
console.error('No JSON found in LLM response:', content.slice(0, 500))
return NextResponse.json({ error: 'Failed to parse report' }, { status: 500 })
}
const report = JSON.parse(jsonMatch[0])
cache.set(cacheKey, { data: report, timestamp: Date.now() })
return NextResponse.json(report)
} catch (e: any) {
console.error('Analyze error:', e)
return NextResponse.json({ error: e.message || 'Analysis failed' }, { status: 500 })
}
}