← back to Norma
app/api/petitions/draft/route.ts
112 lines
/**
* Petition Draft Generation API
* POST /api/petitions/draft — generate AI petition draft from a selected topic
*
* Uses Gemini 2.0 Flash to create a compelling public petition.
* NO organizational branding in output.
*/
import { NextRequest, NextResponse } from 'next/server';
import { requireRole } from '@/lib/require-role';
const GEMINI_KEY = process.env.GEMINI_API_KEY || '';
const GEMINI_URL = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${GEMINI_KEY}`;
export async function POST(req: NextRequest) {
const auth = requireRole(req, 'admin', 'staff');
if (auth instanceof NextResponse) return auth;
let topicTitle = 'Important Issue';
try {
const body = await req.json();
topicTitle = body.topicTitle || 'Important Issue';
const { topicDescription, category, source } = body;
if (!topicTitle) {
return NextResponse.json({ error: 'topicTitle is required' }, { status: 400 });
}
const prompt = `You are a professional petition writer creating a public petition for concerned citizens.
TOPIC: "${topicTitle}"
${topicDescription ? `CONTEXT: ${topicDescription}` : ''}
${category ? `CATEGORY: ${category}` : ''}
${source ? `SOURCE TYPE: ${source}` : ''}
Generate a compelling, professional public petition. The petition should:
1. Have a strong, action-oriented title (max 80 characters)
2. Include a persuasive body (250-400 words) that explains the issue, why it matters, and what action is needed
3. Identify a clear target (who should take action — e.g., "U.S. Congress", "President", specific agency)
4. Include 3-5 talking points that summarize the key arguments
5. Suggest a realistic signature goal
IMPORTANT:
- Write from the perspective of concerned citizens and voters
- Do NOT reference any specific organization or group as the author
- Be factual and persuasive, not inflammatory
- Include specific data points or policy references where relevant
- The tone should be urgent but professional
Return ONLY valid JSON (no markdown fences) with this exact structure:
{
"title": "string",
"body": "string",
"target": "string",
"talkingPoints": ["point1", "point2", "point3"],
"suggestedGoal": 10000,
"confidence": 0.85
}`;
const res = await fetch(GEMINI_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
contents: [{ parts: [{ text: prompt }] }],
generationConfig: { temperature: 0.7, maxOutputTokens: 3000 },
}),
});
if (!res.ok) {
throw new Error(`Gemini API error: ${res.status}`);
}
const data = await res.json();
const text = data.candidates?.[0]?.content?.parts?.[0]?.text || '';
// Parse JSON from response (strip markdown fences if present)
const cleaned = text.replace(/```json\s*/g, '').replace(/```\s*/g, '').trim();
const parsed = JSON.parse(cleaned);
return NextResponse.json({
title: parsed.title || `Take Action: ${topicTitle}`,
body: parsed.body || '',
target: parsed.target || 'U.S. Congress',
talkingPoints: parsed.talkingPoints || [],
suggestedGoal: parsed.suggestedGoal || 10000,
aiConfidence: parsed.confidence || 0.7,
aiModel: 'gemini-2.0-flash',
sourceTopic: topicTitle,
});
} catch (err) {
const msg = (err as Error).message;
console.error('[draft] POST error:', msg);
// Fallback: return a template if AI fails
return NextResponse.json({
title: `Take Action: ${topicTitle}`,
body: `We, the undersigned, call for immediate action on ${topicTitle}. This issue affects millions of Americans and demands the attention of our elected officials.\n\nThe time for action is now. We urge our representatives to prioritize this critical matter and take concrete steps to address it.`,
target: 'U.S. Congress',
talkingPoints: [
'This issue directly impacts American families',
'Immediate legislative action is needed',
'Public support for change is overwhelming',
],
suggestedGoal: 10000,
aiConfidence: 0.3,
aiModel: 'fallback-template',
sourceTopic: topicTitle,
error: 'AI generation failed, using template',
});
}
}