Files
ngo-toolkit/app/api/[tool]/generate-draft/route.ts
T

117 lines
4.5 KiB
TypeScript

import { NextRequest, NextResponse } from 'next/server';
import { query, getConfig } from '@/lib/db';
import { generate } from '@/lib/ai/client';
import { prompts } from '@/lib/ai/prompts';
import { ToolName, Session } from '@/lib/types';
const validTools: ToolName[] = ['logframe', 'funder-rewriter', 'smart-indicators', 'theory-of-change'];
export async function POST(
request: NextRequest,
{ params }: { params: { tool: string } }
) {
try {
const tool = params.tool as ToolName;
if (!validTools.includes(tool)) {
return NextResponse.json({ error: 'Invalid tool' }, { status: 400 });
}
const body = await request.json();
const { input, email } = body as { input: Record<string, string>; email?: string };
if (!input) {
return NextResponse.json({ error: 'Input required' }, { status: 400 });
}
// --- Free tier check ---
// Email is required to use the free tier. If no email, require payment immediately.
let isFreeTier = false;
if (email) {
const freeTierEnabled = (await getConfig('free_tier_enabled')) !== 'false';
if (freeTierEnabled) {
// Check if this email has ever generated before (any tool)
const existing = await query<{ id: number }>(
'SELECT id FROM leads WHERE email = $1 LIMIT 1',
[email]
);
if (existing.length === 0) {
// First time — insert lead, grant free tier
await query(
`INSERT INTO leads (email, org_name, tool) VALUES ($1, $2, $3)
ON CONFLICT (email) DO NOTHING`,
[email, input.organisation || input.project_title || null, tool]
);
isFreeTier = true;
}
// If email already exists → not free, falls through to paid path below
}
}
// Create session — track whether it starts as paid (free tier unlock)
const sessions = await query<Session>(
`INSERT INTO sessions (tool, email, input, is_paid) VALUES ($1, $2, $3, $4) RETURNING *`,
[tool, email || null, JSON.stringify(input), isFreeTier]
);
const session = sessions[0];
// Generate the draft
const toolPrompts = prompts[tool];
const systemPrompt = `You are a seasoned international development consultant with 20+ years of experience designing and evaluating programmes for major donors including USAID, DFID/FCDO, the EU, UN agencies, the World Bank, and large private foundations. You have written and reviewed hundreds of successful grant applications, logframes, theories of change, and M&E frameworks across sub-Saharan Africa, South and Southeast Asia, the Middle East, and Latin America.
Your writing is:
- Precise, evidence-informed, and donor-ready
- Grounded in current best practice (adaptive management, systems thinking, feminist M&E, localisation agendas)
- Free of jargon and buzzwords unless they carry specific technical meaning
- Structured clearly with logical hierarchy and consistent formatting
- Calibrated to the specific sector, geography, and donor context provided
Always produce outputs that a programme officer could submit with minimal revision. Favour concrete, measurable language over vague aspirations.`;
const userPrompt = toolPrompts.master(input);
const result = await generate(systemPrompt, userPrompt);
// Store full output in session
await query(
`UPDATE sessions SET output = $1, updated_at = NOW() WHERE id = $2`,
[result.text, session.id]
);
// Log usage
await query(
`INSERT INTO usage (session_id, tool, action, tokens_used) VALUES ($1, $2, $3, $4)`,
[session.id, tool, 'generate', result.tokens]
);
// Preview = first ~40% of content (chars)
const previewEnd = Math.floor(result.text.length * 0.40);
const preview = result.text.substring(0, previewEnd);
// If free tier or already paid → return full output
if (isFreeTier) {
return NextResponse.json({
session_id: session.id,
preview,
full_output: result.text,
is_paid: true,
free_tier_used: true,
});
}
// Otherwise return preview only — requires payment to unlock
return NextResponse.json({
session_id: session.id,
preview,
full_length: result.text.length,
is_paid: false,
requires_email: !email, // hint to frontend to prompt for email
});
} catch (error) {
console.error('Generate draft error:', error);
return NextResponse.json(
{ error: 'Failed to generate draft' },
{ status: 500 }
);
}
}