Skip to content

Commit 60c58f9

Browse files
authored
Merge pull request #7 from tpaulshippy/flashcard-plan
Flashcards Study Feature Plan
2 parents a2f0ce4 + 46b281a commit 60c58f9

2 files changed

Lines changed: 369 additions & 103 deletions

File tree

PLAN_FLASHCARDS.md

Lines changed: 369 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,369 @@
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

Comments
 (0)