milwright commited on
Commit
b451c6e
·
1 Parent(s): 82a4372

fix: clean up AI response processing and improve UI

Browse files

Remove problematic regex that was causing text artifacts in responses.
Also improve mobile chat interface and update styling.

.dockerignore ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Node modules
2
+ node_modules/
3
+ npm-debug.log*
4
+ yarn-debug.log*
5
+
6
+ # Git
7
+ .git/
8
+ .gitignore
9
+
10
+ # Environment files
11
+ .env
12
+ .env.*
13
+
14
+ # IDE files
15
+ .vscode/
16
+ .idea/
17
+ *.swp
18
+ *.swo
19
+
20
+ # OS files
21
+ .DS_Store
22
+ Thumbs.db
23
+
24
+ # Logs
25
+ *.log
26
+ logs/
27
+
28
+ # Temporary files
29
+ tmp/
30
+ temp/
31
+ .aider*
32
+
33
+ # Documentation
34
+ *.md
35
+ !README.md
36
+
37
+ # Planning documents
38
+ *_PLAN.md
39
+ IMPLEMENTATION_*.md
40
+
41
+ # Python cache
42
+ __pycache__/
43
+ *.pyc
44
+ *.pyo
45
+ *.pyd
46
+
47
+ # Coverage
48
+ coverage/
49
+ .nyc_output/
50
+
51
+ # Build artifacts
52
+ dist/
53
+ build/
.gitignore CHANGED
@@ -55,7 +55,17 @@ tmp/
55
  temp/
56
  .aider*
57
 
 
 
 
 
 
 
58
  # Planning documents
59
  HF_DATASET_INTEGRATION_PLAN.md
60
  *_PLAN.md
61
  IMPLEMENTATION_*.md
 
 
 
 
 
55
  temp/
56
  .aider*
57
 
58
+ # Python
59
+ __pycache__/
60
+ *.py[cod]
61
+ *$py.class
62
+ *.so
63
+
64
  # Planning documents
65
  HF_DATASET_INTEGRATION_PLAN.md
66
  *_PLAN.md
67
  IMPLEMENTATION_*.md
68
+
69
+ # Local configuration files
70
+ *.local
71
+ .env
Makefile ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .PHONY: help dev dev-python dev-docker build test clean install docker-build docker-run docker-dev
2
+
3
+ help: ## Show this help message
4
+ @echo "Available commands:"
5
+ @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}'
6
+
7
+ install: ## Install dependencies
8
+ @echo "Installing Python dependencies..."
9
+ pip install -r requirements.txt
10
+ @echo "Installing Node.js dependencies..."
11
+ npm install
12
+
13
+ dev: ## Start local development server (Python HTTP server)
14
+ @echo "Starting local development server on http://localhost:8000"
15
+ python local-server.py 8000
16
+
17
+ dev-python: ## Start FastAPI development server
18
+ @echo "Starting FastAPI server on http://localhost:7860"
19
+ python app.py
20
+
21
+ dev-docker: ## Start development environment with Docker Compose
22
+ docker-compose --profile dev up --build
23
+
24
+ build: ## Build the application (no-op for vanilla JS)
25
+ @echo "No build step needed for vanilla JS application"
26
+
27
+ test: ## Run tests (placeholder)
28
+ @echo "No tests configured yet"
29
+
30
+ clean: ## Clean temporary files
31
+ find . -type f -name "*.pyc" -delete
32
+ find . -type d -name "__pycache__" -delete
33
+ find . -type d -name "node_modules" -exec rm -rf {} + 2>/dev/null || true
34
+
35
+ docker-build: ## Build Docker image
36
+ docker build -t cloze-reader .
37
+
38
+ docker-run: ## Run Docker container
39
+ docker run -p 7860:7860 --env-file .env cloze-reader
40
+
41
+ docker-dev: ## Start with docker-compose
42
+ docker-compose up --build
43
+
44
+ logs: ## Show Docker logs
45
+ docker-compose logs -f
46
+
47
+ stop: ## Stop Docker containers
48
+ docker-compose down
docker-compose.yml ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3.8'
2
+
3
+ services:
4
+ cloze-reader:
5
+ build: .
6
+ ports:
7
+ - "7860:7860"
8
+ environment:
9
+ - OPENROUTER_API_KEY=${OPENROUTER_API_KEY:-}
10
+ - HF_API_KEY=${HF_API_KEY:-}
11
+ volumes:
12
+ - ./src:/app/src:ro
13
+ - ./index.html:/app/index.html:ro
14
+ restart: unless-stopped
15
+
16
+ dev-server:
17
+ image: python:3.9-slim
18
+ working_dir: /app
19
+ ports:
20
+ - "8000:8000"
21
+ volumes:
22
+ - .:/app
23
+ command: python -m http.server 8000
24
+ environment:
25
+ - PYTHONUNBUFFERED=1
26
+ profiles:
27
+ - dev
local-server.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Local development server for cloze-reader
4
+ Serves static files with CORS enabled for local testing
5
+ """
6
+
7
+ import http.server
8
+ import socketserver
9
+ import os
10
+ from urllib.parse import urlparse
11
+ import json
12
+
13
+ class LocalHandler(http.server.SimpleHTTPRequestHandler):
14
+ def end_headers(self):
15
+ # Enable CORS for local development
16
+ self.send_header('Access-Control-Allow-Origin', '*')
17
+ self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
18
+ self.send_header('Access-Control-Allow-Headers', 'Content-Type')
19
+ super().end_headers()
20
+
21
+ def do_GET(self):
22
+ # Handle root path
23
+ if self.path == '/':
24
+ self.path = '/index.html'
25
+
26
+ # Handle icon.png (serve local file if exists, otherwise redirect)
27
+ if self.path == '/icon.png':
28
+ if os.path.exists('icon.png'):
29
+ return super().do_GET()
30
+ else:
31
+ self.send_response(302)
32
+ self.send_header('Location', 'https://raw.githubusercontent.com/zmuhls/cloze-reader/main/icon.png')
33
+ self.end_headers()
34
+ return
35
+
36
+ # Serve static files
37
+ return super().do_GET()
38
+
39
+ def do_OPTIONS(self):
40
+ self.send_response(200)
41
+ self.end_headers()
42
+
43
+ def run_server(port=8000):
44
+ handler = LocalHandler
45
+
46
+ try:
47
+ with socketserver.TCPServer(("", port), handler) as httpd:
48
+ print(f"Local development server running at http://localhost:{port}/")
49
+ print("Press Ctrl+C to stop")
50
+ httpd.serve_forever()
51
+ except KeyboardInterrupt:
52
+ print("\nServer stopped")
53
+ except OSError as e:
54
+ if e.errno == 48: # Address already in use
55
+ print(f"Port {port} is already in use. Try a different port.")
56
+ else:
57
+ print(f"Error starting server: {e}")
58
+
59
+ if __name__ == "__main__":
60
+ import sys
61
+ port = int(sys.argv[1]) if len(sys.argv) > 1 else 8000
62
+ run_server(port)
package.json ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "cloze-reader",
3
+ "version": "1.0.0",
4
+ "description": "Interactive cloze reading comprehension game",
5
+ "main": "src/app.js",
6
+ "type": "module",
7
+ "scripts": {
8
+ "dev": "python -m http.server 8000",
9
+ "dev:python": "python app.py",
10
+ "build": "echo 'No build step needed for vanilla JS'",
11
+ "test": "echo 'No tests specified yet'",
12
+ "lint": "echo 'No linting configured yet'",
13
+ "start": "python app.py",
14
+ "docker:build": "docker build -t cloze-reader .",
15
+ "docker:run": "docker run -p 7860:7860 --env-file .env cloze-reader",
16
+ "docker:dev": "docker-compose up --build",
17
+ "docker:stop": "docker-compose down",
18
+ "docker:logs": "docker-compose logs -f",
19
+ "docker:clean": "docker-compose down -v --remove-orphans"
20
+ },
21
+ "keywords": [
22
+ "education",
23
+ "reading",
24
+ "comprehension",
25
+ "cloze",
26
+ "game"
27
+ ],
28
+ "author": "",
29
+ "license": "MIT",
30
+ "devDependencies": {
31
+ "http-server": "^14.1.1"
32
+ },
33
+ "dependencies": {},
34
+ "engines": {
35
+ "node": ">=14.0.0",
36
+ "python": ">=3.9"
37
+ }
38
+ }
src/aiService.js CHANGED
@@ -239,7 +239,6 @@ Passage: "${passage}"`
239
  content = content
240
  .replace(/^\s*["']|["']\s*$/g, '') // Remove leading/trailing quotes
241
  .replace(/^\s*[:;]+\s*/, '') // Remove leading colons and semicolons
242
- .replace(/^\s*[a-z]+:\s*/i, '') // Remove any word followed by colon (like "mas:")
243
  .replace(/\*+/g, '') // Remove asterisks (markdown bold/italic)
244
  .replace(/_+/g, '') // Remove underscores (markdown)
245
  .replace(/#+\s*/g, '') // Remove hash symbols (markdown headers)
 
239
  content = content
240
  .replace(/^\s*["']|["']\s*$/g, '') // Remove leading/trailing quotes
241
  .replace(/^\s*[:;]+\s*/, '') // Remove leading colons and semicolons
 
242
  .replace(/\*+/g, '') // Remove asterisks (markdown bold/italic)
243
  .replace(/_+/g, '') // Remove underscores (markdown)
244
  .replace(/#+\s*/g, '') // Remove hash symbols (markdown headers)
src/chatInterface.js CHANGED
@@ -44,7 +44,12 @@ class ChatUI {
44
  <!-- Question buttons area -->
45
  <div class="p-4 border-t">
46
  <div class="text-sm text-gray-600 mb-3">Choose a question:</div>
47
- <div id="question-buttons" class="grid grid-cols-2 gap-2">
 
 
 
 
 
48
  <!-- Question buttons will be inserted here -->
49
  </div>
50
  </div>
@@ -107,6 +112,7 @@ class ChatUI {
107
  messagesContainer.innerHTML = `
108
  <div class="text-center text-gray-500 text-sm">
109
  Choose a question below to get help with this word.
 
110
  </div>
111
  `;
112
  }
@@ -159,27 +165,39 @@ class ChatUI {
159
  // Load question buttons with disabled state for used questions
160
  loadQuestionButtons() {
161
  const buttonsContainer = document.getElementById('question-buttons');
 
162
  const questions = this.game.getSuggestedQuestionsForBlank(this.activeChatBlank);
163
 
164
- let html = '';
 
 
 
 
 
165
  questions.forEach(question => {
166
  const isDisabled = question.used;
167
  const buttonClass = isDisabled
168
- ? 'question-btn px-3 py-2 bg-gray-200 text-gray-500 rounded cursor-not-allowed text-sm font-medium'
169
- : 'question-btn px-3 py-2 bg-blue-100 text-blue-800 rounded hover:bg-blue-200 text-sm font-medium';
170
 
171
- html += `
 
172
  <button class="${buttonClass}"
173
  data-type="${question.type}"
174
  ${isDisabled ? 'disabled' : ''}>
175
  ${question.text}${isDisabled ? ' ✓' : ''}
176
  </button>
177
  `;
 
 
 
 
 
178
  });
179
 
180
- buttonsContainer.innerHTML = html;
181
 
182
- // Add click listeners to individual question buttons (not the container)
183
  buttonsContainer.querySelectorAll('.question-btn').forEach(btn => {
184
  btn.addEventListener('click', (e) => {
185
  if (!btn.disabled) {
@@ -190,6 +208,14 @@ class ChatUI {
190
  }
191
  });
192
  });
 
 
 
 
 
 
 
 
193
  }
194
 
195
  // Ask a specific question
 
44
  <!-- Question buttons area -->
45
  <div class="p-4 border-t">
46
  <div class="text-sm text-gray-600 mb-3">Choose a question:</div>
47
+ <!-- Dropdown for mobile -->
48
+ <select id="question-dropdown" class="w-full p-2 border rounded md:hidden mb-2">
49
+ <option value="">Select a question...</option>
50
+ </select>
51
+ <!-- Button grid for desktop -->
52
+ <div id="question-buttons" class="hidden md:grid grid-cols-2 gap-2">
53
  <!-- Question buttons will be inserted here -->
54
  </div>
55
  </div>
 
112
  messagesContainer.innerHTML = `
113
  <div class="text-center text-gray-500 text-sm">
114
  Choose a question below to get help with this word.
115
+ <br>
116
  </div>
117
  `;
118
  }
 
165
  // Load question buttons with disabled state for used questions
166
  loadQuestionButtons() {
167
  const buttonsContainer = document.getElementById('question-buttons');
168
+ const dropdown = document.getElementById('question-dropdown');
169
  const questions = this.game.getSuggestedQuestionsForBlank(this.activeChatBlank);
170
 
171
+ // Clear existing content
172
+ buttonsContainer.innerHTML = '';
173
+ dropdown.innerHTML = '<option value="">Select a question...</option>';
174
+
175
+ // Build button grid for desktop and dropdown options
176
+ let buttonHtml = '';
177
  questions.forEach(question => {
178
  const isDisabled = question.used;
179
  const buttonClass = isDisabled
180
+ ? 'question-btn px-2 py-1 bg-gray-100 text-gray-500 rounded cursor-not-allowed text-xs'
181
+ : 'question-btn px-2 py-1 bg-blue-50 text-blue-700 rounded hover:bg-blue-100 text-xs border border-blue-200';
182
 
183
+ // Desktop buttons
184
+ buttonHtml += `
185
  <button class="${buttonClass}"
186
  data-type="${question.type}"
187
  ${isDisabled ? 'disabled' : ''}>
188
  ${question.text}${isDisabled ? ' ✓' : ''}
189
  </button>
190
  `;
191
+
192
+ // Mobile dropdown options
193
+ if (!isDisabled) {
194
+ dropdown.innerHTML += `<option value="${question.type}">${question.text}</option>`;
195
+ }
196
  });
197
 
198
+ buttonsContainer.innerHTML = buttonHtml;
199
 
200
+ // Add click listeners to desktop buttons
201
  buttonsContainer.querySelectorAll('.question-btn').forEach(btn => {
202
  btn.addEventListener('click', (e) => {
203
  if (!btn.disabled) {
 
208
  }
209
  });
210
  });
211
+
212
+ // Add change listener to mobile dropdown
213
+ dropdown.addEventListener('change', (e) => {
214
+ if (e.target.value) {
215
+ this.askQuestion(e.target.value);
216
+ e.target.value = ''; // Reset dropdown
217
+ }
218
+ });
219
  }
220
 
221
  // Ask a specific question
src/clozeGameEngine.js CHANGED
@@ -392,8 +392,8 @@ class ClozeGame {
392
  const inputHtml = `<input type="text"
393
  class="cloze-input"
394
  data-blank-index="${index}"
395
- placeholder="${'_'.repeat(Math.max(3, blank.originalWord.length))}"
396
- style="width: ${Math.max(80, blank.originalWord.length * 12)}px;">`;
397
 
398
  html = html.replace(`___BLANK_${index}___`, inputHtml);
399
  });
 
392
  const inputHtml = `<input type="text"
393
  class="cloze-input"
394
  data-blank-index="${index}"
395
+ placeholder="${'_ '.repeat(Math.max(3, blank.originalWord.length)).trim()}"
396
+ style="width: ${Math.max(80, blank.originalWord.length * 16)}px;">`;
397
 
398
  html = html.replace(`___BLANK_${index}___`, inputHtml);
399
  });
src/styles.css CHANGED
@@ -224,7 +224,7 @@
224
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', Arial, sans-serif;
225
  background-color: transparent;
226
  border: none;
227
- border-bottom: 2px solid black;
228
  color: var(--typewriter-ink);
229
  text-align: center;
230
  outline: none;
@@ -238,18 +238,18 @@
238
  }
239
 
240
  .cloze-input:focus {
241
- border-bottom-color: black;
242
  box-shadow: 0 2px 0 rgba(0, 0, 0, 0.2);
243
  background-color: rgba(0, 0, 0, 0.05);
244
  }
245
 
246
  .cloze-input.correct {
247
- border-bottom-color: #10b981;
248
  background-color: rgba(16, 185, 129, 0.1);
249
  }
250
 
251
  .cloze-input.incorrect {
252
- border-bottom-color: #ef4444;
253
  background-color: rgba(239, 68, 68, 0.1);
254
  }
255
 
@@ -405,6 +405,34 @@
405
  }
406
  }
407
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
408
  /* Print styles */
409
  @media print {
410
  body {
 
224
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', Arial, sans-serif;
225
  background-color: transparent;
226
  border: none;
227
+ border-bottom: 2px dotted black;
228
  color: var(--typewriter-ink);
229
  text-align: center;
230
  outline: none;
 
238
  }
239
 
240
  .cloze-input:focus {
241
+ border-bottom: 2px dotted black;
242
  box-shadow: 0 2px 0 rgba(0, 0, 0, 0.2);
243
  background-color: rgba(0, 0, 0, 0.05);
244
  }
245
 
246
  .cloze-input.correct {
247
+ border-bottom: 2px dotted #10b981;
248
  background-color: rgba(16, 185, 129, 0.1);
249
  }
250
 
251
  .cloze-input.incorrect {
252
+ border-bottom: 2px dotted #ef4444;
253
  background-color: rgba(239, 68, 68, 0.1);
254
  }
255
 
 
405
  }
406
  }
407
 
408
+ /* Compact question buttons for chat interface */
409
+ #chat-modal .question-btn {
410
+ padding: 6px 8px;
411
+ min-height: auto;
412
+ font-size: 12px;
413
+ line-height: 1.3;
414
+ font-weight: 400;
415
+ transition: all 0.15s ease;
416
+ }
417
+
418
+ /* Mobile dropdown styling */
419
+ #question-dropdown {
420
+ background: #f8f9fa;
421
+ border: 2px solid #e9ecef;
422
+ border-radius: 8px;
423
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', Arial, sans-serif;
424
+ font-size: 15px;
425
+ font-weight: 500;
426
+ color: #495057;
427
+ transition: all 0.2s ease;
428
+ }
429
+
430
+ #question-dropdown:focus {
431
+ outline: none;
432
+ border-color: #007bff;
433
+ box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1);
434
+ }
435
+
436
  /* Print styles */
437
  @media print {
438
  body {