milwright commited on
Commit
53d138c
Β·
0 Parent(s):

feat: clean cloze reader for HuggingFace Space

Browse files

Simple cloze reading app with progressive levels and no external dependencies.

Files changed (7) hide show
  1. .gitignore +61 -0
  2. .parcelrc +7 -0
  3. .postcssrc.json +5 -0
  4. README.md +38 -0
  5. app.py +12 -0
  6. index.html +211 -0
  7. requirements.txt +2 -0
.gitignore ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dependencies
2
+ node_modules/
3
+ npm-debug.log*
4
+ yarn-debug.log*
5
+ yarn-error.log*
6
+
7
+ # Build outputs
8
+ dist/
9
+ .parcel-cache/
10
+
11
+ # Environment variables
12
+ .env
13
+ .env.local
14
+ .env.development.local
15
+ .env.test.local
16
+ .env.production.local
17
+
18
+ # IDE and editor files
19
+ .vscode/
20
+ .idea/
21
+ *.swp
22
+ *.swo
23
+ *~
24
+
25
+ # OS generated files
26
+ .DS_Store
27
+ .DS_Store?
28
+ ._*
29
+ .Spotlight-V100
30
+ .Trashes
31
+ ehthumbs.db
32
+ Thumbs.db
33
+
34
+ # Logs
35
+ logs
36
+ *.log
37
+
38
+ # Runtime data
39
+ pids
40
+ *.pid
41
+ *.seed
42
+ *.pid.lock
43
+
44
+ # Optional npm cache directory
45
+ .npm
46
+
47
+ # Optional eslint cache
48
+ .eslintcache
49
+
50
+ # Coverage directory used by tools like istanbul
51
+ coverage/
52
+
53
+ # Temporary folders
54
+ tmp/
55
+ temp/
56
+ .aider*
57
+
58
+ # Planning documents
59
+ HF_DATASET_INTEGRATION_PLAN.md
60
+ *_PLAN.md
61
+ IMPLEMENTATION_*.md
.parcelrc ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "extends": "@parcel/config-default",
3
+ "transformers": {
4
+ "*.{js,mjs,jsx,cjs,ts,tsx}": ["@parcel/transformer-js"],
5
+ "*.css": ["@parcel/transformer-css"]
6
+ }
7
+ }
.postcssrc.json ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ {
2
+ "plugins": {
3
+ "tailwindcss": {}
4
+ }
5
+ }
README.md ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Cloze Reader
3
+ emoji: πŸ“š
4
+ colorFrom: blue
5
+ colorTo: purple
6
+ sdk: docker
7
+ pinned: false
8
+ ---
9
+
10
+ # Cloze Reader
11
+
12
+ An interactive cloze reading practice application with AI-powered assistance. Practice reading comprehension by filling in blanks in classic literature passages.
13
+
14
+ ## Features
15
+
16
+ - **Progressive Level System**: Start with 1 blank, advance to 2-3 blanks as you improve
17
+ - **Smart Hints**: Get word length, first letter, and contextual clues
18
+ - **AI Chat Help**: Click πŸ’¬ for intelligent hints about any blank
19
+ - **Classic Literature**: Passages from Project Gutenberg's collection
20
+ - **Level-Appropriate Challenges**: Hints adapt based on your current level
21
+
22
+ ## How to Use
23
+
24
+ 1. Read the passage and literary context
25
+ 2. Fill in the blank(s) with appropriate words
26
+ 3. Use hints or chat help if needed
27
+ 4. Submit to see your results and advance levels
28
+ 5. Continue practicing with new passages
29
+
30
+ ## Level System
31
+
32
+ - **Levels 1-2**: 1 blank, hints show first and last letter
33
+ - **Levels 3-4**: 2 blanks, hints show first letter only
34
+ - **Level 5+**: 3 blanks, first letter hints
35
+
36
+ ## Technology
37
+
38
+ Built with vanilla JavaScript, powered by AI for intelligent word selection and contextual assistance.
app.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+ from fastapi.responses import FileResponse
3
+
4
+ app = FastAPI()
5
+
6
+ @app.get("/")
7
+ async def read_root():
8
+ return FileResponse("index.html")
9
+
10
+ if __name__ == "__main__":
11
+ import uvicorn
12
+ uvicorn.run(app, host="0.0.0.0", port=7860)
index.html ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Cloze Reader</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link href="https://fonts.googleapis.com/css2?family=Special+Elite&display=swap" rel="stylesheet">
9
+ <style>
10
+ body { font-family: 'Special Elite', monospace; background: #faf7f0; }
11
+ .paper-sheet { background: #fefcf7; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
12
+ .cloze-input { border: none; border-bottom: 2px solid #000; background: transparent; text-align: center; font-family: inherit; }
13
+ .typewriter-button { background: #f5f1e8; border: 2px solid #000; font-family: inherit; padding: 8px 16px; cursor: pointer; }
14
+ .correct { background-color: rgba(16, 185, 129, 0.1); border-color: #10b981; }
15
+ .incorrect { background-color: rgba(239, 68, 68, 0.1); border-color: #ef4444; }
16
+ </style>
17
+ </head>
18
+ <body class="min-h-screen p-4">
19
+ <div class="max-w-4xl mx-auto">
20
+ <header class="text-center mb-8">
21
+ <div class="flex items-center justify-center gap-3 mb-2">
22
+ <span class="text-4xl">πŸ“š</span>
23
+ <h1 class="text-4xl font-bold">Cloze Reader</h1>
24
+ </div>
25
+ <p class="text-gray-600">Fill in the blanks to practice reading comprehension</p>
26
+ </header>
27
+
28
+ <main id="game-container">
29
+ <div id="loading" class="text-center py-8">
30
+ <p class="text-lg">Loading passages...</p>
31
+ </div>
32
+
33
+ <div id="game-area" class="paper-sheet rounded-lg p-6 hidden">
34
+ <div id="book-info" class="text-sm italic text-gray-600 mb-4"></div>
35
+ <div id="round-info" class="text-sm bg-amber-100 px-3 py-1 rounded-full inline-block mb-4"></div>
36
+ <div id="contextualization" class="bg-amber-50 border-l-4 border-amber-500 p-3 mb-4 text-sm"></div>
37
+ <div id="passage-content" class="text-lg leading-relaxed mb-6"></div>
38
+ <div id="hints-section" class="bg-yellow-50 border-l-4 border-yellow-500 p-3 mb-4 hidden">
39
+ <div class="font-semibold mb-2 text-sm">πŸ’‘ Hints:</div>
40
+ <div id="hints-list" class="text-sm space-y-1"></div>
41
+ </div>
42
+ <div class="flex gap-4 justify-center flex-wrap">
43
+ <button id="submit-btn" class="typewriter-button">Submit</button>
44
+ <button id="next-btn" class="typewriter-button hidden">Next Passage</button>
45
+ <button id="hint-btn" class="typewriter-button">Show Hints</button>
46
+ </div>
47
+ <div id="result" class="mt-4 text-center font-semibold"></div>
48
+ </div>
49
+ </main>
50
+ </div>
51
+
52
+ <script>
53
+ // Simple cloze reader implementation
54
+ class SimpleClozeReader {
55
+ constructor() {
56
+ this.currentLevel = 1;
57
+ this.currentRound = 1;
58
+ this.blanks = [];
59
+ this.hints = [];
60
+ this.books = [
61
+ {
62
+ title: "Pride and Prejudice",
63
+ author: "Jane Austen",
64
+ text: "It is a truth universally acknowledged, that a single man in possession of a good fortune, must be in want of a wife. However little known the feelings or views of such a man may be on his first entering a neighbourhood, this truth is so well fixed in the minds of the surrounding families, that he is considered the rightful property of some one or other of their daughters."
65
+ },
66
+ {
67
+ title: "The Adventures of Tom Sawyer",
68
+ author: "Mark Twain",
69
+ text: "Tom! No answer. Tom! No answer. What's gone with that boy, I wonder? You TOM! No answer. The old lady pulled her spectacles down and looked over them about the room; then she put them up and looked out under them. She seldom or never looked through them for so small a thing as a boy."
70
+ }
71
+ ];
72
+ this.init();
73
+ }
74
+
75
+ init() {
76
+ this.setupEventListeners();
77
+ this.startNewRound();
78
+ }
79
+
80
+ setupEventListeners() {
81
+ document.getElementById('submit-btn').onclick = () => this.submitAnswers();
82
+ document.getElementById('next-btn').onclick = () => this.nextRound();
83
+ document.getElementById('hint-btn').onclick = () => this.toggleHints();
84
+ }
85
+
86
+ startNewRound() {
87
+ const book = this.books[Math.floor(Math.random() * this.books.length)];
88
+ const blanksCount = this.currentLevel <= 2 ? 1 : this.currentLevel <= 4 ? 2 : 3;
89
+
90
+ // Simple word selection
91
+ const words = book.text.split(' ');
92
+ const selectedIndices = [];
93
+ const contentWords = ['truth', 'man', 'fortune', 'wife', 'feelings', 'neighbourhood', 'families', 'daughters', 'answer', 'boy', 'lady', 'spectacles', 'room'];
94
+
95
+ for (let i = 0; i < blanksCount && i < contentWords.length; i++) {
96
+ const wordIndex = words.findIndex(w => w.toLowerCase().includes(contentWords[i]));
97
+ if (wordIndex !== -1) selectedIndices.push(wordIndex);
98
+ }
99
+
100
+ // Create blanks
101
+ this.blanks = selectedIndices.map((index, i) => ({
102
+ index: i,
103
+ originalWord: words[index].replace(/[^\w]/g, ''),
104
+ wordIndex: index
105
+ }));
106
+
107
+ // Create hints
108
+ this.hints = this.blanks.map((blank, i) => {
109
+ const word = blank.originalWord;
110
+ if (this.currentLevel <= 2) {
111
+ return `${word.length} letters, starts with "${word[0]}", ends with "${word[word.length-1]}"`;
112
+ } else {
113
+ return `${word.length} letters, starts with "${word[0]}"`;
114
+ }
115
+ });
116
+
117
+ // Create display text
118
+ let displayText = book.text;
119
+ this.blanks.forEach((blank, i) => {
120
+ const word = words[blank.wordIndex];
121
+ const input = `<input type="text" class="cloze-input w-20 mx-1" data-index="${i}" placeholder="${'_'.repeat(Math.max(3, blank.originalWord.length))}">`;
122
+ displayText = displayText.replace(word, input);
123
+ });
124
+
125
+ // Update UI
126
+ document.getElementById('book-info').innerHTML = `<strong>${book.title}</strong> by ${book.author}`;
127
+ document.getElementById('round-info').textContent = `Level ${this.currentLevel} β€’ ${blanksCount} blank${blanksCount > 1 ? 's' : ''}`;
128
+ document.getElementById('contextualization').innerHTML = `πŸ“š Practice with classic literature from ${book.author}'s "${book.title}"`;
129
+ document.getElementById('passage-content').innerHTML = displayText;
130
+ document.getElementById('hints-list').innerHTML = this.hints.map((hint, i) => `<div>${i+1}. ${hint}</div>`).join('');
131
+
132
+ // Show game area
133
+ document.getElementById('loading').classList.add('hidden');
134
+ document.getElementById('game-area').classList.remove('hidden');
135
+ document.getElementById('hints-section').classList.add('hidden');
136
+ document.getElementById('result').textContent = '';
137
+ document.getElementById('submit-btn').classList.remove('hidden');
138
+ document.getElementById('next-btn').classList.add('hidden');
139
+ }
140
+
141
+ submitAnswers() {
142
+ const inputs = document.querySelectorAll('.cloze-input');
143
+ let correct = 0;
144
+
145
+ inputs.forEach((input, i) => {
146
+ const userAnswer = input.value.trim().toLowerCase();
147
+ const correctAnswer = this.blanks[i].originalWord.toLowerCase();
148
+
149
+ if (userAnswer === correctAnswer) {
150
+ input.classList.add('correct');
151
+ correct++;
152
+ } else {
153
+ input.classList.add('incorrect');
154
+ // Show correct answer
155
+ const span = document.createElement('span');
156
+ span.className = 'text-green-600 font-semibold ml-2 text-sm';
157
+ span.textContent = `βœ“ ${this.blanks[i].originalWord}`;
158
+ input.parentNode.appendChild(span);
159
+ }
160
+ input.disabled = true;
161
+ });
162
+
163
+ const percentage = Math.round((correct / this.blanks.length) * 100);
164
+ const passed = correct >= (this.blanks.length === 1 ? 1 : this.blanks.length - 1);
165
+
166
+ let message = `Score: ${correct}/${this.blanks.length} (${percentage}%)`;
167
+ if (passed) {
168
+ message += ` - Excellent! Advancing to Level ${this.currentLevel + 1}! πŸŽ‰`;
169
+ document.getElementById('result').className = 'mt-4 text-center font-semibold text-green-600';
170
+ this.currentLevel++;
171
+ } else {
172
+ message += ` - Keep practicing! πŸ’ͺ`;
173
+ document.getElementById('result').className = 'mt-4 text-center font-semibold text-red-600';
174
+ }
175
+
176
+ document.getElementById('result').textContent = message;
177
+ document.getElementById('submit-btn').classList.add('hidden');
178
+ document.getElementById('next-btn').classList.remove('hidden');
179
+ this.currentRound++;
180
+ }
181
+
182
+ nextRound() {
183
+ document.querySelectorAll('.cloze-input').forEach(input => {
184
+ input.classList.remove('correct', 'incorrect');
185
+ input.disabled = false;
186
+ input.value = '';
187
+ });
188
+ document.querySelectorAll('.text-green-600').forEach(el => el.remove());
189
+ this.startNewRound();
190
+ }
191
+
192
+ toggleHints() {
193
+ const hintsSection = document.getElementById('hints-section');
194
+ const btn = document.getElementById('hint-btn');
195
+ if (hintsSection.classList.contains('hidden')) {
196
+ hintsSection.classList.remove('hidden');
197
+ btn.textContent = 'Hide Hints';
198
+ } else {
199
+ hintsSection.classList.add('hidden');
200
+ btn.textContent = 'Show Hints';
201
+ }
202
+ }
203
+ }
204
+
205
+ // Initialize when page loads
206
+ document.addEventListener('DOMContentLoaded', () => {
207
+ new SimpleClozeReader();
208
+ });
209
+ </script>
210
+ </body>
211
+ </html>
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ fastapi==0.104.1
2
+ uvicorn[standard]==0.24.0