This page is generated from the following source files:
This project is a gamified writing learning platform designed for sixth-grade students, built with Next.js 14 App Router, TypeScript, and Zustand for state management. The architecture follows a modular design with clear separation between presentation, business logic, and data layers, enabling progressive skill development through a structured writing tools system.
The application implements a modern React-based architecture leveraging Next.js server-side rendering capabilities combined with client-side state management. The system is organized into distinct layers: the presentation layer (React components and pages), the state management layer (Zustand store), the data layer (TypeScript interfaces and static tool definitions), and the infrastructure layer (Next.js configuration and build tools).
正在加载图表渲染器...
Key Architecture Points:
Provider Pattern: The RootLayout wraps all children with NotificationProvider, establishing a global notification system accessible throughout the component tree.
Centralized State: The Zustand store serves as the single source of truth for student progress, essays, AI configuration, and habit tracking, eliminating prop drilling.
Type-Safe Data Models: All data structures are defined in TypeScript interfaces, ensuring compile-time validation across the application.
Modular Tool System: Writing tools follow a DAG (Directed Acyclic Graph) structure defined in tools.ts, allowing flexible prerequisite relationships.
Responsibility Boundary: The Zustand store (src/lib/store.ts) manages all application state including student progress, essay collections, AI configuration, and habit tracking. It does NOT handle UI state or component-level concerns.
Entry Points and Key APIs:
| API Method | Purpose | Input/Output |
|---|---|---|
setProgress | Update student progress | Input: StudentProgress, Output: void |
completeLevel | Mark tool level complete | Input: toolId: string, score: number |
addEssay | Create new essay | Input: Omit<Essay, 'id' | 'createdAt'>, Output: string (ID) |
addEssayVersion | Add version to essay | Input: essayId, content, feedback?, actionItems?, parentId?, metadata? |
updateActionItem | Toggle action item status | Input: essayId, versionId, actionItemId, completed |
passTest | Mark comprehension test passed | Input: toolId: string |
Key Data Structures:
typescript1// From src/lib/store.ts:7-44 2interface AppState { 3 // Student progress tracking 4 progress: StudentProgress; 5 6 // AI configuration for feedback generation 7 aiConfig: AIConfig | null; 8 9 // Essay collection with version history 10 essays: Essay[]; 11 12 // Habit tracking for gamification 13 habitTracker: HabitTracker; 14}
Critical Call Chain:
When a student completes a writing challenge, the flow is: handleChallengeComplete() in page.tsx → setDailyChallenge() → addAchievement() if streak threshold met → UI re-renders with updated progress.
Error Handling:
The store uses TypeScript's type system for compile-time validation. Runtime validation occurs at the API boundary when persisting data. The aiConfig can be null, requiring null checks before AI operations.
Responsibility Boundary: The writing tools system (src/data/tools.ts and src/lib/tool-config.ts) defines the pedagogical structure of writing techniques. It handles tool definitions, unlock conditions, and categorization but NOT progress tracking (delegated to store).
Entry Points:
typescript1// Main data export - src/data/tools.ts:9 2export const writingTools: WritingTool[] = [...] 3 4// Configuration mapping - src/lib/tool-config.ts:2-69 5export const topLevelToolsConfig = [...] 6export const subToolToParentMap: Record<string, string> = {}
Key Data Structures:
typescript1// From src/types/index.ts:10-49 2interface WritingTool { 3 id: string; 4 name: string; 5 mantra: string; // Memory aid phrase 6 examples: { bad: string; good: string; }[]; 7 comprehensionTest?: ComprehensionTest; 8 unlockConditions?: { 9 prerequisiteTools?: string[]; 10 minMasteryLevel?: number; 11 minPracticeCount?: number; 12 minWritingStreak?: number; 13 password?: string; 14 }; 15 subTools?: string[]; // DAG relationship 16 guidance?: { 17 coreConcepts?: string[]; 18 techniques?: Technique[]; 19 }; 20}
DAG Structure Implementation: As documented in tools.ts:3-7, tools form a DAG rather than a tree, allowing sub-tools to be reused across multiple parent tools. The mapping is built dynamically:
typescript1// From src/lib/tool-config.ts:77-82 2topLevelToolsConfig.forEach(tool => { 3 tool.subTools.forEach(subToolId => { 4 subToolToParentMap[subToolId] = tool.id; 5 }); 6});
Unlock Logic:
Tools have progressive unlock conditions defined per tool. For example, "具体化" (tool-1) requires minPracticeCount: 2 (tools.ts:127), while "慢镜头" (tool-2) requires minPracticeCount: 3 and minWritingStreak: 1 (tools.ts:252-255).
Responsibility Boundary: The notification system provides global user feedback through a context-based provider pattern. It handles success, error, and warning notifications with auto-dismiss functionality but does NOT handle modal dialogs or persistent alerts.
Entry Points and APIs:
typescript1// From src/contexts/NotificationContext.tsx:7-11 2interface NotificationContextType { 3 showSuccess: (message: string, duration?: number) => void; 4 showError: (message: string, duration?: number) => void; 5 showWarning: (message: string, duration?: number) => void; 6}
Key Implementation Details:
The NotificationProvider wraps the application and renders notifications in a fixed position container:
typescript1// From src/contexts/NotificationContext.tsx:30-44 2return ( 3 <NotificationContext.Provider value={{ showSuccess, showError, showWarning }}> 4 {children} 5 <div className="fixed top-4 right-4 z-50 space-y-2"> 6 {notifications.map((notification) => ( 7 <Notification 8 key={notification.id} 9 type={notification.type} 10 message={notification.message} 11 duration={notification.duration} 12 onClose={() => removeNotification(notification.id)} 13 /> 14 ))} 15 </div> 16 </NotificationContext.Provider> 17);
Auto-Dismiss Mechanism: The useNotification hook implements automatic removal:
typescript1// From src/lib/hooks/useNotification.ts:13-22 2const addNotification = (notification: Omit<Notification, 'id'>) => { 3 const id = Math.random().toString(36).substr(2, 9); 4 const newNotification = { ...notification, id }; 5 setNotifications(prev => [...prev, newNotification]); 6 7 setTimeout(() => { 8 removeNotification(id); 9 }, notification.duration || 5000); 10};
Error Handling:
The context throws an error if useNotificationContext is called outside the provider (NotificationContext.tsx:50-52), ensuring proper usage patterns.
Responsibility Boundary: The writing page (src/app/write/page.tsx) handles essay creation, editing, version management, and AI-powered feedback. It does NOT handle tool selection or progress tracking (delegated to home page and store).
Entry Points:
/writesrc/app/write/page.tsxKey Data Structures:
typescript1// Version history preparation - src/app/write/page.tsx:254-288 2const prepareEssayHistoryData = (essay: Essay, maxVersions = 10) => { 3 let versions = essay.versions ?? []; 4 5 // Limit version count for context optimization 6 if (versions.length > maxVersions) { 7 versions = [...versions] 8 .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()) 9 .slice(0, maxVersions); 10 } 11 // ... find latest version 12}
AI Feedback Generation:
The runOverallReview function (write/page.tsx:1209-1235) constructs prompts for AI evaluation:
typescript1// From src/app/write/page.tsx:1219-1232 2const overallPrompt = `请作为小学六年级作文指导老师,基于自由写作的评价标准,对作文《${essay.title}》进行整体批改... 3 4最新版本(${latestLabel}): 5${latestContent} 6 7版本历史演进: 8${simplifiedHistory} 9 10请按照以下格式输出整体反馈: 11⭐ 星星1:[引用具体亮点] 12⭐ 星星2:[引用具体亮点] 13🙏 愿望:[给出下一步改进建议]`;
OCR Fallback Mechanism: The page implements a degraded OCR strategy using Pollinations Vision API (write/page.tsx:719-734):
typescript1const recognizeWithPollinations = async (base64Image: string, signal: AbortSignal): Promise<string> => { 2 const response = await fetch('/api/pollinations/vision', { 3 method: 'POST', 4 headers: { 'Content-Type': 'application/json' }, 5 body: JSON.stringify({ 6 imageDataUrl: base64Image, 7 prompt: '请识别这张图片中的所有手写文字...', 8 maxTokens: 800, 9 timeoutMs: 45000, 10 }), 11 signal, 12 }); 13 // ... 14}
Cancellation Support:
The page implements proper cleanup with AbortController (write/page.tsx:706-717):
typescript1const handleCancelRecognition = () => { 2 if (recognitionAbortControllerRef.current) { 3 recognitionAbortControllerRef.current.abort(); 4 recognitionAbortControllerRef.current = null; 5 } 6 setIsRecognizing(false); 7 clearMediaState(); 8 showWarning('已取消识别'); 9};
正在加载图表渲染器...
Flow Explanation:
Challenge Generation: The home page generates daily challenges using generateDailyChallenge() (page.tsx:31-44), selecting from available tools based on student progress.
Completion Handling: When a user completes a challenge, handleChallengeComplete() (page.tsx:47-69) updates the streak count and checks for achievement unlocks.
Achievement Unlocks: Achievements are awarded at specific streak milestones (1, 7 days) with corresponding titles and icons.
State Persistence: All changes flow through the Zustand store, ensuring consistent state across components.
正在加载图表渲染器...
Flow Explanation:
Version History Optimization: The prepareEssayHistoryData function (write/page.tsx:254-288) limits version history to 10 most recent versions to optimize AI context length.
Simplified History Generation: The generateSimplifiedVersionHistory function (write/page.tsx:291) creates a condensed view of changes for AI analysis.
AI Feedback Request: The runOverallReview function constructs a structured prompt requesting specific feedback format (2 stars + 1 wish).
Rationale: Zustand provides a simpler API with less boilerplate than Redux while maintaining similar capabilities. The store in src/lib/store.ts demonstrates this with direct mutation-style updates through immer middleware.
Trade-off: Reduced ecosystem and middleware options compared to Redux, but sufficient for this application's scope.
Rationale: As documented in tools.ts:3-7, tools use a DAG structure allowing sub-tools to be shared across multiple parent categories. This enables flexible learning paths without duplicating tool definitions.
Trade-off: More complex unlock logic compared to a simple tree structure, but provides better pedagogical flexibility.
Rationale: The notification system uses React Context (NotificationContext.tsx) rather than a third-party library, reducing dependencies while providing global access.
Trade-off: Limited features compared to dedicated notification libraries (no queuing priorities, no persistence), but meets the application's feedback requirements.
Rationale: AI feedback is generated client-side through API routes, keeping sensitive API keys server-side while allowing dynamic prompt construction based on essay content.
Trade-off: Increased client-side complexity and network latency, but maintains security and enables personalized feedback.
Rationale: The maxVersions = 10 limit in prepareEssayHistoryData (write/page.tsx:266) prevents excessive token usage when sending context to AI models.
Trade-off: Older versions are excluded from AI analysis, but recent progress is prioritized.
Rationale: The custom Morandi color palette in tailwind.config.js provides a calming, low-saturation aesthetic suitable for an educational application targeting young students.
Trade-off: Additional configuration complexity compared to default Tailwind colors, but provides better visual hierarchy and reduced eye strain.
Rationale: The OCR recognition uses AbortController (write/page.tsx:708-710) to allow cancellation of long-running operations, improving user experience.
Trade-off: Additional state management complexity, but prevents resource leaks and improves responsiveness.
| Technology | Purpose | Selection Rationale | Alternative Considered |
|---|---|---|---|
| Next.js 14 | Full-stack framework | App Router with RSC support, optimized for Vercel deployment | Create React App, Vite |
| TypeScript | Type safety | Compile-time validation, better IDE support, self-documenting code | JavaScript with JSDoc |
| Zustand | State management | Minimal boilerplate, TypeScript-first, no providers needed | Redux Toolkit, Jotai |
| Tailwind CSS | Styling | Utility-first approach, custom color palette support, small bundle size | CSS Modules, Styled Components |
| React Context | Dependency injection | Built-in solution for global state like notifications | Redux, custom event system |
| Mermaid | Documentation diagrams | Native Markdown support, multiple diagram types | PlantUML, Draw.io |
| Pollinations API | OCR fallback | Free tier availability, vision model access | Google Vision, Tesseract.js |
Build Configuration:
The next.config.js includes specific optimizations:
javascript1// From next.config.js:2-17 2const nextConfig = { 3 reactStrictMode: true, 4 swcMinify: true, 5 images: { unoptimized: true }, 6 webpack: (config) => { 7 config.module.rules.push({ 8 test: /\.mjs$/, 9 include: /node_modules/, 10 type: 'javascript/auto', 11 }); 12 return config; 13 } 14}
The webpack configuration handles .mjs files in node_modules, resolving module resolution issues during Vercel deployment.
正在加载图表渲染器...
Dependency Analysis:
Core Dependencies: The types module (src/types/index.ts) has no internal dependencies, serving as the foundation for all other modules.
Store Isolation: The Zustand store (src/lib/store.ts) depends only on types, making it highly testable and portable.
Context Layering: The notification system follows a layered architecture: Context → Hook → Component, enabling each layer to be tested independently.
Page Complexity: The write page has the highest dependency count, connecting to store, context, hooks, and utilities, reflecting its role as the primary feature module.
Next.js Initialization: The framework loads next.config.js and initializes the build system with SWC minification enabled.
Root Layout Mounting: The RootLayout component mounts, establishing:
zh-CN)Provider Initialization: The NotificationProvider initializes the notification state through useNotification hook.
Store Hydration: Zustand store hydrates from localStorage (if configured) or initializes with default state.
Page Rendering: The requested page component renders within the provider tree, accessing store and context through hooks.
The tailwind.config.js defines extensive customization:
javascript1// Custom color palette (excerpt from tailwind.config.js:11-50) 2colors: { 3 primary: { '50': '#f8f9fa', /* ... */ '900': '#212529' }, 4 morandi: { 5 gray: { /* custom gray tones */ }, 6 blue: { /* custom blue tones */ }, 7 green: { /* custom green tones */ }, 8 // ... additional Morandi palette colors 9 } 10}
The configuration includes custom animations for accordion components (tailwind.config.js:252-273) and custom box shadows for visual depth.
The postcss.config.js configures the CSS processing pipeline:
javascript1module.exports = { 2 plugins: { 3 tailwindcss: {}, 4 autoprefixer: {}, 5 }, 6}
This enables Tailwind's JIT compilation and automatic vendor prefixing for cross-browser compatibility.