Vue d’ensemble de l’architecture
Fichiers source
Cette page est générée à partir des fichiers source suivants :
- src/app/layout.tsx
- src/lib/store.ts
- src/types/index.ts
- src/contexts/NotificationContext.tsx
- src/hooks/useVoiceRecorder.ts
- src/components/WritingToolGuide.tsx
- src/app/write/page.tsx
- src/app/essays/page.tsx
- src/app/settings/page.tsx
- src/components/ui/dialog.tsx
- package.json
- .spec-workflow/templates/design-template.md
- next-env.d.ts
- next.config.js
- postcss.config.js
- tailwind.config.js
- src/app/page.tsx
- src/lib/utils.ts
- src/data/tools.ts
- src/app/not-found.tsx
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 leNotificationProvideret 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
.mjsafin 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 :
| Interface | Propriétés clés | Usage |
|---|---|---|
StudentProgress | currentLevel, levels, totalScore, unlockedTools, dailyChallenge, habitTracker | Suivi de la progression globale de l'étudiant |
AIConfig | apiKey, baseURL, model, models[] | Configuration de connexion aux services AI |
ActionItem | id, task, completed | Tâches actionnables pour l'amélioration des essais |
EssayVersion | id, content, feedback, createdAt, actionItems, parentId, contentType, imageUrl, audioUrl, transcribedText | Versions 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 :
typescript1// 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é :
typescript1interface 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 :
| Composant | Rôle | Classes par défaut |
|---|---|---|
DialogHeader | En-tête du dialog | flex flex-col space-y-1.5 text-center sm:text-left |
DialogFooter | Pied de page avec actions | flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2 |
DialogTitle | Titre accessible | text-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 :
typescript1const 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 :
typescript1interface VersionNode extends EssayVersion { 2 order: number; 3 children: VersionNode[]; 4}
Calcul de différences textuelles :
La fonction calculateTextDiff compare deux versions et retourne :
typescript1{ 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 :
typescript1const 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 :
typescript1const 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 :
| Principe | Description | Application dans le projet |
|---|---|---|
| Single File Responsibility | Chaque fichier gère un domaine spécifique | store.ts pour l'état, tools.ts pour les données d'outils |
| Component Isolation | Composants focalisés plutôt que monolithiques | WritingToolGuide.tsx pour l'affichage des guides |
| Service Layer Separation | Séparation données/logique/présentation | Contextes pour services, pages pour présentation |
| Utility Modularity | Utilitaires à but unique | utils.ts pour les fonctions communes |
Composant WritingToolGuide
Le composant src/components/WritingToolGuide.tsx:6-49 illustre l'isolation des composants :
typescript1interface 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 :
typescript1const 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é :
typescript1const 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 :
-
Interaction → Hook → Store : Les actions utilisateur déclenchent des hooks personnalisés qui encapsulent la logique métier avant de muter le store.
-
Validation TypeScript : Les types définis dans
src/types/index.tsvalident les structures de données à chaque mutation du store. -
Feedback Contextuel : Le
NotificationContextfournit un retour utilisateur asynchrone pour les opérations critiques (sauvegarde, suppression, achievements). -
Persistance Implicite : Zustand persiste automatiquement l'état, synchronisant les données entre les onglets et sessions.
Décisions de conception et compromis
Choix technologiques
| Technologie | Usage | Justification | Alternatives considérées |
|---|---|---|---|
| Next.js App Router | Framework principal | Routage basé sur fichiers, SSR/SSG, optimisation automatique | Remix, Gatsby |
| Zustand | Gestion d'état | API minimaliste, pas de boilerplate, persistance intégrée | Redux Toolkit, Jotai, Recoil |
| Tailwind CSS | Styling | Utility-first, customisation Morandi, performances | Styled Components, CSS Modules |
| Radix UI | Composants accessibles | Primitives sans style, accessibilité ARIA native | Headless UI, Reach UI |
| TypeScript | Typage statique | Sécurité compile-time, autocomplétion IDE | JavaScript pur, JSDoc |
| Mermaid | Diagrammes | Intégration Markdown, rendu côté client | PlantUML, D2 |
| Lucide React | Icônes | Tree-shakable, cohérence visuelle | Heroicons, Feather Icons |
| MediaRecorder API | Enregistrement audio | Native browser API, pas de dépendances | Web Audio API bas niveau |
Limitations connues
-
Images non optimisées :
next.config.jsconfigureimages: { unoptimized: true }pour la compatibilité Vercel, désactivant l'optimisation automatique Next.js. -
Enregistrement vocal limité : L'API MediaRecorder dépend du support navigateur et peut ne pas fonctionner sur tous les appareils mobiles.
-
Persistance côté client : Zustand persiste dans localStorage, limitant la portabilité des données entre appareils sans backend.
-
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
| Fichier | Rôle | Ligne d'entrée |
|---|---|---|
src/app/layout.tsx | Layout racine, providers globaux | export default function RootLayout (L14) |
src/app/page.tsx | Page d'accueil, gamification | export default function HomePage (L16) |
src/lib/store.ts | Store Zustand centralisé | interface AppState (L7) |
src/types/index.ts | Définitions de types | export interface StudentProgress (L103) |
Flux de démarrage
- Initialisation Next.js : Le framework charge
layout.tsx, instancie leNotificationProvider. - Hydratation Zustand : Le store restaure l'état depuis localStorage.
- Rendu page : La page active consomme le store via
useAppStore(). - Contextes : Les providers (
NotificationContext) injectent leurs valeurs dans l'arbre React. - Types : TypeScript valide les props et les appels de store à la compilation.
