124 lines
6.1 KiB
JavaScript
124 lines
6.1 KiB
JavaScript
import express from 'express';
|
||
import cors from 'cors';
|
||
import dotenv from 'dotenv';
|
||
import { GoogleGenerativeAI } from '@google/generative-ai';
|
||
|
||
dotenv.config();
|
||
|
||
const app = express();
|
||
const port = 3001;
|
||
|
||
app.use(cors());
|
||
app.use(express.json());
|
||
|
||
// Initialize Gemini API
|
||
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY || '');
|
||
|
||
app.post('/api/surprise', async (req, res) => {
|
||
try {
|
||
if (!process.env.GEMINI_API_KEY) {
|
||
return res.status(500).json({ error: 'Missing API Key' });
|
||
}
|
||
const model = genAI.getGenerativeModel({ model: "gemini-2.5-flash" });
|
||
const prompt = `
|
||
Generate ONE creative, fun, specific theme for a children's worksheet (e.g. "Space-Rex Coding", "Underwater Hamster Tea Party").
|
||
Pick a random age between 3 and 7.
|
||
Pick a random activity type from: coloring, numbers, writing, find-the-way, counting.
|
||
|
||
Return JSON ONLY:
|
||
{
|
||
"theme": "string",
|
||
"age": number,
|
||
"section": "string"
|
||
}
|
||
`;
|
||
|
||
const result = await model.generateContent(prompt);
|
||
const text = result.response.text();
|
||
const jsonStr = text.replace(/```json/g, '').replace(/```/g, '').trim();
|
||
res.json(JSON.parse(jsonStr));
|
||
} catch (error) {
|
||
console.error('Surprise error:', error);
|
||
res.status(500).json({ error: 'Failed' });
|
||
}
|
||
});
|
||
|
||
app.post('/api/generate', async (req, res) => {
|
||
try {
|
||
const { theme, age, sections } = req.body;
|
||
console.log('Received generation request:', { theme, age, sections });
|
||
|
||
if (!process.env.GEMINI_API_KEY) {
|
||
console.warn('Missing GEMINI_API_KEY');
|
||
return res.status(500).json({ error: 'Server configuration error: Missing API Key' });
|
||
}
|
||
|
||
const model = genAI.getGenerativeModel({ model: "gemini-2.5-flash" });
|
||
|
||
const prompt = `
|
||
|
||
You are a professional illustrator of high-quality children's coloring books. Create ONLY clean, simple, bold-outline black-and-white SVGs in classic kid-book style: thick lines, large closed colorable areas, recognizable cute subjects, no distortions/blobs/weird features.
|
||
|
||
Create educational worksheet metadata for a ${age}-year-old child.
|
||
|
||
Theme: ${theme}
|
||
|
||
Sections: ${sections.join(', ')}
|
||
|
||
Return ONLY valid JSON. No other text, no explanations.
|
||
|
||
MANDATORY STYLE & QUALITY RULES — MUST FOLLOW EXACTLY:
|
||
- Depict the theme literally and recognizably using standard, cute children's illustration style (e.g. animals look like real animals with big eyes/smiles; no invented blobs, hybrids, pigtails/bows on wrong things, distorted proportions, extra random parts).
|
||
- Every visible element MUST be FULLY CLOSED paths (use Z/z to close <path>, or <circle>/<ellipse>/<polygon>/<rect>). NO open paths, NO <line>, NO <polyline> unless closed, NO stroke-only lines without enclosed fill regions.
|
||
- Forbidden forever: blobs, distorted/wrong anatomy (e.g. elephant with pigtails, sheep with leaf legs), floating disconnected lines/parts, random dots/scribbles/noise/artifacts, self-intersecting paths making uncolorable slivers, cut-off/incomplete shapes, inconsistent stroke widths, tiny details, fill="none" on main objects (except holes), any greyscale/shading.
|
||
- Stroke: #000000 solid (no dasharray except for traceable writing). Fill: #ffffff on all colorable regions.
|
||
- ViewBox: exactly '0 0 500 500'. Center content with 50–100px margins on all sides. No touching edges.
|
||
- Attributes: single quotes ' only for all XML.
|
||
- SVG: complete valid XML — <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 500 500'> ... </svg>. Use smooth curves (C, Q, S) for natural shapes. 8–20 large closed regions for coloring fun.
|
||
- High-quality kid style: bold thick outlines, big simple shapes, cute friendly expressions (big round eyes as circles/ovals, smiling curved mouth), balanced proportions (big head for cuteness), no complexity/noise.
|
||
|
||
AGE-SPECIFIC RULES — STRICT:
|
||
- For 3–4 years: Ultra-simple (1–2 huge objects max), stroke width 10–16px, very large regions (4–10 total), cartoon-big features, no small details/textures/patterns inside.
|
||
- For 5–7 years: Slightly more (3–6 elements), stroke 4–8px, allow small closed interior shapes (e.g. spots as tiny circles), but still bold and clear.
|
||
|
||
SECTION-SPECIFIC LOGIC — MUST BE EDUCATIONAL & COHERENT:
|
||
- colouring: One main cute subject (or 2–3 related) in simple pose/scene, many large closed areas (body parts, accessories), bold outlines, friendly look.
|
||
- numbers: Large closed-path digits (1–10 or similar) + theme-related countable objects grouped nearby.
|
||
- writing: Dotted/dashed traceable letters/numbers/words (use closed shapes for forms + dots/arrows for direction), clean practice space.
|
||
- find-the-way: Simple maze with closed barrier walls (rect/curved), clear start (arrow/circle) + end (goal object), theme elements along path, no traps.
|
||
- counting: Groups of identical closed objects (e.g. 3–10 apples) matching a shown number, tied to theme.
|
||
|
||
JSON exactly this structure:
|
||
{
|
||
"title": "Short fun title",
|
||
"subtitle": "Clear one-sentence kid instruction (e.g. Color this happy animal!)",
|
||
"sections": [
|
||
{
|
||
"id": "coloring-main | numbers-1-5 | writing-letters | find-the-way-maze | counting-objects",
|
||
"type": "coloring|numbers|writing|find-the-way|counting",
|
||
"content_svg": "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 500 500'>...FULL complete valid SVG code here...</svg>"
|
||
}
|
||
// exactly one object per section in array
|
||
]
|
||
}
|
||
|
||
CRITICAL: content_svg MUST be a string with single quotes in SVG attributes. Paths smooth and 100% closed.`;
|
||
const result = await model.generateContent(prompt);
|
||
const response = await result.response;
|
||
const text = response.text();
|
||
|
||
// Cleanup markdown code blocks if present
|
||
const jsonStr = text.replace(/```json/g, '').replace(/```/g, '').trim();
|
||
const data = JSON.parse(jsonStr);
|
||
|
||
res.json(data);
|
||
} catch (error) {
|
||
console.error('Generation error:', error);
|
||
res.status(500).json({ error: 'Failed to generate content' });
|
||
}
|
||
});
|
||
|
||
app.listen(port, () => {
|
||
console.log(`Server running at http://localhost:${port}`);
|
||
});
|