Tarifs

Vue d’ensemble de l’architecture

Fichiers source

Cette page est générée à partir des fichiers source suivants :

Ce projet est une plateforme d'apprentissage gamifiée pour l'écriture destinée aux élèves de sixième. L'application adopte une architecture Next.js moderne avec App Router, intégrant un système de gestion d'état Zustand, des contextes React pour les fonctionnalités transversales, et un design system personnalisé basé sur Tailwind CSS avec une palette de couleurs Morandi.

Structure globale de lapplication

L'architecture repose sur Next.js avec App Router, organisant le code selon une structure de répertoires basée sur le routage. Le layout racine définit la structure HTML fondamentale, les providers globaux et les métadonnées de l'application.

正在加载图表渲染器...

Points clés de l'architecture :

  • Layout racine : Le fichier src/app/layout.tsx:1-30 définit la structure HTML avec lang="zh-CN", intègre le NotificationProvider et configure les métadonnées SEO incluant titre, description et mots-clés pour la plateforme d'écriture.

  • Configuration Next.js : Le fichier next.config.js:1-20 active le mode strict React, SWC minification, et configure webpack pour gérer les modules .mjs afin de résoudre les erreurs de syntaxe JavaScript lors du déploiement Vercel.

  • Design System Morandi : La configuration tailwind.config.js:1-277 étend Tailwind avec des palettes personnalisées (morandi-blue, morandi-green, morandi-beige, etc.), des ombres card personnalisées et des animations d'accordéon.

  • Séparation des responsabilités : Chaque page (home, write, essays, settings) est isolée dans son propre fichier, consommant le store global et les composants UI partagés.

Gestion détat et modèles de données

Le cœur de la gestion d'état repose sur Zustand, offrant un store centralisé avec une interface TypeScript fortement typée. Cette architecture permet une gestion prévisible de la progression étudiante, des essais et de la configuration AI.

正在加载图表渲染器...

Détails du store global :

L'interface src/lib/store.ts:6-45 définit l'AppState avec des méthodes pour gérer la progression (setProgress, completeLevel, unlockNextLevel, passTest), la configuration AI (setAIConfig, setAvailableModels), les essais (addEssay, updateEssay, deleteEssay, addEssayVersion, updateEssayVersion, deleteEssayVersion), le suivi des habitudes (setDailyChallenge, updateHabitTracker, addAchievement) et la maîtrise des outils (updateToolMastery).

Modèles de données TypeScript :

Les types définis dans src/types/index.ts:103-142 incluent :

InterfacePropriétés clésUsage
StudentProgresscurrentLevel, levels, totalScore, unlockedTools, dailyChallenge, habitTrackerSuivi de la progression globale de l'étudiant
AIConfigapiKey, baseURL, model, models[]Configuration de connexion aux services AI
ActionItemid, task, completedTâches actionnables pour l'amélioration des essais
EssayVersionid, content, feedback, createdAt, actionItems, parentId, contentType, imageUrl, audioUrl, transcribedTextVersions d'essais avec support multimédia

Arbre de versions d'essais :

Le système supporte un arbre de versions via parentId dans EssayVersion, permettant de tracer l'évolution d'un essai à travers ses révisions. Le champ contentType distingue les entrées texte ('text'), image ('image') et audio ('audio'), avec les URLs correspondantes stockées dans imageUrl et audioUrl.

Système de composants et contexte

L'application utilise des contextes React pour les fonctionnalités transversales et des hooks personnalisés pour encapsuler la logique réutilisable. Les composants UI s'appuient sur Radix UI primitives pour l'accessibilité.

Contexte de notification

Le src/contexts/NotificationContext.tsx:14-53 expose un provider avec trois méthodes :

typescript
1// Interface du contexte de notification
2interface NotificationContextType {
3  showSuccess: (message: string, duration?: number) => void;
4  showError: (message: string, duration?: number) => void;
5  showWarning: (message: string, duration?: number) => void;
6}

Le provider rend les notifications en position fixed top-4 right-4 z-50, itérant sur un tableau de notifications avec leurs types (success, error, warning), messages et durées configurables. Le hook useNotificationContext lance une erreur descriptive s'il est utilisé hors du provider.

Hook d'enregistrement vocal

Le hook src/hooks/useVoiceRecorder.ts:14-53 implémente un système d'enregistrement segmenté :

typescript
1interface UseVoiceRecorderReturn {
2  isRecording: boolean;
3  isPaused: boolean;
4  recordingTime: number;
5  segments: VoiceSegment[];
6  startRecording: () => Promise<void>;
7  pauseRecording: () => void;
8  resumeRecording: () => void;
9  stopRecording: () => void;
10  resetRecording: () => void;
11  addTranscriptToSegment: (segmentId: string, transcript: string) => void;
12}

Paramètres de segmentation :

  • SEGMENT_DURATION = 20000 (20 secondes par segment)
  • OVERLAP_DURATION = 2000 (2 secondes de chevauchement)

Le hook utilise des refs pour MediaRecorder, les chunks audio, les timers et l'ID de segment courant, assurant un nettoyage approprié dans le useEffect de cleanup.

Composants Dialog UI

Les composants src/components/ui/dialog.tsx:55-94 encapsulent Radix Dialog primitives :

ComposantRôleClasses par défaut
DialogHeaderEn-tête du dialogflex flex-col space-y-1.5 text-center sm:text-left
DialogFooterPied de page avec actionsflex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2
DialogTitleTitre accessibletext-lg font-semibold leading-none tracking-tight

Pages principales et flux utilisateur

L'application organise ses fonctionnalités en quatre pages principales, chacune responsable d'un domaine spécifique de l'expérience utilisateur.

Page d'accueil avec gamification

La page src/app/page.tsx:1-352 orchestre le système de gamification :

Génération de défis quotidiens :

typescript
1const generateDailyChallenge = () => {
2  const available = getAvailableToolsForChallenge();
3  const selected = available.length > 0 ? pickRandom(available) : writingTools[0];
4  const exercises = selected.exercises || [];
5  const task = exercises.length > 0 ? pickRandom(exercises) : '自由写作:...';
6  return {
7    date: new Date(),
8    task,
9    completed: false,
10    streak: currentChallenge?.streak || 0,
11    recommendedToolId: selected.id,
12    canMakeup: false,
13  };
14};

Système d'achievements :

  • streak === 1 → "写作新手" (Premier défi complété)
  • streak === 7 → "一周坚持" (7 jours consécutifs)
  • streak === 30 → "写作达人" (30 jours consécutifs)

Logique de rattrapage (makeup) : Si le défi précédent n'était pas complété, canMakeup est activé pour permettre une récupération qui incrémente le streak sans marquer le défi comme complété.

Éditeur d'écriture avec gestion des versions

La page src/app/write/page.tsx:19-248 implémente un système sophistiqué de gestion de versions :

Structure de données VersionNode :

typescript
1interface VersionNode extends EssayVersion {
2  order: number;
3  children: VersionNode[];
4}

Calcul de différences textuelles : La fonction calculateTextDiff compare deux versions et retourne :

typescript
1{
2  added: string[];
3  removed: string[];
4  modified: string[];
5  shouldUseFullContent: boolean;
6}

Construction de l'arbre de versions : La fonction buildVersionTree organise les versions en arbre en utilisant parentId, assignant un ordre séquentiel et triant récursivement les enfants.

Configuration AI et paramètres

La page src/app/settings/page.tsx:25-147 gère la configuration AI avec auto-sauvegarde :

Fonction de sauvegarde avec debounce :

typescript
1const setApiKeyAndSave = (value: string) => {
2  setApiKey(value);
3  if (saveTimeoutRef.current) {
4    clearTimeout(saveTimeoutRef.current);
5  }
6  saveTimeoutRef.current = setTimeout(() => {
7    saveAIConfig(value, baseURL, model, aiConfig?.models || []);
8  }, 1000);
9};

Récupération des modèles disponibles :

typescript
1const fetchModels = async () => {
2  const endpoint = getActualEndpoint(baseURL);
3  const response = await fetch(`${endpoint}/models`, {
4    headers: {
5      'Authorization': `Bearer ${apiKey}`,
6      'Content-Type': 'application/json',
7    },
8  });
9  const data = await response.json();
10  const modelNames = data.data.map((model: any) => model.id).filter(Boolean);
11  setModels(modelNames);
12  setAvailableModels(modelNames);
13};

Architecture modulaire et principes de conception

Le projet suit des principes d'architecture modulaire documentés dans le template de conception.

Principes de conception modulaire

D'après .spec-workflow/templates/design-template.md:1-96, l'architecture adhère à quatre principes fondamentaux :

PrincipeDescriptionApplication dans le projet
Single File ResponsibilityChaque fichier gère un domaine spécifiquestore.ts pour l'état, tools.ts pour les données d'outils
Component IsolationComposants focalisés plutôt que monolithiquesWritingToolGuide.tsx pour l'affichage des guides
Service Layer SeparationSéparation données/logique/présentationContextes pour services, pages pour présentation
Utility ModularityUtilitaires à but uniqueutils.ts pour les fonctions communes

Composant WritingToolGuide

Le composant src/components/WritingToolGuide.tsx:6-49 illustre l'isolation des composants :

typescript
1interface WritingToolGuideProps {
2  toolId: string;
3}
4
5const WritingToolGuide = ({ toolId }: WritingToolGuideProps) => {
6  const [activeSection, setActiveSection] = useState<string | null>(null);
7  const tool = writingTools.find(t => t.id === toolId);
8  
9  if (!tool || !tool.guidance) {
10    return null;
11  }
12  
13  // Génération dynamique des sections basée sur guidance
14};

Le composant retourne null si l'outil n'existe pas ou n'a pas de contenu de guidage, évitant le rendu conditionnel dans le parent.

Page Essays avec séparation logique

La page src/app/essays/page.tsx:20-77 démontre la séparation entre gestion d'état et affichage :

Gestion de suppression avec confirmation :

typescript
1const handleDeleteEssay = (id: string) => {
2  setEssayToDelete(id);
3  setIsConfirmDialogOpen(true);
4};
5
6const handleConfirmDelete = () => {
7  if (essayToDelete) {
8    deleteEssay(essayToDelete);
9    if (selectedEssay?.id === essayToDelete) {
10      setSelectedEssay(null);
11      setSelectedVersion(null);
12    }
13    showSuccess('作文已删除');
14  }
15  setIsConfirmDialogOpen(false);
16  setEssayToDelete(null);
17};

Sélection de contenu versionné :

typescript
1const getCurrentContent = () => {
2  if (selectedVersion) {
3    return selectedVersion.content;
4  }
5  return selectedEssay ? getCurrentEssayContent(selectedEssay) : '';
6};

Flux de données et chaînes dappels

Le diagramme suivant illustre le flux de données depuis l'interaction utilisateur jusqu'à la persistance dans le store.

正在加载图表渲染器...

Points clés du flux :

  1. Interaction → Hook → Store : Les actions utilisateur déclenchent des hooks personnalisés qui encapsulent la logique métier avant de muter le store.

  2. Validation TypeScript : Les types définis dans src/types/index.ts valident les structures de données à chaque mutation du store.

  3. Feedback Contextuel : Le NotificationContext fournit un retour utilisateur asynchrone pour les opérations critiques (sauvegarde, suppression, achievements).

  4. Persistance Implicite : Zustand persiste automatiquement l'état, synchronisant les données entre les onglets et sessions.

Décisions de conception et compromis

Choix technologiques

TechnologieUsageJustificationAlternatives considérées
Next.js App RouterFramework principalRoutage basé sur fichiers, SSR/SSG, optimisation automatiqueRemix, Gatsby
ZustandGestion d'étatAPI minimaliste, pas de boilerplate, persistance intégréeRedux Toolkit, Jotai, Recoil
Tailwind CSSStylingUtility-first, customisation Morandi, performancesStyled Components, CSS Modules
Radix UIComposants accessiblesPrimitives sans style, accessibilité ARIA nativeHeadless UI, Reach UI
TypeScriptTypage statiqueSécurité compile-time, autocomplétion IDEJavaScript pur, JSDoc
MermaidDiagrammesIntégration Markdown, rendu côté clientPlantUML, D2
Lucide ReactIcônesTree-shakable, cohérence visuelleHeroicons, Feather Icons
MediaRecorder APIEnregistrement audioNative browser API, pas de dépendancesWeb Audio API bas niveau

Limitations connues

  1. Images non optimisées : next.config.js configure images: { unoptimized: true } pour la compatibilité Vercel, désactivant l'optimisation automatique Next.js.

  2. Enregistrement vocal limité : L'API MediaRecorder dépend du support navigateur et peut ne pas fonctionner sur tous les appareils mobiles.

  3. Persistance côté client : Zustand persiste dans localStorage, limitant la portabilité des données entre appareils sans backend.

  4. Internationalisation partielle : L'application cible principalement les utilisateurs chinois (lang="zh-CN"), sans système i18n complet.

Configuration et démarrage

Structure de configuration

├── next.config.js      # Configuration Next.js (SWC, webpack, images)
├── tailwind.config.js  # Design system Morandi, animations
├── postcss.config.js   # Pipeline CSS (Tailwind, Autoprefixer)
├── next-env.d.ts       # Types TypeScript Next.js
└── src/
    ├── app/            # Pages et layouts App Router
    ├── lib/            # Store et utilitaires
    ├── types/          # Définitions TypeScript
    ├── contexts/       # Providers React Context
    ├── hooks/          # Hooks personnalisés
    ├── components/     # Composants réutilisables
    └── data/           # Données statiques (tools.ts)

Points d'entrée

FichierRôleLigne d'entrée
src/app/layout.tsxLayout racine, providers globauxexport default function RootLayout (L14)
src/app/page.tsxPage d'accueil, gamificationexport default function HomePage (L16)
src/lib/store.tsStore Zustand centraliséinterface AppState (L7)
src/types/index.tsDéfinitions de typesexport interface StudentProgress (L103)

Flux de démarrage

  1. Initialisation Next.js : Le framework charge layout.tsx, instancie le NotificationProvider.
  2. Hydratation Zustand : Le store restaure l'état depuis localStorage.
  3. Rendu page : La page active consomme le store via useAppStore().
  4. Contextes : Les providers (NotificationContext) injectent leurs valeurs dans l'arbre React.
  5. Types : TypeScript valide les props et les appels de store à la compilation.