StoryKids - Architecture Technique
Date : 20/12/2025
Priorite : MOYENNE - FIL ROUGE
Status : Draft
Projet : Application d'histoires interactives pour enfants avec IA
1. Vue d'ensemble
┌─────────────────────────────────────────────────────────────────────┐
│ APPLICATIONS │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ App Web │ │ App Mobile │ │ Tablette │ │
│ │ (Qwik) │ │ (PWA) │ │ Optimisée │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ API │
│ Elysia (Bun) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ /stories │ │/generate │ │ /library │ │ /audio │ │ /images │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Supabase │ │ Ollama │ │ ComfyUI │ │ TTS │
│ PostgreSQL │ │ Story │ │ Images │ │ Audio │
│ + Storage │ │ Generator │ │ Generator │ │ Narration │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
2. Concept
Types d'histoires
| Type |
Description |
Interactivité |
| Conte classique |
Histoire linéaire avec illustrations |
Lecture simple |
| Aventure interactive |
L'enfant fait des choix |
Choix multiples (2-3 options) |
| Histoire personnalisée |
Prénom de l'enfant intégré |
Personnalisation |
| Conte audio |
Narration vocale |
Écoute passive |
Catégories
- Animaux
- Aventures
- Fées & Magie
- Espace & Science
- Amitié
- Nature
- Super-héros
- Pirates
Tranches d'âge
| Âge |
Caractéristiques |
| 3-5 ans |
Phrases courtes, beaucoup d'images, histoires simples |
| 6-8 ans |
Histoires plus longues, vocabulaire enrichi |
| 9-12 ans |
Aventures complexes, choix multiples |
3. Stack Technique
Frontend
| Techno |
Usage |
| Qwik |
App web (PWA) |
| TailwindCSS |
Styling coloré enfants |
| Framer Motion |
Animations pages |
| Howler.js |
Lecture audio |
Backend
| Techno |
Usage |
| Elysia (Bun) |
API REST |
| Supabase |
Auth, DB, Storage |
| Redis |
Cache, queue génération |
IA - Génération
| Techno |
Usage |
| Ollama (mistral:7b) |
Génération texte histoire |
| ComfyUI |
Génération illustrations |
| Coqui TTS / Edge TTS |
Narration audio |
4. Schéma Base de Données
-- Extensions
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- ============================================
-- UTILISATEURS (PARENTS)
-- ============================================
CREATE TABLE profiles (
id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
display_name VARCHAR(100),
email VARCHAR(255),
subscription_type VARCHAR(20) DEFAULT 'free', -- free, premium, family
subscription_expires_at TIMESTAMPTZ,
settings JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- ============================================
-- PROFILS ENFANTS
-- ============================================
CREATE TABLE children (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
parent_id UUID REFERENCES profiles(id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL, -- Prénom pour personnalisation
avatar_url TEXT,
birth_date DATE,
age_group VARCHAR(10), -- 3-5, 6-8, 9-12
-- Préférences
favorite_themes TEXT[] DEFAULT '{}', -- animaux, magie, espace, etc
favorite_characters TEXT[] DEFAULT '{}',
-- Stats
stories_read INTEGER DEFAULT 0,
total_reading_time INTEGER DEFAULT 0, -- minutes
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- ============================================
-- HISTOIRES (Templates + Générées)
-- ============================================
-- Templates d'histoires (créés par nous)
CREATE TABLE story_templates (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
title VARCHAR(300) NOT NULL,
description TEXT,
category VARCHAR(50), -- animaux, magie, aventure, etc
age_group VARCHAR(10),
duration_minutes INTEGER, -- durée estimée
-- Type
story_type VARCHAR(20), -- linear, interactive, personalized
-- Contenu (structure)
content_structure JSONB, -- { pages: [...], choices: [...] }
-- Assets
cover_image_url TEXT,
is_premium BOOLEAN DEFAULT false,
-- Stats
times_read INTEGER DEFAULT 0,
avg_rating DECIMAL(2,1),
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Histoires générées par IA
CREATE TABLE generated_stories (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
child_id UUID REFERENCES children(id) ON DELETE CASCADE,
template_id UUID REFERENCES story_templates(id), -- NULL si 100% généré
-- Paramètres de génération
generation_params JSONB, -- { theme, characters, childName, etc }
-- Contenu généré
title VARCHAR(300),
pages JSONB, -- [{ text: "...", image_url: "...", choices: [...] }]
-- Status
status VARCHAR(20) DEFAULT 'generating', -- generating, ready, failed
generation_progress INTEGER DEFAULT 0, -- 0-100
-- Audio
has_audio BOOLEAN DEFAULT false,
audio_url TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
completed_at TIMESTAMPTZ
);
-- Pages individuelles (pour histoires interactives)
CREATE TABLE story_pages (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
story_id UUID REFERENCES generated_stories(id) ON DELETE CASCADE,
page_number INTEGER NOT NULL,
content TEXT NOT NULL,
image_url TEXT,
audio_url TEXT,
-- Choix (pour interactif)
choices JSONB, -- [{ text: "...", next_page_id: "..." }]
is_ending BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- ============================================
-- BIBLIOTHÈQUE UTILISATEUR
-- ============================================
CREATE TABLE library (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
child_id UUID REFERENCES children(id) ON DELETE CASCADE,
story_id UUID, -- Peut référencer template ou generated
story_type VARCHAR(20), -- template, generated
is_favorite BOOLEAN DEFAULT false,
-- Progression
current_page INTEGER DEFAULT 1,
completed BOOLEAN DEFAULT false,
completed_at TIMESTAMPTZ,
-- Stats
times_read INTEGER DEFAULT 1,
total_time_spent INTEGER DEFAULT 0, -- secondes
added_at TIMESTAMPTZ DEFAULT NOW(),
last_read_at TIMESTAMPTZ
);
-- ============================================
-- IMAGES GÉNÉRÉES
-- ============================================
CREATE TABLE generated_images (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
story_id UUID REFERENCES generated_stories(id) ON DELETE CASCADE,
page_number INTEGER,
-- Prompt utilisé
prompt TEXT NOT NULL,
negative_prompt TEXT,
style VARCHAR(50), -- cartoon, watercolor, storybook, etc
-- Résultat
image_url TEXT,
thumbnail_url TEXT,
-- Status
status VARCHAR(20) DEFAULT 'pending', -- pending, generating, ready, failed
created_at TIMESTAMPTZ DEFAULT NOW(),
generated_at TIMESTAMPTZ
);
-- ============================================
-- AUDIO GÉNÉRÉS
-- ============================================
CREATE TABLE generated_audio (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
story_id UUID REFERENCES generated_stories(id) ON DELETE CASCADE,
page_number INTEGER,
text_content TEXT NOT NULL,
voice VARCHAR(50), -- nom de la voix TTS
language VARCHAR(10) DEFAULT 'fr',
audio_url TEXT,
duration_seconds INTEGER,
status VARCHAR(20) DEFAULT 'pending',
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- ============================================
-- MODÉRATION / SÉCURITÉ
-- ============================================
CREATE TABLE content_reports (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
story_id UUID,
reported_by UUID REFERENCES profiles(id),
reason TEXT,
status VARCHAR(20) DEFAULT 'pending', -- pending, reviewed, actioned
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Log des générations (pour audit)
CREATE TABLE generation_logs (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
story_id UUID,
step VARCHAR(50), -- text_generation, image_generation, audio_generation
input_params JSONB,
output_preview TEXT,
duration_ms INTEGER,
created_at TIMESTAMPTZ DEFAULT NOW()
);
5. API Endpoints
Auth & Profiles
POST /api/auth/register # Inscription parent
POST /api/auth/login # Connexion
GET /api/profile # Profil parent
Children (Profils enfants)
GET /api/children # Liste enfants
POST /api/children # Ajouter enfant
PATCH /api/children/:id # Modifier
DELETE /api/children/:id # Supprimer
Story Templates
GET /api/templates # Liste templates
GET /api/templates/:id # Détail template
GET /api/templates/categories # Catégories disponibles
GET /api/templates/featured # Histoires mises en avant
Story Generation
POST /api/generate/story # Lancer génération
Body: {
childId: "...",
theme: "animaux",
mainCharacter: "un petit lapin",
childName: "Emma",
storyType: "interactive",
ageGroup: "3-5"
}
GET /api/generate/:id/status # Status génération
GET /api/generate/:id # Récupérer histoire générée
Library
GET /api/library/:childId # Bibliothèque enfant
POST /api/library/:childId/add # Ajouter à bibliothèque
PATCH /api/library/:id/progress # Sauvegarder progression
DELETE /api/library/:id # Retirer
Reading
GET /api/stories/:id # Lire histoire complète
GET /api/stories/:id/page/:num # Page spécifique
POST /api/stories/:id/choice # Faire un choix (interactif)
Audio
GET /api/audio/:storyId # Audio complet
GET /api/audio/:storyId/page/:num # Audio page
POST /api/audio/generate # Générer audio (si pas existant)
6. Génération IA
Workflow de génération
1. Utilisateur configure l'histoire
└─> { theme, characters, childName, ageGroup, storyType }
2. API crée entry "generating"
└─> Redis queue: story_generation
3. Worker Text Generation
└─> Ollama génère le texte
└─> 5-10 pages selon l'âge
└─> Structure avec choix si interactif
4. Worker Image Generation (parallèle)
└─> Pour chaque page, ComfyUI génère illustration
└─> Style: storybook, cartoon, watercolor
5. Worker Audio Generation (optionnel)
└─> TTS pour chaque page
└─> Voix adaptée enfants
6. Assemblage final
└─> Status: "ready"
└─> Notification parent
Prompts Ollama
// /workers/story-generator.ts
const SYSTEM_PROMPT = `Tu es un auteur de livres pour enfants. Tu écris des histoires:
- Adaptées à l'âge indiqué
- Avec un vocabulaire approprié
- Positives et bienveillantes
- Sans violence ni contenu inapproprié
- Avec une morale ou un apprentissage
IMPORTANT: Tu dois TOUJOURS générer du contenu sûr pour les enfants.`;
async function generateStory(params: StoryParams) {
const prompt = `
Écris une histoire pour un enfant de ${params.ageGroup} ans.
Paramètres:
- Thème: ${params.theme}
- Personnage principal: ${params.mainCharacter}
- Prénom de l'enfant à intégrer: ${params.childName}
- Type: ${params.storyType === 'interactive' ? 'histoire interactive avec choix' : 'histoire linéaire'}
- Nombre de pages: ${params.ageGroup === '3-5' ? 5 : params.ageGroup === '6-8' ? 8 : 10}
${params.storyType === 'interactive' ? `
Pour les choix:
- 2 options maximum par page
- Les choix doivent être clairs et simples
- Tous les chemins mènent à une fin positive
` : ''}
Réponds en JSON:
{
"title": "Titre de l'histoire",
"pages": [
{
"number": 1,
"text": "Texte de la page...",
"imagePrompt": "Description pour générer l'illustration",
"choices": [ // Seulement si interactif
{ "text": "Choix 1", "nextPage": 2 },
{ "text": "Choix 2", "nextPage": 3 }
]
}
]
}
`;
const response = await fetch('http://192.168.1.30:11434/api/generate', {
method: 'POST',
body: JSON.stringify({
model: 'mistral:7b',
prompt,
system: SYSTEM_PROMPT,
options: { temperature: 0.8 }
})
});
return JSON.parse(await response.text());
}
Génération Images (ComfyUI)
// /workers/image-generator.ts
const STYLE_PROMPTS = {
'cartoon': 'cute cartoon style, bright colors, simple shapes, children book illustration',
'watercolor': 'soft watercolor painting, gentle colors, dreamy atmosphere, storybook art',
'storybook': 'classic storybook illustration, detailed, warm colors, cozy feeling',
'anime': 'cute anime style, big eyes, colorful, kawaii'
};
async function generateIllustration(prompt: string, style: string = 'storybook') {
const fullPrompt = `${prompt}, ${STYLE_PROMPTS[style]}, safe for children, no scary elements`;
const negativePrompt = 'scary, violent, blood, weapon, adult content, nsfw, realistic, photo';
const workflow = {
// ComfyUI workflow JSON
// Utiliser SDXL ou modèle adapté enfants
prompt: fullPrompt,
negative_prompt: negativePrompt,
steps: 20,
cfg_scale: 7,
width: 768,
height: 512,
seed: Math.floor(Math.random() * 1000000)
};
const response = await fetch('http://192.168.1.30:8188/prompt', {
method: 'POST',
body: JSON.stringify(workflow)
});
// Récupérer l'image générée
const result = await response.json();
return result.images[0];
}
Génération Audio (TTS)
// /workers/audio-generator.ts
// Option 1: Edge TTS (Microsoft, gratuit)
import edge_tts from 'edge-tts';
async function generateAudio(text: string, voice: string = 'fr-FR-DeniseNeural') {
const communicate = edge_tts.Communicate(text, voice);
const audioBuffer = await communicate.save();
// Upload to Supabase Storage
const filename = `audio/${uuid()}.mp3`;
await supabase.storage.from('stories').upload(filename, audioBuffer);
return supabase.storage.from('stories').getPublicUrl(filename);
}
// Voix recommandées pour enfants (français)
const VOICES = {
'narrateur': 'fr-FR-DeniseNeural', // Femme douce
'narratrice': 'fr-FR-HenriNeural', // Homme chaleureux
'enfant': 'fr-FR-EloiseNeural' // Voix enfant
};
7. Interface Utilisateur
Écran d'accueil (Enfant)
┌─────────────────────────────────────────┐
│ 🌟 Bonjour Emma ! [👤]│
├─────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────┐│
│ │ 🎨 Créer une nouvelle histoire ││
│ │ [Grande illustration] ││
│ └─────────────────────────────────────┘│
│ │
│ 📚 Continuer à lire │
│ ┌───────┐ ┌───────┐ ┌───────┐ │
│ │ 🐰 │ │ 🚀 │ │ 🧚 │ │
│ │Lapin │ │Espace │ │Fée │ │
│ │p.3/10 │ │p.5/8 │ │Terminé│ │
│ └───────┘ └───────┘ └───────┘ │
│ │
│ ⭐ Histoires populaires │
│ ┌───────┐ ┌───────┐ ┌───────┐ │
│ │ │ │ │ │ │ │
│ └───────┘ └───────┘ └───────┘ │
│ │
└─────────────────────────────────────────┘
Création d'histoire
┌─────────────────────────────────────────┐
│ ← Nouvelle histoire │
├─────────────────────────────────────────┤
│ │
│ Choisis un thème: │
│ ┌───────┐ ┌───────┐ ┌───────┐ │
│ │ 🐾 │ │ ✨ │ │ 🚀 │ │
│ │Animaux│ │Magie │ │Espace │ │
│ └───────┘ └───────┘ └───────┘ │
│ ┌───────┐ ┌───────┐ ┌───────┐ │
│ │ 🏴☠️ │ │ 🦸 │ │ 🌳 │ │
│ │Pirates│ │Héros │ │Nature │ │
│ └───────┘ └───────┘ └───────┘ │
│ │
│ Qui est le héros ? │
│ ┌─────────────────────────────────┐ │
│ │ Un petit lapin courageux 🐰 │ │
│ └─────────────────────────────────┘ │
│ │
│ ☑️ Mettre mon prénom dans l'histoire │
│ ☑️ Avec des choix à faire │
│ ☑️ Avec la voix du narrateur │
│ │
│ ┌─────────────────────────────────────┐│
│ │ ✨ Créer mon histoire ✨ ││
│ └─────────────────────────────────────┘│
└─────────────────────────────────────────┘
Lecture d'histoire
┌─────────────────────────────────────────┐
│ ← Le Lapin et la Forêt Magique 🔊 │
├─────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────┐│
│ │ ││
│ │ [Illustration pleine page] ││
│ │ 🐰 dans la forêt ││
│ │ ││
│ └─────────────────────────────────────┘│
│ │
│ "Emma marchait dans la forêt quand │
│ elle rencontra un petit lapin blanc. │
│ Le lapin avait l'air perdu." │
│ │
│ ┌─────────────────────────────────────┐│
│ │ 🤔 Que fait Emma ? ││
│ │ ││
│ │ ┌─────────────────────────────┐ ││
│ │ │ 🤝 Aider le lapin │ ││
│ │ └─────────────────────────────┘ ││
│ │ ┌─────────────────────────────┐ ││
│ │ │ 👀 Observer de loin │ ││
│ │ └─────────────────────────────┘ ││
│ └─────────────────────────────────────┘│
│ │
│ ← Page 3/10 → │
└─────────────────────────────────────────┘
8. Sécurité Enfants
Modération contenu
// Vérification avant affichage
async function moderateContent(text: string): Promise<boolean> {
const bannedPatterns = [
/violence/i,
/sang/i,
/mort/i,
/tuer/i,
// ... liste complète
];
for (const pattern of bannedPatterns) {
if (pattern.test(text)) {
await reportContent(text, 'banned_word');
return false;
}
}
return true;
}
// Vérification images (avant stockage)
async function moderateImage(imageUrl: string): Promise<boolean> {
// Utiliser un service de modération (optionnel)
// Ou vérification manuelle pour contenu premium
return true;
}
Contrôle parental
- Limite de temps par session
- Historique de lecture visible par parents
- Verrouillage par PIN pour paramètres
- Pas de publicités
- Pas de liens externes
- Pas de chat ou interactions sociales
9. MVP - Phases
Phase 1 : Core (2-3 semaines)
- [ ] Setup Qwik + Elysia
- [ ] Auth parents + profils enfants
- [ ] 10 templates d'histoires manuels
- [ ] Lecteur d'histoires basique
- [ ] Bibliothèque
Phase 2 : Génération texte (2 semaines)
- [ ] Génération histoire Ollama
- [ ] Interface de création
- [ ] Personnalisation prénom
Phase 3 : Images (2 semaines)
- [ ] Intégration ComfyUI
- [ ] Génération illustrations
- [ ] Styles variés
Phase 4 : Audio (1-2 semaines)
- [ ] Intégration TTS
- [ ] Lecteur audio
- [ ] Voix narrateur
Phase 5 : Interactivité (2 semaines)
- [ ] Histoires à choix
- [ ] Branches narratives
- [ ] Plusieurs fins
Phase 6 : Polish & Monétisation
- [ ] Abonnements (Stripe)
- [ ] PWA optimisée tablettes
- [ ] Animations
- [ ] Mode offline
10. Monétisation
| Plan |
Prix |
Fonctionnalités |
| Gratuit |
0€ |
5 histoires/mois, templates basiques |
| Premium |
4.99€/mois |
Illimité, génération IA, audio, tous styles |
| Famille |
7.99€/mois |
Premium + 5 profils enfants |
11. Déploiement
services:
storykids-app:
build: ./app
ports:
- "4900:3000"
storykids-api:
build: ./api
ports:
- "4901:3001"
environment:
- OLLAMA_URL=http://192.168.1.30:11434
- COMFYUI_URL=http://192.168.1.30:8188
storykids-workers:
build: ./workers
URLs