33800 Docs

← Retour

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

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


9. MVP - Phases

Phase 1 : Core (2-3 semaines)

Phase 2 : Génération texte (2 semaines)

Phase 3 : Images (2 semaines)

Phase 4 : Audio (1-2 semaines)

Phase 5 : Interactivité (2 semaines)

Phase 6 : Polish & Monétisation


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

Service URL
App https://storykids.33800.nowhere84.com
API https://storykids-api.33800.nowhere84.com