Commit ·
5a88b6d
1
Parent(s): 1269f33
Refactor the question card
Browse files- package-lock.json +357 -1
- package.json +9 -5
- src/app/api/chat/route.ts +16 -0
- src/app/api/generate-question/route.ts +124 -0
- src/app/api/generate-quiz/route.ts +36 -0
- src/app/layout.tsx +2 -2
- src/app/page.tsx +313 -97
- src/components/QuestionParameterForm.tsx +210 -0
- src/prompts/quiz-generation.ts +372 -0
- src/types/quiz.ts +19 -0
package-lock.json
CHANGED
|
@@ -8,9 +8,13 @@
|
|
| 8 |
"name": "quizflash",
|
| 9 |
"version": "0.1.0",
|
| 10 |
"dependencies": {
|
|
|
|
|
|
|
|
|
|
| 11 |
"next": "15.4.5",
|
| 12 |
"react": "19.1.0",
|
| 13 |
-
"react-dom": "19.1.0"
|
|
|
|
| 14 |
},
|
| 15 |
"devDependencies": {
|
| 16 |
"@eslint/eslintrc": "^3",
|
|
@@ -24,6 +28,109 @@
|
|
| 24 |
"typescript": "^5"
|
| 25 |
}
|
| 26 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
"node_modules/@alloc/quick-lru": {
|
| 28 |
"version": "5.2.0",
|
| 29 |
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
|
|
@@ -634,6 +741,14 @@
|
|
| 634 |
"node": ">=12.4.0"
|
| 635 |
}
|
| 636 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 637 |
"node_modules/@rtsao/scc": {
|
| 638 |
"version": "1.1.0",
|
| 639 |
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/@rtsao/scc/-/scc-1.1.0.tgz",
|
|
@@ -646,6 +761,11 @@
|
|
| 646 |
"integrity": "sha512-5EwMtOqvJMMa3HbmxLlF74e+3/HhwBTMcvt3nqVJgGCozO6hzIPOBlwm8mGVNR9SN2IJpxSnlxczyDjcn7qIyw==",
|
| 647 |
"dev": true
|
| 648 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 649 |
"node_modules/@swc/helpers": {
|
| 650 |
"version": "0.5.15",
|
| 651 |
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/@swc/helpers/-/helpers-0.5.15.tgz",
|
|
@@ -1351,6 +1471,55 @@
|
|
| 1351 |
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
| 1352 |
}
|
| 1353 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1354 |
"node_modules/ajv": {
|
| 1355 |
"version": "6.12.6",
|
| 1356 |
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/ajv/-/ajv-6.12.6.tgz",
|
|
@@ -1842,6 +2011,14 @@
|
|
| 1842 |
"node": ">= 0.4"
|
| 1843 |
}
|
| 1844 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1845 |
"node_modules/detect-libc": {
|
| 1846 |
"version": "2.0.4",
|
| 1847 |
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/detect-libc/-/detect-libc-2.0.4.tgz",
|
|
@@ -2454,6 +2631,14 @@
|
|
| 2454 |
"node": ">=0.10.0"
|
| 2455 |
}
|
| 2456 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2457 |
"node_modules/fast-deep-equal": {
|
| 2458 |
"version": "3.1.3",
|
| 2459 |
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
|
@@ -3212,6 +3397,11 @@
|
|
| 3212 |
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
|
| 3213 |
"dev": true
|
| 3214 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3215 |
"node_modules/json-schema-traverse": {
|
| 3216 |
"version": "0.4.1",
|
| 3217 |
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
|
@@ -4463,6 +4653,14 @@
|
|
| 4463 |
"node": ">=18"
|
| 4464 |
}
|
| 4465 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4466 |
"node_modules/tinyglobby": {
|
| 4467 |
"version": "0.2.14",
|
| 4468 |
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/tinyglobby/-/tinyglobby-0.2.14.tgz",
|
|
@@ -4805,9 +5003,93 @@
|
|
| 4805 |
"engines": {
|
| 4806 |
"node": ">=10"
|
| 4807 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4808 |
}
|
| 4809 |
},
|
| 4810 |
"dependencies": {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4811 |
"@alloc/quick-lru": {
|
| 4812 |
"version": "5.2.0",
|
| 4813 |
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
|
|
@@ -5287,6 +5569,11 @@
|
|
| 5287 |
"integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==",
|
| 5288 |
"dev": true
|
| 5289 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5290 |
"@rtsao/scc": {
|
| 5291 |
"version": "1.1.0",
|
| 5292 |
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/@rtsao/scc/-/scc-1.1.0.tgz",
|
|
@@ -5299,6 +5586,11 @@
|
|
| 5299 |
"integrity": "sha512-5EwMtOqvJMMa3HbmxLlF74e+3/HhwBTMcvt3nqVJgGCozO6hzIPOBlwm8mGVNR9SN2IJpxSnlxczyDjcn7qIyw==",
|
| 5300 |
"dev": true
|
| 5301 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5302 |
"@swc/helpers": {
|
| 5303 |
"version": "0.5.15",
|
| 5304 |
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/@swc/helpers/-/helpers-0.5.15.tgz",
|
|
@@ -5874,6 +6166,39 @@
|
|
| 5874 |
"dev": true,
|
| 5875 |
"requires": {}
|
| 5876 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5877 |
"ajv": {
|
| 5878 |
"version": "6.12.6",
|
| 5879 |
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/ajv/-/ajv-6.12.6.tgz",
|
|
@@ -6270,6 +6595,11 @@
|
|
| 6270 |
"object-keys": "^1.1.1"
|
| 6271 |
}
|
| 6272 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6273 |
"detect-libc": {
|
| 6274 |
"version": "2.0.4",
|
| 6275 |
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/detect-libc/-/detect-libc-2.0.4.tgz",
|
|
@@ -6751,6 +7081,11 @@
|
|
| 6751 |
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
|
| 6752 |
"dev": true
|
| 6753 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6754 |
"fast-deep-equal": {
|
| 6755 |
"version": "3.1.3",
|
| 6756 |
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
|
@@ -7352,6 +7687,11 @@
|
|
| 7352 |
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
|
| 7353 |
"dev": true
|
| 7354 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7355 |
"json-schema-traverse": {
|
| 7356 |
"version": "0.4.1",
|
| 7357 |
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
|
@@ -8297,6 +8637,11 @@
|
|
| 8297 |
"yallist": "^5.0.0"
|
| 8298 |
}
|
| 8299 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8300 |
"tinyglobby": {
|
| 8301 |
"version": "0.2.14",
|
| 8302 |
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/tinyglobby/-/tinyglobby-0.2.14.tgz",
|
|
@@ -8565,6 +8910,17 @@
|
|
| 8565 |
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
| 8566 |
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
|
| 8567 |
"dev": true
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8568 |
}
|
| 8569 |
}
|
| 8570 |
}
|
|
|
|
| 8 |
"name": "quizflash",
|
| 9 |
"version": "0.1.0",
|
| 10 |
"dependencies": {
|
| 11 |
+
"@ai-sdk/openai": "^2.0.0",
|
| 12 |
+
"@ai-sdk/react": "^2.0.1",
|
| 13 |
+
"ai": "^5.0.1",
|
| 14 |
"next": "15.4.5",
|
| 15 |
"react": "19.1.0",
|
| 16 |
+
"react-dom": "19.1.0",
|
| 17 |
+
"zod": "^3.25.76"
|
| 18 |
},
|
| 19 |
"devDependencies": {
|
| 20 |
"@eslint/eslintrc": "^3",
|
|
|
|
| 28 |
"typescript": "^5"
|
| 29 |
}
|
| 30 |
},
|
| 31 |
+
"node_modules/@ai-sdk/openai": {
|
| 32 |
+
"version": "2.0.0",
|
| 33 |
+
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/@ai-sdk/openai/-/openai-2.0.0.tgz",
|
| 34 |
+
"integrity": "sha512-G0WY5K81JwGpuX9HEmP2VTdt3N9m43qPnGT4fWkXcpu6Y2B05nnjs8k1r/csCJd8+TkYC6esjBABQYHxdMOejQ==",
|
| 35 |
+
"dependencies": {
|
| 36 |
+
"@ai-sdk/provider": "2.0.0",
|
| 37 |
+
"@ai-sdk/provider-utils": "3.0.0"
|
| 38 |
+
},
|
| 39 |
+
"engines": {
|
| 40 |
+
"node": ">=18"
|
| 41 |
+
},
|
| 42 |
+
"peerDependencies": {
|
| 43 |
+
"zod": "^3.25.76 || ^4"
|
| 44 |
+
}
|
| 45 |
+
},
|
| 46 |
+
"node_modules/@ai-sdk/openai/node_modules/@ai-sdk/provider-utils": {
|
| 47 |
+
"version": "3.0.0",
|
| 48 |
+
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/@ai-sdk/provider-utils/-/provider-utils-3.0.0.tgz",
|
| 49 |
+
"integrity": "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw==",
|
| 50 |
+
"dependencies": {
|
| 51 |
+
"@ai-sdk/provider": "2.0.0",
|
| 52 |
+
"@standard-schema/spec": "^1.0.0",
|
| 53 |
+
"eventsource-parser": "^3.0.3",
|
| 54 |
+
"zod-to-json-schema": "^3.24.1"
|
| 55 |
+
},
|
| 56 |
+
"engines": {
|
| 57 |
+
"node": ">=18"
|
| 58 |
+
},
|
| 59 |
+
"peerDependencies": {
|
| 60 |
+
"zod": "^3.25.76 || ^4"
|
| 61 |
+
}
|
| 62 |
+
},
|
| 63 |
+
"node_modules/@ai-sdk/provider": {
|
| 64 |
+
"version": "2.0.0",
|
| 65 |
+
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/@ai-sdk/provider/-/provider-2.0.0.tgz",
|
| 66 |
+
"integrity": "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==",
|
| 67 |
+
"dependencies": {
|
| 68 |
+
"json-schema": "^0.4.0"
|
| 69 |
+
},
|
| 70 |
+
"engines": {
|
| 71 |
+
"node": ">=18"
|
| 72 |
+
}
|
| 73 |
+
},
|
| 74 |
+
"node_modules/@ai-sdk/react": {
|
| 75 |
+
"version": "2.0.1",
|
| 76 |
+
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/@ai-sdk/react/-/react-2.0.1.tgz",
|
| 77 |
+
"integrity": "sha512-eq2ABukavfeD2mK0ovGrg8hBVynVJ7P42KnfGBScooef0264TLicM6Hv8yahOzwl/kUTBJlot8jYe3t3rWs8lg==",
|
| 78 |
+
"dependencies": {
|
| 79 |
+
"@ai-sdk/provider-utils": "3.0.0",
|
| 80 |
+
"ai": "5.0.1",
|
| 81 |
+
"swr": "^2.2.5",
|
| 82 |
+
"throttleit": "2.1.0"
|
| 83 |
+
},
|
| 84 |
+
"engines": {
|
| 85 |
+
"node": ">=18"
|
| 86 |
+
},
|
| 87 |
+
"peerDependencies": {
|
| 88 |
+
"react": "^18 || ^19 || ^19.0.0-rc",
|
| 89 |
+
"zod": "^3.25.76 || ^4"
|
| 90 |
+
},
|
| 91 |
+
"peerDependenciesMeta": {
|
| 92 |
+
"zod": {
|
| 93 |
+
"optional": true
|
| 94 |
+
}
|
| 95 |
+
}
|
| 96 |
+
},
|
| 97 |
+
"node_modules/@ai-sdk/react/node_modules/@ai-sdk/provider-utils": {
|
| 98 |
+
"version": "3.0.0",
|
| 99 |
+
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/@ai-sdk/provider-utils/-/provider-utils-3.0.0.tgz",
|
| 100 |
+
"integrity": "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw==",
|
| 101 |
+
"dependencies": {
|
| 102 |
+
"@ai-sdk/provider": "2.0.0",
|
| 103 |
+
"@standard-schema/spec": "^1.0.0",
|
| 104 |
+
"eventsource-parser": "^3.0.3",
|
| 105 |
+
"zod-to-json-schema": "^3.24.1"
|
| 106 |
+
},
|
| 107 |
+
"engines": {
|
| 108 |
+
"node": ">=18"
|
| 109 |
+
},
|
| 110 |
+
"peerDependencies": {
|
| 111 |
+
"zod": "^3.25.76 || ^4"
|
| 112 |
+
}
|
| 113 |
+
},
|
| 114 |
+
"node_modules/@ai-sdk/react/node_modules/swr": {
|
| 115 |
+
"version": "2.3.4",
|
| 116 |
+
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/swr/-/swr-2.3.4.tgz",
|
| 117 |
+
"integrity": "sha512-bYd2lrhc+VarcpkgWclcUi92wYCpOgMws9Sd1hG1ntAu0NEy+14CbotuFjshBU2kt9rYj9TSmDcybpxpeTU1fg==",
|
| 118 |
+
"dependencies": {
|
| 119 |
+
"dequal": "^2.0.3",
|
| 120 |
+
"use-sync-external-store": "^1.4.0"
|
| 121 |
+
},
|
| 122 |
+
"peerDependencies": {
|
| 123 |
+
"react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
| 124 |
+
}
|
| 125 |
+
},
|
| 126 |
+
"node_modules/@ai-sdk/react/node_modules/swr/node_modules/use-sync-external-store": {
|
| 127 |
+
"version": "1.5.0",
|
| 128 |
+
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
|
| 129 |
+
"integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==",
|
| 130 |
+
"peerDependencies": {
|
| 131 |
+
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
| 132 |
+
}
|
| 133 |
+
},
|
| 134 |
"node_modules/@alloc/quick-lru": {
|
| 135 |
"version": "5.2.0",
|
| 136 |
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
|
|
|
|
| 741 |
"node": ">=12.4.0"
|
| 742 |
}
|
| 743 |
},
|
| 744 |
+
"node_modules/@opentelemetry/api": {
|
| 745 |
+
"version": "1.9.0",
|
| 746 |
+
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/@opentelemetry/api/-/api-1.9.0.tgz",
|
| 747 |
+
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
|
| 748 |
+
"engines": {
|
| 749 |
+
"node": ">=8.0.0"
|
| 750 |
+
}
|
| 751 |
+
},
|
| 752 |
"node_modules/@rtsao/scc": {
|
| 753 |
"version": "1.1.0",
|
| 754 |
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/@rtsao/scc/-/scc-1.1.0.tgz",
|
|
|
|
| 761 |
"integrity": "sha512-5EwMtOqvJMMa3HbmxLlF74e+3/HhwBTMcvt3nqVJgGCozO6hzIPOBlwm8mGVNR9SN2IJpxSnlxczyDjcn7qIyw==",
|
| 762 |
"dev": true
|
| 763 |
},
|
| 764 |
+
"node_modules/@standard-schema/spec": {
|
| 765 |
+
"version": "1.0.0",
|
| 766 |
+
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/@standard-schema/spec/-/spec-1.0.0.tgz",
|
| 767 |
+
"integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="
|
| 768 |
+
},
|
| 769 |
"node_modules/@swc/helpers": {
|
| 770 |
"version": "0.5.15",
|
| 771 |
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/@swc/helpers/-/helpers-0.5.15.tgz",
|
|
|
|
| 1471 |
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
| 1472 |
}
|
| 1473 |
},
|
| 1474 |
+
"node_modules/ai": {
|
| 1475 |
+
"version": "5.0.1",
|
| 1476 |
+
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/ai/-/ai-5.0.1.tgz",
|
| 1477 |
+
"integrity": "sha512-h3bGaysgeD0N/jFnCwvtKP8oyhv0Put8KtouYl8k03oq822rlsNxz58/LWF1FZt+0DUU77vdq47B6P94+ETwzw==",
|
| 1478 |
+
"dependencies": {
|
| 1479 |
+
"@ai-sdk/gateway": "1.0.0",
|
| 1480 |
+
"@ai-sdk/provider": "2.0.0",
|
| 1481 |
+
"@ai-sdk/provider-utils": "3.0.0",
|
| 1482 |
+
"@opentelemetry/api": "1.9.0"
|
| 1483 |
+
},
|
| 1484 |
+
"engines": {
|
| 1485 |
+
"node": ">=18"
|
| 1486 |
+
},
|
| 1487 |
+
"peerDependencies": {
|
| 1488 |
+
"zod": "^3.25.76 || ^4"
|
| 1489 |
+
}
|
| 1490 |
+
},
|
| 1491 |
+
"node_modules/ai/node_modules/@ai-sdk/gateway": {
|
| 1492 |
+
"version": "1.0.0",
|
| 1493 |
+
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/@ai-sdk/gateway/-/gateway-1.0.0.tgz",
|
| 1494 |
+
"integrity": "sha512-VEm87DyRx1yIPywbTy8ntoyh4jEDv1rJ88m+2I7zOm08jJI5BhFtAWh0OF6YzZu1Vu4NxhOWO4ssGdsqydDQ3A==",
|
| 1495 |
+
"dependencies": {
|
| 1496 |
+
"@ai-sdk/provider": "2.0.0",
|
| 1497 |
+
"@ai-sdk/provider-utils": "3.0.0"
|
| 1498 |
+
},
|
| 1499 |
+
"engines": {
|
| 1500 |
+
"node": ">=18"
|
| 1501 |
+
},
|
| 1502 |
+
"peerDependencies": {
|
| 1503 |
+
"zod": "^3.25.76 || ^4"
|
| 1504 |
+
}
|
| 1505 |
+
},
|
| 1506 |
+
"node_modules/ai/node_modules/@ai-sdk/provider-utils": {
|
| 1507 |
+
"version": "3.0.0",
|
| 1508 |
+
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/@ai-sdk/provider-utils/-/provider-utils-3.0.0.tgz",
|
| 1509 |
+
"integrity": "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw==",
|
| 1510 |
+
"dependencies": {
|
| 1511 |
+
"@ai-sdk/provider": "2.0.0",
|
| 1512 |
+
"@standard-schema/spec": "^1.0.0",
|
| 1513 |
+
"eventsource-parser": "^3.0.3",
|
| 1514 |
+
"zod-to-json-schema": "^3.24.1"
|
| 1515 |
+
},
|
| 1516 |
+
"engines": {
|
| 1517 |
+
"node": ">=18"
|
| 1518 |
+
},
|
| 1519 |
+
"peerDependencies": {
|
| 1520 |
+
"zod": "^3.25.76 || ^4"
|
| 1521 |
+
}
|
| 1522 |
+
},
|
| 1523 |
"node_modules/ajv": {
|
| 1524 |
"version": "6.12.6",
|
| 1525 |
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/ajv/-/ajv-6.12.6.tgz",
|
|
|
|
| 2011 |
"node": ">= 0.4"
|
| 2012 |
}
|
| 2013 |
},
|
| 2014 |
+
"node_modules/dequal": {
|
| 2015 |
+
"version": "2.0.3",
|
| 2016 |
+
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/dequal/-/dequal-2.0.3.tgz",
|
| 2017 |
+
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
|
| 2018 |
+
"engines": {
|
| 2019 |
+
"node": ">=6"
|
| 2020 |
+
}
|
| 2021 |
+
},
|
| 2022 |
"node_modules/detect-libc": {
|
| 2023 |
"version": "2.0.4",
|
| 2024 |
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/detect-libc/-/detect-libc-2.0.4.tgz",
|
|
|
|
| 2631 |
"node": ">=0.10.0"
|
| 2632 |
}
|
| 2633 |
},
|
| 2634 |
+
"node_modules/eventsource-parser": {
|
| 2635 |
+
"version": "3.0.3",
|
| 2636 |
+
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/eventsource-parser/-/eventsource-parser-3.0.3.tgz",
|
| 2637 |
+
"integrity": "sha512-nVpZkTMM9rF6AQ9gPJpFsNAMt48wIzB5TQgiTLdHiuO8XEDhUgZEhqKlZWXbIzo9VmJ/HvysHqEaVeD5v9TPvA==",
|
| 2638 |
+
"engines": {
|
| 2639 |
+
"node": ">=20.0.0"
|
| 2640 |
+
}
|
| 2641 |
+
},
|
| 2642 |
"node_modules/fast-deep-equal": {
|
| 2643 |
"version": "3.1.3",
|
| 2644 |
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
|
|
|
| 3397 |
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
|
| 3398 |
"dev": true
|
| 3399 |
},
|
| 3400 |
+
"node_modules/json-schema": {
|
| 3401 |
+
"version": "0.4.0",
|
| 3402 |
+
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/json-schema/-/json-schema-0.4.0.tgz",
|
| 3403 |
+
"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="
|
| 3404 |
+
},
|
| 3405 |
"node_modules/json-schema-traverse": {
|
| 3406 |
"version": "0.4.1",
|
| 3407 |
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
|
|
|
| 4653 |
"node": ">=18"
|
| 4654 |
}
|
| 4655 |
},
|
| 4656 |
+
"node_modules/throttleit": {
|
| 4657 |
+
"version": "2.1.0",
|
| 4658 |
+
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/throttleit/-/throttleit-2.1.0.tgz",
|
| 4659 |
+
"integrity": "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==",
|
| 4660 |
+
"engines": {
|
| 4661 |
+
"node": ">=18"
|
| 4662 |
+
}
|
| 4663 |
+
},
|
| 4664 |
"node_modules/tinyglobby": {
|
| 4665 |
"version": "0.2.14",
|
| 4666 |
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/tinyglobby/-/tinyglobby-0.2.14.tgz",
|
|
|
|
| 5003 |
"engines": {
|
| 5004 |
"node": ">=10"
|
| 5005 |
}
|
| 5006 |
+
},
|
| 5007 |
+
"node_modules/zod": {
|
| 5008 |
+
"version": "3.25.76",
|
| 5009 |
+
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/zod/-/zod-3.25.76.tgz",
|
| 5010 |
+
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="
|
| 5011 |
+
},
|
| 5012 |
+
"node_modules/zod-to-json-schema": {
|
| 5013 |
+
"version": "3.24.6",
|
| 5014 |
+
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz",
|
| 5015 |
+
"integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==",
|
| 5016 |
+
"peerDependencies": {
|
| 5017 |
+
"zod": "^3.24.1"
|
| 5018 |
+
}
|
| 5019 |
}
|
| 5020 |
},
|
| 5021 |
"dependencies": {
|
| 5022 |
+
"@ai-sdk/openai": {
|
| 5023 |
+
"version": "2.0.0",
|
| 5024 |
+
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/@ai-sdk/openai/-/openai-2.0.0.tgz",
|
| 5025 |
+
"integrity": "sha512-G0WY5K81JwGpuX9HEmP2VTdt3N9m43qPnGT4fWkXcpu6Y2B05nnjs8k1r/csCJd8+TkYC6esjBABQYHxdMOejQ==",
|
| 5026 |
+
"requires": {
|
| 5027 |
+
"@ai-sdk/provider": "2.0.0",
|
| 5028 |
+
"@ai-sdk/provider-utils": "3.0.0"
|
| 5029 |
+
},
|
| 5030 |
+
"dependencies": {
|
| 5031 |
+
"@ai-sdk/provider-utils": {
|
| 5032 |
+
"version": "3.0.0",
|
| 5033 |
+
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/@ai-sdk/provider-utils/-/provider-utils-3.0.0.tgz",
|
| 5034 |
+
"integrity": "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw==",
|
| 5035 |
+
"requires": {
|
| 5036 |
+
"@ai-sdk/provider": "2.0.0",
|
| 5037 |
+
"@standard-schema/spec": "^1.0.0",
|
| 5038 |
+
"eventsource-parser": "^3.0.3",
|
| 5039 |
+
"zod-to-json-schema": "^3.24.1"
|
| 5040 |
+
}
|
| 5041 |
+
}
|
| 5042 |
+
}
|
| 5043 |
+
},
|
| 5044 |
+
"@ai-sdk/provider": {
|
| 5045 |
+
"version": "2.0.0",
|
| 5046 |
+
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/@ai-sdk/provider/-/provider-2.0.0.tgz",
|
| 5047 |
+
"integrity": "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==",
|
| 5048 |
+
"requires": {
|
| 5049 |
+
"json-schema": "^0.4.0"
|
| 5050 |
+
}
|
| 5051 |
+
},
|
| 5052 |
+
"@ai-sdk/react": {
|
| 5053 |
+
"version": "2.0.1",
|
| 5054 |
+
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/@ai-sdk/react/-/react-2.0.1.tgz",
|
| 5055 |
+
"integrity": "sha512-eq2ABukavfeD2mK0ovGrg8hBVynVJ7P42KnfGBScooef0264TLicM6Hv8yahOzwl/kUTBJlot8jYe3t3rWs8lg==",
|
| 5056 |
+
"requires": {
|
| 5057 |
+
"@ai-sdk/provider-utils": "3.0.0",
|
| 5058 |
+
"ai": "5.0.1",
|
| 5059 |
+
"swr": "^2.2.5",
|
| 5060 |
+
"throttleit": "2.1.0"
|
| 5061 |
+
},
|
| 5062 |
+
"dependencies": {
|
| 5063 |
+
"@ai-sdk/provider-utils": {
|
| 5064 |
+
"version": "3.0.0",
|
| 5065 |
+
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/@ai-sdk/provider-utils/-/provider-utils-3.0.0.tgz",
|
| 5066 |
+
"integrity": "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw==",
|
| 5067 |
+
"requires": {
|
| 5068 |
+
"@ai-sdk/provider": "2.0.0",
|
| 5069 |
+
"@standard-schema/spec": "^1.0.0",
|
| 5070 |
+
"eventsource-parser": "^3.0.3",
|
| 5071 |
+
"zod-to-json-schema": "^3.24.1"
|
| 5072 |
+
}
|
| 5073 |
+
},
|
| 5074 |
+
"swr": {
|
| 5075 |
+
"version": "2.3.4",
|
| 5076 |
+
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/swr/-/swr-2.3.4.tgz",
|
| 5077 |
+
"integrity": "sha512-bYd2lrhc+VarcpkgWclcUi92wYCpOgMws9Sd1hG1ntAu0NEy+14CbotuFjshBU2kt9rYj9TSmDcybpxpeTU1fg==",
|
| 5078 |
+
"requires": {
|
| 5079 |
+
"dequal": "^2.0.3",
|
| 5080 |
+
"use-sync-external-store": "^1.4.0"
|
| 5081 |
+
},
|
| 5082 |
+
"dependencies": {
|
| 5083 |
+
"use-sync-external-store": {
|
| 5084 |
+
"version": "1.5.0",
|
| 5085 |
+
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
|
| 5086 |
+
"integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==",
|
| 5087 |
+
"requires": {}
|
| 5088 |
+
}
|
| 5089 |
+
}
|
| 5090 |
+
}
|
| 5091 |
+
}
|
| 5092 |
+
},
|
| 5093 |
"@alloc/quick-lru": {
|
| 5094 |
"version": "5.2.0",
|
| 5095 |
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
|
|
|
|
| 5569 |
"integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==",
|
| 5570 |
"dev": true
|
| 5571 |
},
|
| 5572 |
+
"@opentelemetry/api": {
|
| 5573 |
+
"version": "1.9.0",
|
| 5574 |
+
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/@opentelemetry/api/-/api-1.9.0.tgz",
|
| 5575 |
+
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="
|
| 5576 |
+
},
|
| 5577 |
"@rtsao/scc": {
|
| 5578 |
"version": "1.1.0",
|
| 5579 |
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/@rtsao/scc/-/scc-1.1.0.tgz",
|
|
|
|
| 5586 |
"integrity": "sha512-5EwMtOqvJMMa3HbmxLlF74e+3/HhwBTMcvt3nqVJgGCozO6hzIPOBlwm8mGVNR9SN2IJpxSnlxczyDjcn7qIyw==",
|
| 5587 |
"dev": true
|
| 5588 |
},
|
| 5589 |
+
"@standard-schema/spec": {
|
| 5590 |
+
"version": "1.0.0",
|
| 5591 |
+
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/@standard-schema/spec/-/spec-1.0.0.tgz",
|
| 5592 |
+
"integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="
|
| 5593 |
+
},
|
| 5594 |
"@swc/helpers": {
|
| 5595 |
"version": "0.5.15",
|
| 5596 |
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/@swc/helpers/-/helpers-0.5.15.tgz",
|
|
|
|
| 6166 |
"dev": true,
|
| 6167 |
"requires": {}
|
| 6168 |
},
|
| 6169 |
+
"ai": {
|
| 6170 |
+
"version": "5.0.1",
|
| 6171 |
+
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/ai/-/ai-5.0.1.tgz",
|
| 6172 |
+
"integrity": "sha512-h3bGaysgeD0N/jFnCwvtKP8oyhv0Put8KtouYl8k03oq822rlsNxz58/LWF1FZt+0DUU77vdq47B6P94+ETwzw==",
|
| 6173 |
+
"requires": {
|
| 6174 |
+
"@ai-sdk/gateway": "1.0.0",
|
| 6175 |
+
"@ai-sdk/provider": "2.0.0",
|
| 6176 |
+
"@ai-sdk/provider-utils": "3.0.0",
|
| 6177 |
+
"@opentelemetry/api": "1.9.0"
|
| 6178 |
+
},
|
| 6179 |
+
"dependencies": {
|
| 6180 |
+
"@ai-sdk/gateway": {
|
| 6181 |
+
"version": "1.0.0",
|
| 6182 |
+
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/@ai-sdk/gateway/-/gateway-1.0.0.tgz",
|
| 6183 |
+
"integrity": "sha512-VEm87DyRx1yIPywbTy8ntoyh4jEDv1rJ88m+2I7zOm08jJI5BhFtAWh0OF6YzZu1Vu4NxhOWO4ssGdsqydDQ3A==",
|
| 6184 |
+
"requires": {
|
| 6185 |
+
"@ai-sdk/provider": "2.0.0",
|
| 6186 |
+
"@ai-sdk/provider-utils": "3.0.0"
|
| 6187 |
+
}
|
| 6188 |
+
},
|
| 6189 |
+
"@ai-sdk/provider-utils": {
|
| 6190 |
+
"version": "3.0.0",
|
| 6191 |
+
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/@ai-sdk/provider-utils/-/provider-utils-3.0.0.tgz",
|
| 6192 |
+
"integrity": "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw==",
|
| 6193 |
+
"requires": {
|
| 6194 |
+
"@ai-sdk/provider": "2.0.0",
|
| 6195 |
+
"@standard-schema/spec": "^1.0.0",
|
| 6196 |
+
"eventsource-parser": "^3.0.3",
|
| 6197 |
+
"zod-to-json-schema": "^3.24.1"
|
| 6198 |
+
}
|
| 6199 |
+
}
|
| 6200 |
+
}
|
| 6201 |
+
},
|
| 6202 |
"ajv": {
|
| 6203 |
"version": "6.12.6",
|
| 6204 |
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/ajv/-/ajv-6.12.6.tgz",
|
|
|
|
| 6595 |
"object-keys": "^1.1.1"
|
| 6596 |
}
|
| 6597 |
},
|
| 6598 |
+
"dequal": {
|
| 6599 |
+
"version": "2.0.3",
|
| 6600 |
+
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/dequal/-/dequal-2.0.3.tgz",
|
| 6601 |
+
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="
|
| 6602 |
+
},
|
| 6603 |
"detect-libc": {
|
| 6604 |
"version": "2.0.4",
|
| 6605 |
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/detect-libc/-/detect-libc-2.0.4.tgz",
|
|
|
|
| 7081 |
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
|
| 7082 |
"dev": true
|
| 7083 |
},
|
| 7084 |
+
"eventsource-parser": {
|
| 7085 |
+
"version": "3.0.3",
|
| 7086 |
+
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/eventsource-parser/-/eventsource-parser-3.0.3.tgz",
|
| 7087 |
+
"integrity": "sha512-nVpZkTMM9rF6AQ9gPJpFsNAMt48wIzB5TQgiTLdHiuO8XEDhUgZEhqKlZWXbIzo9VmJ/HvysHqEaVeD5v9TPvA=="
|
| 7088 |
+
},
|
| 7089 |
"fast-deep-equal": {
|
| 7090 |
"version": "3.1.3",
|
| 7091 |
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
|
|
|
| 7687 |
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
|
| 7688 |
"dev": true
|
| 7689 |
},
|
| 7690 |
+
"json-schema": {
|
| 7691 |
+
"version": "0.4.0",
|
| 7692 |
+
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/json-schema/-/json-schema-0.4.0.tgz",
|
| 7693 |
+
"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="
|
| 7694 |
+
},
|
| 7695 |
"json-schema-traverse": {
|
| 7696 |
"version": "0.4.1",
|
| 7697 |
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
|
|
|
| 8637 |
"yallist": "^5.0.0"
|
| 8638 |
}
|
| 8639 |
},
|
| 8640 |
+
"throttleit": {
|
| 8641 |
+
"version": "2.1.0",
|
| 8642 |
+
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/throttleit/-/throttleit-2.1.0.tgz",
|
| 8643 |
+
"integrity": "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw=="
|
| 8644 |
+
},
|
| 8645 |
"tinyglobby": {
|
| 8646 |
"version": "0.2.14",
|
| 8647 |
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/tinyglobby/-/tinyglobby-0.2.14.tgz",
|
|
|
|
| 8910 |
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
| 8911 |
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
|
| 8912 |
"dev": true
|
| 8913 |
+
},
|
| 8914 |
+
"zod": {
|
| 8915 |
+
"version": "3.25.76",
|
| 8916 |
+
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/zod/-/zod-3.25.76.tgz",
|
| 8917 |
+
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="
|
| 8918 |
+
},
|
| 8919 |
+
"zod-to-json-schema": {
|
| 8920 |
+
"version": "3.24.6",
|
| 8921 |
+
"resolved": "http://artifactory-build.taboolasyndication.com:80/artifactory/api/npm/npm-public/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz",
|
| 8922 |
+
"integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==",
|
| 8923 |
+
"requires": {}
|
| 8924 |
}
|
| 8925 |
}
|
| 8926 |
}
|
package.json
CHANGED
|
@@ -9,19 +9,23 @@
|
|
| 9 |
"lint": "next lint"
|
| 10 |
},
|
| 11 |
"dependencies": {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
"react": "19.1.0",
|
| 13 |
"react-dom": "19.1.0",
|
| 14 |
-
"
|
| 15 |
},
|
| 16 |
"devDependencies": {
|
| 17 |
-
"
|
|
|
|
| 18 |
"@types/node": "^20",
|
| 19 |
"@types/react": "^19",
|
| 20 |
"@types/react-dom": "^19",
|
| 21 |
-
"@tailwindcss/postcss": "^4",
|
| 22 |
-
"tailwindcss": "^4",
|
| 23 |
"eslint": "^9",
|
| 24 |
"eslint-config-next": "15.4.5",
|
| 25 |
-
"
|
|
|
|
| 26 |
}
|
| 27 |
}
|
|
|
|
| 9 |
"lint": "next lint"
|
| 10 |
},
|
| 11 |
"dependencies": {
|
| 12 |
+
"@ai-sdk/openai": "^2.0.0",
|
| 13 |
+
"@ai-sdk/react": "^2.0.1",
|
| 14 |
+
"ai": "^5.0.1",
|
| 15 |
+
"next": "15.4.5",
|
| 16 |
"react": "19.1.0",
|
| 17 |
"react-dom": "19.1.0",
|
| 18 |
+
"zod": "^3.25.76"
|
| 19 |
},
|
| 20 |
"devDependencies": {
|
| 21 |
+
"@eslint/eslintrc": "^3",
|
| 22 |
+
"@tailwindcss/postcss": "^4",
|
| 23 |
"@types/node": "^20",
|
| 24 |
"@types/react": "^19",
|
| 25 |
"@types/react-dom": "^19",
|
|
|
|
|
|
|
| 26 |
"eslint": "^9",
|
| 27 |
"eslint-config-next": "15.4.5",
|
| 28 |
+
"tailwindcss": "^4",
|
| 29 |
+
"typescript": "^5"
|
| 30 |
}
|
| 31 |
}
|
src/app/api/chat/route.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { openai } from '@ai-sdk/openai';
|
| 2 |
+
import { streamText } from 'ai';
|
| 3 |
+
|
| 4 |
+
// Allow streaming responses up to 30 seconds
|
| 5 |
+
export const maxDuration = 30;
|
| 6 |
+
|
| 7 |
+
export async function POST(req: Request) {
|
| 8 |
+
const { messages } = await req.json();
|
| 9 |
+
|
| 10 |
+
const result = await streamText({
|
| 11 |
+
model: openai('gpt-4o-mini'),
|
| 12 |
+
messages,
|
| 13 |
+
});
|
| 14 |
+
|
| 15 |
+
return result.toDataStreamResponse();
|
| 16 |
+
}
|
src/app/api/generate-question/route.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { openai } from '@ai-sdk/openai';
|
| 2 |
+
import { generateObject } from 'ai';
|
| 3 |
+
import { z } from 'zod';
|
| 4 |
+
|
| 5 |
+
// Schemas for different question types
|
| 6 |
+
const MultipleChoiceSchema = z.object({
|
| 7 |
+
stem: z.string(),
|
| 8 |
+
options: z.array(z.string()),
|
| 9 |
+
correctAnswer: z.number(),
|
| 10 |
+
explanation: z.string(),
|
| 11 |
+
points: z.number().default(1),
|
| 12 |
+
});
|
| 13 |
+
|
| 14 |
+
const ClozeSchema = z.object({
|
| 15 |
+
stem: z.string(),
|
| 16 |
+
blanks: z.array(z.object({
|
| 17 |
+
position: z.number(),
|
| 18 |
+
answer: z.string(),
|
| 19 |
+
alternatives: z.array(z.string()).optional(),
|
| 20 |
+
})),
|
| 21 |
+
explanation: z.string(),
|
| 22 |
+
points: z.number().default(1),
|
| 23 |
+
});
|
| 24 |
+
|
| 25 |
+
const GrammarSchema = z.object({
|
| 26 |
+
stem: z.string(),
|
| 27 |
+
incorrectSentence: z.string(),
|
| 28 |
+
correctSentence: z.string(),
|
| 29 |
+
errors: z.array(z.object({
|
| 30 |
+
type: z.string(),
|
| 31 |
+
position: z.number(),
|
| 32 |
+
correction: z.string(),
|
| 33 |
+
})),
|
| 34 |
+
explanation: z.string(),
|
| 35 |
+
points: z.number().default(1),
|
| 36 |
+
});
|
| 37 |
+
|
| 38 |
+
const EssaySchema = z.object({
|
| 39 |
+
stem: z.string(),
|
| 40 |
+
prompt: z.string(),
|
| 41 |
+
rubric: z.array(z.object({
|
| 42 |
+
criterion: z.string(),
|
| 43 |
+
description: z.string(),
|
| 44 |
+
points: z.number(),
|
| 45 |
+
})),
|
| 46 |
+
sampleAnswer: z.string().optional(),
|
| 47 |
+
points: z.number().default(5),
|
| 48 |
+
});
|
| 49 |
+
|
| 50 |
+
export async function POST(req: Request) {
|
| 51 |
+
try {
|
| 52 |
+
const { type, parameters, sourceMaterial } = await req.json();
|
| 53 |
+
|
| 54 |
+
let schema;
|
| 55 |
+
let prompt = '';
|
| 56 |
+
|
| 57 |
+
switch (type) {
|
| 58 |
+
case 'multiple-choice':
|
| 59 |
+
schema = MultipleChoiceSchema;
|
| 60 |
+
prompt = `Create a ${parameters.difficulty || 'intermediate'} level multiple choice question with ${parameters.numOptions || 4} options.
|
| 61 |
+
${sourceMaterial ? `Base the question on this source material: "${sourceMaterial}"` : 'Create an educational question on any relevant topic.'}
|
| 62 |
+
Make sure one answer is clearly correct and the others are plausible distractors.
|
| 63 |
+
Provide a clear explanation for why the correct answer is right.`;
|
| 64 |
+
break;
|
| 65 |
+
|
| 66 |
+
case 'cloze':
|
| 67 |
+
schema = ClozeSchema;
|
| 68 |
+
prompt = `Create a cloze (fill-in-the-blank) question with ${parameters.numBlanks || 1} blank(s).
|
| 69 |
+
${parameters.partOfSpeech !== 'any' ? `Focus on ${parameters.partOfSpeech} words.` : ''}
|
| 70 |
+
${sourceMaterial ? `Use this source material: "${sourceMaterial}"` : 'Create an educational text.'}
|
| 71 |
+
Mark blanks with their position and provide the correct answers.
|
| 72 |
+
Include alternative acceptable answers where appropriate.`;
|
| 73 |
+
break;
|
| 74 |
+
|
| 75 |
+
case 'grammar':
|
| 76 |
+
schema = GrammarSchema;
|
| 77 |
+
prompt = `Create a grammar correction question at ${parameters.difficulty || 'intermediate'} level.
|
| 78 |
+
Present a sentence with grammatical errors for students to identify and correct.
|
| 79 |
+
${sourceMaterial ? `Base it on this text style: "${sourceMaterial}"` : 'Use common grammatical concepts.'}
|
| 80 |
+
Specify the type of error, its position, and the correction needed.`;
|
| 81 |
+
break;
|
| 82 |
+
|
| 83 |
+
case 'essay':
|
| 84 |
+
schema = EssaySchema;
|
| 85 |
+
prompt = `Create an essay question at ${parameters.difficulty || 'intermediate'} level.
|
| 86 |
+
${sourceMaterial ? `Base the essay prompt on this source material: "${sourceMaterial}"` : 'Create a thought-provoking prompt.'}
|
| 87 |
+
Include a detailed rubric with specific criteria and point values.
|
| 88 |
+
Optionally provide a sample answer outline.`;
|
| 89 |
+
break;
|
| 90 |
+
|
| 91 |
+
case 'reading-comp':
|
| 92 |
+
schema = MultipleChoiceSchema;
|
| 93 |
+
prompt = `Create a reading comprehension question based on the provided text.
|
| 94 |
+
${sourceMaterial || 'Please provide source material for reading comprehension questions.'}
|
| 95 |
+
Create a question that tests understanding, inference, or analysis of the text.
|
| 96 |
+
Include ${parameters.numOptions || 4} options with clear explanations.`;
|
| 97 |
+
break;
|
| 98 |
+
|
| 99 |
+
default:
|
| 100 |
+
return Response.json(
|
| 101 |
+
{ error: 'Unsupported question type' },
|
| 102 |
+
{ status: 400 }
|
| 103 |
+
);
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
const result = await generateObject({
|
| 107 |
+
model: openai('gpt-4o-mini'),
|
| 108 |
+
schema,
|
| 109 |
+
prompt,
|
| 110 |
+
});
|
| 111 |
+
|
| 112 |
+
return Response.json({
|
| 113 |
+
type,
|
| 114 |
+
...result.object,
|
| 115 |
+
});
|
| 116 |
+
|
| 117 |
+
} catch (error) {
|
| 118 |
+
console.error('Error generating question:', error);
|
| 119 |
+
return Response.json(
|
| 120 |
+
{ error: 'Failed to generate question' },
|
| 121 |
+
{ status: 500 }
|
| 122 |
+
);
|
| 123 |
+
}
|
| 124 |
+
}
|
src/app/api/generate-quiz/route.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { openai } from '@ai-sdk/openai';
|
| 2 |
+
import { generateObject } from 'ai';
|
| 3 |
+
import { z } from 'zod';
|
| 4 |
+
import { generateQuizPrompt } from '@/prompts/quiz-generation';
|
| 5 |
+
|
| 6 |
+
// Schema for quiz questions
|
| 7 |
+
const QuizSchema = z.object({
|
| 8 |
+
questions: z.array(
|
| 9 |
+
z.object({
|
| 10 |
+
question: z.string(),
|
| 11 |
+
options: z.array(z.string()).length(4),
|
| 12 |
+
correctAnswer: z.number().min(0).max(3),
|
| 13 |
+
explanation: z.string(),
|
| 14 |
+
})
|
| 15 |
+
),
|
| 16 |
+
});
|
| 17 |
+
|
| 18 |
+
export async function POST(req: Request) {
|
| 19 |
+
try {
|
| 20 |
+
const { topic, difficulty, numQuestions } = await req.json();
|
| 21 |
+
|
| 22 |
+
const result = await generateObject({
|
| 23 |
+
model: openai('gpt-4o-mini'),
|
| 24 |
+
schema: QuizSchema,
|
| 25 |
+
prompt: generateQuizPrompt({ topic, difficulty, numQuestions }, 'basic'),
|
| 26 |
+
});
|
| 27 |
+
|
| 28 |
+
return Response.json(result.object);
|
| 29 |
+
} catch (error) {
|
| 30 |
+
console.error('Error generating quiz:', error);
|
| 31 |
+
return Response.json(
|
| 32 |
+
{ error: 'Failed to generate quiz' },
|
| 33 |
+
{ status: 500 }
|
| 34 |
+
);
|
| 35 |
+
}
|
| 36 |
+
}
|
src/app/layout.tsx
CHANGED
|
@@ -13,8 +13,8 @@ const geistMono = Geist_Mono({
|
|
| 13 |
});
|
| 14 |
|
| 15 |
export const metadata: Metadata = {
|
| 16 |
-
title: "
|
| 17 |
-
description: "
|
| 18 |
};
|
| 19 |
|
| 20 |
export default function RootLayout({
|
|
|
|
| 13 |
});
|
| 14 |
|
| 15 |
export const metadata: Metadata = {
|
| 16 |
+
title: "QuizFlash - AI-Powered Quiz Generation",
|
| 17 |
+
description: "Generate interactive quizzes on any topic using AI. Test your knowledge with QuizFlash!",
|
| 18 |
};
|
| 19 |
|
| 20 |
export default function RootLayout({
|
src/app/page.tsx
CHANGED
|
@@ -1,103 +1,319 @@
|
|
| 1 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
-
export default function Home() {
|
| 4 |
return (
|
| 5 |
-
<div className="
|
| 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 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
</div>
|
| 53 |
-
</
|
| 54 |
-
<footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center">
|
| 55 |
-
<a
|
| 56 |
-
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
| 57 |
-
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
| 58 |
-
target="_blank"
|
| 59 |
-
rel="noopener noreferrer"
|
| 60 |
-
>
|
| 61 |
-
<Image
|
| 62 |
-
aria-hidden
|
| 63 |
-
src="/file.svg"
|
| 64 |
-
alt="File icon"
|
| 65 |
-
width={16}
|
| 66 |
-
height={16}
|
| 67 |
-
/>
|
| 68 |
-
Learn
|
| 69 |
-
</a>
|
| 70 |
-
<a
|
| 71 |
-
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
| 72 |
-
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
| 73 |
-
target="_blank"
|
| 74 |
-
rel="noopener noreferrer"
|
| 75 |
-
>
|
| 76 |
-
<Image
|
| 77 |
-
aria-hidden
|
| 78 |
-
src="/window.svg"
|
| 79 |
-
alt="Window icon"
|
| 80 |
-
width={16}
|
| 81 |
-
height={16}
|
| 82 |
-
/>
|
| 83 |
-
Examples
|
| 84 |
-
</a>
|
| 85 |
-
<a
|
| 86 |
-
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
| 87 |
-
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
| 88 |
-
target="_blank"
|
| 89 |
-
rel="noopener noreferrer"
|
| 90 |
-
>
|
| 91 |
-
<Image
|
| 92 |
-
aria-hidden
|
| 93 |
-
src="/globe.svg"
|
| 94 |
-
alt="Globe icon"
|
| 95 |
-
width={16}
|
| 96 |
-
height={16}
|
| 97 |
-
/>
|
| 98 |
-
Go to nextjs.org →
|
| 99 |
-
</a>
|
| 100 |
-
</footer>
|
| 101 |
</div>
|
| 102 |
);
|
| 103 |
}
|
|
|
|
| 1 |
+
'use client';
|
| 2 |
+
|
| 3 |
+
import { useState } from 'react';
|
| 4 |
+
import { useChat } from '@ai-sdk/react';
|
| 5 |
+
import QuestionParameterForm from '@/components/QuestionParameterForm';
|
| 6 |
+
import { QuestionType, QuestionParameters, GeneratedQuestion } from '@/types/quiz';
|
| 7 |
+
|
| 8 |
+
const QUESTION_TYPES: QuestionType[] = [
|
| 9 |
+
{ id: 'multiple-choice', name: 'Multiple Choice', icon: '☑️', description: 'Traditional A, B, C, D questions' },
|
| 10 |
+
{ id: 'cloze', name: 'Cloze (Fill-in-the-blank)', icon: '🔤', description: 'Fill in missing words or phrases' },
|
| 11 |
+
{ id: 'grammar', name: 'Grammar Check', icon: '📝', description: 'Identify and correct grammatical errors' },
|
| 12 |
+
{ id: 'reading-comp', name: 'Reading Comprehension', icon: '📖', description: 'Questions based on text passages' },
|
| 13 |
+
{ id: 'essay', name: 'Essay Question', icon: '✍️', description: 'Open-ended written responses' },
|
| 14 |
+
];
|
| 15 |
+
|
| 16 |
+
export default function QuestionBuilder() {
|
| 17 |
+
// Chat builder state
|
| 18 |
+
const [currentStep, setCurrentStep] = useState<'type-selection' | 'parameters' | 'generation' | 'editing'>('type-selection');
|
| 19 |
+
const [selectedQuestionType, setSelectedQuestionType] = useState<QuestionType | null>(null);
|
| 20 |
+
const [questionParameters, setQuestionParameters] = useState<QuestionParameters>({});
|
| 21 |
+
const [isGenerating, setIsGenerating] = useState(false);
|
| 22 |
+
|
| 23 |
+
// Source material state
|
| 24 |
+
const [sourceMaterial, setSourceMaterial] = useState('');
|
| 25 |
+
const [isSourceLocked, setIsSourceLocked] = useState(false);
|
| 26 |
+
const [wordCount, setWordCount] = useState(0);
|
| 27 |
+
|
| 28 |
+
// Question bank state
|
| 29 |
+
const [questions, setQuestions] = useState<GeneratedQuestion[]>([]);
|
| 30 |
+
const [selectedQuestions, setSelectedQuestions] = useState<Set<string>>(new Set());
|
| 31 |
+
|
| 32 |
+
// Chat functionality
|
| 33 |
+
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat();
|
| 34 |
+
|
| 35 |
+
const updateWordCount = (text: string) => {
|
| 36 |
+
const words = text.trim().split(/\s+/).length;
|
| 37 |
+
setWordCount(text.trim() === '' ? 0 : words);
|
| 38 |
+
};
|
| 39 |
+
|
| 40 |
+
const handleSourceMaterialChange = (text: string) => {
|
| 41 |
+
if (!isSourceLocked) {
|
| 42 |
+
setSourceMaterial(text);
|
| 43 |
+
updateWordCount(text);
|
| 44 |
+
}
|
| 45 |
+
};
|
| 46 |
+
|
| 47 |
+
const handleQuestionTypeSelect = (questionType: QuestionType) => {
|
| 48 |
+
setSelectedQuestionType(questionType);
|
| 49 |
+
setCurrentStep('parameters');
|
| 50 |
+
};
|
| 51 |
+
|
| 52 |
+
const generateQuestion = async () => {
|
| 53 |
+
if (!selectedQuestionType) return;
|
| 54 |
+
|
| 55 |
+
setIsGenerating(true);
|
| 56 |
+
try {
|
| 57 |
+
const response = await fetch('/api/generate-question', {
|
| 58 |
+
method: 'POST',
|
| 59 |
+
headers: { 'Content-Type': 'application/json' },
|
| 60 |
+
body: JSON.stringify({
|
| 61 |
+
type: selectedQuestionType.id,
|
| 62 |
+
parameters: questionParameters,
|
| 63 |
+
sourceMaterial,
|
| 64 |
+
}),
|
| 65 |
+
});
|
| 66 |
+
|
| 67 |
+
if (response.ok) {
|
| 68 |
+
const newQuestion = await response.json();
|
| 69 |
+
setQuestions(prev => [...prev, {
|
| 70 |
+
...newQuestion,
|
| 71 |
+
id: Date.now().toString(),
|
| 72 |
+
createdAt: new Date(),
|
| 73 |
+
}]);
|
| 74 |
+
setCurrentStep('type-selection');
|
| 75 |
+
setSelectedQuestionType(null);
|
| 76 |
+
setQuestionParameters({});
|
| 77 |
+
}
|
| 78 |
+
} catch (error) {
|
| 79 |
+
console.error('Error generating question:', error);
|
| 80 |
+
} finally {
|
| 81 |
+
setIsGenerating(false);
|
| 82 |
+
}
|
| 83 |
+
};
|
| 84 |
+
|
| 85 |
+
const removeQuestion = (id: string) => {
|
| 86 |
+
setQuestions(prev => prev.filter(q => q.id !== id));
|
| 87 |
+
setSelectedQuestions(prev => {
|
| 88 |
+
const newSet = new Set(prev);
|
| 89 |
+
newSet.delete(id);
|
| 90 |
+
return newSet;
|
| 91 |
+
});
|
| 92 |
+
};
|
| 93 |
+
|
| 94 |
+
|
| 95 |
|
|
|
|
| 96 |
return (
|
| 97 |
+
<div className="min-h-screen bg-gray-50 dark:bg-gray-900">
|
| 98 |
+
{/* Header */}
|
| 99 |
+
<div className="bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
|
| 100 |
+
<div className="px-6 py-4">
|
| 101 |
+
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
|
| 102 |
+
🎓 QuizFlash - Question Builder
|
| 103 |
+
</h1>
|
| 104 |
+
<p className="text-gray-600 dark:text-gray-300">
|
| 105 |
+
AI-powered assessment creation tool for educators
|
| 106 |
+
</p>
|
| 107 |
+
</div>
|
| 108 |
+
</div>
|
| 109 |
+
|
| 110 |
+
{/* Three-panel layout */}
|
| 111 |
+
<div className="flex h-[calc(100vh-120px)]">
|
| 112 |
+
{/* Left Panel: Chat-Driven Question Builder */}
|
| 113 |
+
<div className="w-1/3 bg-white dark:bg-gray-800 border-r border-gray-200 dark:border-gray-700 flex flex-col">
|
| 114 |
+
<div className="p-4 border-b border-gray-200 dark:border-gray-700">
|
| 115 |
+
<h2 className="text-lg font-semibold text-gray-900 dark:text-white mb-2">
|
| 116 |
+
💬 Question Builder
|
| 117 |
+
</h2>
|
| 118 |
+
</div>
|
| 119 |
+
|
| 120 |
+
<div className="flex-1 overflow-y-auto p-4">
|
| 121 |
+
{currentStep === 'type-selection' && (
|
| 122 |
+
<div className="space-y-4">
|
| 123 |
+
<div className="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4">
|
| 124 |
+
<p className="text-blue-800 dark:text-blue-200">
|
| 125 |
+
🤖 Hi! What type of question would you like to create?
|
| 126 |
+
</p>
|
| 127 |
+
</div>
|
| 128 |
+
|
| 129 |
+
<div className="space-y-2">
|
| 130 |
+
{QUESTION_TYPES.map((type) => (
|
| 131 |
+
<button
|
| 132 |
+
key={type.id}
|
| 133 |
+
onClick={() => handleQuestionTypeSelect(type)}
|
| 134 |
+
className="w-full p-3 text-left border border-gray-200 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
| 135 |
+
>
|
| 136 |
+
<div className="flex items-center space-x-3">
|
| 137 |
+
<span className="text-lg">{type.icon}</span>
|
| 138 |
+
<div>
|
| 139 |
+
<div className="font-medium text-gray-900 dark:text-white">{type.name}</div>
|
| 140 |
+
<div className="text-sm text-gray-500 dark:text-gray-400">{type.description}</div>
|
| 141 |
+
</div>
|
| 142 |
+
</div>
|
| 143 |
+
</button>
|
| 144 |
+
))}
|
| 145 |
+
</div>
|
| 146 |
+
</div>
|
| 147 |
+
)}
|
| 148 |
+
|
| 149 |
+
{currentStep === 'parameters' && selectedQuestionType && (
|
| 150 |
+
<div className="space-y-4">
|
| 151 |
+
<div className="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4">
|
| 152 |
+
<p className="text-blue-800 dark:text-blue-200">
|
| 153 |
+
Great choice! Let's configure your {selectedQuestionType.name} question.
|
| 154 |
+
</p>
|
| 155 |
+
</div>
|
| 156 |
+
|
| 157 |
+
<QuestionParameterForm
|
| 158 |
+
questionType={selectedQuestionType}
|
| 159 |
+
parameters={questionParameters}
|
| 160 |
+
onParametersChange={setQuestionParameters}
|
| 161 |
+
/>
|
| 162 |
+
|
| 163 |
+
<div className="flex space-x-2">
|
| 164 |
+
<button
|
| 165 |
+
onClick={() => setCurrentStep('type-selection')}
|
| 166 |
+
className="px-4 py-2 text-gray-600 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700"
|
| 167 |
+
>
|
| 168 |
+
Back
|
| 169 |
+
</button>
|
| 170 |
+
<button
|
| 171 |
+
onClick={generateQuestion}
|
| 172 |
+
disabled={isGenerating}
|
| 173 |
+
className="flex-1 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:bg-gray-400"
|
| 174 |
+
>
|
| 175 |
+
{isGenerating ? 'Generating...' : 'Generate Question'}
|
| 176 |
+
</button>
|
| 177 |
+
</div>
|
| 178 |
+
</div>
|
| 179 |
+
)}
|
| 180 |
+
|
| 181 |
+
{/* Chat History */}
|
| 182 |
+
<div className="mt-6 space-y-4">
|
| 183 |
+
{messages.map((message, index) => (
|
| 184 |
+
<div
|
| 185 |
+
key={index}
|
| 186 |
+
className={`p-3 rounded-lg ${
|
| 187 |
+
message.role === 'user'
|
| 188 |
+
? 'bg-blue-100 dark:bg-blue-900 ml-4'
|
| 189 |
+
: 'bg-gray-100 dark:bg-gray-700 mr-4'
|
| 190 |
+
}`}
|
| 191 |
+
>
|
| 192 |
+
<div className="text-sm font-medium mb-1">
|
| 193 |
+
{message.role === 'user' ? 'You' : 'AI Assistant'}
|
| 194 |
+
</div>
|
| 195 |
+
<div>{message.content}</div>
|
| 196 |
+
</div>
|
| 197 |
+
))}
|
| 198 |
+
</div>
|
| 199 |
+
</div>
|
| 200 |
+
|
| 201 |
+
{/* Chat Input */}
|
| 202 |
+
<div className="p-4 border-t border-gray-200 dark:border-gray-700">
|
| 203 |
+
<form onSubmit={handleSubmit} className="flex space-x-2">
|
| 204 |
+
<input
|
| 205 |
+
value={input}
|
| 206 |
+
onChange={handleInputChange}
|
| 207 |
+
placeholder="Ask for modifications or help..."
|
| 208 |
+
className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white"
|
| 209 |
+
/>
|
| 210 |
+
<button
|
| 211 |
+
type="submit"
|
| 212 |
+
disabled={isLoading}
|
| 213 |
+
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:bg-gray-400"
|
| 214 |
+
>
|
| 215 |
+
Send
|
| 216 |
+
</button>
|
| 217 |
+
</form>
|
| 218 |
+
</div>
|
| 219 |
+
</div>
|
| 220 |
+
|
| 221 |
+
{/* Right side: Two-panel stack */}
|
| 222 |
+
<div className="flex-1 flex flex-col">
|
| 223 |
+
{/* Upper Right Panel: Source Material Editor */}
|
| 224 |
+
<div className="h-1/2 bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
|
| 225 |
+
<div className="p-4 border-b border-gray-200 dark:border-gray-700 flex justify-between items-center">
|
| 226 |
+
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">
|
| 227 |
+
📄 Source Material
|
| 228 |
+
</h2>
|
| 229 |
+
<div className="flex items-center space-x-4">
|
| 230 |
+
<span className="text-sm text-gray-500 dark:text-gray-400">
|
| 231 |
+
{wordCount} words
|
| 232 |
+
</span>
|
| 233 |
+
<label className="flex items-center space-x-2">
|
| 234 |
+
<input
|
| 235 |
+
type="checkbox"
|
| 236 |
+
checked={isSourceLocked}
|
| 237 |
+
onChange={(e) => setIsSourceLocked(e.target.checked)}
|
| 238 |
+
className="rounded"
|
| 239 |
+
/>
|
| 240 |
+
<span className="text-sm">🔒 Lock</span>
|
| 241 |
+
</label>
|
| 242 |
+
</div>
|
| 243 |
+
</div>
|
| 244 |
+
|
| 245 |
+
<div className="h-[calc(100%-80px)] p-4">
|
| 246 |
+
<textarea
|
| 247 |
+
value={sourceMaterial}
|
| 248 |
+
onChange={(e) => handleSourceMaterialChange(e.target.value)}
|
| 249 |
+
placeholder="Paste or type your source material here (articles, passages, etc.)..."
|
| 250 |
+
disabled={isSourceLocked}
|
| 251 |
+
className="w-full h-full resize-none border border-gray-300 dark:border-gray-600 rounded-lg p-4 focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white disabled:bg-gray-100 dark:disabled:bg-gray-600"
|
| 252 |
+
/>
|
| 253 |
+
</div>
|
| 254 |
+
</div>
|
| 255 |
+
|
| 256 |
+
{/* Lower Right Panel: Question Bank */}
|
| 257 |
+
<div className="h-1/2 bg-white dark:bg-gray-800">
|
| 258 |
+
<div className="p-4 border-b border-gray-200 dark:border-gray-700">
|
| 259 |
+
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">
|
| 260 |
+
🗂️ Question Bank ({questions.length})
|
| 261 |
+
</h2>
|
| 262 |
+
</div>
|
| 263 |
+
|
| 264 |
+
<div className="h-[calc(100%-80px)] overflow-y-auto p-4">
|
| 265 |
+
{questions.length === 0 ? (
|
| 266 |
+
<div className="text-center text-gray-500 dark:text-gray-400 mt-8">
|
| 267 |
+
<div className="text-4xl mb-4">📝</div>
|
| 268 |
+
<p>No questions yet. Start by creating your first question!</p>
|
| 269 |
+
</div>
|
| 270 |
+
) : (
|
| 271 |
+
<div className="space-y-3">
|
| 272 |
+
{questions.map((question) => (
|
| 273 |
+
<div
|
| 274 |
+
key={question.id}
|
| 275 |
+
className="border border-gray-200 dark:border-gray-600 rounded-lg p-4 hover:bg-gray-50 dark:hover:bg-gray-700 group"
|
| 276 |
+
>
|
| 277 |
+
<div className="flex items-start justify-between">
|
| 278 |
+
<div className="flex-1">
|
| 279 |
+
<div className="flex items-center space-x-2 mb-2">
|
| 280 |
+
<span className="text-sm font-medium text-blue-600 dark:text-blue-400">
|
| 281 |
+
{QUESTION_TYPES.find(t => t.id === question.type)?.name}
|
| 282 |
+
</span>
|
| 283 |
+
<span className="text-xs text-gray-500 dark:text-gray-400">
|
| 284 |
+
{question.points} pts
|
| 285 |
+
</span>
|
| 286 |
+
</div>
|
| 287 |
+
<p className="text-gray-900 dark:text-white line-clamp-2">
|
| 288 |
+
{question.stem}
|
| 289 |
+
</p>
|
| 290 |
+
</div>
|
| 291 |
+
<div className="flex items-center space-x-2 opacity-0 group-hover:opacity-100 transition-opacity">
|
| 292 |
+
<button className="p-1 text-gray-400 hover:text-blue-600">
|
| 293 |
+
✏️
|
| 294 |
+
</button>
|
| 295 |
+
<button className="p-1 text-gray-400 hover:text-green-600">
|
| 296 |
+
📄
|
| 297 |
+
</button>
|
| 298 |
+
<button className="p-1 text-gray-400 hover:text-blue-600">
|
| 299 |
+
👁️
|
| 300 |
+
</button>
|
| 301 |
+
<button
|
| 302 |
+
onClick={() => removeQuestion(question.id)}
|
| 303 |
+
className="p-1 text-gray-400 hover:text-red-600"
|
| 304 |
+
>
|
| 305 |
+
✕
|
| 306 |
+
</button>
|
| 307 |
+
</div>
|
| 308 |
+
</div>
|
| 309 |
+
</div>
|
| 310 |
+
))}
|
| 311 |
+
</div>
|
| 312 |
+
)}
|
| 313 |
+
</div>
|
| 314 |
+
</div>
|
| 315 |
</div>
|
| 316 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 317 |
</div>
|
| 318 |
);
|
| 319 |
}
|
src/components/QuestionParameterForm.tsx
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client';
|
| 2 |
+
|
| 3 |
+
import { QuestionType, QuestionParameters } from '@/types/quiz';
|
| 4 |
+
|
| 5 |
+
interface QuestionParameterFormProps {
|
| 6 |
+
questionType: QuestionType;
|
| 7 |
+
parameters: QuestionParameters;
|
| 8 |
+
onParametersChange: (parameters: QuestionParameters) => void;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
export default function QuestionParameterForm({
|
| 12 |
+
questionType,
|
| 13 |
+
parameters,
|
| 14 |
+
onParametersChange,
|
| 15 |
+
}: QuestionParameterFormProps) {
|
| 16 |
+
const updateParameter = (key: string, value: any) => {
|
| 17 |
+
onParametersChange({ ...parameters, [key]: value });
|
| 18 |
+
};
|
| 19 |
+
|
| 20 |
+
switch (questionType.id) {
|
| 21 |
+
case 'multiple-choice':
|
| 22 |
+
return (
|
| 23 |
+
<div className="space-y-4">
|
| 24 |
+
<div>
|
| 25 |
+
<label className="block text-sm font-medium mb-2">Difficulty Level</label>
|
| 26 |
+
<select
|
| 27 |
+
value={parameters.difficulty || 'intermediate'}
|
| 28 |
+
onChange={(e) => updateParameter('difficulty', e.target.value)}
|
| 29 |
+
className="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
|
| 30 |
+
>
|
| 31 |
+
<option value="beginner">Beginner</option>
|
| 32 |
+
<option value="intermediate">Intermediate</option>
|
| 33 |
+
<option value="advanced">Advanced</option>
|
| 34 |
+
</select>
|
| 35 |
+
</div>
|
| 36 |
+
<div>
|
| 37 |
+
<label className="block text-sm font-medium mb-2">Number of Options</label>
|
| 38 |
+
<select
|
| 39 |
+
value={parameters.numOptions || 4}
|
| 40 |
+
onChange={(e) => updateParameter('numOptions', parseInt(e.target.value))}
|
| 41 |
+
className="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
|
| 42 |
+
>
|
| 43 |
+
<option value={3}>3 Options</option>
|
| 44 |
+
<option value={4}>4 Options</option>
|
| 45 |
+
<option value={5}>5 Options</option>
|
| 46 |
+
</select>
|
| 47 |
+
</div>
|
| 48 |
+
</div>
|
| 49 |
+
);
|
| 50 |
+
|
| 51 |
+
case 'cloze':
|
| 52 |
+
return (
|
| 53 |
+
<div className="space-y-4">
|
| 54 |
+
<div>
|
| 55 |
+
<label className="block text-sm font-medium mb-2">Target Part of Speech</label>
|
| 56 |
+
<select
|
| 57 |
+
value={parameters.partOfSpeech || 'any'}
|
| 58 |
+
onChange={(e) => updateParameter('partOfSpeech', e.target.value)}
|
| 59 |
+
className="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
|
| 60 |
+
>
|
| 61 |
+
<option value="any">Any</option>
|
| 62 |
+
<option value="noun">Nouns</option>
|
| 63 |
+
<option value="verb">Verbs</option>
|
| 64 |
+
<option value="adjective">Adjectives</option>
|
| 65 |
+
<option value="adverb">Adverbs</option>
|
| 66 |
+
</select>
|
| 67 |
+
</div>
|
| 68 |
+
<div>
|
| 69 |
+
<label className="block text-sm font-medium mb-2">Number of Blanks</label>
|
| 70 |
+
<input
|
| 71 |
+
type="range"
|
| 72 |
+
min="1"
|
| 73 |
+
max="5"
|
| 74 |
+
value={parameters.numBlanks || 1}
|
| 75 |
+
onChange={(e) => updateParameter('numBlanks', parseInt(e.target.value))}
|
| 76 |
+
className="w-full"
|
| 77 |
+
/>
|
| 78 |
+
<div className="text-center text-sm text-gray-600">{parameters.numBlanks || 1} blank(s)</div>
|
| 79 |
+
</div>
|
| 80 |
+
</div>
|
| 81 |
+
);
|
| 82 |
+
|
| 83 |
+
case 'grammar':
|
| 84 |
+
return (
|
| 85 |
+
<div className="space-y-4">
|
| 86 |
+
<div>
|
| 87 |
+
<label className="block text-sm font-medium mb-2">Difficulty Level</label>
|
| 88 |
+
<select
|
| 89 |
+
value={parameters.difficulty || 'intermediate'}
|
| 90 |
+
onChange={(e) => updateParameter('difficulty', e.target.value)}
|
| 91 |
+
className="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
|
| 92 |
+
>
|
| 93 |
+
<option value="beginner">Beginner</option>
|
| 94 |
+
<option value="intermediate">Intermediate</option>
|
| 95 |
+
<option value="advanced">Advanced</option>
|
| 96 |
+
</select>
|
| 97 |
+
</div>
|
| 98 |
+
<div>
|
| 99 |
+
<label className="block text-sm font-medium mb-2">Grammar Focus</label>
|
| 100 |
+
<select
|
| 101 |
+
value={parameters.grammarFocus || 'any'}
|
| 102 |
+
onChange={(e) => updateParameter('grammarFocus', e.target.value)}
|
| 103 |
+
className="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
|
| 104 |
+
>
|
| 105 |
+
<option value="any">Any Grammar Rule</option>
|
| 106 |
+
<option value="verb-tenses">Verb Tenses</option>
|
| 107 |
+
<option value="subject-verb">Subject-Verb Agreement</option>
|
| 108 |
+
<option value="punctuation">Punctuation</option>
|
| 109 |
+
<option value="sentence-structure">Sentence Structure</option>
|
| 110 |
+
</select>
|
| 111 |
+
</div>
|
| 112 |
+
</div>
|
| 113 |
+
);
|
| 114 |
+
|
| 115 |
+
case 'reading-comp':
|
| 116 |
+
return (
|
| 117 |
+
<div className="space-y-4">
|
| 118 |
+
<div>
|
| 119 |
+
<label className="block text-sm font-medium mb-2">Difficulty Level</label>
|
| 120 |
+
<select
|
| 121 |
+
value={parameters.difficulty || 'intermediate'}
|
| 122 |
+
onChange={(e) => updateParameter('difficulty', e.target.value)}
|
| 123 |
+
className="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
|
| 124 |
+
>
|
| 125 |
+
<option value="beginner">Beginner</option>
|
| 126 |
+
<option value="intermediate">Intermediate</option>
|
| 127 |
+
<option value="advanced">Advanced</option>
|
| 128 |
+
</select>
|
| 129 |
+
</div>
|
| 130 |
+
<div>
|
| 131 |
+
<label className="block text-sm font-medium mb-2">Question Focus</label>
|
| 132 |
+
<select
|
| 133 |
+
value={parameters.focus || 'comprehension'}
|
| 134 |
+
onChange={(e) => updateParameter('focus', e.target.value)}
|
| 135 |
+
className="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
|
| 136 |
+
>
|
| 137 |
+
<option value="comprehension">General Comprehension</option>
|
| 138 |
+
<option value="inference">Making Inferences</option>
|
| 139 |
+
<option value="main-idea">Main Ideas</option>
|
| 140 |
+
<option value="details">Supporting Details</option>
|
| 141 |
+
<option value="vocabulary">Vocabulary in Context</option>
|
| 142 |
+
</select>
|
| 143 |
+
</div>
|
| 144 |
+
</div>
|
| 145 |
+
);
|
| 146 |
+
|
| 147 |
+
case 'essay':
|
| 148 |
+
return (
|
| 149 |
+
<div className="space-y-4">
|
| 150 |
+
<div>
|
| 151 |
+
<label className="block text-sm font-medium mb-2">Difficulty Level</label>
|
| 152 |
+
<select
|
| 153 |
+
value={parameters.difficulty || 'intermediate'}
|
| 154 |
+
onChange={(e) => updateParameter('difficulty', e.target.value)}
|
| 155 |
+
className="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
|
| 156 |
+
>
|
| 157 |
+
<option value="beginner">Beginner</option>
|
| 158 |
+
<option value="intermediate">Intermediate</option>
|
| 159 |
+
<option value="advanced">Advanced</option>
|
| 160 |
+
</select>
|
| 161 |
+
</div>
|
| 162 |
+
<div>
|
| 163 |
+
<label className="block text-sm font-medium mb-2">Essay Type</label>
|
| 164 |
+
<select
|
| 165 |
+
value={parameters.essayType || 'argumentative'}
|
| 166 |
+
onChange={(e) => updateParameter('essayType', e.target.value)}
|
| 167 |
+
className="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
|
| 168 |
+
>
|
| 169 |
+
<option value="argumentative">Argumentative</option>
|
| 170 |
+
<option value="descriptive">Descriptive</option>
|
| 171 |
+
<option value="narrative">Narrative</option>
|
| 172 |
+
<option value="expository">Expository</option>
|
| 173 |
+
<option value="compare-contrast">Compare & Contrast</option>
|
| 174 |
+
</select>
|
| 175 |
+
</div>
|
| 176 |
+
<div>
|
| 177 |
+
<label className="block text-sm font-medium mb-2">Word Count Range</label>
|
| 178 |
+
<select
|
| 179 |
+
value={parameters.wordCount || '300-500'}
|
| 180 |
+
onChange={(e) => updateParameter('wordCount', e.target.value)}
|
| 181 |
+
className="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
|
| 182 |
+
>
|
| 183 |
+
<option value="150-250">150-250 words</option>
|
| 184 |
+
<option value="300-500">300-500 words</option>
|
| 185 |
+
<option value="500-750">500-750 words</option>
|
| 186 |
+
<option value="750-1000">750-1000 words</option>
|
| 187 |
+
</select>
|
| 188 |
+
</div>
|
| 189 |
+
</div>
|
| 190 |
+
);
|
| 191 |
+
|
| 192 |
+
default:
|
| 193 |
+
return (
|
| 194 |
+
<div className="space-y-4">
|
| 195 |
+
<div>
|
| 196 |
+
<label className="block text-sm font-medium mb-2">Difficulty Level</label>
|
| 197 |
+
<select
|
| 198 |
+
value={parameters.difficulty || 'intermediate'}
|
| 199 |
+
onChange={(e) => updateParameter('difficulty', e.target.value)}
|
| 200 |
+
className="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
|
| 201 |
+
>
|
| 202 |
+
<option value="beginner">Beginner</option>
|
| 203 |
+
<option value="intermediate">Intermediate</option>
|
| 204 |
+
<option value="advanced">Advanced</option>
|
| 205 |
+
</select>
|
| 206 |
+
</div>
|
| 207 |
+
</div>
|
| 208 |
+
);
|
| 209 |
+
}
|
| 210 |
+
}
|
src/prompts/quiz-generation.ts
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export interface QuizPromptParams {
|
| 2 |
+
topic: string;
|
| 3 |
+
difficulty: string;
|
| 4 |
+
numQuestions: number;
|
| 5 |
+
questionType?: 'multiple-choice' | 'true-false' | 'fill-blank' | 'short-answer';
|
| 6 |
+
focus?: 'conceptual' | 'practical' | 'analytical' | 'memorization';
|
| 7 |
+
}
|
| 8 |
+
|
| 9 |
+
export interface PromptTemplate {
|
| 10 |
+
template: string;
|
| 11 |
+
description: string;
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
export const QUIZ_PROMPTS = {
|
| 15 |
+
'multiple-choice': {
|
| 16 |
+
basic: {
|
| 17 |
+
template: `Generate {{numQuestions}} {{difficulty}} level multiple-choice quiz questions about {{topic}}.
|
| 18 |
+
|
| 19 |
+
For each question, provide:
|
| 20 |
+
- A clear, well-written question
|
| 21 |
+
- Exactly 4 answer options (labeled A, B, C, D)
|
| 22 |
+
- The correct answer should be indicated by its index (0 for A, 1 for B, 2 for C, 3 for D)
|
| 23 |
+
- A detailed explanation of why the correct answer is right and why the others are wrong
|
| 24 |
+
|
| 25 |
+
Make the questions educational and engaging. Ensure incorrect options are plausible but clearly wrong.
|
| 26 |
+
|
| 27 |
+
Return the data in this exact structure:
|
| 28 |
+
{
|
| 29 |
+
"questions": [
|
| 30 |
+
{
|
| 31 |
+
"question": "Your question here?",
|
| 32 |
+
"options": ["Option A", "Option B", "Option C", "Option D"],
|
| 33 |
+
"correctAnswer": 0,
|
| 34 |
+
"explanation": "Detailed explanation here..."
|
| 35 |
+
}
|
| 36 |
+
]
|
| 37 |
+
}`,
|
| 38 |
+
description: 'Standard multiple choice questions'
|
| 39 |
+
},
|
| 40 |
+
conceptual: {
|
| 41 |
+
template: `Create {{numQuestions}} {{difficulty}} level conceptual multiple-choice questions about {{topic}}.
|
| 42 |
+
|
| 43 |
+
Focus on understanding core principles and concepts rather than memorization.
|
| 44 |
+
|
| 45 |
+
For each question, provide:
|
| 46 |
+
- A thought-provoking question that tests deep understanding
|
| 47 |
+
- Exactly 4 answer options that require conceptual reasoning
|
| 48 |
+
- The correct answer index (0-3)
|
| 49 |
+
- A detailed explanation that reinforces the underlying concepts
|
| 50 |
+
|
| 51 |
+
Make incorrect options represent common misconceptions.
|
| 52 |
+
|
| 53 |
+
Return the data in this exact structure:
|
| 54 |
+
{
|
| 55 |
+
"questions": [
|
| 56 |
+
{
|
| 57 |
+
"question": "Your conceptual question here?",
|
| 58 |
+
"options": ["Option A", "Option B", "Option C", "Option D"],
|
| 59 |
+
"correctAnswer": 0,
|
| 60 |
+
"explanation": "Detailed conceptual explanation here..."
|
| 61 |
+
}
|
| 62 |
+
]
|
| 63 |
+
}`,
|
| 64 |
+
description: 'Conceptual understanding focused questions'
|
| 65 |
+
},
|
| 66 |
+
practical: {
|
| 67 |
+
template: `Generate {{numQuestions}} {{difficulty}} level practical multiple-choice questions about {{topic}}.
|
| 68 |
+
|
| 69 |
+
Focus on real-world applications and problem-solving scenarios.
|
| 70 |
+
|
| 71 |
+
For each question, provide:
|
| 72 |
+
- A scenario-based question from real-world contexts
|
| 73 |
+
- Exactly 4 realistic solution options
|
| 74 |
+
- The correct answer index (0-3)
|
| 75 |
+
- An explanation with practical examples and applications
|
| 76 |
+
|
| 77 |
+
Make options represent realistic choices someone might face in practice.
|
| 78 |
+
|
| 79 |
+
Return the data in this exact structure:
|
| 80 |
+
{
|
| 81 |
+
"questions": [
|
| 82 |
+
{
|
| 83 |
+
"question": "Your practical scenario question here?",
|
| 84 |
+
"options": ["Option A", "Option B", "Option C", "Option D"],
|
| 85 |
+
"correctAnswer": 0,
|
| 86 |
+
"explanation": "Practical explanation with real-world examples..."
|
| 87 |
+
}
|
| 88 |
+
]
|
| 89 |
+
}`,
|
| 90 |
+
description: 'Practical application focused questions'
|
| 91 |
+
}
|
| 92 |
+
},
|
| 93 |
+
'true-false': {
|
| 94 |
+
basic: {
|
| 95 |
+
template: `Create {{numQuestions}} {{difficulty}} level true/false questions about {{topic}}.
|
| 96 |
+
|
| 97 |
+
For each question, provide:
|
| 98 |
+
- A clear statement that can be evaluated as true or false
|
| 99 |
+
- Exactly 4 answer options: "True", "False", and 2 additional options like "Partially True" or "Cannot be determined"
|
| 100 |
+
- The correct answer index (0-3)
|
| 101 |
+
- A detailed explanation of why the statement is true or false
|
| 102 |
+
|
| 103 |
+
Make statements clear and unambiguous.
|
| 104 |
+
|
| 105 |
+
Return the data in this exact structure:
|
| 106 |
+
{
|
| 107 |
+
"questions": [
|
| 108 |
+
{
|
| 109 |
+
"question": "Your true/false statement here",
|
| 110 |
+
"options": ["True", "False", "Partially True", "Cannot be determined"],
|
| 111 |
+
"correctAnswer": 0,
|
| 112 |
+
"explanation": "Detailed explanation here..."
|
| 113 |
+
}
|
| 114 |
+
]
|
| 115 |
+
}`,
|
| 116 |
+
description: 'Standard true/false questions'
|
| 117 |
+
},
|
| 118 |
+
conceptual: {
|
| 119 |
+
template: `Create {{numQuestions}} {{difficulty}} level conceptual true/false questions about {{topic}}.
|
| 120 |
+
|
| 121 |
+
Focus on testing understanding of core principles and concepts.
|
| 122 |
+
|
| 123 |
+
For each question, provide:
|
| 124 |
+
- A nuanced statement requiring deep conceptual thinking
|
| 125 |
+
- Exactly 4 answer options: "True", "False", "Depends on context", "Partially True"
|
| 126 |
+
- The correct answer index (0-3)
|
| 127 |
+
- A detailed explanation reinforcing the underlying concepts
|
| 128 |
+
|
| 129 |
+
Ensure statements require deep thinking to evaluate correctly.
|
| 130 |
+
|
| 131 |
+
Return the data in this exact structure:
|
| 132 |
+
{
|
| 133 |
+
"questions": [
|
| 134 |
+
{
|
| 135 |
+
"question": "Your conceptual statement here",
|
| 136 |
+
"options": ["True", "False", "Depends on context", "Partially True"],
|
| 137 |
+
"correctAnswer": 0,
|
| 138 |
+
"explanation": "Detailed conceptual explanation here..."
|
| 139 |
+
}
|
| 140 |
+
]
|
| 141 |
+
}`,
|
| 142 |
+
description: 'Conceptual true/false questions'
|
| 143 |
+
},
|
| 144 |
+
practical: {
|
| 145 |
+
template: `Generate {{numQuestions}} {{difficulty}} level practical true/false questions about {{topic}}.
|
| 146 |
+
|
| 147 |
+
Focus on real-world scenarios and applications.
|
| 148 |
+
|
| 149 |
+
For each question, provide:
|
| 150 |
+
- A statement about practical applications or real-world scenarios
|
| 151 |
+
- Exactly 4 answer options: "True", "False", "Sometimes True", "Rarely True"
|
| 152 |
+
- The correct answer index (0-3)
|
| 153 |
+
- An explanation with practical examples
|
| 154 |
+
|
| 155 |
+
Include statements that test practical knowledge and experience.
|
| 156 |
+
|
| 157 |
+
Return the data in this exact structure:
|
| 158 |
+
{
|
| 159 |
+
"questions": [
|
| 160 |
+
{
|
| 161 |
+
"question": "Your practical statement here",
|
| 162 |
+
"options": ["True", "False", "Sometimes True", "Rarely True"],
|
| 163 |
+
"correctAnswer": 0,
|
| 164 |
+
"explanation": "Practical explanation with examples..."
|
| 165 |
+
}
|
| 166 |
+
]
|
| 167 |
+
}`,
|
| 168 |
+
description: 'Practical true/false questions'
|
| 169 |
+
}
|
| 170 |
+
},
|
| 171 |
+
'fill-blank': {
|
| 172 |
+
basic: {
|
| 173 |
+
template: `Create {{numQuestions}} {{difficulty}} level fill-in-the-blank questions about {{topic}}.
|
| 174 |
+
|
| 175 |
+
For each question, provide:
|
| 176 |
+
- A sentence with one important term or phrase replaced with a blank (use _____ for the blank)
|
| 177 |
+
- Exactly 4 answer options: the correct term and 3 plausible distractors
|
| 178 |
+
- The correct answer index (0-3)
|
| 179 |
+
- An explanation of why the correct term fits and why others don't
|
| 180 |
+
|
| 181 |
+
Make sure the sentence context gives enough clues for the correct answer.
|
| 182 |
+
|
| 183 |
+
Return the data in this exact structure:
|
| 184 |
+
{
|
| 185 |
+
"questions": [
|
| 186 |
+
{
|
| 187 |
+
"question": "The _____ is responsible for...",
|
| 188 |
+
"options": ["correct term", "distractor 1", "distractor 2", "distractor 3"],
|
| 189 |
+
"correctAnswer": 0,
|
| 190 |
+
"explanation": "Detailed explanation here..."
|
| 191 |
+
}
|
| 192 |
+
]
|
| 193 |
+
}`,
|
| 194 |
+
description: 'Fill-in-the-blank questions'
|
| 195 |
+
},
|
| 196 |
+
conceptual: {
|
| 197 |
+
template: `Create {{numQuestions}} {{difficulty}} level conceptual fill-in-the-blank questions about {{topic}}.
|
| 198 |
+
|
| 199 |
+
Focus on key concepts and principles.
|
| 200 |
+
|
| 201 |
+
For each question, provide:
|
| 202 |
+
- A sentence testing understanding of important terminology (use _____ for the blank)
|
| 203 |
+
- Exactly 4 answer options: the correct concept and 3 related but incorrect terms
|
| 204 |
+
- The correct answer index (0-3)
|
| 205 |
+
- A detailed explanation demonstrating conceptual knowledge
|
| 206 |
+
|
| 207 |
+
Create sentences that test deep understanding of terminology.
|
| 208 |
+
|
| 209 |
+
Return the data in this exact structure:
|
| 210 |
+
{
|
| 211 |
+
"questions": [
|
| 212 |
+
{
|
| 213 |
+
"question": "The fundamental concept of _____ explains...",
|
| 214 |
+
"options": ["correct concept", "related concept 1", "related concept 2", "related concept 3"],
|
| 215 |
+
"correctAnswer": 0,
|
| 216 |
+
"explanation": "Detailed conceptual explanation here..."
|
| 217 |
+
}
|
| 218 |
+
]
|
| 219 |
+
}`,
|
| 220 |
+
description: 'Conceptual fill-in-the-blank questions'
|
| 221 |
+
},
|
| 222 |
+
practical: {
|
| 223 |
+
template: `Generate {{numQuestions}} {{difficulty}} level practical fill-in-the-blank questions about {{topic}}.
|
| 224 |
+
|
| 225 |
+
Focus on real-world applications and scenarios.
|
| 226 |
+
|
| 227 |
+
For each question, provide:
|
| 228 |
+
- A practical sentence from real-world contexts (use _____ for the blank)
|
| 229 |
+
- Exactly 4 answer options: the correct practical term and 3 realistic alternatives
|
| 230 |
+
- The correct answer index (0-3)
|
| 231 |
+
- An explanation showing how the term applies in practice
|
| 232 |
+
|
| 233 |
+
Create sentences from practical examples and applications.
|
| 234 |
+
|
| 235 |
+
Return the data in this exact structure:
|
| 236 |
+
{
|
| 237 |
+
"questions": [
|
| 238 |
+
{
|
| 239 |
+
"question": "In practice, you would use _____ to...",
|
| 240 |
+
"options": ["correct tool/method", "alternative 1", "alternative 2", "alternative 3"],
|
| 241 |
+
"correctAnswer": 0,
|
| 242 |
+
"explanation": "Practical explanation with examples..."
|
| 243 |
+
}
|
| 244 |
+
]
|
| 245 |
+
}`,
|
| 246 |
+
description: 'Practical fill-in-the-blank questions'
|
| 247 |
+
}
|
| 248 |
+
},
|
| 249 |
+
'short-answer': {
|
| 250 |
+
basic: {
|
| 251 |
+
template: `Generate {{numQuestions}} {{difficulty}} level short-answer questions about {{topic}}.
|
| 252 |
+
|
| 253 |
+
For each question, provide:
|
| 254 |
+
- A question that normally requires a 2-3 sentence response
|
| 255 |
+
- Exactly 4 answer options: one comprehensive correct answer and 3 incomplete/incorrect responses
|
| 256 |
+
- The correct answer index (0-3)
|
| 257 |
+
- An explanation of why the correct answer is complete and accurate
|
| 258 |
+
|
| 259 |
+
Make the correct option a well-structured short answer covering key points.
|
| 260 |
+
|
| 261 |
+
Return the data in this exact structure:
|
| 262 |
+
{
|
| 263 |
+
"questions": [
|
| 264 |
+
{
|
| 265 |
+
"question": "Explain why/how/what...",
|
| 266 |
+
"options": [
|
| 267 |
+
"Complete correct answer covering all key points...",
|
| 268 |
+
"Incomplete answer missing key points...",
|
| 269 |
+
"Incorrect answer with misconceptions...",
|
| 270 |
+
"Partially correct but lacking depth..."
|
| 271 |
+
],
|
| 272 |
+
"correctAnswer": 0,
|
| 273 |
+
"explanation": "Detailed explanation here..."
|
| 274 |
+
}
|
| 275 |
+
]
|
| 276 |
+
}`,
|
| 277 |
+
description: 'Short answer questions requiring brief explanations'
|
| 278 |
+
},
|
| 279 |
+
conceptual: {
|
| 280 |
+
template: `Create {{numQuestions}} {{difficulty}} level conceptual short-answer questions about {{topic}}.
|
| 281 |
+
|
| 282 |
+
Focus on testing deep understanding of principles and concepts.
|
| 283 |
+
|
| 284 |
+
For each question, provide:
|
| 285 |
+
- A question requiring explanation of 'why' and 'how' rather than just 'what'
|
| 286 |
+
- Exactly 4 answer options: one that demonstrates deep conceptual understanding and 3 superficial responses
|
| 287 |
+
- The correct answer index (0-3)
|
| 288 |
+
- An explanation reinforcing the underlying concepts
|
| 289 |
+
|
| 290 |
+
Questions should test deep understanding, not memorization.
|
| 291 |
+
|
| 292 |
+
Return the data in this exact structure:
|
| 293 |
+
{
|
| 294 |
+
"questions": [
|
| 295 |
+
{
|
| 296 |
+
"question": "Explain the underlying principle of...",
|
| 297 |
+
"options": [
|
| 298 |
+
"Deep conceptual answer explaining principles...",
|
| 299 |
+
"Surface-level answer without depth...",
|
| 300 |
+
"Answer with conceptual errors...",
|
| 301 |
+
"Answer missing key concepts..."
|
| 302 |
+
],
|
| 303 |
+
"correctAnswer": 0,
|
| 304 |
+
"explanation": "Detailed conceptual explanation here..."
|
| 305 |
+
}
|
| 306 |
+
]
|
| 307 |
+
}`,
|
| 308 |
+
description: 'Conceptual short answer questions'
|
| 309 |
+
},
|
| 310 |
+
practical: {
|
| 311 |
+
template: `Generate {{numQuestions}} {{difficulty}} level practical short-answer questions about {{topic}}.
|
| 312 |
+
|
| 313 |
+
Focus on real-world applications and problem-solving scenarios.
|
| 314 |
+
|
| 315 |
+
For each question, provide:
|
| 316 |
+
- A question asking students to apply knowledge to practical situations
|
| 317 |
+
- Exactly 4 answer options: one practical, actionable solution and 3 less effective approaches
|
| 318 |
+
- The correct answer index (0-3)
|
| 319 |
+
- An explanation with real-world examples and applications
|
| 320 |
+
|
| 321 |
+
Questions should test practical application, not theoretical knowledge.
|
| 322 |
+
|
| 323 |
+
Return the data in this exact structure:
|
| 324 |
+
{
|
| 325 |
+
"questions": [
|
| 326 |
+
{
|
| 327 |
+
"question": "How would you solve/approach/handle...",
|
| 328 |
+
"options": [
|
| 329 |
+
"Practical, actionable solution with clear steps...",
|
| 330 |
+
"Theoretical approach lacking practicality...",
|
| 331 |
+
"Incomplete solution missing key steps...",
|
| 332 |
+
"Impractical approach with flaws..."
|
| 333 |
+
],
|
| 334 |
+
"correctAnswer": 0,
|
| 335 |
+
"explanation": "Practical explanation with examples..."
|
| 336 |
+
}
|
| 337 |
+
]
|
| 338 |
+
}`,
|
| 339 |
+
description: 'Practical short answer questions'
|
| 340 |
+
}
|
| 341 |
+
}
|
| 342 |
+
} as const;
|
| 343 |
+
|
| 344 |
+
// Template rendering function
|
| 345 |
+
export const renderTemplate = (template: string, params: Record<string, any>): string => {
|
| 346 |
+
return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
|
| 347 |
+
return params[key]?.toString() || match;
|
| 348 |
+
});
|
| 349 |
+
};
|
| 350 |
+
|
| 351 |
+
// Main prompt generation function
|
| 352 |
+
export const generateQuizPrompt = (params: QuizPromptParams, promptType: 'basic' | 'conceptual' | 'practical' = 'basic'): string => {
|
| 353 |
+
const questionType = params.questionType || 'multiple-choice';
|
| 354 |
+
const promptTemplate = QUIZ_PROMPTS[questionType]?.[promptType];
|
| 355 |
+
|
| 356 |
+
if (!promptTemplate) {
|
| 357 |
+
throw new Error(`Prompt not found for question type: ${questionType}, prompt type: ${promptType}`);
|
| 358 |
+
}
|
| 359 |
+
|
| 360 |
+
return renderTemplate(promptTemplate.template, params);
|
| 361 |
+
};
|
| 362 |
+
|
| 363 |
+
// Helper function to get available prompts
|
| 364 |
+
export const getAvailablePrompts = () => {
|
| 365 |
+
return Object.entries(QUIZ_PROMPTS).map(([questionType, prompts]) => ({
|
| 366 |
+
questionType,
|
| 367 |
+
prompts: Object.entries(prompts).map(([type, config]) => ({
|
| 368 |
+
type,
|
| 369 |
+
description: config.description
|
| 370 |
+
}))
|
| 371 |
+
}));
|
| 372 |
+
};
|
src/types/quiz.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export interface QuestionType {
|
| 2 |
+
id: string;
|
| 3 |
+
name: string;
|
| 4 |
+
icon: string;
|
| 5 |
+
description: string;
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
export interface QuestionParameters {
|
| 9 |
+
[key: string]: any;
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
export interface GeneratedQuestion {
|
| 13 |
+
id: string;
|
| 14 |
+
type: string;
|
| 15 |
+
stem: string;
|
| 16 |
+
content: any;
|
| 17 |
+
points: number;
|
| 18 |
+
createdAt: Date;
|
| 19 |
+
}
|