// Flashcards API service export interface ExampleImage { id: string; imageUrl: string; isPrimary: boolean; qualityScore?: number; fileSize?: number; createdAt: string; } export interface Flashcard { id: string; word: string; translation: string; definition: string; partOfSpeech: string; pronunciation: string; example: string; exampleTranslation?: string; masteryLevel: number; timesReviewed: number; isFavorite: boolean; nextReviewDate: string; difficultyLevel: string; createdAt: string; updatedAt?: string; // 新增圖片相關欄位 exampleImages: ExampleImage[]; hasExampleImage: boolean; primaryImageUrl?: string; } export interface CreateFlashcardRequest { word: string; translation: string; definition: string; pronunciation: string; partOfSpeech: string; example: string; exampleTranslation?: string; difficultyLevel?: string; // A1, A2, B1, B2, C1, C2 } export interface ApiResponse { success: boolean; data?: T; error?: string; message?: string; } class FlashcardsService { private readonly baseURL = `${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:5008'}/api`; private async makeRequest(endpoint: string, options: RequestInit = {}): Promise { const token = localStorage.getItem('auth_token'); const response = await fetch(`${this.baseURL}${endpoint}`, { headers: { 'Content-Type': 'application/json', 'Authorization': token ? `Bearer ${token}` : '', ...options.headers, }, ...options, }); if (!response.ok) { const errorData = await response.json().catch(() => ({ error: 'Network error' })); throw new Error(errorData.error || errorData.details || `HTTP ${response.status}`); } return response.json(); } // 詞卡查詢方法 (支援進階篩選、排序和分頁) async getFlashcards( search?: string, favoritesOnly: boolean = false, cefrLevel?: string, partOfSpeech?: string, masteryLevel?: string, sortBy?: string, sortOrder?: 'asc' | 'desc', page?: number, limit?: number ): Promise> { try { const params = new URLSearchParams(); if (search) params.append('search', search); if (favoritesOnly) params.append('favoritesOnly', 'true'); if (cefrLevel) params.append('cefrLevel', cefrLevel); if (partOfSpeech) params.append('partOfSpeech', partOfSpeech); if (masteryLevel) params.append('masteryLevel', masteryLevel); // 排序和分頁參數 if (sortBy) params.append('sortBy', sortBy); if (sortOrder) params.append('sortOrder', sortOrder); if (page) params.append('page', page.toString()); if (limit) params.append('limit', limit.toString()); const queryString = params.toString(); const endpoint = `/flashcards${queryString ? `?${queryString}` : ''}`; return await this.makeRequest>(endpoint); } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'Failed to fetch flashcards', }; } } async createFlashcard(data: CreateFlashcardRequest): Promise> { try { return await this.makeRequest>('/flashcards', { method: 'POST', body: JSON.stringify(data), }); } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'Failed to create flashcard', }; } } async deleteFlashcard(id: string): Promise> { try { return await this.makeRequest>(`/flashcards/${id}`, { method: 'DELETE', }); } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'Failed to delete flashcard', }; } } async getFlashcard(id: string): Promise> { try { return await this.makeRequest>(`/flashcards/${id}`); } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'Failed to get flashcard', }; } } async updateFlashcard(id: string, data: CreateFlashcardRequest): Promise> { try { return await this.makeRequest>(`/flashcards/${id}`, { method: 'PUT', body: JSON.stringify(data), }); } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'Failed to update flashcard', }; } } async toggleFavorite(id: string): Promise> { try { return await this.makeRequest>(`/flashcards/${id}/favorite`, { method: 'POST', }); } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'Failed to toggle favorite', }; } } // ===================================================== // 智能複習系統相關方法 // ===================================================== async getDueFlashcards(limit = 50): Promise> { try { console.log('🚀 API調用開始:', `/flashcards/due?limit=${limit}`); const response = await this.makeRequest<{ success: boolean; data: any[]; count: number }>(`/flashcards/due?limit=${limit}`); console.log('🔍 makeRequest回應:', response); console.log('📊 response.data類型:', typeof response.data, '長度:', response.data?.length); if (!response.data || !Array.isArray(response.data)) { console.log('❌ response.data不是數組:', response.data); return { success: false, error: 'Invalid response data format', }; } // 轉換後端格式為前端期望格式 const flashcards = response.data.map((card: any) => ({ id: card.id, word: card.word, translation: card.translation, definition: card.definition, partOfSpeech: card.partOfSpeech, pronunciation: card.pronunciation, example: card.example, exampleTranslation: card.exampleTranslation, masteryLevel: card.masteryLevel || card.currentMasteryLevel || 0, timesReviewed: card.timesReviewed || 0, isFavorite: card.isFavorite || false, nextReviewDate: card.nextReviewDate, difficultyLevel: card.difficultyLevel || 'A2', createdAt: card.createdAt, updatedAt: card.updatedAt, // 智能複習擴展欄位 (數值欄位已移除,改用即時CEFR轉換) baseMasteryLevel: card.baseMasteryLevel || card.masteryLevel || 0, lastReviewDate: card.lastReviewDate || card.lastReviewedAt, currentInterval: card.currentInterval || card.intervalDays || 1, isOverdue: card.isOverdue || false, overdueDays: card.overdueDays || 0, // 圖片相關欄位 exampleImages: card.exampleImages || [], hasExampleImage: card.hasExampleImage || false, primaryImageUrl: card.primaryImageUrl })); console.log('✅ 數據轉換完成:', flashcards.length, '張詞卡'); return { success: true, data: flashcards }; } catch (error) { console.error('💥 API request failed:', error); return { success: false, error: error instanceof Error ? error.message : 'Failed to get due flashcards', }; } } async getNextReviewCard(): Promise> { try { return await this.makeRequest>('/flashcards/next-review'); } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'Failed to get next review card', }; } } async getOptimalReviewMode(cardId: string, userCEFRLevel: string, wordCEFRLevel: string): Promise> { try { const response = await this.makeRequest<{ success: boolean; data: any }>(`/flashcards/${cardId}/optimal-review-mode`, { method: 'POST', body: JSON.stringify({ userCEFRLevel, wordCEFRLevel, includeHistory: true }), }); return { success: response.success, data: { selectedMode: response.data.selectedMode } }; } catch (error) { console.error('Optimal review mode API failed, using fallback:', error); return { success: false, error: error instanceof Error ? error.message : 'Failed to get optimal review mode', }; } } async submitReview(id: string, reviewData: { isCorrect: boolean; confidenceLevel?: number; questionType: string; userAnswer?: string; timeTaken?: number; }): Promise> { try { const response = await this.makeRequest<{ success: boolean; data: any }>(`/flashcards/${id}/review`, { method: 'POST', body: JSON.stringify({ ...reviewData, timestamp: Date.now() }), }); return { success: response.success, data: { newInterval: response.data.newInterval || response.data.newIntervalDays || 1, nextReviewDate: response.data.nextReviewDate, masteryLevel: response.data.masteryLevel || response.data.newMasteryLevel || 0 } }; } catch (error) { console.error('Submit review API failed, using fallback:', error); return { success: false, error: error instanceof Error ? error.message : 'Failed to submit review', }; } } async generateQuestionOptions(cardId: string, questionType: string): Promise> { try { return await this.makeRequest>(`/flashcards/${cardId}/question`, { method: 'POST', body: JSON.stringify({ questionType }), }); } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'Failed to generate question options', }; } } /** * 獲取已完成的測驗記錄 */ async getCompletedTests(cardIds?: string[]): Promise<{ success: boolean; data: Array<{ flashcardId: string; testType: string; isCorrect: boolean; completedAt: string; userAnswer?: string; }> | null; error?: string; }> { try { const params = cardIds && cardIds.length > 0 ? `?cardIds=${cardIds.join(',')}` : ''; const result = await this.makeRequest(`/study/completed-tests${params}`); return { success: true, data: result.data || [], error: undefined }; } catch (error) { console.warn('Failed to get completed tests:', error); return { success: false, data: [], error: error instanceof Error ? error.message : 'Failed to get completed tests' }; } } /** * 記錄測驗完成狀態 (立即保存到StudyRecord表) */ async recordTestCompletion(request: { flashcardId: string; testType: string; isCorrect: boolean; userAnswer?: string; confidenceLevel?: number; responseTimeMs?: number; }): Promise<{ success: boolean; data: any | null; error?: string }> { try { const result = await this.makeRequest('/study/record-test', { method: 'POST', body: JSON.stringify({ flashcardId: request.flashcardId, testType: request.testType, isCorrect: request.isCorrect, userAnswer: request.userAnswer, confidenceLevel: request.confidenceLevel, responseTimeMs: request.responseTimeMs || 2000 }) }); return { success: true, data: result.data || result, error: undefined }; } catch (error) { console.warn('Failed to record test completion:', error); return { success: false, data: null, error: error instanceof Error ? error.message : 'Failed to record test completion' }; } } } export const flashcardsService = new FlashcardsService();