@rookiestar/eng-lang-tutor 1.2.4 → 1.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +0 -51
- package/README.md +68 -0
- package/examples/sample_quiz_a1.json +4 -4
- package/examples/sample_quiz_a2.json +4 -4
- package/examples/sample_quiz_b1.json +2 -0
- package/examples/sample_quiz_b2.json +3 -3
- package/examples/sample_quiz_c1.json +2 -2
- package/examples/sample_quiz_c2.json +3 -3
- package/package.json +3 -3
- package/scripts/release.py +255 -0
- package/templates/prompts/quiz_generation.md +192 -11
- package/.claude/settings.local.json +0 -22
- package/backups/state_backup_20260227_063358.json +0 -47
- package/backups/state_backup_20260227_063405.json +0 -47
- package/backups/state_backup_20260227_064016.json +0 -47
package/CHANGELOG.md
CHANGED
|
@@ -2,57 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
-
## [1.2.4] - 2026-02-27
|
|
6
|
-
|
|
7
|
-
### Fixed
|
|
8
|
-
- **TTS voice selection**: Audio composer now automatically selects appropriate voices based on TTS provider
|
|
9
|
-
- Edge-TTS: `en-US-JennyNeural`, `en-US-EricNeural`
|
|
10
|
-
- XunFei: `catherine`, `henry`
|
|
11
|
-
- **CLI import paths**: Fixed broken imports in `state_manager.py` CLI module
|
|
12
|
-
|
|
13
|
-
### Changed
|
|
14
|
-
- **npm package**: Removed automatic `pip install` from postinstall script
|
|
15
|
-
- Added `eng-lang-tutor-setup` CLI tool for manual dependency installation
|
|
16
|
-
- Supports `--venv`, `--user`, and `--check` options
|
|
17
|
-
- **Test coverage**: Added 38 new tests covering:
|
|
18
|
-
- `scripts/audio/utils.py` (6 tests)
|
|
19
|
-
- `scripts/audio/converter.py` (16 tests)
|
|
20
|
-
- `scripts/utils/helpers.py` (16 tests)
|
|
21
|
-
- **Test fixes**: Fixed pre-existing test failures in CLI and TTS modules
|
|
22
|
-
|
|
23
|
-
## [1.2.3] - 2026-02-27
|
|
24
|
-
|
|
25
|
-
### Changed
|
|
26
|
-
- Version bump for npm publishing
|
|
27
|
-
|
|
28
|
-
## [1.2.0] - 2026-02-27
|
|
29
|
-
|
|
30
|
-
### Changed
|
|
31
|
-
- Initial npm package setup
|
|
32
|
-
|
|
33
|
-
## [1.0.1] - 2025-02-27
|
|
34
|
-
|
|
35
|
-
### Fixed
|
|
36
|
-
- **TTS voice selection**: Audio composer now automatically selects appropriate voices based on TTS provider
|
|
37
|
-
- Edge-TTS: `en-US-JennyNeural`, `en-US-EricNeural`
|
|
38
|
-
- XunFei: `catherine`, `henry`
|
|
39
|
-
- **CLI import paths**: Fixed broken imports in `state_manager.py` CLI module
|
|
40
|
-
|
|
41
|
-
### Changed
|
|
42
|
-
- **npm package**: Removed automatic `pip install` from postinstall script
|
|
43
|
-
- Added `eng-lang-tutor-setup` CLI tool for manual dependency installation
|
|
44
|
-
- Supports `--venv`, `--user`, and `--check` options
|
|
45
|
-
- **Test coverage**: Added 38 new tests covering:
|
|
46
|
-
- `scripts/audio/utils.py` (6 tests)
|
|
47
|
-
- `scripts/audio/converter.py` (16 tests)
|
|
48
|
-
- `scripts/utils/helpers.py` (16 tests)
|
|
49
|
-
- **Test fixes**: Fixed pre-existing test failures in CLI and TTS modules
|
|
50
|
-
|
|
51
|
-
## [1.0.1] - 2025-02-27
|
|
52
|
-
|
|
53
|
-
### Changed
|
|
54
|
-
- Version bump for npm publishing (1.0.0 was previously unpublished)
|
|
55
|
-
|
|
56
5
|
## [1.0.0] - 2025-02-26
|
|
57
6
|
|
|
58
7
|
Initial release.
|
package/README.md
CHANGED
|
@@ -84,6 +84,21 @@ openclaw skills list
|
|
|
84
84
|
openclaw skills info eng-lang-tutor
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
+
### 卸载
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# 卸载 npm 包并清除所有数据(包括状态和媒体文件)
|
|
91
|
+
npm uninstall -g @rookiestar/eng-lang-tutor && rm -rf ~/.openclaw/state/eng-lang-tutor ~/.openclaw/media/eng-lang-tutor
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**如需保留学习数据,仅卸载软件:**
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
npm uninstall -g @rookiestar/eng-lang-tutor
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
> 注意:`~/.openclaw/state/eng-lang-tutor/` 存储学习进度、XP、连胜等数据;`~/.openclaw/media/eng-lang-tutor/` 存储生成的音频文件。
|
|
101
|
+
|
|
87
102
|
**重启 Gateway:**
|
|
88
103
|
|
|
89
104
|
```bash
|
|
@@ -180,6 +195,59 @@ export XUNFEI_API_SECRET=xxx
|
|
|
180
195
|
| 快速 | 1.3 | 听力挑战 |
|
|
181
196
|
| 非常快 | 1.7 | 进阶训练 |
|
|
182
197
|
|
|
198
|
+
## 🛠️ 飞书语音气泡魔改指南
|
|
199
|
+
|
|
200
|
+
> 通过修改 OpenClaw 飞书插件,可使 `.opus` 音频文件以语音气泡形式展示,提升用户体验。
|
|
201
|
+
|
|
202
|
+
**文件位置:** `/home/linuxbrew/.linuxbrew/lib/node_modules/openclaw/extensions/feishu/src/media.ts`
|
|
203
|
+
|
|
204
|
+
### 📍 第一处修改:扩充类型定义 (约第 276 行)
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
// 【修改前】
|
|
208
|
+
msgType?: "file" | "media";
|
|
209
|
+
|
|
210
|
+
// 【修改后】
|
|
211
|
+
msgType?: "file" | "media" | "audio";
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### 📍 第二处修改:重构路由逻辑 (约第 375 行)
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
// 【修改前】
|
|
218
|
+
// Feishu requires msg_type "media" for audio/video, "file" for documents
|
|
219
|
+
const isMedia = fileType === "mp4" || fileType === "opus";
|
|
220
|
+
return sendFileFeishu({
|
|
221
|
+
cfg,
|
|
222
|
+
to,
|
|
223
|
+
fileKey,
|
|
224
|
+
msgType: isMedia ? "media" : "file",
|
|
225
|
+
replyToMessageId,
|
|
226
|
+
accountId,
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// 【修改后】
|
|
230
|
+
// 精细化路由:mp4 走 media (视频), opus 走 audio (语音气泡), 其余走 file
|
|
231
|
+
let msgType: "file" | "media" | "audio" = "file";
|
|
232
|
+
if (fileType === "mp4") {
|
|
233
|
+
msgType = "media";
|
|
234
|
+
} else if (fileType === "opus") {
|
|
235
|
+
msgType = "audio";
|
|
236
|
+
}
|
|
237
|
+
return sendFileFeishu({
|
|
238
|
+
cfg,
|
|
239
|
+
to,
|
|
240
|
+
fileKey,
|
|
241
|
+
msgType,
|
|
242
|
+
replyToMessageId,
|
|
243
|
+
accountId,
|
|
244
|
+
});
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**效果:**
|
|
248
|
+
- `.opus` 文件 → 语音气泡形式播放
|
|
249
|
+
- 其他文件 → 普通文件附件形式
|
|
250
|
+
|
|
183
251
|
## 命令列表
|
|
184
252
|
|
|
185
253
|
| 命令 | 别名 | 描述 |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_meta": {
|
|
3
|
-
"prompt_version": "quiz_gen_v1.
|
|
3
|
+
"prompt_version": "quiz_gen_v1.3"
|
|
4
4
|
},
|
|
5
5
|
"quiz_date": "2026-02-25",
|
|
6
6
|
"cefr_level": "A1",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"⬜ C. Goodbye",
|
|
32
32
|
"⬜ D. Sorry"
|
|
33
33
|
],
|
|
34
|
-
"hint": "💡
|
|
34
|
+
"hint": "💡 What's the polite thing to say?",
|
|
35
35
|
"correct_feedback": "✅ Correct! **'Thank you'** = 谢谢!",
|
|
36
36
|
"wrong_feedback": "❌ Not quite. When someone helps you, say **'Thank you'**!",
|
|
37
37
|
"xp_display": "💎 +10 XP"
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"type_name": "Chinglish 修正 | Fix the Chinglish",
|
|
51
51
|
"question_formatted": "🔧 What's wrong with this sentence?",
|
|
52
52
|
"sentence_formatted": "📝 \"Thank you very **many** for your help!\"",
|
|
53
|
-
"hint": "💡
|
|
53
|
+
"hint": "💡 Think about which word describes amount or degree",
|
|
54
54
|
"correct_feedback": "✅ Fixed! **'Thank you very much!'** sounds natural! 🎉",
|
|
55
55
|
"wrong_feedback": "❌ Use **'much'** not 'many' here. **'Thank you very much!'**",
|
|
56
56
|
"xp_display": "💎 +15 XP"
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"question_formatted": "✏️ What do you say?",
|
|
72
72
|
"context_formatted": "🚪 Your friend opens the door for you. You say: '**___**!'",
|
|
73
73
|
"word_bank_formatted": "📦 Options: [ **Thanks** | Hello | Goodbye ]",
|
|
74
|
-
"hint": "💡 Which one
|
|
74
|
+
"hint": "💡 Which one is for showing appreciation?",
|
|
75
75
|
"correct_feedback": "✅ Perfect! **'Thanks!'** is casual and friendly! 🎯",
|
|
76
76
|
"wrong_feedback": "❌ The answer was **'Thanks!'** - it means 谢谢!",
|
|
77
77
|
"xp_display": "💎 +12 XP"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_meta": {
|
|
3
|
-
"prompt_version": "quiz_gen_v1.
|
|
3
|
+
"prompt_version": "quiz_gen_v1.3"
|
|
4
4
|
},
|
|
5
5
|
"quiz_date": "2026-02-25",
|
|
6
6
|
"cefr_level": "A2",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"⬜ C. When you say goodbye",
|
|
32
32
|
"⬜ D. When you ask for help"
|
|
33
33
|
],
|
|
34
|
-
"hint": "💡
|
|
34
|
+
"hint": "💡 Think about when you use this phrase in introductions",
|
|
35
35
|
"correct_feedback": "✅ Correct! **'Nice to meet you'** = first time only!",
|
|
36
36
|
"wrong_feedback": "❌ **'Nice to meet you'** is for first-time introductions only!",
|
|
37
37
|
"xp_display": "💎 +10 XP"
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"type_name": "Chinglish 修正 | Fix the Chinglish",
|
|
51
51
|
"question_formatted": "🔧 Find and fix the error:",
|
|
52
52
|
"sentence_formatted": "📝 \"Hi John! **Nice to see you.** This is our first meeting.\"",
|
|
53
|
-
"hint": "💡
|
|
53
|
+
"hint": "💡 Think about the difference between first-time and repeat meetings",
|
|
54
54
|
"correct_feedback": "✅ Fixed! **'Nice to meet you!'** for first introductions! 🎉",
|
|
55
55
|
"wrong_feedback": "❌ First time = **'Nice to meet you'**, not 'see you'!",
|
|
56
56
|
"xp_display": "💎 +15 XP"
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"question_formatted": "✏️ Complete the response:",
|
|
72
72
|
"context_formatted": "🎉 Friend: \"This is Sarah.\" You: \"**___!**\"",
|
|
73
73
|
"word_bank_formatted": "📦 Options: [ **Nice to meet you** | Nice to see you | Good to know you ]",
|
|
74
|
-
"hint": "💡
|
|
74
|
+
"hint": "💡 Consider the context - is this person new to you?",
|
|
75
75
|
"correct_feedback": "✅ Perfect! **'Nice to meet you!'** is the standard introduction! 🎯",
|
|
76
76
|
"wrong_feedback": "❌ For first introductions, say **'Nice to meet you!'**",
|
|
77
77
|
"xp_display": "💎 +12 XP"
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"⬜ C. They want to cancel the project",
|
|
29
29
|
"⬜ D. They want you to write the report"
|
|
30
30
|
],
|
|
31
|
+
"hint": "💡 Think about what kind of conversation this implies",
|
|
31
32
|
"correct_feedback": "✅ Correct! **'Touch base'** = quick check-in ⚡",
|
|
32
33
|
"wrong_feedback": "❌ Not quite. **'Touch base'** means a brief, informal chat!",
|
|
33
34
|
"key_phrase": "**touch base**",
|
|
@@ -70,6 +71,7 @@
|
|
|
70
71
|
"question_formatted": "✏️ Complete the sentence:",
|
|
71
72
|
"context_formatted": "💼 To your manager: \"I wanted to **___** on the project timeline.\"",
|
|
72
73
|
"word_bank_formatted": "📦 Word Bank: [ **touch base** | 'discuss together' ❌ | 'communicate' ❌ ]",
|
|
74
|
+
"hint": "💡 Which sounds natural for a quick check-in with your manager?",
|
|
73
75
|
"correct_feedback": "✅ Perfect! **'Touch base'** is the way to go! 🎯",
|
|
74
76
|
"wrong_feedback": "❌ 'Discuss together' sounds awkward. Try **'touch base'**!",
|
|
75
77
|
"key_phrase": "**touch base**",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_meta": {
|
|
3
|
-
"prompt_version": "quiz_gen_v1.
|
|
3
|
+
"prompt_version": "quiz_gen_v1.3"
|
|
4
4
|
},
|
|
5
5
|
"quiz_date": "2026-02-25",
|
|
6
6
|
"cefr_level": "B2",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"type_name": "Chinglish 修正 | Fix the Chinglish",
|
|
51
51
|
"question_formatted": "🔧 Make this sound more corporate:",
|
|
52
52
|
"sentence_formatted": "📝 \"We need to **discuss this topic again** after the meeting.\"",
|
|
53
|
-
"hint": "💡
|
|
53
|
+
"hint": "💡 Which sounds most natural in American corporate culture?",
|
|
54
54
|
"correct_feedback": "✅ Much better! **'Let's circle back on this'** sounds professional! 🎉",
|
|
55
55
|
"wrong_feedback": "❌ Try **'Let's circle back on this'** instead of 'discuss again'",
|
|
56
56
|
"xp_display": "💎 +15 XP"
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"question_formatted": "✏️ Complete the sentence:",
|
|
72
72
|
"context_formatted": "📧 \"I'll **___** on the project updates.\"",
|
|
73
73
|
"word_bank_formatted": "📦 Options: [ **loop you in** | circle back | touch base | reach out ]",
|
|
74
|
-
"hint": "💡
|
|
74
|
+
"hint": "💡 Think about the relationship - you're keeping them informed",
|
|
75
75
|
"correct_feedback": "✅ Perfect! **'Loop you in'** means include in communication! 🎯",
|
|
76
76
|
"wrong_feedback": "❌ **'Loop you in'** is for including someone in future communication!",
|
|
77
77
|
"xp_display": "💎 +12 XP"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_meta": {
|
|
3
|
-
"prompt_version": "quiz_gen_v1.
|
|
3
|
+
"prompt_version": "quiz_gen_v1.3"
|
|
4
4
|
},
|
|
5
5
|
"quiz_date": "2026-02-25",
|
|
6
6
|
"cefr_level": "C1",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"question_formatted": "✏️ Complete with the right idiom:",
|
|
72
72
|
"context_formatted": "🔄 30 minutes, no progress. \"We're **___** here.\"",
|
|
73
73
|
"word_bank_formatted": "📦 Options: [ **spinning our wheels** | boiling the ocean | touching base | circling back ]",
|
|
74
|
-
"hint": "💡
|
|
74
|
+
"hint": "💡 Think about which idiom describes lack of progress",
|
|
75
75
|
"correct_feedback": "✅ Perfect! **'Spinning our wheels'** = unproductive! 🎯",
|
|
76
76
|
"wrong_feedback": "❌ **'Spinning our wheels'** describes unproductive discussion!",
|
|
77
77
|
"xp_display": "💎 +12 XP"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_meta": {
|
|
3
|
-
"prompt_version": "quiz_gen_v1.
|
|
3
|
+
"prompt_version": "quiz_gen_v1.3"
|
|
4
4
|
},
|
|
5
5
|
"quiz_date": "2026-02-25",
|
|
6
6
|
"cefr_level": "C2",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"type_name": "Chinglish 修正 | Fix the Chinglish",
|
|
51
51
|
"question_formatted": "🔧 Make this sound more C-suite:",
|
|
52
52
|
"sentence_formatted": "📝 \"That's **too ambitious**. We need to **reduce the scope**.\"",
|
|
53
|
-
"hint": "💡
|
|
53
|
+
"hint": "💡 What's the C-suite way to say 'too ambitious'?",
|
|
54
54
|
"correct_feedback": "✅ Perfect! **'Boiling the ocean' + 'phased approach'** = executive language! 🎉",
|
|
55
55
|
"wrong_feedback": "❌ Try **'That's boiling the ocean. We need a phased approach.'**",
|
|
56
56
|
"xp_display": "💎 +15 XP"
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"⬜ C. Let's keep it confidential",
|
|
82
82
|
"⬜ D. We should decide now"
|
|
83
83
|
],
|
|
84
|
-
"hint": "💡
|
|
84
|
+
"hint": "💡 What's the corporate process for building consensus?",
|
|
85
85
|
"correct_feedback": "✅ Perfect! **'Socialize this'** = systematic stakeholder buy-in! 🎯",
|
|
86
86
|
"wrong_feedback": "❌ **'Socialize this with stakeholders'** is the C-suite process!",
|
|
87
87
|
"xp_display": "💎 +15 XP"
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rookiestar/eng-lang-tutor",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.6",
|
|
4
4
|
"description": "地道美式英语导师 - OpenClaw Skill for learning authentic American English",
|
|
5
5
|
"main": "scripts/cli/cli.py",
|
|
6
|
-
"
|
|
7
|
-
"
|
|
6
|
+
"scripts": {
|
|
7
|
+
"postinstall": "python3 -m pip install -r requirements.txt --quiet"
|
|
8
8
|
},
|
|
9
9
|
"keywords": [
|
|
10
10
|
"english",
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Release Script - Manages file visibility across local repo, remote repo, and npm package.
|
|
4
|
+
|
|
5
|
+
This script handles three layers:
|
|
6
|
+
1. Local repo - All tracked files (managed by .gitignore)
|
|
7
|
+
2. Remote repo - Public files only
|
|
8
|
+
3. npm package - Minimal user-facing files
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
python3 scripts/release.py --check # Show file distribution
|
|
12
|
+
python3 scripts/release.py --sync-remote # Sync to remote (dry-run)
|
|
13
|
+
python3 scripts/release.py --npm-pack # Create npm tarball
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import subprocess
|
|
18
|
+
import json
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Set
|
|
21
|
+
|
|
22
|
+
# Files/directories to EXCLUDE from remote repo (not publicly visible)
|
|
23
|
+
REMOTE_EXCLUDE = {
|
|
24
|
+
"tests/",
|
|
25
|
+
"package.json",
|
|
26
|
+
"package-lock.json",
|
|
27
|
+
"bin/",
|
|
28
|
+
"scripts/setup.py",
|
|
29
|
+
".gitignore",
|
|
30
|
+
".gitattributes",
|
|
31
|
+
".npmignore",
|
|
32
|
+
"*.egg-info/",
|
|
33
|
+
".pytest_cache/",
|
|
34
|
+
"__pycache__/",
|
|
35
|
+
".coverage",
|
|
36
|
+
"htmlcov/",
|
|
37
|
+
".tox/",
|
|
38
|
+
".nox/",
|
|
39
|
+
".claude/",
|
|
40
|
+
"*.pyc",
|
|
41
|
+
"*.pyo",
|
|
42
|
+
"*.tgz",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# Files/directories to EXCLUDE from npm package (in addition to REMOTE_EXCLUDE)
|
|
46
|
+
# npm package should be minimal - only what users need to run
|
|
47
|
+
NPM_EXCLUDE = REMOTE_EXCLUDE | {
|
|
48
|
+
"CLAUDE.md", # Development guide for Claude AI
|
|
49
|
+
"docs/", # Internal documentation
|
|
50
|
+
"examples/", # Sample files (not needed for runtime)
|
|
51
|
+
"references/", # Reference materials
|
|
52
|
+
"*.md", # All markdown except README.md
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# Files that MUST be included in npm package
|
|
56
|
+
NPM_INCLUDE = {
|
|
57
|
+
"README.md", # Keep README for npm display
|
|
58
|
+
"package.json", # Required for npm
|
|
59
|
+
"requirements.txt", # Python dependencies
|
|
60
|
+
"scripts/", # All scripts (excluding setup.py)
|
|
61
|
+
"templates/", # Prompt templates
|
|
62
|
+
"SKILL.md", # Skill documentation
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def get_tracked_files() -> Set[str]:
|
|
67
|
+
"""Get all files tracked by git."""
|
|
68
|
+
result = subprocess.run(
|
|
69
|
+
["git", "ls-files"],
|
|
70
|
+
capture_output=True,
|
|
71
|
+
text=True,
|
|
72
|
+
cwd=Path(__file__).parent.parent
|
|
73
|
+
)
|
|
74
|
+
return set(result.stdout.strip().split("\n"))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def should_exclude(path: str, exclude_patterns: Set[str]) -> bool:
|
|
78
|
+
"""Check if a path should be excluded based on patterns."""
|
|
79
|
+
for pattern in exclude_patterns:
|
|
80
|
+
if pattern.endswith("/"):
|
|
81
|
+
if path.startswith(pattern) or "/" + pattern in path:
|
|
82
|
+
return True
|
|
83
|
+
elif pattern.startswith("*."):
|
|
84
|
+
if path.endswith(pattern[1:]):
|
|
85
|
+
return True
|
|
86
|
+
else:
|
|
87
|
+
if path == pattern or path.startswith(pattern + "/"):
|
|
88
|
+
return True
|
|
89
|
+
return False
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def categorize_files():
|
|
93
|
+
"""Categorize files into local, remote, and npm groups."""
|
|
94
|
+
tracked = get_tracked_files()
|
|
95
|
+
|
|
96
|
+
local_only = set()
|
|
97
|
+
remote_files = set()
|
|
98
|
+
npm_files = set()
|
|
99
|
+
|
|
100
|
+
for file in tracked:
|
|
101
|
+
# Skip empty strings
|
|
102
|
+
if not file:
|
|
103
|
+
continue
|
|
104
|
+
|
|
105
|
+
# Check if in remote exclude list
|
|
106
|
+
if should_exclude(file, REMOTE_EXCLUDE):
|
|
107
|
+
local_only.add(file)
|
|
108
|
+
else:
|
|
109
|
+
remote_files.add(file)
|
|
110
|
+
|
|
111
|
+
# Check if should be in npm package
|
|
112
|
+
if should_exclude(file, NPM_EXCLUDE):
|
|
113
|
+
# Check if explicitly included
|
|
114
|
+
included = False
|
|
115
|
+
for inc_pattern in NPM_INCLUDE:
|
|
116
|
+
if inc_pattern.endswith("/"):
|
|
117
|
+
if file.startswith(inc_pattern):
|
|
118
|
+
included = True
|
|
119
|
+
break
|
|
120
|
+
elif file == inc_pattern:
|
|
121
|
+
included = True
|
|
122
|
+
break
|
|
123
|
+
|
|
124
|
+
if included:
|
|
125
|
+
npm_files.add(file)
|
|
126
|
+
else:
|
|
127
|
+
npm_files.add(file)
|
|
128
|
+
|
|
129
|
+
return local_only, remote_files, npm_files
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def show_distribution():
|
|
133
|
+
"""Show file distribution across three layers."""
|
|
134
|
+
local_only, remote_files, npm_files = categorize_files()
|
|
135
|
+
|
|
136
|
+
print("=" * 60)
|
|
137
|
+
print("FILE DISTRIBUTION ACROSS THREE LAYERS")
|
|
138
|
+
print("=" * 60)
|
|
139
|
+
|
|
140
|
+
print(f"\n📦 LOCAL REPO ONLY ({len(local_only)} files)")
|
|
141
|
+
print("-" * 40)
|
|
142
|
+
for f in sorted(local_only):
|
|
143
|
+
print(f" {f}")
|
|
144
|
+
|
|
145
|
+
print(f"\n🌐 REMOTE REPO (public, {len(remote_files)} files)")
|
|
146
|
+
print("-" * 40)
|
|
147
|
+
for f in sorted(remote_files):
|
|
148
|
+
print(f" {f}")
|
|
149
|
+
|
|
150
|
+
print(f"\n📦 NPM PACKAGE ({len(npm_files)} files)")
|
|
151
|
+
print("-" * 40)
|
|
152
|
+
for f in sorted(npm_files):
|
|
153
|
+
print(f" {f}")
|
|
154
|
+
|
|
155
|
+
print("\n" + "=" * 60)
|
|
156
|
+
print(f"SUMMARY: Local={len(local_only) + len(remote_files)} | Remote={len(remote_files)} | npm={len(npm_files)}")
|
|
157
|
+
print("=" * 60)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def create_gitattributes():
|
|
161
|
+
"""Create .gitattributes with export-ignore for local-only files."""
|
|
162
|
+
eng_lang_tutor_dir = Path(__file__).parent.parent
|
|
163
|
+
gitattributes_path = eng_lang_tutor_dir / ".gitattributes"
|
|
164
|
+
|
|
165
|
+
# Patterns to exclude from git archive (remote export)
|
|
166
|
+
export_ignore_patterns = [
|
|
167
|
+
"tests/",
|
|
168
|
+
".pytest_cache/",
|
|
169
|
+
"*.pyc",
|
|
170
|
+
"*.pyo",
|
|
171
|
+
".coverage",
|
|
172
|
+
"htmlcov/",
|
|
173
|
+
".tox/",
|
|
174
|
+
".nox/",
|
|
175
|
+
".claude/",
|
|
176
|
+
"*.egg-info/",
|
|
177
|
+
"*.tgz",
|
|
178
|
+
]
|
|
179
|
+
|
|
180
|
+
content = "# Files to exclude from git archive export\n"
|
|
181
|
+
content += "# These files are tracked locally but not exported to remote archives\n\n"
|
|
182
|
+
|
|
183
|
+
for pattern in export_ignore_patterns:
|
|
184
|
+
content += f"{pattern} export-ignore\n"
|
|
185
|
+
|
|
186
|
+
gitattributes_path.write_text(content)
|
|
187
|
+
print(f"✓ Created {gitattributes_path}")
|
|
188
|
+
print("\nNote: This affects 'git archive' only.")
|
|
189
|
+
print("For selective push to remote, consider using a separate public branch.")
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def npm_pack():
|
|
193
|
+
"""Create npm tarball with proper file filtering."""
|
|
194
|
+
eng_lang_tutor_dir = Path(__file__).parent.parent
|
|
195
|
+
|
|
196
|
+
# Update package.json files field for npm
|
|
197
|
+
package_json_path = eng_lang_tutor_dir / "package.json"
|
|
198
|
+
|
|
199
|
+
with open(package_json_path) as f:
|
|
200
|
+
package = json.load(f)
|
|
201
|
+
|
|
202
|
+
# Set files field to include only what we want
|
|
203
|
+
package["files"] = [
|
|
204
|
+
"scripts/",
|
|
205
|
+
"templates/",
|
|
206
|
+
"SKILL.md",
|
|
207
|
+
"README.md",
|
|
208
|
+
"requirements.txt",
|
|
209
|
+
]
|
|
210
|
+
|
|
211
|
+
# Remove tests from npm package
|
|
212
|
+
if "!tests/**" in package.get("files", []):
|
|
213
|
+
pass # Already excluded
|
|
214
|
+
|
|
215
|
+
print("npm pack would include:")
|
|
216
|
+
print(json.dumps(package.get("files", []), indent=2))
|
|
217
|
+
|
|
218
|
+
print("\nRun the following to create npm package:")
|
|
219
|
+
print(f" cd {eng_lang_tutor_dir} && npm pack")
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def main():
|
|
223
|
+
parser = argparse.ArgumentParser(
|
|
224
|
+
description="Manage file visibility across local repo, remote repo, and npm package"
|
|
225
|
+
)
|
|
226
|
+
parser.add_argument(
|
|
227
|
+
"--check",
|
|
228
|
+
action="store_true",
|
|
229
|
+
help="Show file distribution across three layers"
|
|
230
|
+
)
|
|
231
|
+
parser.add_argument(
|
|
232
|
+
"--create-gitattributes",
|
|
233
|
+
action="store_true",
|
|
234
|
+
help="Create .gitattributes with export-ignore rules"
|
|
235
|
+
)
|
|
236
|
+
parser.add_argument(
|
|
237
|
+
"--npm-pack",
|
|
238
|
+
action="store_true",
|
|
239
|
+
help="Show npm pack configuration"
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
args = parser.parse_args()
|
|
243
|
+
|
|
244
|
+
if args.check:
|
|
245
|
+
show_distribution()
|
|
246
|
+
elif args.create_gitattributes:
|
|
247
|
+
create_gitattributes()
|
|
248
|
+
elif args.npm_pack:
|
|
249
|
+
npm_pack()
|
|
250
|
+
else:
|
|
251
|
+
parser.print_help()
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
if __name__ == "__main__":
|
|
255
|
+
main()
|
|
@@ -19,18 +19,147 @@ Based on today's knowledge point, generate a 3-question quiz calibrated to user'
|
|
|
19
19
|
## KNOWLEDGE POINT
|
|
20
20
|
{keypoint_json}
|
|
21
21
|
|
|
22
|
-
## CEFR DIFFICULTY CALIBRATION
|
|
22
|
+
## CEFR DIFFICULTY CALIBRATION - 6 LEVELS
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|-------|-------------------------|
|
|
26
|
-
| A1-A2 | Simple recognition, obvious distractors, direct context matching |
|
|
27
|
-
| B1-B2 | Nuanced usage, subtle distractors, requires understanding context |
|
|
28
|
-
| C1-C2 | Complex scenarios, idiomatic variations, cultural nuance testing |
|
|
24
|
+
### A1 - Beginner 入门级
|
|
29
25
|
|
|
30
|
-
**
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
26
|
+
**Multiple Choice:**
|
|
27
|
+
- 4 选项,1 个明显错误(语法/拼写)
|
|
28
|
+
- 其他 3 个语法正确但语义不符
|
|
29
|
+
- 场景简单日常:购物、问路、问候
|
|
30
|
+
|
|
31
|
+
**Chinglish Fix:**
|
|
32
|
+
- 错误类型:基本语法、词序、明显直译
|
|
33
|
+
- 示例错误:"I very like it" → "I really like it"
|
|
34
|
+
- 示例错误:"She go to school yesterday" → "She went"
|
|
35
|
+
|
|
36
|
+
**Fill Blank:**
|
|
37
|
+
- 3 选 1 词库
|
|
38
|
+
- 直接匹配,无需理解语境
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
### A2 - Elementary 初级
|
|
43
|
+
|
|
44
|
+
**Multiple Choice:**
|
|
45
|
+
- 4 选项全部语法正确
|
|
46
|
+
- 干扰项语义差异明显
|
|
47
|
+
- 场景:日常生活、简单工作
|
|
48
|
+
|
|
49
|
+
**Chinglish Fix:**
|
|
50
|
+
- 错误类型:常见直译、介词错误
|
|
51
|
+
- 示例错误:"I agree your opinion" → "I agree with your opinion"
|
|
52
|
+
- 示例错误:"Discuss about this" → "Discuss this"
|
|
53
|
+
|
|
54
|
+
**Fill Blank:**
|
|
55
|
+
- 3 选 1 词库
|
|
56
|
+
- 需要简单语境理解
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
### B1 - Intermediate 中级
|
|
61
|
+
|
|
62
|
+
**Multiple Choice:**
|
|
63
|
+
- 4 选项全部语法正确
|
|
64
|
+
- 干扰项在**其他语境**下可用
|
|
65
|
+
- 包含 1 个常见学习者错误作为陷阱
|
|
66
|
+
- 场景:工作沟通、旅行、社交
|
|
67
|
+
|
|
68
|
+
**Example - B1 MC:**
|
|
69
|
+
Context: "You want to end a phone call politely."
|
|
70
|
+
Options:
|
|
71
|
+
A. "I'll let you go now" ← Correct
|
|
72
|
+
B. "I'll hang up now" ← 太直接
|
|
73
|
+
C. "Let's stop talking" ← 不礼貌
|
|
74
|
+
D. "I'm going now" ← 语境不符
|
|
75
|
+
|
|
76
|
+
**Chinglish Fix:**
|
|
77
|
+
- 错误类型:介词搭配、冠词、固定搭配
|
|
78
|
+
- 示例错误:"Make a decision for" → "Make a decision about"
|
|
79
|
+
|
|
80
|
+
**Fill Blank:**
|
|
81
|
+
- 3-4 选 1 词库
|
|
82
|
+
- 需要理解表达用法
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
### B2 - Upper-Intermediate 中高级 ⚠️ 常见级别
|
|
87
|
+
|
|
88
|
+
**Multiple Choice:**
|
|
89
|
+
- 4 选项全部语法正确且听起来自然
|
|
90
|
+
- 干扰项必须是 **plausible in OTHER contexts**
|
|
91
|
+
- 至少 1 个"高级陷阱":常见学习者错误
|
|
92
|
+
- 场景:商务会议、专业沟通
|
|
93
|
+
|
|
94
|
+
**Example - B2 MC:**
|
|
95
|
+
Context: "In a team meeting, you want to return to a topic later."
|
|
96
|
+
Options:
|
|
97
|
+
A. "Let's circle back on this" ← Correct
|
|
98
|
+
B. "Let's follow up on this" ← Plausible (different meaning - action vs discussion)
|
|
99
|
+
C. "We should revisit this later" ← Plausible (more formal, less idiomatic)
|
|
100
|
+
D. "Let's discuss this again" ← Trap (grammatically correct but not idiomatic)
|
|
101
|
+
|
|
102
|
+
**Chinglish Fix:**
|
|
103
|
+
- 错误类型:细微介词错误、语体不当、搭配错误
|
|
104
|
+
- 示例错误:"We need to discuss about the project"
|
|
105
|
+
- 示例错误:"I am interesting in this topic" (interested)
|
|
106
|
+
|
|
107
|
+
**Fill Blank:**
|
|
108
|
+
- 4 选 1 词库
|
|
109
|
+
- 需要理解细微语义差别
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
### C1 - Advanced 高级
|
|
114
|
+
|
|
115
|
+
**Multiple Choice:**
|
|
116
|
+
- 所有选项对非母语者都听起来自然
|
|
117
|
+
- 需要文化知识或习语理解
|
|
118
|
+
- 可包含 1-2 个英式/美式差异陷阱
|
|
119
|
+
- 场景:复杂商务、学术讨论
|
|
120
|
+
|
|
121
|
+
**Example - C1 MC:**
|
|
122
|
+
Context: "You want to politely decline a request without saying no directly."
|
|
123
|
+
Options:
|
|
124
|
+
A. "That might be challenging to fit in" ← Correct (indirect)
|
|
125
|
+
B. "I'm afraid I can't" ← Too direct
|
|
126
|
+
C. "Let me think about it" ← Implies maybe, not decline
|
|
127
|
+
D. "I'll have to pass on this" ← Correct but more casual
|
|
128
|
+
|
|
129
|
+
**Chinglish Fix:**
|
|
130
|
+
- 错误类型:文化得体性、语域错误、隐含意义
|
|
131
|
+
- 示例错误:"Please kindly check" (过度礼貌)
|
|
132
|
+
- 示例错误:在非正式场合使用过于正式的表达
|
|
133
|
+
|
|
134
|
+
**Fill Blank / Dialogue Completion:**
|
|
135
|
+
- 4 选 1,需要文化理解
|
|
136
|
+
- 多个答案可能都"可以",但只有一个"最地道"
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
### C2 - Proficiency 精通级
|
|
141
|
+
|
|
142
|
+
**Multiple Choice:**
|
|
143
|
+
- 所有选项都符合语法且自然
|
|
144
|
+
- 区别在于**细微语气差异**或**文化隐含意义**
|
|
145
|
+
- 场景:专业演讲、文化敏感话题
|
|
146
|
+
|
|
147
|
+
**Example - C2 MC:**
|
|
148
|
+
Context: "Giving feedback to a senior colleague on their presentation."
|
|
149
|
+
Options:
|
|
150
|
+
A. "I had a thought on slide 3" ← Correct (hedges, collaborative)
|
|
151
|
+
B. "You should change slide 3" ← Too direct
|
|
152
|
+
C. "Slide 3 could be better" ← Vague
|
|
153
|
+
D. "I think slide 3 is wrong" ← Too confrontational
|
|
154
|
+
|
|
155
|
+
**Chinglish Fix:**
|
|
156
|
+
- 错误类型:深层文化差异、语用失误
|
|
157
|
+
- 示例错误:不理解美式间接沟通文化
|
|
158
|
+
- 示例错误:在需要 hedge 的情况下过于直接
|
|
159
|
+
|
|
160
|
+
**Dialogue Completion:**
|
|
161
|
+
- 开放式,需要综合理解
|
|
162
|
+
- 可能没有"唯一正确答案",而是"最符合语境"
|
|
34
163
|
|
|
35
164
|
## QUESTION TYPE REQUIREMENTS (3 questions total)
|
|
36
165
|
1. **multiple_choice** (required): Test expression recognition - 10 XP
|
|
@@ -73,6 +202,36 @@ Based on today's knowledge point, generate a 3-question quiz calibrated to user'
|
|
|
73
202
|
- Hint format: "💡 Think about what Americans say in this situation"
|
|
74
203
|
- FORBIDDEN: "💡 The answer is 'touch base'" or showing the phrase directly
|
|
75
204
|
|
|
205
|
+
## 💡 HINT DESIGN PRINCIPLES
|
|
206
|
+
|
|
207
|
+
### Core Rule: Hints guide thinking direction, NEVER reveal answers
|
|
208
|
+
|
|
209
|
+
**SAFE Hint Patterns** (通用模板,可复用):
|
|
210
|
+
- "💡 Think about the formality level of this situation"
|
|
211
|
+
- "💡 Consider who you're talking to - friend or boss?"
|
|
212
|
+
- "💡 What would sound natural in American English?"
|
|
213
|
+
- "💡 Is this formal or casual context?"
|
|
214
|
+
- "💡 Think about the relationship between the speakers"
|
|
215
|
+
- "💡 Consider the tone - professional or friendly?"
|
|
216
|
+
- "💡 What's appropriate for a workplace setting?"
|
|
217
|
+
|
|
218
|
+
**FORBIDDEN Hint Patterns**:
|
|
219
|
+
- ❌ "💡 Use a spatial metaphor" (太具体,直接指向特定答案)
|
|
220
|
+
- ❌ "💡 The phrase starts with 't'" (字母提示)
|
|
221
|
+
- ❌ "💡 Include someone = ?" (等同告诉答案)
|
|
222
|
+
- ❌ "💡 'A' or 'B' - which one?" (二选一)
|
|
223
|
+
- ❌ "💡 Think about [specific word/concept from answer]" (指向答案)
|
|
224
|
+
- ❌ 直接用中文翻译作为提示
|
|
225
|
+
|
|
226
|
+
**Validation Test**:
|
|
227
|
+
如果 hint 只适用于 ONE possible answer,则太具体。
|
|
228
|
+
Good hint 应该对 2-3 个选项都"听起来合理"。
|
|
229
|
+
|
|
230
|
+
**Bad Hint Example:**
|
|
231
|
+
- Question: "Let's ___ on this later." (Answer: circle back)
|
|
232
|
+
- Bad hint: "💡 Think about a shape" ← 太具体,直接指向 circle
|
|
233
|
+
- Good hint: "💡 What's the idiomatic way to say 'return to a topic'?"
|
|
234
|
+
|
|
76
235
|
3. **NEVER make distractors obviously wrong**
|
|
77
236
|
- All 4 options should sound plausible
|
|
78
237
|
- For B2+: At least one distractor should be a common learner error
|
|
@@ -85,6 +244,28 @@ Based on today's knowledge point, generate a 3-question quiz calibrated to user'
|
|
|
85
244
|
3. Include encouraging feedback in display fields
|
|
86
245
|
4. Total XP should be around 35-40
|
|
87
246
|
5. **Use NEW contexts** - never copy-paste from keypoint examples
|
|
247
|
+
|
|
248
|
+
## CONTEXT VARIATION RULES
|
|
249
|
+
|
|
250
|
+
### Multiple Choice - 场景类型(轮换使用)
|
|
251
|
+
1. **Email**: "You're writing to a client..."
|
|
252
|
+
2. **Meeting**: "In a team meeting..."
|
|
253
|
+
3. **Casual chat**: "Talking to a coworker at lunch..."
|
|
254
|
+
4. **Phone/Video call**: "On a call with your manager..."
|
|
255
|
+
5. **Presentation**: "During your presentation..."
|
|
256
|
+
6. **Networking**: "At a professional event..."
|
|
257
|
+
|
|
258
|
+
### Chinglish Fix - 错误类型分布(按级别)
|
|
259
|
+
| Level | 主导错误类型 | 占比 |
|
|
260
|
+
|-------|-------------|------|
|
|
261
|
+
| A1-A2 | 基本语法、词序、直译 | 80% |
|
|
262
|
+
| B1-B2 | 介词、冠词、搭配 | 60% |
|
|
263
|
+
| C1-C2 | 语域、文化、语用 | 50% |
|
|
264
|
+
|
|
265
|
+
### Fill Blank - 语境深度
|
|
266
|
+
- A1-A2: 单句,明确场景
|
|
267
|
+
- B1-B2: 对话语境,需理解关系
|
|
268
|
+
- C1-C2: 复杂语境,可能有多重解读
|
|
88
269
|
```
|
|
89
270
|
|
|
90
271
|
---
|
|
@@ -96,7 +277,7 @@ Based on today's knowledge point, generate a 3-question quiz calibrated to user'
|
|
|
96
277
|
```json
|
|
97
278
|
{
|
|
98
279
|
"_meta": {
|
|
99
|
-
"prompt_version": "quiz_gen_v1.
|
|
280
|
+
"prompt_version": "quiz_gen_v1.3"
|
|
100
281
|
},
|
|
101
282
|
"quiz_date": "{today_date}",
|
|
102
283
|
"keypoint_fingerprint": "{fingerprint}",
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"permissions": {
|
|
3
|
-
"allow": [
|
|
4
|
-
"Bash(python3 -m pytest:*)",
|
|
5
|
-
"Bash(pytest:*)",
|
|
6
|
-
"Bash(find:*)",
|
|
7
|
-
"Bash(git add:*)",
|
|
8
|
-
"Bash(git commit:*)",
|
|
9
|
-
"Bash(git push)",
|
|
10
|
-
"mcp__brave-search__brave_web_search",
|
|
11
|
-
"mcp__web-search-prime__webSearchPrime",
|
|
12
|
-
"mcp__zread__get_repo_structure",
|
|
13
|
-
"mcp__zread__read_file",
|
|
14
|
-
"mcp__zread__search_doc",
|
|
15
|
-
"mcp__web-reader__webReader",
|
|
16
|
-
"WebSearch",
|
|
17
|
-
"mcp__fetch__fetch",
|
|
18
|
-
"Bash(wc:*)",
|
|
19
|
-
"Bash(python3 -m py_compile:*)"
|
|
20
|
-
]
|
|
21
|
-
}
|
|
22
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 2,
|
|
3
|
-
"initialized": false,
|
|
4
|
-
"onboarding_step": 0,
|
|
5
|
-
"completion_status": {
|
|
6
|
-
"quiz_completed_date": null,
|
|
7
|
-
"keypoint_view_history": []
|
|
8
|
-
},
|
|
9
|
-
"schedule": {
|
|
10
|
-
"keypoint_time": "06:45",
|
|
11
|
-
"quiz_time": "22:45",
|
|
12
|
-
"timezone": "Asia/Shanghai"
|
|
13
|
-
},
|
|
14
|
-
"user": {
|
|
15
|
-
"xp": 0,
|
|
16
|
-
"level": 1,
|
|
17
|
-
"streak": 0,
|
|
18
|
-
"streak_freeze": 0,
|
|
19
|
-
"gems": 0,
|
|
20
|
-
"badges": []
|
|
21
|
-
},
|
|
22
|
-
"preferences": {
|
|
23
|
-
"cefr_level": "B1",
|
|
24
|
-
"oral_written_ratio": 0.7,
|
|
25
|
-
"topics": {
|
|
26
|
-
"movies": 0.2,
|
|
27
|
-
"news": 0.15,
|
|
28
|
-
"gaming": 0.15,
|
|
29
|
-
"sports": 0.1,
|
|
30
|
-
"workplace": 0.2,
|
|
31
|
-
"social": 0.1,
|
|
32
|
-
"daily_life": 0.1
|
|
33
|
-
},
|
|
34
|
-
"tutor_style": "humorous",
|
|
35
|
-
"dedup_days": 14
|
|
36
|
-
},
|
|
37
|
-
"progress": {
|
|
38
|
-
"total_quizzes": 0,
|
|
39
|
-
"correct_rate": 0.0,
|
|
40
|
-
"last_study_date": null,
|
|
41
|
-
"perfect_quizzes": 0,
|
|
42
|
-
"expressions_learned": 0
|
|
43
|
-
},
|
|
44
|
-
"recent_topics": [],
|
|
45
|
-
"error_notebook": [],
|
|
46
|
-
"error_archive": []
|
|
47
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 2,
|
|
3
|
-
"initialized": false,
|
|
4
|
-
"onboarding_step": 0,
|
|
5
|
-
"completion_status": {
|
|
6
|
-
"quiz_completed_date": null,
|
|
7
|
-
"keypoint_view_history": []
|
|
8
|
-
},
|
|
9
|
-
"schedule": {
|
|
10
|
-
"keypoint_time": "06:45",
|
|
11
|
-
"quiz_time": "22:45",
|
|
12
|
-
"timezone": "Asia/Shanghai"
|
|
13
|
-
},
|
|
14
|
-
"user": {
|
|
15
|
-
"xp": 0,
|
|
16
|
-
"level": 1,
|
|
17
|
-
"streak": 0,
|
|
18
|
-
"streak_freeze": 0,
|
|
19
|
-
"gems": 0,
|
|
20
|
-
"badges": []
|
|
21
|
-
},
|
|
22
|
-
"preferences": {
|
|
23
|
-
"cefr_level": "B1",
|
|
24
|
-
"oral_written_ratio": 0.7,
|
|
25
|
-
"topics": {
|
|
26
|
-
"movies": 0.2,
|
|
27
|
-
"news": 0.15,
|
|
28
|
-
"gaming": 0.15,
|
|
29
|
-
"sports": 0.1,
|
|
30
|
-
"workplace": 0.2,
|
|
31
|
-
"social": 0.1,
|
|
32
|
-
"daily_life": 0.1
|
|
33
|
-
},
|
|
34
|
-
"tutor_style": "humorous",
|
|
35
|
-
"dedup_days": 14
|
|
36
|
-
},
|
|
37
|
-
"progress": {
|
|
38
|
-
"total_quizzes": 0,
|
|
39
|
-
"correct_rate": 0.0,
|
|
40
|
-
"last_study_date": null,
|
|
41
|
-
"perfect_quizzes": 0,
|
|
42
|
-
"expressions_learned": 0
|
|
43
|
-
},
|
|
44
|
-
"recent_topics": [],
|
|
45
|
-
"error_notebook": [],
|
|
46
|
-
"error_archive": []
|
|
47
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 2,
|
|
3
|
-
"initialized": false,
|
|
4
|
-
"onboarding_step": 0,
|
|
5
|
-
"completion_status": {
|
|
6
|
-
"quiz_completed_date": null,
|
|
7
|
-
"keypoint_view_history": []
|
|
8
|
-
},
|
|
9
|
-
"schedule": {
|
|
10
|
-
"keypoint_time": "06:45",
|
|
11
|
-
"quiz_time": "22:45",
|
|
12
|
-
"timezone": "Asia/Shanghai"
|
|
13
|
-
},
|
|
14
|
-
"user": {
|
|
15
|
-
"xp": 0,
|
|
16
|
-
"level": 1,
|
|
17
|
-
"streak": 0,
|
|
18
|
-
"streak_freeze": 0,
|
|
19
|
-
"gems": 0,
|
|
20
|
-
"badges": []
|
|
21
|
-
},
|
|
22
|
-
"preferences": {
|
|
23
|
-
"cefr_level": "B1",
|
|
24
|
-
"oral_written_ratio": 0.7,
|
|
25
|
-
"topics": {
|
|
26
|
-
"movies": 0.2,
|
|
27
|
-
"news": 0.15,
|
|
28
|
-
"gaming": 0.15,
|
|
29
|
-
"sports": 0.1,
|
|
30
|
-
"workplace": 0.2,
|
|
31
|
-
"social": 0.1,
|
|
32
|
-
"daily_life": 0.1
|
|
33
|
-
},
|
|
34
|
-
"tutor_style": "humorous",
|
|
35
|
-
"dedup_days": 14
|
|
36
|
-
},
|
|
37
|
-
"progress": {
|
|
38
|
-
"total_quizzes": 0,
|
|
39
|
-
"correct_rate": 0.0,
|
|
40
|
-
"last_study_date": null,
|
|
41
|
-
"perfect_quizzes": 0,
|
|
42
|
-
"expressions_learned": 0
|
|
43
|
-
},
|
|
44
|
-
"recent_topics": [],
|
|
45
|
-
"error_notebook": [],
|
|
46
|
-
"error_archive": []
|
|
47
|
-
}
|