|
| 1 | +# Flashcards Study Feature - Implementation Plan |
| 2 | + |
| 3 | +## Overview |
| 4 | +Add a flashcard system to the app where: |
| 5 | +- Users can create, edit, and delete flashcard decks (e.g., "Biology Test Terms", "Memory Verses") |
| 6 | +- Each deck contains multiple flashcards with front/back text |
| 7 | +- Bot can create flashcards via tool calls during chat (optionally adding to a deck) |
| 8 | +- Study mode uses flip card interface per deck |
| 9 | +- Hamburger menu in header switches between Chat and Flashcards views |
| 10 | + |
| 11 | +## Requirements |
| 12 | + |
| 13 | +### Storage |
| 14 | +- Flashcard decks and flashcards stored per profile (child account) |
| 15 | +- No limit on number of decks or cards per profile |
| 16 | + |
| 17 | +### Creation |
| 18 | +- Manual entry: Users create decks and add cards with front/back text |
| 19 | +- Auto-generate: Bot can create flashcards via tool call during chat |
| 20 | + |
| 21 | +### Study Interface |
| 22 | +- Flip card animation to reveal answer |
| 23 | +- Navigate between cards in a deck (prev/next) |
| 24 | +- Select which deck to study |
| 25 | + |
| 26 | +### Access |
| 27 | +- Hamburger menu at top-left to switch between Chat list and Flashcards |
| 28 | + |
| 29 | +--- |
| 30 | + |
| 31 | +## Backend Implementation (Django) |
| 32 | + |
| 33 | +### 1. Create Deck Model |
| 34 | +**File:** `back/bots/models/flashcard.py` |
| 35 | + |
| 36 | +```python |
| 37 | +class Deck(models.Model): |
| 38 | + deck_id = models.UUIDField(default=uuid.uuid4, unique=True) |
| 39 | + profile = models.ForeignKey(Profile, on_delete=models.CASCADE, related_name='decks') |
| 40 | + chat = models.ForeignKey(Chat, on_delete=models.SET_NULL, null=True, blank=True, related_name='decks') |
| 41 | + name = models.CharField(max_length=255) |
| 42 | + description = models.TextField(blank=True, default="") |
| 43 | + created_at = models.DateTimeField(auto_now_add=True) |
| 44 | + updated_at = models.DateTimeField(auto_now=True) |
| 45 | + |
| 46 | + |
| 47 | +class Flashcard(models.Model): |
| 48 | + flashcard_id = models.UUIDField(default=uuid.uuid4, unique=True) |
| 49 | + deck = models.ForeignKey(Deck, on_delete=models.CASCADE, related_name='flashcards') |
| 50 | + front = models.TextField() |
| 51 | + back = models.TextField() |
| 52 | + order = models.PositiveIntegerField(default=0) |
| 53 | + created_at = models.DateTimeField(auto_now_add=True) |
| 54 | + updated_at = models.DateTimeField(auto_now=True) |
| 55 | + |
| 56 | + class Meta: |
| 57 | + constraints = [ |
| 58 | + models.UniqueConstraint(fields=["deck", "order"], name="unique_flashcard_order_per_deck") |
| 59 | + ] |
| 60 | + indexes = [ |
| 61 | + models.Index(fields=["deck", "order"]) |
| 62 | + ] |
| 63 | +``` |
| 64 | + |
| 65 | +Add to `back/bots/models/__init__.py` |
| 66 | + |
| 67 | +### 2. Create Flashcard Serializers |
| 68 | +**File:** `back/bots/serializers/flashcard_serializer.py` |
| 69 | + |
| 70 | +```python |
| 71 | +class FlashcardSerializer(serializers.HyperlinkedModelSerializer): |
| 72 | + class Meta: |
| 73 | + model = Flashcard |
| 74 | + fields = ['id', 'flashcard_id', 'deck', 'front', 'back', 'order', 'created_at', 'updated_at'] |
| 75 | + |
| 76 | + |
| 77 | +class DeckSerializer(serializers.HyperlinkedModelSerializer): |
| 78 | + flashcards = FlashcardSerializer(many=True, read_only=True) |
| 79 | + card_count = serializers.SerializerMethodField() |
| 80 | + |
| 81 | + class Meta: |
| 82 | + model = Deck |
| 83 | + fields = ['id', 'deck_id', 'profile', 'chat', 'name', 'description', 'flashcards', 'card_count', 'created_at', 'updated_at'] |
| 84 | + |
| 85 | + |
| 86 | +class DeckListSerializer(serializers.HyperlinkedModelSerializer): |
| 87 | + card_count = serializers.SerializerMethodField() |
| 88 | + |
| 89 | + class Meta: |
| 90 | + model = Deck |
| 91 | + fields = ['id', 'deck_id', 'name', 'description', 'card_count', 'created_at', 'updated_at'] |
| 92 | +``` |
| 93 | + |
| 94 | +Add to `back/bots/serializers/__init__.py` |
| 95 | + |
| 96 | +### 3. Create Flashcard Viewsets |
| 97 | +**File:** `back/bots/viewsets/flashcard_viewset.py` |
| 98 | + |
| 99 | +#### DeckViewSet |
| 100 | +- `GET /decks.json` - List all decks for profile |
| 101 | +- `POST /decks.json` - Create new deck |
| 102 | +- `GET /decks/{deck_id}.json` - Retrieve deck with all flashcards |
| 103 | +- `PUT /decks/{deck_id}.json` - Update deck |
| 104 | +- `DELETE /decks/{deck_id}.json` - Delete deck (cascades to flashcards) |
| 105 | + |
| 106 | +#### FlashcardViewSet |
| 107 | +- `GET /decks/{deck_id}/flashcards.json` - List cards in a deck |
| 108 | +- `POST /decks/{deck_id}/flashcards.json` - Add card to deck |
| 109 | +- `GET /decks/{deck_id}/flashcards/{flashcard_id}.json` - Retrieve card |
| 110 | +- `PUT /decks/{deck_id}/flashcards/{flashcard_id}.json` - Update card |
| 111 | +- `DELETE /decks/{deck_id}/flashcards/{flashcard_id}.json` - Delete card |
| 112 | + |
| 113 | +Filter decks by profile from query params. Permission: IsOwner. |
| 114 | + |
| 115 | +Scope decks to profiles the authenticated user is authorized to access. If a profile filter is provided, validate it belongs to the authenticated user before applying it. Permission: enforce object-level ownership checks server-side (not query-param trust). |
| 116 | + |
| 117 | +Annotate with card_count for list view. |
| 118 | + |
| 119 | +### 4. Register Routes |
| 120 | +**File:** `back/server/urls.py` |
| 121 | + |
| 122 | +Add router registration for FlashcardViewSet. |
| 123 | + |
| 124 | +### 5. Bot Tool Call Handler |
| 125 | +**File:** `back/bots/models/chat.py` (inside existing agent tool-call loop) |
| 126 | + |
| 127 | +Define tools with `@tool` and bind them alongside existing tools. Handle tool calls using LangChain `tool_call["name"]` and `tool_call["args"]`. When bot returns a tool call with name "create_flashcard" or "create_deck", parse and create Deck/Flashcard entries. Persist Deck/Flashcard in the tool implementation and return structured tool results. |
| 128 | + |
| 129 | +Tool call format from bot (LangChain style): |
| 130 | +```json |
| 131 | +{ |
| 132 | + "name": "create_flashcard_deck", |
| 133 | + "args": { |
| 134 | + "name": "Biology Test Terms", |
| 135 | + "description": "Key terms for Chapter 5", |
| 136 | + "flashcards": [ |
| 137 | + {"front": "What is photosynthesis?", "back": "The process by which plants convert light energy into chemical energy"}, |
| 138 | + {"front": "What is cellular respiration?", "back": "The process of converting glucose into ATP"} |
| 139 | + ] |
| 140 | + }, |
| 141 | + "id": "call_abc123" |
| 142 | +} |
| 143 | +``` |
| 144 | + |
| 145 | +Or single card: |
| 146 | +```json |
| 147 | +{ |
| 148 | + "name": "create_flashcard", |
| 149 | + "args": { |
| 150 | + "deck_name": "Memory Verses", |
| 151 | + "front": "John 3:16", |
| 152 | + "back": "For God so loved the world..." |
| 153 | + }, |
| 154 | + "id": "call_xyz789" |
| 155 | +} |
| 156 | +``` |
| 157 | + |
| 158 | +--- |
| 159 | + |
| 160 | +## Frontend Implementation (React Native/Expo) |
| 161 | + |
| 162 | +### 1. Flashcard API Module |
| 163 | +**File:** `front/api/flashcards.ts` |
| 164 | + |
| 165 | +```typescript |
| 166 | +export interface Flashcard { |
| 167 | + id: number; |
| 168 | + flashcard_id: string; |
| 169 | + deck: number; |
| 170 | + front: string; |
| 171 | + back: string; |
| 172 | + order: number; |
| 173 | + created_at: string; |
| 174 | + updated_at: string; |
| 175 | +} |
| 176 | + |
| 177 | +export interface Deck { |
| 178 | + id: number; |
| 179 | + deck_id: string; |
| 180 | + profile: string; |
| 181 | + chat: string | null; |
| 182 | + name: string; |
| 183 | + description: string; |
| 184 | + flashcards: Flashcard[]; |
| 185 | + card_count: number; |
| 186 | + created_at: string; |
| 187 | + updated_at: string; |
| 188 | +} |
| 189 | + |
| 190 | +export interface DeckListItem { |
| 191 | + id: number; |
| 192 | + deck_id: string; |
| 193 | + name: string; |
| 194 | + description: string; |
| 195 | + card_count: number; |
| 196 | + created_at: string; |
| 197 | + updated_at: string; |
| 198 | +} |
| 199 | + |
| 200 | +// Deck endpoints |
| 201 | +export const fetchDecks = async (profileId: string): Promise<DeckListItem[]> |
| 202 | +export const fetchDeck = async (deckId: string): Promise<Deck> |
| 203 | +export const createDeck = async (name: string, description: string, profileId: string, chatId?: string): Promise<Deck> |
| 204 | +export const updateDeck = async (deckId: string, name: string, description: string): Promise<Deck> |
| 205 | +export const deleteDeck = async (deckId: string): Promise<void> |
| 206 | + |
| 207 | +// Flashcard endpoints |
| 208 | +export const fetchFlashcards = async (deckId: string): Promise<Flashcard[]> |
| 209 | +export const createFlashcard = async (deckId: string, front: string, back: string): Promise<Flashcard> |
| 210 | +export const updateFlashcard = async (flashcardId: string, front: string, back: string): Promise<Flashcard> |
| 211 | +export const deleteFlashcard = async (flashcardId: string): Promise<void> |
| 212 | +``` |
| 213 | +
|
| 214 | +### 2. Navigation Update |
| 215 | +**File:** `front/app/_layout.tsx` |
| 216 | +
|
| 217 | +- Replace current headerLeft (empty) with hamburger menu icon (IconSymbol "list.bullet") |
| 218 | +- On press: toggle between "chats" and "flashcards" mode |
| 219 | +- Show different list based on mode |
| 220 | +
|
| 221 | +### 3. Deck List Screen |
| 222 | +**File:** `front/app/flashcards.tsx` |
| 223 | +
|
| 224 | +- FlatList of all flashcard decks |
| 225 | +- Each item shows deck name, card count, and truncated description |
| 226 | +- Tap deck to view/edit cards or start studying |
| 227 | +- FAB to create new deck |
| 228 | +
|
| 229 | +### 4. Deck Detail/Edit Screen |
| 230 | +**File:** `front/app/flashcards/deck.tsx` |
| 231 | +
|
| 232 | +- Header shows deck name (editable) and description |
| 233 | +- FlatList of all flashcards in deck |
| 234 | +- Each card shows truncated front text |
| 235 | +- Tap card to edit |
| 236 | +- FAB to add new card to deck |
| 237 | +- "Study" button in header to start study mode |
| 238 | +
|
| 239 | +### 5. Flashcard Edit Modal/Screen |
| 240 | +**File:** `front/app/flashcards/cardEdit.tsx` (or modal) |
| 241 | +
|
| 242 | +- Form with "Front" and "Back" text inputs |
| 243 | +- Save/Cancel buttons |
| 244 | +- Delete button if editing existing |
| 245 | +
|
| 246 | +### 6. Flashcard Study Screen |
| 247 | +**File:** `front/app/flashcards/study.tsx` |
| 248 | +
|
| 249 | +- Accepts deckId parameter |
| 250 | +- Display current card (front side) |
| 251 | +- Tap card to flip (animate) |
| 252 | +- Previous/Next buttons to navigate |
| 253 | +- Progress indicator (e.g., "3 / 10") |
| 254 | +- Exit button to return to deck |
| 255 | +
|
| 256 | +### 6. Chat Integration |
| 257 | +**File:** `front/app/botChat.tsx` |
| 258 | +
|
| 259 | +- After sending message, check response for flashcard tool call results |
| 260 | +- If bot created flashcards, show toast/notification: "X flashcards created" |
| 261 | +- Store flashcard IDs in response for potential editing |
| 262 | +
|
| 263 | +--- |
| 264 | +
|
| 265 | +## UI Specifications |
| 266 | +
|
| 267 | +### Deck List |
| 268 | +```text |
| 269 | +┌─────────────────────────────────────────┐ |
| 270 | +│ ← My Decks │ |
| 271 | +├─────────────────────────────────────────┤ |
| 272 | +│ ┌─────────────────────────────────────┐ │ |
| 273 | +│ │ Biology Test Terms 12 cards │ │ |
| 274 | +│ │ Chapter 5 vocabulary │ │ |
| 275 | +│ └─────────────────────────────────────┘ │ |
| 276 | +│ ┌─────────────────────────────────────┐ │ |
| 277 | +│ │ Memory Verses 5 cards │ │ |
| 278 | +│ │ Sunday school verses │ │ |
| 279 | +│ └─────────────────────────────────────┘ │ |
| 280 | +│ │ |
| 281 | +│ [+ Create Deck]│ |
| 282 | +└─────────────────────────────────────────┘ |
| 283 | +``` |
| 284 | +
|
| 285 | +### Deck Detail |
| 286 | +```text |
| 287 | +┌─────────────────────────────────────────┐ |
| 288 | +│ ← Back Biology Test [Study] │ |
| 289 | +├─────────────────────────────────────────┤ |
| 290 | +│ Description: Chapter 5 vocabulary │ |
| 291 | +├─────────────────────────────────────────┤ |
| 292 | +│ ┌─────────────────────────────────────┐ │ |
| 293 | +│ │ What is photosynthesis? [...] │ │ |
| 294 | +│ └─────────────────────────────────────┘ │ |
| 295 | +│ ┌─────────────────────────────────────┐ │ |
| 296 | +│ │ What is cellular respiration? [...] │ │ |
| 297 | +│ └─────────────────────────────────────┘ │ |
| 298 | +│ [+ Add Card] │ |
| 299 | +└─────────────────────────────────────────┘ |
| 300 | +``` |
| 301 | +
|
| 302 | +### Study Screen |
| 303 | +```text |
| 304 | +┌─────────────────────────────┐ |
| 305 | +│ ← Back Study (3/10) │ |
| 306 | +├─────────────────────────────┤ |
| 307 | +│ │ |
| 308 | +│ ┌───────────────────┐ │ |
| 309 | +│ │ │ │ |
| 310 | +│ │ What is the │ │ |
| 311 | +│ │ capital of │ │ |
| 312 | +│ │ France? │ │ |
| 313 | +│ │ │ │ |
| 314 | +│ │ Tap to reveal │ │ |
| 315 | +│ │ │ │ |
| 316 | +│ └───────────────────┘ │ |
| 317 | +│ │ |
| 318 | +│ ← Prev Next → │ |
| 319 | +│ │ |
| 320 | +└─────────────────────────────┘ |
| 321 | +``` |
| 322 | +
|
| 323 | +(After tap - shows answer side) |
| 324 | +
|
| 325 | +### Hamburger Menu |
| 326 | +- Icon: "list.bullet" from IconSymbol |
| 327 | +- Position: Header left (replaces any existing back button when on root screens) |
| 328 | +- Behavior: Opens drawer or toggles view mode |
| 329 | +
|
| 330 | +--- |
| 331 | +
|
| 332 | +## File Summary |
| 333 | +
|
| 334 | +### New Backend Files |
| 335 | +- `back/bots/models/flashcard.py` - Deck and Flashcard models |
| 336 | +- `back/bots/serializers/flashcard_serializer.py` - Serializers for both models |
| 337 | +- `back/bots/viewsets/flashcard_viewset.py` - ViewSets for both models |
| 338 | +
|
| 339 | +### Modified Backend Files |
| 340 | +- `back/bots/models/__init__.py` - Export Deck, Flashcard |
| 341 | +- `back/bots/serializers/__init__.py` - Export serializers |
| 342 | +- `back/server/urls.py` - Add flashcard routes |
| 343 | +- `back/bots/models/chat.py` - Handle flashcard tool calls |
| 344 | +
|
| 345 | +### New Frontend Files |
| 346 | +- `front/api/flashcards.ts` - API module for decks and cards |
| 347 | +- `front/app/flashcards.tsx` - List of flashcard decks |
| 348 | +- `front/app/flashcards/deck.tsx` - Deck detail with card list |
| 349 | +- `front/app/flashcards/cardEdit.tsx` - Create/edit card form |
| 350 | +- `front/app/flashcards/study.tsx` - Study mode with flip cards |
| 351 | +
|
| 352 | +### Modified Frontend Files |
| 353 | +- `front/app/_layout.tsx` - Add hamburger menu, toggle between chats/flashcards |
| 354 | +- `front/app/botChat.tsx` - Handle flashcard creation from bot responses |
| 355 | +
|
| 356 | +--- |
| 357 | +
|
| 358 | +## Implementation Order |
| 359 | +
|
| 360 | +1. Backend: Create models, serializers, viewset, routes |
| 361 | +2. Backend: Add tool call handler in chat response view |
| 362 | +3. Frontend: Create API module |
| 363 | +4. Frontend: Update navigation/layout with hamburger menu |
| 364 | +5. Frontend: Create flashcard decks list screen |
| 365 | +6. Frontend: Create deck detail screen with card list |
| 366 | +7. Frontend: Create card edit screen |
| 367 | +8. Frontend: Create study screen with flip animation |
| 368 | +9. Frontend: Integrate flashcard creation in chat |
| 369 | +10. Test and verify end-to-end flow |
0 commit comments