@thanh01.pmt/interactive-quiz-kit 1.0.3 → 1.0.5
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/HEADLESS.md +330 -0
- package/README.md +206 -153
- package/package.json +1 -1
package/HEADLESS.md
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
# Chế độ Headless: Sử dụng Logic của `interactive-quiz-kit` mà không cần Giao diện
|
|
2
|
+
|
|
3
|
+
**`interactive-quiz-kit`** được thiết kế với kiến trúc module hóa, cho phép bạn sử dụng toàn bộ logic lõi của nó một cách độc lập mà không cần đến các thành phần giao diện người dùng React. Chế độ "headless" này lý tưởng cho các trường hợp sử dụng như:
|
|
4
|
+
|
|
5
|
+
* Xây dựng một backend để quản lý và tạo quiz.
|
|
6
|
+
* Tích hợp logic quiz vào một ứng dụng di động (React Native, Flutter,...).
|
|
7
|
+
* Chạy các script tự động để tạo nội dung hoặc xử lý dữ liệu quiz.
|
|
8
|
+
* Tích hợp vào các framework JavaScript khác ngoài React.
|
|
9
|
+
|
|
10
|
+
Tài liệu này sẽ hướng dẫn bạn cách import và sử dụng các tính năng headless chính của thư viện.
|
|
11
|
+
|
|
12
|
+
## I. Tổng quan các Thành phần Headless
|
|
13
|
+
|
|
14
|
+
Các thành phần non-UI của thư viện được phân bổ trong các thư mục sau:
|
|
15
|
+
|
|
16
|
+
| Thư mục/File | Chức năng Chính |
|
|
17
|
+
| :--- | :--- |
|
|
18
|
+
| `types.ts` | **(Cốt lõi)** Định nghĩa tất cả các cấu trúc dữ liệu (interfaces) cho quiz. |
|
|
19
|
+
| `services/` | **(Cốt lõi)** Chứa các service để chạy, quản lý, và đóng gói quiz. |
|
|
20
|
+
| `ai/` | Chứa các flow để tạo nội dung quiz bằng Trí tuệ Nhân tạo. |
|
|
21
|
+
| `utils/idGenerators.ts` | Các hàm tiện ích, ví dụ như tạo ID duy nhất. |
|
|
22
|
+
| `schemas/` | Các file JSON Schema để xác thực cấu trúc dữ liệu của quiz. |
|
|
23
|
+
|
|
24
|
+
### 1. Chế độ Headless là gì và Tại sao nó quan trọng?
|
|
25
|
+
|
|
26
|
+
Hãy tưởng tượng thư viện của bạn như một chiếc xe hơi hoàn chỉnh. Giao diện người dùng (`react-ui/`) chính là phần thân vỏ, nội thất, vô lăng, bảng điều khiển—tất cả những gì bạn thấy và tương tác. "Chế độ headless" chính là việc bạn có thể lấy toàn bộ **động cơ, khung gầm, hệ thống truyền động và bộ não điều khiển (ECU)** của chiếc xe đó ra và lắp vào một thân vỏ khác—ví dụ như một chiếc xe tải, một chiếc thuyền, hoặc thậm chí một cỗ máy công nghiệp.
|
|
27
|
+
|
|
28
|
+
Nói cách khác, **chế độ headless cho phép bạn sử dụng tất cả các logic, quy trình nghiệp vụ, và khả năng xử lý dữ liệu của thư viện mà không cần đến giao diện người dùng React đi kèm.**
|
|
29
|
+
|
|
30
|
+
**Tại sao nó quan trọng?**
|
|
31
|
+
* **Linh hoạt tối đa:** Bạn không bị trói buộc vào React. Bạn có thể xây dựng giao diện bằng Vue, Svelte, Angular, hoặc thậm chí là ứng dụng di động native (iOS, Android) và chỉ cần gọi đến logic lõi của thư viện.
|
|
32
|
+
* **Tự động hóa & Tích hợp Backend:** Bạn có thể chạy các quy trình trên server, ví dụ:
|
|
33
|
+
* Một cron job hàng đêm dùng module AI để tự động tạo ra hàng trăm câu hỏi mới và lưu vào cơ sở dữ liệu.
|
|
34
|
+
* Xây dựng một API endpoint để quản lý, chỉnh sửa, và xuất bản các bài quiz.
|
|
35
|
+
* **Tái sử dụng Logic:** Đảm bảo logic nghiệp vụ (ví dụ: cách chấm điểm một câu hỏi) là nhất quán ở mọi nơi, từ web, mobile đến các script nội bộ.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
### 2. Phân tích chi tiết các Thành phần Headless
|
|
40
|
+
|
|
41
|
+
Dưới đây là từng thành phần, vai trò, và tầm quan trọng của chúng trong chế độ headless.
|
|
42
|
+
|
|
43
|
+
#### **A. `types.ts` & `schemas/` - Nền tảng & Quy tắc**
|
|
44
|
+
|
|
45
|
+
* **Vai trò:** Là "Hiến pháp" và "Bộ luật" của thư viện. Chúng định nghĩa cấu trúc dữ liệu và các quy tắc mà mọi thành phần khác phải tuân theo.
|
|
46
|
+
* **Mô tả chi tiết:**
|
|
47
|
+
* **`types.ts`:** Đây là file quan trọng nhất. Nó chứa các `interface` TypeScript như `QuizConfig`, `QuizQuestion`, `QuizResult`. Đây là "nguồn chân lý duy nhất" (single source of truth) cho hình dạng của dữ liệu. Bất kỳ hàm nào bạn viết để tương tác với quiz đều sẽ nhận và trả về các kiểu dữ liệu được định nghĩa ở đây.
|
|
48
|
+
* **`schemas/`:** Chứa các file JSON Schema. Trong môi trường headless, chúng cực kỳ hữu ích để **xác thực (validate)** dữ liệu. Ví dụ, khi một hệ thống khác gửi cho bạn một file JSON `quiz.json`, bạn có thể dùng schema này để kiểm tra xem file đó có hợp lệ hay không trước khi xử lý.
|
|
49
|
+
* **Tầm quan trọng:** **Nền tảng (Fundamental)**. Không thể sử dụng thư viện nếu không hiểu các kiểu dữ liệu này.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
#### **B. `services/QuizEditorService.ts` - Người Thợ Xây**
|
|
54
|
+
|
|
55
|
+
* **Vai trò:** Công cụ để **tạo, sửa, xóa, và sắp xếp** các câu hỏi bên trong một đối tượng `QuizConfig`. Đây là service dành cho khâu *biên soạn (authoring)*.
|
|
56
|
+
* **Mô tả chi tiết:**
|
|
57
|
+
* Đây là một lớp (class) bạn khởi tạo với một đối tượng `QuizConfig` ban đầu. Nó sẽ làm việc trên một bản sao sâu (deep copy), đảm bảo không làm thay đổi đối tượng gốc một cách không mong muốn (nguyên tắc immutability).
|
|
58
|
+
* **`createNewQuestionTemplate(type)` (Static Method):** Tạo ra một "khuôn mẫu" câu hỏi rỗng với các giá trị mặc định. Rất hữu ích khi bạn muốn thêm một câu hỏi mới.
|
|
59
|
+
* **`addQuestion(question)`:** Thêm một câu hỏi vào danh sách.
|
|
60
|
+
* **`updateQuestion(updatedQuestion)`:** Cập nhật một câu hỏi đã có dựa trên `id` của nó.
|
|
61
|
+
* **`deleteQuestionByIndex(index)`:** Xóa một câu hỏi dựa trên vị trí của nó.
|
|
62
|
+
* **`moveQuestion(fromIndex, toIndex)`:** Thay đổi thứ tự các câu hỏi.
|
|
63
|
+
* **Tầm quan trọng:** **Cốt lõi (Core)** cho bất kỳ tác vụ nào liên quan đến việc quản lý nội dung quiz.
|
|
64
|
+
|
|
65
|
+
* **Ví dụ sử dụng:**
|
|
66
|
+
```typescript
|
|
67
|
+
import { QuizEditorService, emptyQuiz } from 'interactive-quiz-kit/services';
|
|
68
|
+
|
|
69
|
+
// Bắt đầu với một quiz rỗng
|
|
70
|
+
let myQuiz = emptyQuiz;
|
|
71
|
+
const editor = new QuizEditorService(myQuiz);
|
|
72
|
+
|
|
73
|
+
// Thêm một câu hỏi True/False
|
|
74
|
+
const tfQuestion = QuizEditorService.createNewQuestionTemplate('true_false');
|
|
75
|
+
tfQuestion.prompt = "Trái Đất có phải là một hình cầu hoàn hảo không?";
|
|
76
|
+
if (tfQuestion.questionType === 'true_false') tfQuestion.correctAnswer = false;
|
|
77
|
+
|
|
78
|
+
myQuiz = editor.addQuestion(tfQuestion);
|
|
79
|
+
|
|
80
|
+
// Cập nhật lại tiêu đề quiz
|
|
81
|
+
myQuiz.title = "Quiz đã được cập nhật bằng code";
|
|
82
|
+
|
|
83
|
+
console.log(myQuiz);
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
#### **C. `services/QuizEngine.ts` - Nhạc trưởng Điều hành**
|
|
89
|
+
|
|
90
|
+
* **Vai trò:** Quản lý toàn bộ vòng đời của một **phiên làm bài quiz thực tế**. Đây là service dành cho khâu *thực thi (runtime)*.
|
|
91
|
+
* **Mô tả chi tiết:**
|
|
92
|
+
* Khởi tạo với một `QuizConfig` và một bộ `callbacks`. Thiết kế dựa trên callback này làm cho nó cực kỳ linh hoạt để tích hợp vào bất kỳ hệ thống nào.
|
|
93
|
+
* Nó quản lý trạng thái nội bộ: câu hỏi hiện tại, câu trả lời của người dùng, thời gian còn lại.
|
|
94
|
+
* Cung cấp các phương thức công khai (public methods) như `nextQuestion()`, `previousQuestion()`, `submitAnswer()`, và `calculateResults()` để điều khiển luồng làm bài.
|
|
95
|
+
* Logic chấm điểm phức tạp cho 12 loại câu hỏi được đóng gói hoàn toàn bên trong `evaluateQuestion`.
|
|
96
|
+
* **Tầm quan trọng:** **Cốt lõi (Core)** cho bất kỳ ứng dụng nào muốn cho người dùng *làm* bài quiz.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
#### **D. `ai/flows/` - Bộ não Sáng tạo**
|
|
101
|
+
|
|
102
|
+
* **Vai trò:** Cung cấp các hàm `async` để giao tiếp với AI và tự động tạo ra nội dung quiz.
|
|
103
|
+
* **Mô tả chi tiết:**
|
|
104
|
+
* Mỗi file là một "flow" có thể gọi được. Chúng nhận đầu vào có cấu trúc (nhờ `zod`) và trả về kết quả cũng có cấu trúc.
|
|
105
|
+
* **`generate...Question`:** Các flow tạo câu hỏi đơn lẻ.
|
|
106
|
+
* **`generateQuizPlan` & `generateQuestionsFromQuizPlan`:** Một quy trình 2 giai đoạn mạnh mẽ cho phép tạo ra một bài quiz hoàn chỉnh, cân bằng về chủ đề và độ khó, thay vì chỉ là một mớ câu hỏi ngẫu nhiên.
|
|
107
|
+
* **Tầm quan trọng:** **Tính năng nâng cao (Advanced Feature)**. Lý tưởng cho các hệ thống muốn tự động hóa việc tạo nội dung.
|
|
108
|
+
|
|
109
|
+
* **Ví dụ sử dụng (trong một script backend):**
|
|
110
|
+
```typescript
|
|
111
|
+
import { generateQuizPlan, generateQuestionsFromQuizPlan } from 'interactive-quiz-kit/ai';
|
|
112
|
+
|
|
113
|
+
async function generateFullQuiz() {
|
|
114
|
+
const planInput = {
|
|
115
|
+
totalQuestions: 5,
|
|
116
|
+
topics: [{ topic: 'Lịch sử Việt Nam', ratio: 100 }],
|
|
117
|
+
bloomLevels: [{ level: 'remembering', ratio: 60 }, { level: 'understanding', ratio: 40 }],
|
|
118
|
+
selectedQuestionTypes: ['multiple_choice', 'true_false']
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const plan = await generateQuizPlan(planInput);
|
|
122
|
+
|
|
123
|
+
const quizResult = await generateQuestionsFromQuizPlan({ quizPlan: plan.quizPlan });
|
|
124
|
+
|
|
125
|
+
console.log(`${quizResult.generatedQuestions.length} câu hỏi đã được tạo.`);
|
|
126
|
+
// => Lưu các câu hỏi này vào DB
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
#### **E. `services/` (các file liên quan đến SCORM) & `utils/` - Hộp Dụng cụ & Giao tiếp**
|
|
133
|
+
|
|
134
|
+
* **Vai trò:** Cung cấp các công cụ chuyên biệt để hoàn thành các tác vụ cụ thể.
|
|
135
|
+
* **Mô tả chi tiết:**
|
|
136
|
+
* **`SCORMService.ts`:** Một lớp độc lập để nói chuyện với LMS. Nó không biết gì về quiz, chỉ biết về các lệnh SCORM. `QuizEngine` sẽ sử dụng nó.
|
|
137
|
+
* **`generateSCORMManifest`, `generateLauncherHTML`:** Các hàm này nhận dữ liệu và trả về các chuỗi văn bản (XML, HTML). Chúng không có tác dụng phụ (side-effect) và hoàn toàn "thuần khiết" (pure), rất phù hợp cho môi trường headless.
|
|
138
|
+
* **`idGenerators.ts`:** Cung cấp hàm `generateUniqueId`, một tiện ích nhỏ nhưng cần thiết khi bạn tạo dữ liệu mới.
|
|
139
|
+
* **Tầm quan trọng:** **Hỗ trợ (Supporting)**. Cần thiết cho các chức năng cụ thể như SCORM hoặc khi cần tạo dữ liệu mới.
|
|
140
|
+
|
|
141
|
+
Bằng cách kết hợp các thành phần này, bạn có thể xây dựng một hệ thống hoàn chỉnh ở phía backend để tạo, quản lý quiz, và thậm chí mô phỏng các phiên làm bài để kiểm thử mà không cần viết một dòng code giao diện nào.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## II. Các Kiểu Dữ liệu (Types) - Nền tảng của Thư viện
|
|
146
|
+
|
|
147
|
+
Trước khi bắt đầu, bạn cần làm quen với các kiểu dữ liệu chính. Mọi tương tác với thư viện đều xoay quanh chúng.
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import { QuizConfig, QuizQuestion, TrueFalseQuestion } from 'interactive-quiz-kit';
|
|
151
|
+
|
|
152
|
+
// Tạo một câu hỏi
|
|
153
|
+
const myQuestion: TrueFalseQuestion = {
|
|
154
|
+
id: 'tf-001',
|
|
155
|
+
questionType: 'true_false',
|
|
156
|
+
prompt: 'Mặt trời mọc ở hướng Tây.',
|
|
157
|
+
correctAnswer: false,
|
|
158
|
+
points: 10
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// Tạo một cấu hình quiz hoàn chỉnh
|
|
162
|
+
const myQuizConfig: QuizConfig = {
|
|
163
|
+
id: 'my-first-headless-quiz',
|
|
164
|
+
title: 'Quiz Khoa Học Vui',
|
|
165
|
+
questions: [myQuestion],
|
|
166
|
+
settings: {
|
|
167
|
+
shuffleQuestions: true,
|
|
168
|
+
timeLimitMinutes: 10
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**Các kiểu dữ liệu quan trọng nhất:**
|
|
174
|
+
* `QuizConfig`: Toàn bộ cấu hình quiz.
|
|
175
|
+
* `QuizQuestion`: Union type của tất cả các loại câu hỏi.
|
|
176
|
+
* Các kiểu câu hỏi cụ thể: `MultipleChoiceQuestion`, `FillInTheBlanksQuestion`, `BlocklyProgrammingQuestion`, v.v.
|
|
177
|
+
* `QuizResult`: Cấu trúc dữ liệu kết quả trả về.
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## III. Sử dụng Dịch vụ Lõi
|
|
182
|
+
|
|
183
|
+
### 1. `QuizEditorService`: Tạo và Chỉnh sửa Quiz
|
|
184
|
+
|
|
185
|
+
Service này cho phép bạn thực hiện các thao tác CRUD (Create, Read, Update, Delete) trên một đối tượng `QuizConfig`.
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
import { QuizEditorService, emptyQuiz } from 'interactive-quiz-kit/services';
|
|
189
|
+
import type { QuestionTypeStrings } from 'interactive-quiz-kit';
|
|
190
|
+
|
|
191
|
+
// Bắt đầu với một quiz rỗng
|
|
192
|
+
const editor = new QuizEditorService(emptyQuiz);
|
|
193
|
+
|
|
194
|
+
// Tạo một template câu hỏi trắc nghiệm mới
|
|
195
|
+
const mcqTemplate = QuizEditorService.createNewQuestionTemplate('multiple_choice');
|
|
196
|
+
|
|
197
|
+
// Cập nhật thông tin cho câu hỏi
|
|
198
|
+
mcqTemplate.prompt = "Hành tinh nào gần mặt trời nhất?";
|
|
199
|
+
if (mcqTemplate.questionType === 'multiple_choice') {
|
|
200
|
+
mcqTemplate.options = [
|
|
201
|
+
{ id: 'opt1', text: 'Trái Đất' },
|
|
202
|
+
{ id: 'opt2', text: 'Sao Hỏa' },
|
|
203
|
+
{ id: 'opt3', text: 'Sao Thủy' },
|
|
204
|
+
];
|
|
205
|
+
mcqTemplate.correctAnswerId = 'opt3';
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Thêm câu hỏi vào quiz
|
|
209
|
+
editor.addQuestion(mcqTemplate);
|
|
210
|
+
|
|
211
|
+
// Xóa câu hỏi (nếu cần, ví dụ tại index 0)
|
|
212
|
+
// editor.deleteQuestionByIndex(0);
|
|
213
|
+
|
|
214
|
+
// Lấy đối tượng QuizConfig cuối cùng
|
|
215
|
+
const finalQuizConfig = editor.getQuiz();
|
|
216
|
+
|
|
217
|
+
console.log(JSON.stringify(finalQuizConfig, null, 2));
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### 2. `QuizEngine`: Chạy một Phiên làm bài Quiz
|
|
221
|
+
|
|
222
|
+
Service này là bộ não xử lý logic khi một người dùng đang làm bài quiz.
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
import { QuizEngine } from 'interactive-quiz-kit/services';
|
|
226
|
+
import type { QuizConfig, QuizResult } from 'interactive-quiz-kit';
|
|
227
|
+
|
|
228
|
+
// Giả sử bạn đã có một đối tượng myQuizConfig
|
|
229
|
+
const myQuizConfig: QuizConfig = /* ... */;
|
|
230
|
+
|
|
231
|
+
console.log("Bắt đầu quiz...");
|
|
232
|
+
|
|
233
|
+
const engine = new QuizEngine({
|
|
234
|
+
config: myQuizConfig,
|
|
235
|
+
callbacks: {
|
|
236
|
+
onQuestionChange: (question, qNum, total) => {
|
|
237
|
+
console.log(`\n--- Câu hỏi ${qNum}/${total} ---`);
|
|
238
|
+
console.log(question?.prompt);
|
|
239
|
+
},
|
|
240
|
+
onQuizFinish: (result: QuizResult) => {
|
|
241
|
+
console.log("\n--- KẾT QUẢ ---");
|
|
242
|
+
console.log(`Điểm số: ${result.score}/${result.maxScore}`);
|
|
243
|
+
console.log(`Phần trăm: ${result.percentage.toFixed(2)}%`);
|
|
244
|
+
console.log(`Trạng thái: ${result.passed ? 'Đạt' : 'Không đạt'}`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// Mô phỏng quá trình làm bài
|
|
250
|
+
const question1 = engine.getCurrentQuestion();
|
|
251
|
+
if (question1) {
|
|
252
|
+
// Giả sử câu 1 là True/False
|
|
253
|
+
engine.submitAnswer(question1.id, false);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
engine.nextQuestion();
|
|
257
|
+
|
|
258
|
+
const question2 = engine.getCurrentQuestion();
|
|
259
|
+
if (question2) {
|
|
260
|
+
// Giả sử câu 2 là Multiple Choice
|
|
261
|
+
engine.submitAnswer(question2.id, 'id_cua_dap_an_dung');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Kết thúc quiz
|
|
265
|
+
engine.calculateResults();
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## IV. Tạo Nội dung bằng AI
|
|
271
|
+
|
|
272
|
+
Bạn có thể sử dụng các flow AI để tự động tạo câu hỏi. Các hàm này là `async`.
|
|
273
|
+
|
|
274
|
+
*Lưu ý: Để sử dụng, bạn cần có môi trường đã cấu hình Google Genkit và các biến môi trường cần thiết (ví dụ: `GOOGLE_API_KEY`).*
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
import { generateMCQQuestion, generateQuizPlan } from 'interactive-quiz-kit/ai';
|
|
278
|
+
|
|
279
|
+
async function createAIQuestion() {
|
|
280
|
+
try {
|
|
281
|
+
console.log('Đang tạo câu hỏi trắc nghiệm về Hệ Mặt Trời...');
|
|
282
|
+
const result = await generateMCQQuestion({
|
|
283
|
+
topic: 'Hệ Mặt Trời',
|
|
284
|
+
difficulty: 'easy'
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
if (result.question) {
|
|
288
|
+
console.log('Câu hỏi đã được tạo:');
|
|
289
|
+
console.log(JSON.stringify(result.question, null, 2));
|
|
290
|
+
} else {
|
|
291
|
+
console.log('AI không thể tạo câu hỏi.');
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
} catch (error) {
|
|
295
|
+
console.error('Lỗi khi tạo câu hỏi AI:', error);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// createAIQuestion();
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## V. Đóng gói và Xuất bản
|
|
305
|
+
|
|
306
|
+
Các hàm này giúp tạo ra các thành phần của một gói SCORM. Chúng trả về nội dung dạng chuỗi (string) mà bạn có thể ghi ra file.
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
import { generateSCORMManifest, generateLauncherHTML } from 'interactive-quiz-kit/services';
|
|
310
|
+
import type { QuizConfig } from 'interactive-quiz-kit';
|
|
311
|
+
// import * as fs from 'fs'; // Dùng trong môi trường Node.js
|
|
312
|
+
|
|
313
|
+
// Giả sử bạn đã có một đối tượng myQuizConfig
|
|
314
|
+
const myQuizConfig: QuizConfig = /* ... */;
|
|
315
|
+
|
|
316
|
+
// Tạo nội dung manifest
|
|
317
|
+
const manifestXML = generateSCORMManifest(myQuizConfig, "1.2");
|
|
318
|
+
console.log('--- imsmanifest.xml ---');
|
|
319
|
+
console.log(manifestXML);
|
|
320
|
+
// fs.writeFileSync('dist/imsmanifest.xml', manifestXML);
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
// Tạo nội dung file HTML launcher
|
|
324
|
+
const launcherHTML = generateLauncherHTML(myQuizConfig);
|
|
325
|
+
console.log('\n--- index.html ---');
|
|
326
|
+
console.log(launcherHTML);
|
|
327
|
+
// fs.writeFileSync('dist/index.html', launcherHTML);
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
**Lưu ý:** Hàm `exportQuizAsSCORMZip` được thiết kế để chạy trên trình duyệt vì nó tương tác với DOM để kích hoạt tải file. Nếu bạn muốn đóng gói ZIP ở phía backend, bạn cần sử dụng một thư viện như `jszip` và tự mình thực hiện các bước tương tự như trong file `services/scormPackaging.ts`.
|
package/README.md
CHANGED
|
@@ -1,176 +1,229 @@
|
|
|
1
|
-
# Interactive Quiz Kit
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
###
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
6. **Short Answer (Trả lời ngắn)**
|
|
47
|
-
7. **Numeric (Trả lời bằng số)**
|
|
48
|
-
8. **Sequence (Sắp xếp thứ tự)**
|
|
49
|
-
9. **Matching (Ghép nối)** - Hiện UI Player dùng Select thay thế cho từng mục
|
|
50
|
-
10. **Hotspot (Điểm nóng trên ảnh)**
|
|
51
|
-
11. **Blockly Programming (Lập trình kéo thả khối Blockly)**
|
|
52
|
-
12. **Scratch Programming (Lập trình Scratch - sử dụng Blockly với renderer Zelos)**
|
|
53
|
-
|
|
54
|
-
### 5. Quản lý Dữ liệu Quiz
|
|
55
|
-
- **Định nghĩa Kiểu TypeScript:** Cung cấp các interface rõ ràng (\`QuizConfig\`, \`QuizQuestion\`, v.v.) cho cấu trúc dữ liệu quiz.
|
|
56
|
-
- **Nhập/Xuất JSON:** Dễ dàng lưu và tải quiz dưới dạng file JSON.
|
|
57
|
-
- **Đóng gói SCORM:**
|
|
58
|
-
- \`SCORMManifestGenerator\`: Tạo file \`imsmanifest.xml\`.
|
|
59
|
-
- \`HTMLLauncherGenerator\`: Tạo file HTML launcher cho SCORM.
|
|
60
|
-
|
|
61
|
-
### 6. Tích hợp AI (với Genkit & Gemini)
|
|
62
|
-
- Sử dụng các flow Genkit để tương tác với Gemini API.
|
|
63
|
-
- **Tạo Câu hỏi Đơn lẻ:** Các flow như \`generateMCQQuestion\`, \`generateTrueFalseQuestion\`, v.v.
|
|
64
|
-
- **Tạo Toàn bộ Quiz:**
|
|
65
|
-
- \`generate-quiz-plan-flow.ts\`: Lập kế hoạch cấu trúc quiz.
|
|
66
|
-
- \`generateQuestionsFromQuizPlanFlow.ts\`: Tạo các câu hỏi chi tiết dựa trên kế hoạch.
|
|
67
|
-
|
|
68
|
-
## Thành phần và Dịch vụ Chính
|
|
69
|
-
|
|
70
|
-
- **\`QuizPlayer.tsx\`**: Thành phần chính để người dùng làm quiz.
|
|
71
|
-
- **\`QuizAuthoringTool.tsx\`**: Giao diện chính để tạo và chỉnh sửa quiz.
|
|
72
|
-
- **\`QuizEngine.ts\`**: Lớp logic xử lý trạng thái, chấm điểm, và các hoạt động của quiz.
|
|
73
|
-
- **\`SCORMService.ts\`**: Xử lý giao tiếp với SCORM API của LMS.
|
|
74
|
-
- **\`QuestionRenderer.tsx\`**: Quyết định và hiển thị UI phù hợp cho từng loại câu hỏi.
|
|
75
|
-
- **Các thành phần UI câu hỏi (ví dụ: \`MultipleChoiceQuestionUI.tsx\`)**: Hiển thị và xử lý tương tác cho từng loại câu hỏi cụ thể.
|
|
76
|
-
- **Các form câu hỏi (ví dụ: \`MultipleChoiceQuestionForm.tsx\`)**: Được sử dụng trong \`EditQuestionModal\` để chỉnh sửa từng loại câu hỏi.
|
|
77
|
-
- **Các flow AI (trong \`src/ai/flows/\`)**: Logic Genkit để tạo nội dung quiz bằng AI.
|
|
78
|
-
|
|
79
|
-
## Cài đặt (Hướng dẫn nếu tách thành thư viện riêng)
|
|
80
|
-
|
|
81
|
-
Nếu thư viện này được publish lên npm, bạn có thể cài đặt bằng:
|
|
82
|
-
\`\`\`bash
|
|
83
|
-
npm install interactive-quiz-kit
|
|
84
|
-
# hoặc
|
|
85
|
-
yarn add interactive-quiz-kit
|
|
86
|
-
\`\`\`
|
|
87
|
-
Để phát triển local và sử dụng trong project khác, bạn có thể dùng \`npm link\`.
|
|
88
|
-
|
|
89
|
-
## Sử dụng Cơ bản (trong một ứng dụng React)
|
|
90
|
-
|
|
91
|
-
### Hiển thị Quiz cho Người dùng:
|
|
92
|
-
\`\`\`tsx
|
|
1
|
+
# Interactive Quiz Kit
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@thanh01pmt/interactive-quiz-kit)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
**Interactive Quiz Kit** is a comprehensive TypeScript library built with React, designed to effortlessly create, manage, play, and distribute interactive quizzes. It provides a robust core logic (`QuizEngine`), reusable React UI components, and support for a wide variety of question types.
|
|
7
|
+
|
|
8
|
+
The library is architected for easy extension and integration, featuring a powerful authoring tool, AI-powered content generation (using Google's Genkit and Gemini), and SCORM packaging for seamless LMS integration.
|
|
9
|
+
|
|
10
|
+
## ✨ Features
|
|
11
|
+
|
|
12
|
+
* **Robust Core Engine**: Handles state management, navigation, automatic grading, and time tracking.
|
|
13
|
+
* **Rich Question Support**: Includes 12+ question types, from Multiple Choice to complex ones like Blockly/Scratch Programming and Hotspot.
|
|
14
|
+
* **React-based UI Kit**: A full suite of components to build quiz players and authoring tools.
|
|
15
|
+
* `<QuizPlayer>`: The main component for taking quizzes.
|
|
16
|
+
* `<QuizAuthoringTool>`: A powerful interface for creating and editing quizzes.
|
|
17
|
+
* **Headless Mode**: Use all core logic, services, and AI flows independently of the React UI. Ideal for backend integrations, custom frontends, or automation scripts.
|
|
18
|
+
* **AI-Powered Generation**:
|
|
19
|
+
* Generate individual questions based on topic, context, and difficulty.
|
|
20
|
+
* Generate entire, well-structured quizzes using a two-stage planning and generation process.
|
|
21
|
+
* **SCORM & Webhook Integration**:
|
|
22
|
+
* Package quizzes as SCORM 1.2 or 2004 compliant ZIP files.
|
|
23
|
+
* Send detailed quiz results to a specified webhook URL.
|
|
24
|
+
|
|
25
|
+
## 🚀 Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install @thanh01pmt/interactive-quiz-kit
|
|
29
|
+
# or
|
|
30
|
+
yarn add @thanh01pmt/interactive-quiz-kit
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Peer Dependencies:** This library requires `react` and `react-dom` as peer dependencies. Ensure they are installed in your project.
|
|
34
|
+
|
|
35
|
+
## 📚 Usage
|
|
36
|
+
|
|
37
|
+
This library supports two primary modes of usage: with the built-in React UI, or in a "headless" mode using only the core logic.
|
|
38
|
+
|
|
39
|
+
### 1. Standard Mode (with React UI)
|
|
40
|
+
|
|
41
|
+
This is the quickest way to get a fully functional quiz application up and running.
|
|
42
|
+
|
|
43
|
+
#### Example: Displaying a Quiz with `<QuizPlayer>`
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
93
46
|
import React, { useState } from 'react';
|
|
94
|
-
import { QuizPlayer
|
|
47
|
+
import { QuizPlayer } from '@thanh01pmt/interactive-quiz-kit/react-ui';
|
|
48
|
+
import type { QuizConfig, QuizResult } from '@thanh01pmt/interactive-quiz-kit';
|
|
95
49
|
|
|
96
|
-
//
|
|
50
|
+
// 1. Define or load your quiz configuration
|
|
97
51
|
const myQuiz: QuizConfig = {
|
|
98
|
-
id: "my-
|
|
99
|
-
title: "
|
|
100
|
-
questions: [ /* ...
|
|
101
|
-
settings: {
|
|
52
|
+
id: "my-first-quiz",
|
|
53
|
+
title: "React Basics Quiz",
|
|
54
|
+
questions: [ /* ... array of question objects ... */ ],
|
|
55
|
+
settings: {
|
|
56
|
+
shuffleQuestions: true,
|
|
57
|
+
timeLimitMinutes: 15
|
|
58
|
+
}
|
|
102
59
|
};
|
|
103
60
|
|
|
104
|
-
const
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
const handleQuizComplete = (result: QuizResultType) => {
|
|
61
|
+
const MyQuizPage = () => {
|
|
62
|
+
const handleQuizComplete = (result: QuizResult) => {
|
|
108
63
|
console.log("Quiz Complete!", result);
|
|
109
|
-
|
|
110
|
-
// Xử lý kết quả (ví dụ: hiển thị, gửi lên server)
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
const handleExitQuiz = () => {
|
|
114
|
-
console.log("Exiting quiz");
|
|
115
|
-
// Điều hướng người dùng hoặc làm mới trạng thái
|
|
64
|
+
// Send result to your backend, or display a summary
|
|
116
65
|
};
|
|
117
66
|
|
|
118
67
|
return (
|
|
119
|
-
<
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
68
|
+
<div className="container">
|
|
69
|
+
<QuizPlayer
|
|
70
|
+
quizConfig={myQuiz}
|
|
71
|
+
onQuizComplete={handleQuizComplete}
|
|
72
|
+
/>
|
|
73
|
+
</div>
|
|
124
74
|
);
|
|
125
75
|
};
|
|
76
|
+
```
|
|
126
77
|
|
|
127
|
-
|
|
128
|
-
\`\`\`
|
|
78
|
+
#### Example: Using the `<QuizAuthoringTool>`
|
|
129
79
|
|
|
130
|
-
|
|
131
|
-
\`\`\`tsx
|
|
132
|
-
import React, { useState } from 'react';
|
|
133
|
-
import { QuizAuthoringTool, QuizConfig, emptyQuiz } from 'interactive-quiz-kit';
|
|
80
|
+
Embed the complete authoring experience to allow users to create and edit quizzes.
|
|
134
81
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
82
|
+
```tsx
|
|
83
|
+
import React from 'react';
|
|
84
|
+
import { QuizAuthoringTool, emptyQuiz } from '@thanh01pmt/interactive-quiz-kit/react-ui';
|
|
85
|
+
import type { QuizConfig } from '@thanh01pmt/interactive-quiz-kit';
|
|
138
86
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
87
|
+
const MyAuthoringPage = () => {
|
|
88
|
+
const handleSave = (quiz: QuizConfig) => {
|
|
89
|
+
console.log("Quiz saved:", quiz);
|
|
90
|
+
// Persist the quiz config (e.g., to a database or localStorage)
|
|
142
91
|
};
|
|
143
92
|
|
|
144
|
-
|
|
145
|
-
console.log("Exiting authoring tool");
|
|
146
|
-
// Điều hướng người dùng
|
|
147
|
-
};
|
|
148
|
-
|
|
93
|
+
// Start with an empty quiz template for creation
|
|
149
94
|
return (
|
|
150
95
|
<QuizAuthoringTool
|
|
151
|
-
initialQuizConfig={
|
|
152
|
-
onSaveQuiz={
|
|
153
|
-
onExitAuthoring={handleExitAuthoring} // Tùy chọn
|
|
96
|
+
initialQuizConfig={emptyQuiz}
|
|
97
|
+
onSaveQuiz={handleSave}
|
|
154
98
|
/>
|
|
155
99
|
);
|
|
156
100
|
};
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 2. Headless Mode (Core Logic Only)
|
|
104
|
+
|
|
105
|
+
Use the library's services, types, and AI flows in any JavaScript/TypeScript environment (e.g., Node.js backend, other frontend frameworks, scripts).
|
|
106
|
+
|
|
107
|
+
> For detailed examples and a full component list, see [**HEADLESS.md**](./HEADLESS.md).
|
|
108
|
+
|
|
109
|
+
#### Example: Creating a Quiz Programmatically with `QuizEditorService`
|
|
110
|
+
|
|
111
|
+
This service allows you to safely manipulate a `QuizConfig` object.
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
import { QuizEditorService, emptyQuiz } from '@thanh01pmt/interactive-quiz-kit';
|
|
115
|
+
import type { MultipleChoiceQuestion } from '@thanh01pmt/interactive-quiz-kit';
|
|
116
|
+
|
|
117
|
+
// Start with an empty quiz config
|
|
118
|
+
const editor = new QuizEditorService(emptyQuiz);
|
|
119
|
+
|
|
120
|
+
// Create a question template
|
|
121
|
+
const mcqTemplate = QuizEditorService.createNewQuestionTemplate('multiple_choice') as MultipleChoiceQuestion;
|
|
122
|
+
|
|
123
|
+
// Populate the question details
|
|
124
|
+
mcqTemplate.prompt = "What is the capital of France?";
|
|
125
|
+
mcqTemplate.options = [
|
|
126
|
+
{ id: 'opt1', text: 'Berlin' },
|
|
127
|
+
{ id: 'opt2', text: 'Paris' },
|
|
128
|
+
{ id: 'opt3', text: 'Madrid' },
|
|
129
|
+
];
|
|
130
|
+
mcqTemplate.correctAnswerId = 'opt2';
|
|
131
|
+
|
|
132
|
+
// Add the question to the quiz
|
|
133
|
+
editor.addQuestion(mcqTemplate);
|
|
134
|
+
|
|
135
|
+
// Get the final, updated quiz configuration
|
|
136
|
+
const finalQuizConfig = editor.getQuiz();
|
|
137
|
+
|
|
138
|
+
// Now you can save `finalQuizConfig` to your database
|
|
139
|
+
console.log(finalQuizConfig);
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
#### Example: Running a Quiz Session with `QuizEngine`
|
|
143
|
+
|
|
144
|
+
This is useful for backend-driven quizzes or for running simulations.
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
import { QuizEngine } from '@thanh01pmt/interactive-quiz-kit';
|
|
148
|
+
import type { QuizConfig, QuizResult } from '@thanh01pmt/interactive-quiz-kit';
|
|
149
|
+
|
|
150
|
+
const myQuizConfig: QuizConfig = /* ... load your quiz config ... */;
|
|
151
|
+
|
|
152
|
+
const engine = new QuizEngine({
|
|
153
|
+
config: myQuizConfig,
|
|
154
|
+
callbacks: {
|
|
155
|
+
onQuizFinish: (result: QuizResult) => {
|
|
156
|
+
console.log(`Quiz finished! Final score: ${result.score}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Simulate answering the first question
|
|
162
|
+
const firstQuestion = engine.getCurrentQuestion();
|
|
163
|
+
if (firstQuestion) {
|
|
164
|
+
engine.submitAnswer(firstQuestion.id, 'id_of_the_user_answer');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Finalize the quiz
|
|
168
|
+
engine.calculateResults();
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
#### Example: Generating a Question with AI
|
|
172
|
+
|
|
173
|
+
Leverage Google Gemini to create quiz content on the fly. *Requires Genkit environment setup.*
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
import { generateTrueFalseQuestion } from '@thanh01pmt/interactive-quiz-kit/ai';
|
|
177
|
+
|
|
178
|
+
async function createNewQuestion() {
|
|
179
|
+
try {
|
|
180
|
+
const { question } = await generateTrueFalseQuestion({
|
|
181
|
+
topic: "The history of the internet",
|
|
182
|
+
difficulty: "medium"
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
if (question) {
|
|
186
|
+
console.log("AI-generated question:", question);
|
|
187
|
+
// Save to your database
|
|
188
|
+
}
|
|
189
|
+
} catch (error) {
|
|
190
|
+
console.error("Failed to generate question:", error);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Supported Question Types
|
|
157
196
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
-
|
|
163
|
-
-
|
|
164
|
-
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
##
|
|
173
|
-
|
|
174
|
-
*
|
|
175
|
-
* **
|
|
176
|
-
* **
|
|
197
|
+
The library supports a wide array of 12+ question types, including:
|
|
198
|
+
- `multiple_choice`
|
|
199
|
+
- `multiple_response`
|
|
200
|
+
- `true_false`
|
|
201
|
+
- `short_answer`
|
|
202
|
+
- `numeric`
|
|
203
|
+
- `fill_in_the_blanks`
|
|
204
|
+
- `sequence`
|
|
205
|
+
- `matching`
|
|
206
|
+
- `drag_and_drop`
|
|
207
|
+
- `hotspot`
|
|
208
|
+
- `blockly_programming`
|
|
209
|
+
- `scratch_programming`
|
|
210
|
+
|
|
211
|
+
## Known Issues
|
|
212
|
+
|
|
213
|
+
* **Blockly/Scratch Integration**: Requires manual copying of assets (`js`, `css`, `media`) from the `blockly` or `scratch-blocks` packages into your project's `public` folder for the UI components to render correctly.
|
|
214
|
+
* **SCORM Packaging**: The `exportQuizAsSCORMZip` function generates a nearly complete package. However, you **must manually** add the library's JavaScript bundle and any necessary CSS (like for Blockly) to the ZIP archive for it to function in an LMS.
|
|
215
|
+
* **AI Generation Limits**: AI generation is not yet supported for `Drag and Drop`, `Hotspot`, `Blockly`, or `Scratch` question types due to their complexity.
|
|
216
|
+
|
|
217
|
+
## 🤝 Contributing
|
|
218
|
+
|
|
219
|
+
Contributions are welcome! Please feel free to submit a pull request or open an issue if you have ideas for improvements or find a bug.
|
|
220
|
+
|
|
221
|
+
1. Fork the repository.
|
|
222
|
+
2. Create your feature branch (`git checkout -b feature/AmazingFeature`).
|
|
223
|
+
3. Commit your changes (`git commit -m 'feat: Add some AmazingFeature'`).
|
|
224
|
+
4. Push to the branch (`git push origin feature/AmazingFeature`).
|
|
225
|
+
5. Open a Pull Request.
|
|
226
|
+
|
|
227
|
+
## 📄 License
|
|
228
|
+
|
|
229
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
package/package.json
CHANGED