Oviya commited on
Commit
9db5bb0
·
1 Parent(s): 47111c6
package-lock.json CHANGED
@@ -271,6 +271,7 @@
271
  "url": "https://github.com/sponsors/ai"
272
  }
273
  ],
 
274
  "dependencies": {
275
  "nanoid": "^3.3.7",
276
  "picocolors": "^1.0.0",
@@ -354,6 +355,7 @@
354
  "version": "17.3.12",
355
  "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-17.3.12.tgz",
356
  "integrity": "sha512-9hsdWF4gRRcVJtPcCcYLaX1CIyM9wUu6r+xRl6zU5hq8qhl35hig6ounz7CXFAzLf0WDBdM16bPHouVGaG76lg==",
 
357
  "dependencies": {
358
  "tslib": "^2.3.0"
359
  },
@@ -420,6 +422,7 @@
420
  "version": "17.3.12",
421
  "resolved": "https://registry.npmjs.org/@angular/common/-/common-17.3.12.tgz",
422
  "integrity": "sha512-vabJzvrx76XXFrm1RJZ6o/CyG32piTB/1sfFfKHdlH1QrmArb8It4gyk9oEjZ1IkAD0HvBWlfWmn+T6Vx3pdUw==",
 
423
  "dependencies": {
424
  "tslib": "^2.3.0"
425
  },
@@ -435,6 +438,7 @@
435
  "version": "17.3.12",
436
  "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-17.3.12.tgz",
437
  "integrity": "sha512-vwI8oOL/gM+wPnptOVeBbMfZYwzRxQsovojZf+Zol9szl0k3SZ3FycWlxxXZGFu3VIEfrP6pXplTmyODS/Lt1w==",
 
438
  "dependencies": {
439
  "tslib": "^2.3.0"
440
  },
@@ -455,6 +459,7 @@
455
  "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-17.3.12.tgz",
456
  "integrity": "sha512-1F8M7nWfChzurb7obbvuE7mJXlHtY1UG58pcwcomVtpPb+kPavgAO8OEvJHYBMV+bzSxkXt5UIwL9lt9jHUxZA==",
457
  "dev": true,
 
458
  "dependencies": {
459
  "@babel/core": "7.23.9",
460
  "@jridgewell/sourcemap-codec": "^1.4.14",
@@ -527,6 +532,7 @@
527
  "version": "17.3.12",
528
  "resolved": "https://registry.npmjs.org/@angular/core/-/core-17.3.12.tgz",
529
  "integrity": "sha512-MuFt5yKi161JmauUta4Dh0m8ofwoq6Ino+KoOtkYMBGsSx+A7dSm+DUxxNwdj7+DNyg3LjVGCFgBFnq4g8z06A==",
 
530
  "dependencies": {
531
  "tslib": "^2.3.0"
532
  },
@@ -542,6 +548,7 @@
542
  "version": "17.3.12",
543
  "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-17.3.12.tgz",
544
  "integrity": "sha512-tV6r12Q3yEUlXwpVko4E+XscunTIpPkLbaiDn/MTL3Vxi2LZnsLgHyd/i38HaHN+e/H3B0a1ToSOhV5wf3ay4Q==",
 
545
  "dependencies": {
546
  "tslib": "^2.3.0"
547
  },
@@ -624,6 +631,7 @@
624
  "version": "17.3.12",
625
  "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-17.3.12.tgz",
626
  "integrity": "sha512-DYY04ptWh/ulMHzd+y52WCE8QnEYGeIiW3hEIFjCN8z0kbIdFdUtEB0IK5vjNL3ejyhUmphcpeT5PYf3YXtqWQ==",
 
627
  "dependencies": {
628
  "tslib": "^2.3.0"
629
  },
@@ -700,6 +708,7 @@
700
  "version": "7.24.0",
701
  "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.0.tgz",
702
  "integrity": "sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw==",
 
703
  "dependencies": {
704
  "@ampproject/remapping": "^2.2.0",
705
  "@babel/code-frame": "^7.23.5",
@@ -5334,6 +5343,7 @@
5334
  "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
5335
  "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
5336
  "dev": true,
 
5337
  "bin": {
5338
  "acorn": "bin/acorn"
5339
  },
@@ -5404,6 +5414,7 @@
5404
  "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
5405
  "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
5406
  "dev": true,
 
5407
  "dependencies": {
5408
  "fast-deep-equal": "^3.1.1",
5409
  "json-schema-traverse": "^1.0.0",
@@ -5887,6 +5898,7 @@
5887
  "url": "https://github.com/sponsors/ai"
5888
  }
5889
  ],
 
5890
  "dependencies": {
5891
  "caniuse-lite": "^1.0.30001688",
5892
  "electron-to-chromium": "^1.5.73",
@@ -8884,7 +8896,8 @@
8884
  "version": "5.1.2",
8885
  "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.1.2.tgz",
8886
  "integrity": "sha512-2oIUMGn00FdUiqz6epiiJr7xcFyNYj3rDcfmnzfkBnHyBQ3cBQUs4mmyGsOb7TTLb9kxk7dBcmEmqhDKkBoDyA==",
8887
- "dev": true
 
8888
  },
8889
  "node_modules/jest-diff": {
8890
  "version": "30.0.0-alpha.6",
@@ -9594,6 +9607,7 @@
9594
  "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.4.tgz",
9595
  "integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==",
9596
  "dev": true,
 
9597
  "dependencies": {
9598
  "@colors/colors": "1.5.0",
9599
  "body-parser": "^1.19.0",
@@ -9800,6 +9814,7 @@
9800
  "resolved": "https://registry.npmjs.org/less/-/less-4.2.0.tgz",
9801
  "integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==",
9802
  "dev": true,
 
9803
  "dependencies": {
9804
  "copy-anything": "^2.0.1",
9805
  "parse-node-version": "^1.0.1",
@@ -11414,6 +11429,7 @@
11414
  "url": "https://github.com/sponsors/ai"
11415
  }
11416
  ],
 
11417
  "dependencies": {
11418
  "nanoid": "^3.3.7",
11419
  "picocolors": "^1.1.1",
@@ -12245,6 +12261,7 @@
12245
  "version": "7.8.1",
12246
  "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
12247
  "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
 
12248
  "dependencies": {
12249
  "tslib": "^2.1.0"
12250
  }
@@ -12303,6 +12320,7 @@
12303
  "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.1.tgz",
12304
  "integrity": "sha512-wovtnV2PxzteLlfNzbgm1tFXPLoZILYAMJtvoXXkD7/+1uP41eKkIt1ypWq5/q2uT94qHjXehEYfmjKOvjL9sg==",
12305
  "dev": true,
 
12306
  "dependencies": {
12307
  "chokidar": ">=3.0.0 <4.0.0",
12308
  "immutable": "^4.0.0",
@@ -13420,6 +13438,7 @@
13420
  "resolved": "https://registry.npmjs.org/terser/-/terser-5.29.1.tgz",
13421
  "integrity": "sha512-lZQ/fyaIGxsbGxApKmoPTODIzELy3++mXhS5hOqaAWZjQtpq/hFHAc+rm29NND1rYRxRWKcjuARNwULNXa5RtQ==",
13422
  "dev": true,
 
13423
  "dependencies": {
13424
  "@jridgewell/source-map": "^0.3.3",
13425
  "acorn": "^8.8.2",
@@ -13658,6 +13677,7 @@
13658
  "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
13659
  "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
13660
  "dev": true,
 
13661
  "bin": {
13662
  "tsc": "bin/tsc",
13663
  "tsserver": "bin/tsserver"
@@ -13891,6 +13911,7 @@
13891
  "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.8.tgz",
13892
  "integrity": "sha512-mB8ToUuSmzODSpENgvpFk2fTiU/YQ1tmcVJJ4WZbq4fPdGJkFNVcmVL5k7iDug6xzWjjuGDKAuSievIsD6H7Xw==",
13893
  "dev": true,
 
13894
  "dependencies": {
13895
  "esbuild": "^0.19.3",
13896
  "postcss": "^8.4.35",
@@ -14406,6 +14427,7 @@
14406
  "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz",
14407
  "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==",
14408
  "dev": true,
 
14409
  "dependencies": {
14410
  "@types/estree": "^1.0.5",
14411
  "@webassemblyjs/ast": "^1.12.1",
@@ -14480,6 +14502,7 @@
14480
  "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz",
14481
  "integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==",
14482
  "dev": true,
 
14483
  "dependencies": {
14484
  "@types/bonjour": "^3.5.9",
14485
  "@types/connect-history-api-fallback": "^1.3.5",
@@ -14606,6 +14629,7 @@
14606
  "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
14607
  "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
14608
  "dev": true,
 
14609
  "dependencies": {
14610
  "fast-deep-equal": "^3.1.1",
14611
  "fast-json-stable-stringify": "^2.0.0",
@@ -14857,7 +14881,8 @@
14857
  "node_modules/zone.js": {
14858
  "version": "0.14.10",
14859
  "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.10.tgz",
14860
- "integrity": "sha512-YGAhaO7J5ywOXW6InXNlLmfU194F8lVgu7bRntUF3TiG8Y3nBK0x1UJJuHUP/e8IyihkjCYqhCScpSwnlaSRkQ=="
 
14861
  }
14862
  },
14863
  "dependencies": {
@@ -14976,6 +15001,7 @@
14976
  "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
14977
  "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
14978
  "dev": true,
 
14979
  "requires": {
14980
  "nanoid": "^3.3.7",
14981
  "picocolors": "^1.0.0",
@@ -15031,6 +15057,7 @@
15031
  "version": "17.3.12",
15032
  "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-17.3.12.tgz",
15033
  "integrity": "sha512-9hsdWF4gRRcVJtPcCcYLaX1CIyM9wUu6r+xRl6zU5hq8qhl35hig6ounz7CXFAzLf0WDBdM16bPHouVGaG76lg==",
 
15034
  "requires": {
15035
  "tslib": "^2.3.0"
15036
  }
@@ -15075,6 +15102,7 @@
15075
  "version": "17.3.12",
15076
  "resolved": "https://registry.npmjs.org/@angular/common/-/common-17.3.12.tgz",
15077
  "integrity": "sha512-vabJzvrx76XXFrm1RJZ6o/CyG32piTB/1sfFfKHdlH1QrmArb8It4gyk9oEjZ1IkAD0HvBWlfWmn+T6Vx3pdUw==",
 
15078
  "requires": {
15079
  "tslib": "^2.3.0"
15080
  }
@@ -15083,6 +15111,7 @@
15083
  "version": "17.3.12",
15084
  "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-17.3.12.tgz",
15085
  "integrity": "sha512-vwI8oOL/gM+wPnptOVeBbMfZYwzRxQsovojZf+Zol9szl0k3SZ3FycWlxxXZGFu3VIEfrP6pXplTmyODS/Lt1w==",
 
15086
  "requires": {
15087
  "tslib": "^2.3.0"
15088
  }
@@ -15092,6 +15121,7 @@
15092
  "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-17.3.12.tgz",
15093
  "integrity": "sha512-1F8M7nWfChzurb7obbvuE7mJXlHtY1UG58pcwcomVtpPb+kPavgAO8OEvJHYBMV+bzSxkXt5UIwL9lt9jHUxZA==",
15094
  "dev": true,
 
15095
  "requires": {
15096
  "@babel/core": "7.23.9",
15097
  "@jridgewell/sourcemap-codec": "^1.4.14",
@@ -15146,6 +15176,7 @@
15146
  "version": "17.3.12",
15147
  "resolved": "https://registry.npmjs.org/@angular/core/-/core-17.3.12.tgz",
15148
  "integrity": "sha512-MuFt5yKi161JmauUta4Dh0m8ofwoq6Ino+KoOtkYMBGsSx+A7dSm+DUxxNwdj7+DNyg3LjVGCFgBFnq4g8z06A==",
 
15149
  "requires": {
15150
  "tslib": "^2.3.0"
15151
  }
@@ -15154,6 +15185,7 @@
15154
  "version": "17.3.12",
15155
  "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-17.3.12.tgz",
15156
  "integrity": "sha512-tV6r12Q3yEUlXwpVko4E+XscunTIpPkLbaiDn/MTL3Vxi2LZnsLgHyd/i38HaHN+e/H3B0a1ToSOhV5wf3ay4Q==",
 
15157
  "requires": {
15158
  "tslib": "^2.3.0"
15159
  }
@@ -15217,6 +15249,7 @@
15217
  "version": "17.3.12",
15218
  "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-17.3.12.tgz",
15219
  "integrity": "sha512-DYY04ptWh/ulMHzd+y52WCE8QnEYGeIiW3hEIFjCN8z0kbIdFdUtEB0IK5vjNL3ejyhUmphcpeT5PYf3YXtqWQ==",
 
15220
  "requires": {
15221
  "tslib": "^2.3.0"
15222
  }
@@ -15256,6 +15289,7 @@
15256
  "version": "7.24.0",
15257
  "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.0.tgz",
15258
  "integrity": "sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw==",
 
15259
  "requires": {
15260
  "@ampproject/remapping": "^2.2.0",
15261
  "@babel/code-frame": "^7.23.5",
@@ -18715,7 +18749,8 @@
18715
  "version": "8.14.0",
18716
  "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
18717
  "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
18718
- "dev": true
 
18719
  },
18720
  "acorn-import-attributes": {
18721
  "version": "1.9.5",
@@ -18768,6 +18803,7 @@
18768
  "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
18769
  "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
18770
  "dev": true,
 
18771
  "requires": {
18772
  "fast-deep-equal": "^3.1.1",
18773
  "json-schema-traverse": "^1.0.0",
@@ -19112,6 +19148,7 @@
19112
  "version": "4.24.3",
19113
  "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.3.tgz",
19114
  "integrity": "sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==",
 
19115
  "requires": {
19116
  "caniuse-lite": "^1.0.30001688",
19117
  "electron-to-chromium": "^1.5.73",
@@ -21309,7 +21346,8 @@
21309
  "version": "5.1.2",
21310
  "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.1.2.tgz",
21311
  "integrity": "sha512-2oIUMGn00FdUiqz6epiiJr7xcFyNYj3rDcfmnzfkBnHyBQ3cBQUs4mmyGsOb7TTLb9kxk7dBcmEmqhDKkBoDyA==",
21312
- "dev": true
 
21313
  },
21314
  "jest-diff": {
21315
  "version": "30.0.0-alpha.6",
@@ -21852,6 +21890,7 @@
21852
  "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.4.tgz",
21853
  "integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==",
21854
  "dev": true,
 
21855
  "requires": {
21856
  "@colors/colors": "1.5.0",
21857
  "body-parser": "^1.19.0",
@@ -22019,6 +22058,7 @@
22019
  "resolved": "https://registry.npmjs.org/less/-/less-4.2.0.tgz",
22020
  "integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==",
22021
  "dev": true,
 
22022
  "requires": {
22023
  "copy-anything": "^2.0.1",
22024
  "errno": "^0.1.1",
@@ -23193,6 +23233,7 @@
23193
  "version": "8.4.49",
23194
  "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
23195
  "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
 
23196
  "requires": {
23197
  "nanoid": "^3.3.7",
23198
  "picocolors": "^1.1.1",
@@ -23765,6 +23806,7 @@
23765
  "version": "7.8.1",
23766
  "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
23767
  "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
 
23768
  "requires": {
23769
  "tslib": "^2.1.0"
23770
  }
@@ -23802,6 +23844,7 @@
23802
  "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.1.tgz",
23803
  "integrity": "sha512-wovtnV2PxzteLlfNzbgm1tFXPLoZILYAMJtvoXXkD7/+1uP41eKkIt1ypWq5/q2uT94qHjXehEYfmjKOvjL9sg==",
23804
  "dev": true,
 
23805
  "requires": {
23806
  "chokidar": ">=3.0.0 <4.0.0",
23807
  "immutable": "^4.0.0",
@@ -24643,6 +24686,7 @@
24643
  "resolved": "https://registry.npmjs.org/terser/-/terser-5.29.1.tgz",
24644
  "integrity": "sha512-lZQ/fyaIGxsbGxApKmoPTODIzELy3++mXhS5hOqaAWZjQtpq/hFHAc+rm29NND1rYRxRWKcjuARNwULNXa5RtQ==",
24645
  "dev": true,
 
24646
  "requires": {
24647
  "@jridgewell/source-map": "^0.3.3",
24648
  "acorn": "^8.8.2",
@@ -24810,7 +24854,8 @@
24810
  "version": "5.3.3",
24811
  "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
24812
  "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
24813
- "dev": true
 
24814
  },
24815
  "ua-parser-js": {
24816
  "version": "0.7.40",
@@ -24957,6 +25002,7 @@
24957
  "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.8.tgz",
24958
  "integrity": "sha512-mB8ToUuSmzODSpENgvpFk2fTiU/YQ1tmcVJJ4WZbq4fPdGJkFNVcmVL5k7iDug6xzWjjuGDKAuSievIsD6H7Xw==",
24959
  "dev": true,
 
24960
  "requires": {
24961
  "esbuild": "^0.19.3",
24962
  "fsevents": "~2.3.3",
@@ -25210,6 +25256,7 @@
25210
  "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz",
25211
  "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==",
25212
  "dev": true,
 
25213
  "requires": {
25214
  "@types/estree": "^1.0.5",
25215
  "@webassemblyjs/ast": "^1.12.1",
@@ -25241,6 +25288,7 @@
25241
  "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
25242
  "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
25243
  "dev": true,
 
25244
  "requires": {
25245
  "fast-deep-equal": "^3.1.1",
25246
  "fast-json-stable-stringify": "^2.0.0",
@@ -25308,6 +25356,7 @@
25308
  "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz",
25309
  "integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==",
25310
  "dev": true,
 
25311
  "requires": {
25312
  "@types/bonjour": "^3.5.9",
25313
  "@types/connect-history-api-fallback": "^1.3.5",
@@ -25509,7 +25558,8 @@
25509
  "zone.js": {
25510
  "version": "0.14.10",
25511
  "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.10.tgz",
25512
- "integrity": "sha512-YGAhaO7J5ywOXW6InXNlLmfU194F8lVgu7bRntUF3TiG8Y3nBK0x1UJJuHUP/e8IyihkjCYqhCScpSwnlaSRkQ=="
 
25513
  }
25514
  }
25515
  }
 
271
  "url": "https://github.com/sponsors/ai"
272
  }
273
  ],
274
+ "peer": true,
275
  "dependencies": {
276
  "nanoid": "^3.3.7",
277
  "picocolors": "^1.0.0",
 
355
  "version": "17.3.12",
356
  "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-17.3.12.tgz",
357
  "integrity": "sha512-9hsdWF4gRRcVJtPcCcYLaX1CIyM9wUu6r+xRl6zU5hq8qhl35hig6ounz7CXFAzLf0WDBdM16bPHouVGaG76lg==",
358
+ "peer": true,
359
  "dependencies": {
360
  "tslib": "^2.3.0"
361
  },
 
422
  "version": "17.3.12",
423
  "resolved": "https://registry.npmjs.org/@angular/common/-/common-17.3.12.tgz",
424
  "integrity": "sha512-vabJzvrx76XXFrm1RJZ6o/CyG32piTB/1sfFfKHdlH1QrmArb8It4gyk9oEjZ1IkAD0HvBWlfWmn+T6Vx3pdUw==",
425
+ "peer": true,
426
  "dependencies": {
427
  "tslib": "^2.3.0"
428
  },
 
438
  "version": "17.3.12",
439
  "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-17.3.12.tgz",
440
  "integrity": "sha512-vwI8oOL/gM+wPnptOVeBbMfZYwzRxQsovojZf+Zol9szl0k3SZ3FycWlxxXZGFu3VIEfrP6pXplTmyODS/Lt1w==",
441
+ "peer": true,
442
  "dependencies": {
443
  "tslib": "^2.3.0"
444
  },
 
459
  "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-17.3.12.tgz",
460
  "integrity": "sha512-1F8M7nWfChzurb7obbvuE7mJXlHtY1UG58pcwcomVtpPb+kPavgAO8OEvJHYBMV+bzSxkXt5UIwL9lt9jHUxZA==",
461
  "dev": true,
462
+ "peer": true,
463
  "dependencies": {
464
  "@babel/core": "7.23.9",
465
  "@jridgewell/sourcemap-codec": "^1.4.14",
 
532
  "version": "17.3.12",
533
  "resolved": "https://registry.npmjs.org/@angular/core/-/core-17.3.12.tgz",
534
  "integrity": "sha512-MuFt5yKi161JmauUta4Dh0m8ofwoq6Ino+KoOtkYMBGsSx+A7dSm+DUxxNwdj7+DNyg3LjVGCFgBFnq4g8z06A==",
535
+ "peer": true,
536
  "dependencies": {
537
  "tslib": "^2.3.0"
538
  },
 
548
  "version": "17.3.12",
549
  "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-17.3.12.tgz",
550
  "integrity": "sha512-tV6r12Q3yEUlXwpVko4E+XscunTIpPkLbaiDn/MTL3Vxi2LZnsLgHyd/i38HaHN+e/H3B0a1ToSOhV5wf3ay4Q==",
551
+ "peer": true,
552
  "dependencies": {
553
  "tslib": "^2.3.0"
554
  },
 
631
  "version": "17.3.12",
632
  "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-17.3.12.tgz",
633
  "integrity": "sha512-DYY04ptWh/ulMHzd+y52WCE8QnEYGeIiW3hEIFjCN8z0kbIdFdUtEB0IK5vjNL3ejyhUmphcpeT5PYf3YXtqWQ==",
634
+ "peer": true,
635
  "dependencies": {
636
  "tslib": "^2.3.0"
637
  },
 
708
  "version": "7.24.0",
709
  "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.0.tgz",
710
  "integrity": "sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw==",
711
+ "peer": true,
712
  "dependencies": {
713
  "@ampproject/remapping": "^2.2.0",
714
  "@babel/code-frame": "^7.23.5",
 
5343
  "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
5344
  "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
5345
  "dev": true,
5346
+ "peer": true,
5347
  "bin": {
5348
  "acorn": "bin/acorn"
5349
  },
 
5414
  "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
5415
  "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
5416
  "dev": true,
5417
+ "peer": true,
5418
  "dependencies": {
5419
  "fast-deep-equal": "^3.1.1",
5420
  "json-schema-traverse": "^1.0.0",
 
5898
  "url": "https://github.com/sponsors/ai"
5899
  }
5900
  ],
5901
+ "peer": true,
5902
  "dependencies": {
5903
  "caniuse-lite": "^1.0.30001688",
5904
  "electron-to-chromium": "^1.5.73",
 
8896
  "version": "5.1.2",
8897
  "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.1.2.tgz",
8898
  "integrity": "sha512-2oIUMGn00FdUiqz6epiiJr7xcFyNYj3rDcfmnzfkBnHyBQ3cBQUs4mmyGsOb7TTLb9kxk7dBcmEmqhDKkBoDyA==",
8899
+ "dev": true,
8900
+ "peer": true
8901
  },
8902
  "node_modules/jest-diff": {
8903
  "version": "30.0.0-alpha.6",
 
9607
  "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.4.tgz",
9608
  "integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==",
9609
  "dev": true,
9610
+ "peer": true,
9611
  "dependencies": {
9612
  "@colors/colors": "1.5.0",
9613
  "body-parser": "^1.19.0",
 
9814
  "resolved": "https://registry.npmjs.org/less/-/less-4.2.0.tgz",
9815
  "integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==",
9816
  "dev": true,
9817
+ "peer": true,
9818
  "dependencies": {
9819
  "copy-anything": "^2.0.1",
9820
  "parse-node-version": "^1.0.1",
 
11429
  "url": "https://github.com/sponsors/ai"
11430
  }
11431
  ],
11432
+ "peer": true,
11433
  "dependencies": {
11434
  "nanoid": "^3.3.7",
11435
  "picocolors": "^1.1.1",
 
12261
  "version": "7.8.1",
12262
  "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
12263
  "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
12264
+ "peer": true,
12265
  "dependencies": {
12266
  "tslib": "^2.1.0"
12267
  }
 
12320
  "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.1.tgz",
12321
  "integrity": "sha512-wovtnV2PxzteLlfNzbgm1tFXPLoZILYAMJtvoXXkD7/+1uP41eKkIt1ypWq5/q2uT94qHjXehEYfmjKOvjL9sg==",
12322
  "dev": true,
12323
+ "peer": true,
12324
  "dependencies": {
12325
  "chokidar": ">=3.0.0 <4.0.0",
12326
  "immutable": "^4.0.0",
 
13438
  "resolved": "https://registry.npmjs.org/terser/-/terser-5.29.1.tgz",
13439
  "integrity": "sha512-lZQ/fyaIGxsbGxApKmoPTODIzELy3++mXhS5hOqaAWZjQtpq/hFHAc+rm29NND1rYRxRWKcjuARNwULNXa5RtQ==",
13440
  "dev": true,
13441
+ "peer": true,
13442
  "dependencies": {
13443
  "@jridgewell/source-map": "^0.3.3",
13444
  "acorn": "^8.8.2",
 
13677
  "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
13678
  "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
13679
  "dev": true,
13680
+ "peer": true,
13681
  "bin": {
13682
  "tsc": "bin/tsc",
13683
  "tsserver": "bin/tsserver"
 
13911
  "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.8.tgz",
13912
  "integrity": "sha512-mB8ToUuSmzODSpENgvpFk2fTiU/YQ1tmcVJJ4WZbq4fPdGJkFNVcmVL5k7iDug6xzWjjuGDKAuSievIsD6H7Xw==",
13913
  "dev": true,
13914
+ "peer": true,
13915
  "dependencies": {
13916
  "esbuild": "^0.19.3",
13917
  "postcss": "^8.4.35",
 
14427
  "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz",
14428
  "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==",
14429
  "dev": true,
14430
+ "peer": true,
14431
  "dependencies": {
14432
  "@types/estree": "^1.0.5",
14433
  "@webassemblyjs/ast": "^1.12.1",
 
14502
  "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz",
14503
  "integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==",
14504
  "dev": true,
14505
+ "peer": true,
14506
  "dependencies": {
14507
  "@types/bonjour": "^3.5.9",
14508
  "@types/connect-history-api-fallback": "^1.3.5",
 
14629
  "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
14630
  "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
14631
  "dev": true,
14632
+ "peer": true,
14633
  "dependencies": {
14634
  "fast-deep-equal": "^3.1.1",
14635
  "fast-json-stable-stringify": "^2.0.0",
 
14881
  "node_modules/zone.js": {
14882
  "version": "0.14.10",
14883
  "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.10.tgz",
14884
+ "integrity": "sha512-YGAhaO7J5ywOXW6InXNlLmfU194F8lVgu7bRntUF3TiG8Y3nBK0x1UJJuHUP/e8IyihkjCYqhCScpSwnlaSRkQ==",
14885
+ "peer": true
14886
  }
14887
  },
14888
  "dependencies": {
 
15001
  "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
15002
  "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
15003
  "dev": true,
15004
+ "peer": true,
15005
  "requires": {
15006
  "nanoid": "^3.3.7",
15007
  "picocolors": "^1.0.0",
 
15057
  "version": "17.3.12",
15058
  "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-17.3.12.tgz",
15059
  "integrity": "sha512-9hsdWF4gRRcVJtPcCcYLaX1CIyM9wUu6r+xRl6zU5hq8qhl35hig6ounz7CXFAzLf0WDBdM16bPHouVGaG76lg==",
15060
+ "peer": true,
15061
  "requires": {
15062
  "tslib": "^2.3.0"
15063
  }
 
15102
  "version": "17.3.12",
15103
  "resolved": "https://registry.npmjs.org/@angular/common/-/common-17.3.12.tgz",
15104
  "integrity": "sha512-vabJzvrx76XXFrm1RJZ6o/CyG32piTB/1sfFfKHdlH1QrmArb8It4gyk9oEjZ1IkAD0HvBWlfWmn+T6Vx3pdUw==",
15105
+ "peer": true,
15106
  "requires": {
15107
  "tslib": "^2.3.0"
15108
  }
 
15111
  "version": "17.3.12",
15112
  "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-17.3.12.tgz",
15113
  "integrity": "sha512-vwI8oOL/gM+wPnptOVeBbMfZYwzRxQsovojZf+Zol9szl0k3SZ3FycWlxxXZGFu3VIEfrP6pXplTmyODS/Lt1w==",
15114
+ "peer": true,
15115
  "requires": {
15116
  "tslib": "^2.3.0"
15117
  }
 
15121
  "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-17.3.12.tgz",
15122
  "integrity": "sha512-1F8M7nWfChzurb7obbvuE7mJXlHtY1UG58pcwcomVtpPb+kPavgAO8OEvJHYBMV+bzSxkXt5UIwL9lt9jHUxZA==",
15123
  "dev": true,
15124
+ "peer": true,
15125
  "requires": {
15126
  "@babel/core": "7.23.9",
15127
  "@jridgewell/sourcemap-codec": "^1.4.14",
 
15176
  "version": "17.3.12",
15177
  "resolved": "https://registry.npmjs.org/@angular/core/-/core-17.3.12.tgz",
15178
  "integrity": "sha512-MuFt5yKi161JmauUta4Dh0m8ofwoq6Ino+KoOtkYMBGsSx+A7dSm+DUxxNwdj7+DNyg3LjVGCFgBFnq4g8z06A==",
15179
+ "peer": true,
15180
  "requires": {
15181
  "tslib": "^2.3.0"
15182
  }
 
15185
  "version": "17.3.12",
15186
  "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-17.3.12.tgz",
15187
  "integrity": "sha512-tV6r12Q3yEUlXwpVko4E+XscunTIpPkLbaiDn/MTL3Vxi2LZnsLgHyd/i38HaHN+e/H3B0a1ToSOhV5wf3ay4Q==",
15188
+ "peer": true,
15189
  "requires": {
15190
  "tslib": "^2.3.0"
15191
  }
 
15249
  "version": "17.3.12",
15250
  "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-17.3.12.tgz",
15251
  "integrity": "sha512-DYY04ptWh/ulMHzd+y52WCE8QnEYGeIiW3hEIFjCN8z0kbIdFdUtEB0IK5vjNL3ejyhUmphcpeT5PYf3YXtqWQ==",
15252
+ "peer": true,
15253
  "requires": {
15254
  "tslib": "^2.3.0"
15255
  }
 
15289
  "version": "7.24.0",
15290
  "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.0.tgz",
15291
  "integrity": "sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw==",
15292
+ "peer": true,
15293
  "requires": {
15294
  "@ampproject/remapping": "^2.2.0",
15295
  "@babel/code-frame": "^7.23.5",
 
18749
  "version": "8.14.0",
18750
  "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
18751
  "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
18752
+ "dev": true,
18753
+ "peer": true
18754
  },
18755
  "acorn-import-attributes": {
18756
  "version": "1.9.5",
 
18803
  "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
18804
  "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
18805
  "dev": true,
18806
+ "peer": true,
18807
  "requires": {
18808
  "fast-deep-equal": "^3.1.1",
18809
  "json-schema-traverse": "^1.0.0",
 
19148
  "version": "4.24.3",
19149
  "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.3.tgz",
19150
  "integrity": "sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==",
19151
+ "peer": true,
19152
  "requires": {
19153
  "caniuse-lite": "^1.0.30001688",
19154
  "electron-to-chromium": "^1.5.73",
 
21346
  "version": "5.1.2",
21347
  "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.1.2.tgz",
21348
  "integrity": "sha512-2oIUMGn00FdUiqz6epiiJr7xcFyNYj3rDcfmnzfkBnHyBQ3cBQUs4mmyGsOb7TTLb9kxk7dBcmEmqhDKkBoDyA==",
21349
+ "dev": true,
21350
+ "peer": true
21351
  },
21352
  "jest-diff": {
21353
  "version": "30.0.0-alpha.6",
 
21890
  "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.4.tgz",
21891
  "integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==",
21892
  "dev": true,
21893
+ "peer": true,
21894
  "requires": {
21895
  "@colors/colors": "1.5.0",
21896
  "body-parser": "^1.19.0",
 
22058
  "resolved": "https://registry.npmjs.org/less/-/less-4.2.0.tgz",
22059
  "integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==",
22060
  "dev": true,
22061
+ "peer": true,
22062
  "requires": {
22063
  "copy-anything": "^2.0.1",
22064
  "errno": "^0.1.1",
 
23233
  "version": "8.4.49",
23234
  "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
23235
  "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
23236
+ "peer": true,
23237
  "requires": {
23238
  "nanoid": "^3.3.7",
23239
  "picocolors": "^1.1.1",
 
23806
  "version": "7.8.1",
23807
  "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
23808
  "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
23809
+ "peer": true,
23810
  "requires": {
23811
  "tslib": "^2.1.0"
23812
  }
 
23844
  "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.1.tgz",
23845
  "integrity": "sha512-wovtnV2PxzteLlfNzbgm1tFXPLoZILYAMJtvoXXkD7/+1uP41eKkIt1ypWq5/q2uT94qHjXehEYfmjKOvjL9sg==",
23846
  "dev": true,
23847
+ "peer": true,
23848
  "requires": {
23849
  "chokidar": ">=3.0.0 <4.0.0",
23850
  "immutable": "^4.0.0",
 
24686
  "resolved": "https://registry.npmjs.org/terser/-/terser-5.29.1.tgz",
24687
  "integrity": "sha512-lZQ/fyaIGxsbGxApKmoPTODIzELy3++mXhS5hOqaAWZjQtpq/hFHAc+rm29NND1rYRxRWKcjuARNwULNXa5RtQ==",
24688
  "dev": true,
24689
+ "peer": true,
24690
  "requires": {
24691
  "@jridgewell/source-map": "^0.3.3",
24692
  "acorn": "^8.8.2",
 
24854
  "version": "5.3.3",
24855
  "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
24856
  "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
24857
+ "dev": true,
24858
+ "peer": true
24859
  },
24860
  "ua-parser-js": {
24861
  "version": "0.7.40",
 
25002
  "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.8.tgz",
25003
  "integrity": "sha512-mB8ToUuSmzODSpENgvpFk2fTiU/YQ1tmcVJJ4WZbq4fPdGJkFNVcmVL5k7iDug6xzWjjuGDKAuSievIsD6H7Xw==",
25004
  "dev": true,
25005
+ "peer": true,
25006
  "requires": {
25007
  "esbuild": "^0.19.3",
25008
  "fsevents": "~2.3.3",
 
25256
  "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz",
25257
  "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==",
25258
  "dev": true,
25259
+ "peer": true,
25260
  "requires": {
25261
  "@types/estree": "^1.0.5",
25262
  "@webassemblyjs/ast": "^1.12.1",
 
25288
  "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
25289
  "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
25290
  "dev": true,
25291
+ "peer": true,
25292
  "requires": {
25293
  "fast-deep-equal": "^3.1.1",
25294
  "fast-json-stable-stringify": "^2.0.0",
 
25356
  "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz",
25357
  "integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==",
25358
  "dev": true,
25359
+ "peer": true,
25360
  "requires": {
25361
  "@types/bonjour": "^3.5.9",
25362
  "@types/connect-history-api-fallback": "^1.3.5",
 
25558
  "zone.js": {
25559
  "version": "0.14.10",
25560
  "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.10.tgz",
25561
+ "integrity": "sha512-YGAhaO7J5ywOXW6InXNlLmfU194F8lVgu7bRntUF3TiG8Y3nBK0x1UJJuHUP/e8IyihkjCYqhCScpSwnlaSRkQ==",
25562
+ "peer": true
25563
  }
25564
  }
25565
  }
src/app/generate-questions/generate-questions.component.html CHANGED
@@ -83,8 +83,8 @@
83
  pattern="[A-Za-z]*"
84
  (input)="onInput($event, question.index); onAnswerChange(question.index)"
85
  [ngClass]="{
86
- 'correct-answer': !isLoading && isChecked && userAnswers[question.index] === questionsWithAnswers[question.index].correctAnswer,
87
- 'wrong-answer': !isLoading && isChecked && userAnswers[question.index] !== questionsWithAnswers[question.index].correctAnswer
88
  }" />
89
  </span>
90
 
@@ -124,7 +124,7 @@
124
 
125
  <ul class="hint-list" role="list">
126
  <ng-container *ngFor="let hint of hints; let i = index">
127
- <li *ngIf="hint && hint.trim() !== '' && userAnswers[i] !== questionsWithAnswers[i]?.correctAnswer"
128
  class="hint-item">
129
  <strong>Hint for Question {{ i + 1 }}:</strong> {{ hint }}
130
  </li>
 
83
  pattern="[A-Za-z]*"
84
  (input)="onInput($event, question.index); onAnswerChange(question.index)"
85
  [ngClass]="{
86
+ 'correct-answer': answerStatuses[question.index] === 'correct',
87
+ 'wrong-answer': answerStatuses[question.index] === 'incorrect'
88
  }" />
89
  </span>
90
 
 
124
 
125
  <ul class="hint-list" role="list">
126
  <ng-container *ngFor="let hint of hints; let i = index">
127
+ <li *ngIf="hint && hint.trim() !== ''"
128
  class="hint-item">
129
  <strong>Hint for Question {{ i + 1 }}:</strong> {{ hint }}
130
  </li>
src/app/generate-questions/generate-questions.component.ts CHANGED
@@ -1,13 +1,11 @@
1
- import { Component } from '@angular/core';
2
  import { FormsModule } from '@angular/forms';
3
  import { CommonModule } from '@angular/common';
4
  import { GenerateQuestionsService } from './generate-questions.service';
5
  import { Router } from '@angular/router';
6
  import confetti from 'canvas-confetti';
7
- import { ChangeDetectorRef } from '@angular/core';
8
  import { HeaderComponent } from '../shared/header/header.component';
9
- import { ButtonComponent } from '../shared/button/button.component';
10
-
11
 
12
  interface QuestionWithAnswer {
13
  question: string;
@@ -19,530 +17,450 @@ interface Question {
19
  index: number;
20
  }
21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  @Component({
23
  selector: 'app-generate-questions',
24
  standalone: true,
25
- imports: [FormsModule, CommonModule, HeaderComponent, ButtonComponent],
26
  templateUrl: './generate-questions.component.html',
27
  styleUrls: ['./generate-questions.component.css']
28
  })
29
- export class GenerateQuestionsComponent {
30
- topic: string = "";
31
- hardcodedTopics: string[] = [
32
- "Noun",
33
- "Verb",
34
- "Past Tense",
35
- "Adjective",
36
- "Present Continuous",
37
- ];
38
-
39
- showSuggestions: boolean = false;
40
 
41
  userAnswers: string[] = [];
 
42
  feedback: string[] = [];
43
- error: string = "";
44
- isChecked: boolean = false;
45
- isAnswerModified: boolean = false;
46
- questions: { parts: string[]; index: number }[] = [];
47
- attemptCounts: { [key: number]: number } = {};
48
- correctAnswers: string[] = [];
49
- questionsWithAnswers: QuestionWithAnswer[] = [];
50
- currentDifficulty: string = "basic";
51
- // difficultyLevels: string[] = ['basic', 'elementary', 'pre-intermediate', 'intermediate', 'upper-intermediate', 'advanced', 'hard'];
52
- difficultyLevels: string[] = ["basic", "intermediate", "expert"];
53
- isDropdownDisabled: boolean = false;
54
- isValidationInProgress: boolean = false;
55
- isTopicLocked: boolean = false;
56
- isAllLevelsCompleted: boolean = false;
57
- readonlyAnswers: boolean[] = [];
58
  hints: string[] = [];
59
- attempts: number[] = [];
60
- isQuestionsGenerated: boolean = false;
61
- isGenerateDisabled: boolean = true;
62
- isResetDisabled: boolean = true;
63
- isFirstAttemptDone: boolean = false;
64
- hasNewHints: boolean = false;
65
- isLoading: boolean = false;
66
- isValidationCompleted = false;
67
-
68
- showLevelTooltip: boolean = false;
69
- activateLevelDot: boolean = false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
 
71
- isTyping: boolean = false;
72
- validated: boolean = false;
 
 
 
73
 
74
  constructor(
75
- private generateQuestionsService: GenerateQuestionsService,
76
- private router: Router,
77
- private cdr: ChangeDetectorRef,
78
- ) { }
79
-
80
- onTopicChange() {
81
- if (this.topic.trim().length > 0) {
82
- this.isGenerateDisabled = false;
83
- } else {
84
- this.isGenerateDisabled = true;
85
- }
86
- if (this.topic.trim().length === 0) {
87
- this.showSuggestions = true;
88
- } else {
89
- this.showSuggestions = false;
90
- }
 
 
 
 
91
  }
92
 
93
  getProgressWidth(): string {
94
  const index = this.difficultyLevels.indexOf(this.currentDifficulty);
95
- if (index === -1 || this.difficultyLevels.length <= 1) return "0%";
96
- const percent = (index / (this.difficultyLevels.length - 1)) * 100;
97
- return `${percent}%`;
98
  }
99
 
100
- generateQuestions() {
101
- this.hints = [];
102
- this.isQuestionsGenerated = false;
103
- this.showLevelTooltip = true;
104
- this.activateLevelDot = true;
105
-
106
  if (this.isAllLevelsCompleted) {
107
- this.error = "Please reset to start over.";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  return;
109
  }
110
 
111
- this.isTopicLocked = true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  this.showSuggestions = false;
113
  this.readonlyAnswers = [];
114
- this.isDropdownDisabled = true;
115
-
116
- console.log(`Generating questions for difficulty level: ${this.currentDifficulty}`);
117
-
118
- this.generateQuestionsService
119
- .generateQuestions(this.topic, this.currentDifficulty)
120
- .subscribe(
121
- (response) => {
122
- console.log("Response from backend:", response);
123
-
124
- // --- Minimal change: accept new shape { text } and fall back to old generations[0].text ---
125
- const rawQuestions = (response?.text ?? response?.generations?.[0]?.text ?? '').trim();
126
-
127
- if (rawQuestions) {
128
- console.log("Raw Questions:", rawQuestions);
129
- this.isQuestionsGenerated = true;
130
-
131
- this.isGenerateDisabled = true;
132
- this.isResetDisabled = false;
133
-
134
- this.questionsWithAnswers = [];
135
-
136
- // same regex you already use
137
- const regex = /(\d+\.\s*.+?_______)\s*(.*?)\s*\(([^)]+)\)\s*$/gm;
138
- let match;
139
-
140
- while ((match = regex.exec(rawQuestions)) !== null) {
141
- console.log("Match:", match);
142
-
143
- const questionText = (match[1] ?? "").trim(); // up to _______
144
- const afterBlankRaw = (match[2] ?? ""); // text after the blank
145
- const correctAnswer = (match[3] ?? "").trim(); // answer in parentheses
146
-
147
- // clean any stray "_" or "-" right after the blank
148
- const afterBlankClean = afterBlankRaw
149
- .replace(/^[_-]+/, "")
150
- .replace(/^\s+/, " ")
151
- .replace(/\s{2,}/g, " ");
152
-
153
- if (questionText && correctAnswer) {
154
- this.questionsWithAnswers.push({
155
- question: `${questionText}${afterBlankClean}`,
156
- correctAnswer,
157
- });
158
- }
159
- }
160
-
161
- console.log("Extracted Questions with Answers:", this.questionsWithAnswers);
162
-
163
- // Build renderable questions (parts before/after the blank)
164
- this.questions = this.questionsWithAnswers.map((q, index) => {
165
- const normalized = q.question.replace(/\s*_{3,}\s*/g, "_______");
166
- const parts = normalized.split("_______");
167
-
168
- if (parts.length === 2) {
169
- parts[0] = parts[0].replace(/_+$/, "").trim();
170
- parts[1] = parts[1].replace(/^_+/, "").trim();
171
- }
172
-
173
- return { parts, index };
174
- });
175
-
176
- this.userAnswers = new Array(this.questions.length).fill("");
177
- this.feedback = new Array(this.questions.length).fill("");
178
- this.readonlyAnswers = new Array(this.questions.length).fill(false);
179
- this.isChecked = false;
180
-
181
- this.attemptCounts = this.questions.reduce((acc, _q, i) => {
182
- acc[i] = 0;
183
- return acc;
184
- }, {} as { [key: number]: number });
185
-
186
- console.log("Questions without answers:", this.questions);
187
- } else {
188
- this.error = "No questions generated. Please try again.";
189
- }
190
- },
191
- (error) => {
192
- if (error.status === 400 && error.error.message) {
193
- this.error = error.error.message;
194
- } else {
195
- this.error = "Failed to fetch questions. Please try again later.";
196
- }
197
- console.error("Error fetching questions:", error);
198
- },
199
- );
200
- }
201
-
202
- resetTopic() {
203
- this.topic = "";
204
  this.questions = [];
205
  this.questionsWithAnswers = [];
206
  this.userAnswers = [];
 
207
  this.readonlyAnswers = [];
208
  this.feedback = [];
209
  this.hints = [];
210
- this.attemptCounts = [];
211
- this.isChecked = false;
 
212
  this.isValidationInProgress = false;
213
  this.isFirstAttemptDone = false;
214
  this.isAllLevelsCompleted = false;
215
  this.hasNewHints = false;
216
- this.isQuestionsGenerated = false;
217
- this.isTopicLocked = false;
218
- this.validated = false;
219
- this.currentDifficulty = "basic";
220
- this.error = "";
221
- this.isGenerateDisabled = true;
222
- this.isResetDisabled = true;
223
- this.showSuggestions = false;
224
- this.showLevelTooltip = false;
225
- this.activateLevelDot = false;
226
  this.showSuggestions = false;
227
- this.isDropdownDisabled = false;
228
- this.isTopicLocked = false;
 
 
 
229
  this.stopConfetti();
230
- // Stop any active countdowns
231
- Object.keys(this.countdownTimers).forEach(k => clearInterval(this.countdownTimers[+k]));
232
- this.countdownTimers = {};
233
- this.countdowns = [];
234
- this.showCountdown = [];
235
- this.stopGlobalCountdown();
236
- this.stopOverlay()
237
  }
238
 
239
  areAllAnswersFilled(): boolean {
240
- return this.userAnswers.every((answer) => answer?.trim() !== "");
241
  }
242
 
243
- closeErrorPopup() {
244
- this.error = "";
245
- this.isGenerateDisabled = true;
246
- this.isResetDisabled = false;
247
  }
248
 
249
- // Hint icon one-time flags
250
- showHintIcon: boolean = false; // controls rendering of the “i” button
251
- hasShownHintIcon: boolean = false; // remembers if it was shown once
252
- isHintMenuVisible: boolean = false; // remove if you already have it
 
 
 
253
 
254
- checkAllAnswers() {
255
- this.isChecked = true;
 
 
256
  this.isValidationInProgress = true;
257
  this.isLoading = true;
258
- this.isValidationCompleted = true;
259
- this.validated = true;
260
  this.isFirstAttemptDone = true;
261
 
262
- const requestPayload = this.questions.map((questionObj, index) => ({
263
  topic: this.topic,
264
- question: questionObj.parts.join("_______"),
265
- user_answer: this.userAnswers[index],
266
  }));
267
 
268
- console.log("Checking answers...");
269
- console.log("User Answers:", this.userAnswers);
270
- console.log(
271
- "Correct Answers:",
272
- this.questionsWithAnswers.map((q) => q.correctAnswer),
273
- );
274
-
275
- this.generateQuestionsService.validateAllAnswers(requestPayload).subscribe(
276
- (response) => {
277
- console.log("Response from backend for all answers validation:", response);
278
-
279
- if (response.results && response.results.length > 0) {
280
- let allAttemptsCompleted = true;
281
-
282
- // Track what happened this validation cycle
283
- let hadFirstAttemptWrong = false; // someone is on first wrong attempt
284
- let hadSecondAttemptWrong = false; // someone is on second wrong attempt (autofill path)
285
-
286
- response.results.forEach((result: any, index: number) => {
287
- this.attemptCounts[index]++;
288
- const validationResponse = result.validation_response;
289
- const hint = result.hint ? result.hint.trim() : "";
290
-
291
- const userAnswerRaw = this.userAnswers[index] ?? '';
292
- const userAnswer = userAnswerRaw.trim().toLowerCase();
293
-
294
- const correctAnswerRaw = this.questionsWithAnswers[index].correctAnswer ?? '';
295
- const correctAnswer = correctAnswerRaw.trim().toLowerCase();
296
-
297
- console.log(`Validation Response for Question ${index + 1}: ${validationResponse}`);
298
- console.log(`User Answer: ${userAnswer}, Correct Answer: ${correctAnswer}`);
299
-
300
- if (userAnswer === correctAnswer) {
301
- console.log(`✅ Correct answer for question ${index + 1}: ${this.userAnswers[index]}`);
302
- this.readonlyAnswers[index] = true;
303
- this.hints[index] = "";
304
- this.userAnswers[index] = this.questionsWithAnswers[index].correctAnswer;
305
- } else {
306
- console.log(`❌ Incorrect answer for question ${index + 1}: ${this.userAnswers[index]}`);
307
-
308
- // ====== 1) FIRST WRONG ATTEMPT: show "Try again" countdown, then clear input ======
309
- if (this.attemptCounts[index] === 1) {
310
- hadFirstAttemptWrong = true;
311
- if (!this.globalCountdownActive) {
312
- this.startOverlay("Try again in");
313
- }
314
-
315
- setTimeout(() => {
316
- console.log(`🔄 Resetting incorrect answer for Question ${index + 1}`);
317
- this.userAnswers[index] = ""; // give user a clean slate
318
- this.stopOverlay(); // ensure the HUD disappears
319
- }, this.COUNTDOWN_SECS * 1000);
320
- }
321
- // ====== 2) SECOND WRONG ATTEMPT: show "Showing correct answer" countdown, then autofill ======
322
- else if (this.attemptCounts[index] >= 2) {
323
- hadSecondAttemptWrong = true;
324
- if (!this.globalCountdownActive) {
325
- this.startOverlay("Showing correct answer in");
326
- }
327
-
328
- setTimeout(() => {
329
- // after countdown, force the correct answer
330
- if (!this.userAnswers[index] || this.userAnswers[index].trim() === "" || userAnswer !== correctAnswer) {
331
- this.userAnswers[index] = this.questionsWithAnswers[index].correctAnswer;
332
- console.log(`✔️ Auto-filled correct answer for Question ${index + 1}: ${this.userAnswers[index]}`);
333
- }
334
- this.stopOverlay(); // hide the first overlay (showing correct answer)
335
-
336
- // ====== 3) LAST LEVEL: show a second countdown before congratulations ======
337
- if (this.isLastLevel() && index === this.questions.length - 1) {
338
- // Give time to read the just-filled correct answer
339
- this.startOverlay("Finishing in");
340
- setTimeout(() => {
341
- this.stopOverlay();
342
- this.isAllLevelsCompleted = true;
343
- console.log("🎉 You have completed all difficulty levels.");
344
- this.triggerConfetti();
345
- }, this.COUNTDOWN_SECS * 1000);
346
- }
347
-
348
- }, this.COUNTDOWN_SECS * 1000);
349
- }
350
-
351
- this.hints[index] = hint.length > 0 ? hint : null;
352
- }
353
-
354
- this.feedback[index] = validationResponse || "No feedback provided.";
355
-
356
- // minor 2s reflection (kept as-is)
357
- setTimeout(() => {
358
- if (userAnswer === correctAnswer) {
359
- this.userAnswers[index] = this.userAnswers[index];
360
- } else {
361
- this.userAnswers[index] = this.userAnswers[index];
362
- }
363
- }, 2000);
364
-
365
- if (this.attemptCounts[index] < 2) {
366
- allAttemptsCompleted = false;
367
- }
368
- });
369
-
370
- // Hints auto-hide (unchanged)
371
- this.hasNewHints = this.hints.some((h) => h && h.trim() !== "");
372
- if (this.hasNewHints) {
373
- setTimeout(() => { this.hasNewHints = false; }, 5000);
374
- }
375
-
376
- // --- ONE-TIME “i” icon reveal right after the first validation ---
377
- const hasAnyHint = Array.isArray(this.hints) && this.hints.some(h => !!h && h.trim() !== "");
378
- const shouldRevealIconOnce = hasAnyHint || hadFirstAttemptWrong || hadSecondAttemptWrong;
379
-
380
- if (!this.hasShownHintIcon && shouldRevealIconOnce) {
381
- this.showHintIcon = true; // reveal once
382
- this.hasShownHintIcon = true; // lock it
383
- } else {
384
- this.showHintIcon = false; // never show again in later validations
385
- }
386
- // --- END ONE-TIME “i” icon reveal ---
387
-
388
- if (this.isLastLevel() && this.areAllCorrectAnswersDisplayed()) {
389
- // Last level, all correct: show finish counter, then congratulations
390
- this.stopOverlay(); // ensure no old timer remains
391
- this.startOverlay("Finishing in");
392
- setTimeout(() => {
393
- this.stopOverlay();
394
- this.isAllLevelsCompleted = true;
395
- this.triggerConfetti();
396
- }, this.COUNTDOWN_SECS * 1000);
397
-
398
- } else if (!this.isLastLevel() && (this.areAllCorrectAnswersDisplayed() || allAttemptsCompleted)) {
399
- // Not last level: even if all answers were correct on first try,
400
- // show the counter before moving to the next level
401
- this.stopOverlay(); // ensure fresh HUD
402
- this.startOverlay("Moving to next level in");
403
- setTimeout(() => {
404
- this.stopOverlay();
405
- this.triggerNextLevelWithDelay();
406
- }, this.COUNTDOWN_SECS * 1000);
407
- }
408
 
 
 
 
 
 
 
 
 
 
409
  } else {
410
- this.error = "Failed to validate answers. Please try again.";
411
  }
 
 
 
412
 
413
- this.isValidationInProgress = false;
414
- this.isLoading = false;
415
- },
416
- (error) => {
417
- console.error("Error validating answers:", error);
418
- this.error = "Error validating answers. Please try again later.";
419
- this.isValidationInProgress = false;
420
- this.isLoading = false;
421
- },
422
- );
423
  }
424
 
425
- isLastLevel(): boolean {
426
- return (
427
- this.currentDifficulty ===
428
- this.difficultyLevels[this.difficultyLevels.length - 1]
429
- );
 
 
 
 
 
 
430
  }
431
 
432
- triggerNextLevelWithDelay() {
 
 
 
 
 
 
 
 
 
 
 
 
433
  setTimeout(() => {
434
- this.transitionDifficulty();
435
- }, 2000);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
436
  }
437
 
438
  areAllCorrectAnswersDisplayed(): boolean {
439
- return this.userAnswers.every(
440
- (answer, index) =>
441
- (answer ?? "").trim().toLowerCase() ===
442
- (this.questionsWithAnswers[index]?.correctAnswer ?? "")
443
- .trim()
444
- .toLowerCase(),
445
- );
446
  }
447
 
448
- transitionDifficulty() {
449
  const currentIndex = this.difficultyLevels.indexOf(this.currentDifficulty);
 
450
  if (currentIndex < this.difficultyLevels.length - 1) {
451
  this.currentDifficulty = this.difficultyLevels[currentIndex + 1];
452
- console.log(`Switching to ${this.currentDifficulty} difficulty level.`);
453
  this.generateQuestions();
454
  } else {
455
  setTimeout(() => {
456
  this.questions = [];
457
  this.isAllLevelsCompleted = true;
458
- this.isChecked = false;
459
- this.isValidationInProgress = false;
460
- this.isLoading = false;
461
- console.log("🎉 You have completed all difficulty levels.");
462
  this.triggerConfetti();
463
- }, 3000);
464
  }
465
  }
466
 
467
- shouldShowHint(index: number): boolean {
468
- return this.attemptCounts[index] < 1;
469
- }
470
-
471
- resetAllLevels() {
472
- this.isAllLevelsCompleted = false;
473
- this.currentDifficulty = "basic";
474
- this.questions = [];
475
- this.userAnswers = [];
476
- this.feedback = [];
477
- this.isChecked = false;
478
- this.isQuestionsGenerated = false;
479
- this.isValidationInProgress = false;
480
- this.isTopicLocked = false;
481
- this.hints = [];
482
- this.error = "";
483
- this.isGenerateDisabled = true;
484
- this.isResetDisabled = false;
485
- console.log("All levels reset. Starting from basic level.");
486
- }
487
-
488
- onAnswerChange(index: number) {
489
- if (!this.isValidationInProgress) {
490
- this.isChecked = false;
491
- this.validated = false;
492
- }
493
- const userAnswer = this.userAnswers[index]?.trim();
494
- console.log("User Answers:", this.userAnswers);
495
- console.log("Are All Answers Filled:", this.areAllAnswersFilled());
496
- if (userAnswer) {
497
- this.userAnswers[index] = userAnswer;
498
- } else {
499
- this.userAnswers[index] = "";
500
- }
501
  this.userAnswers = [...this.userAnswers];
502
  }
503
 
504
- goToHome() {
505
- this.router.navigate(["/home"]);
506
  }
507
 
508
- private confettiInterval: any;
509
-
510
- triggerConfetti() {
511
- if (this.confettiInterval) {
512
- clearInterval(this.confettiInterval);
513
- }
514
 
515
  this.confettiInterval = setInterval(() => {
516
  confetti({
517
  startVelocity: 30,
518
  spread: 360,
519
  ticks: 60,
520
- origin: {
521
- x: Math.random(),
522
- y: Math.random() - 0.2,
523
- },
524
  });
525
- }, 250);
526
  }
527
 
528
- stopConfetti() {
529
  this.isAllLevelsCompleted = false;
 
 
530
 
 
531
  if (this.confettiInterval) {
532
  clearInterval(this.confettiInterval);
533
  this.confettiInterval = null;
534
  }
535
-
536
- const canvas = document.querySelector("canvas.confetti-canvas");
537
- if (canvas) {
538
- canvas.remove();
539
- }
540
-
541
- const confettiDivs = document.querySelectorAll(".confetti, .ts-confetti");
542
- confettiDivs.forEach((el) => el.remove());
543
  }
544
 
545
- stopConfettiAndReset() {
546
  this.stopConfetti();
547
  this.resetTopic();
548
  }
@@ -550,27 +468,10 @@ export class GenerateQuestionsComponent {
550
  selectTopic(suggestion: string): void {
551
  this.topic = suggestion;
552
  this.showSuggestions = false;
553
- this.isGenerateDisabled = false;
554
- this.isResetDisabled = true;
555
- this.isDropdownDisabled = true;
556
  }
557
 
558
  hideSuggestions(): void {
559
- setTimeout(() => {
560
- this.showSuggestions = false;
561
- }, 150);
562
- }
563
-
564
- processHints(hints: string[]) {
565
- this.hints = hints;
566
-
567
- this.hasNewHints = hints.length > 0;
568
-
569
- if (this.hasNewHints) {
570
- setTimeout(() => {
571
- this.hasNewHints = false;
572
- }, 5000);
573
- }
574
  }
575
 
576
  closeHints(): void {
@@ -578,110 +479,23 @@ export class GenerateQuestionsComponent {
578
  this.hasNewHints = false;
579
  }
580
 
581
- openHints(): void {
582
- if (this.hints?.length) {
583
- this.isHintMenuVisible = true;
584
- this.hasNewHints = false;
585
- }
586
- }
587
-
588
- toggleHintMenu() {
589
  this.isHintMenuVisible = !this.isHintMenuVisible;
590
- if (this.isHintMenuVisible) this.showHintIcon = false; // hide after first use
591
- }
592
-
593
- onInput(event: any, index: number) {
594
- const value = event.target.value;
595
- const filteredValue = value.replace(/[^a-zA-Z]/g, "");
596
- this.userAnswers[index] = filteredValue;
597
- event.target.value = filteredValue;
598
- }
599
-
600
- //timer implemetation
601
-
602
- // --- Countdown state (per question) ---
603
- countdowns: number[] = []; // current number (5..0) for each question
604
- showCountdown: boolean[] = []; // whether to show the badge
605
- private countdownTimers: { [idx: number]: any } = {}; // interval handles
606
- private readonly COUNTDOWN_SECS = 10;
607
- // === Global countdown overlay state ===
608
- showGlobalCountdown = false; // controls the centered overlay
609
- globalCountdown = 0; // 5..1
610
- private globalCountdownActive = false;
611
- private globalTimer: any = null;
612
-
613
- overlayCaption = '';
614
-
615
- private beginResetCountdown(index: number, seconds: number = this.COUNTDOWN_SECS): void {
616
- // avoid duplicate timers for the same question
617
- if (this.countdownTimers[index]) { return; }
618
-
619
- this.countdowns[index] = seconds;
620
- this.showCountdown[index] = true;
621
-
622
- this.countdownTimers[index] = setInterval(() => {
623
- this.countdowns[index] = this.countdowns[index] - 1;
624
-
625
- if (this.countdowns[index] <= 0) {
626
- this.finishCountdown(index);
627
- }
628
- }, 1000);
629
- }
630
-
631
- /** Stop timer, hide badge, clear the wrong input, and allow retry. */
632
- /** Stop timer, hide badge, clear the wrong input, and allow retry. */
633
- private finishCountdown(index: number): void {
634
- const t = this.countdownTimers[index]; // FIX: use `this`, not `self`
635
- if (t) {
636
- clearInterval(t);
637
- delete this.countdownTimers[index];
638
- }
639
-
640
- this.showCountdown[index] = false;
641
- this.countdowns[index] = 0;
642
-
643
- // Clear only the wrong answer and allow the learner to try again.
644
- this.userAnswers[index] = '';
645
-
646
- // Turn off the global "checked" flag so inputs are not stuck red.
647
- this.isChecked = false;
648
- }
649
-
650
- private startGlobalCountdown(): void {
651
- if (this.globalCountdownActive) return; // prevent duplicates
652
- this.globalCountdownActive = true;
653
- this.showGlobalCountdown = true;
654
- this.globalCountdown = this.COUNTDOWN_SECS;
655
-
656
- this.globalTimer = setInterval(() => {
657
- this.globalCountdown--;
658
- if (this.globalCountdown <= 0) {
659
- this.stopGlobalCountdown();
660
- }
661
- }, 1000);
662
  }
663
 
664
- private stopGlobalCountdown(): void {
665
- if (this.globalTimer) {
666
- clearInterval(this.globalTimer);
667
- this.globalTimer = null;
668
- }
669
- this.showGlobalCountdown = false;
670
- this.globalCountdownActive = false;
671
- this.globalCountdown = 0;
672
  }
673
 
674
- ngOnDestroy(): void {
675
- this.stopGlobalCountdown();
676
- }
677
- private startOverlay(baseText: string, seconds: number = this.COUNTDOWN_SECS): void {
678
- if (this.globalTimer) { clearTimeout(this.globalTimer as any); this.globalTimer = null; }
679
- if (this.overlayTicker) { clearInterval(this.overlayTicker); this.overlayTicker = null; }
680
 
681
  this.globalCountdownActive = true;
682
  this.showGlobalCountdown = true;
683
- this.overlayCaption = baseText;
684
-
685
  this.globalCountdown = seconds;
686
  this.overlayEndTs = Date.now() + seconds * 1000;
687
  this.ringDashoffset = this.ringCircumference;
@@ -692,31 +506,11 @@ private finishCountdown(index: number): void {
692
  const elapsedMs = seconds * 1000 - remainingMs;
693
 
694
  this.globalCountdown = Math.ceil(remainingMs / 1000);
 
695
 
696
- const progress = Math.min(1, elapsedMs / (seconds * 1000));
697
- this.ringDashoffset = this.ringCircumference * (1 - progress);
698
-
699
- if (remainingMs <= 0) this.stopOverlay();
700
- }, 50) as any;
701
-
702
- this.globalTimer = setTimeout(() => this.stopOverlay(), seconds * 1000) as any;
703
- }
704
-
705
- private stopOverlay(): void {
706
- if (this.overlayTicker) { clearInterval(this.overlayTicker); this.overlayTicker = null; }
707
- if (this.globalTimer) { clearTimeout(this.globalTimer as any); this.globalTimer = null; }
708
 
709
- this.showGlobalCountdown = false;
710
- this.globalCountdownActive = false;
711
- this.overlayCaption = '';
712
- this.globalCountdown = 0;
713
- this.ringDashoffset = this.ringCircumference;
714
  }
715
-
716
- ringRadius = 90;
717
- ringCircumference = 2 * Math.PI * this.ringRadius;
718
- ringDashoffset = this.ringCircumference;
719
-
720
- private overlayTicker: any = null;
721
- private overlayEndTs = 0;
722
  }
 
1
+ import { Component, OnDestroy } from '@angular/core';
2
  import { FormsModule } from '@angular/forms';
3
  import { CommonModule } from '@angular/common';
4
  import { GenerateQuestionsService } from './generate-questions.service';
5
  import { Router } from '@angular/router';
6
  import confetti from 'canvas-confetti';
 
7
  import { HeaderComponent } from '../shared/header/header.component';
8
+ import { ButtonComponent } from '../shared/button/button.component';
 
9
 
10
  interface QuestionWithAnswer {
11
  question: string;
 
17
  index: number;
18
  }
19
 
20
+ const COUNTDOWN_SECONDS = 10;
21
+ const HINT_AUTO_HIDE_MS = 5000;
22
+ const NEXT_LEVEL_DELAY_MS = 2000;
23
+ const COMPLETION_DELAY_MS = 3000;
24
+ const CONFETTI_INTERVAL_MS = 250;
25
+ const RING_RADIUS = 90;
26
+
27
+ const INCORRECT_INDICATORS = [
28
+ 'incorrect', 'not correct', 'wrong', 'not right',
29
+ 'is not', 'are not', 'isn\'t correct', 'aren\'t correct'
30
+ ];
31
+
32
+ const CORRECT_INDICATORS = [
33
+ 'correct', 'right', 'yes', 'accurate',
34
+ 'well done', 'good job', 'perfect'
35
+ ];
36
+
37
  @Component({
38
  selector: 'app-generate-questions',
39
  standalone: true,
40
+ imports: [FormsModule, CommonModule, HeaderComponent, ButtonComponent],
41
  templateUrl: './generate-questions.component.html',
42
  styleUrls: ['./generate-questions.component.css']
43
  })
44
+ export class GenerateQuestionsComponent implements OnDestroy {
45
+ topic = '';
46
+ readonly hardcodedTopics = ['Noun', 'Verb', 'Past Tense', 'Adjective', 'Present Continuous'];
47
+ questions: Question[] = [];
48
+ questionsWithAnswers: QuestionWithAnswer[] = [];
 
 
 
 
 
 
49
 
50
  userAnswers: string[] = [];
51
+ answerStatuses: ('correct' | 'incorrect' | 'pending')[] = [];
52
  feedback: string[] = [];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  hints: string[] = [];
54
+ readonlyAnswers: boolean[] = [];
55
+ attemptCounts: Record<number, number> = {};
56
+
57
+ currentDifficulty = 'basic';
58
+ readonly difficultyLevels = ['basic', 'intermediate', 'expert'];
59
+
60
+ showSuggestions = false;
61
+ isQuestionsGenerated = false;
62
+ isValidationInProgress = false;
63
+ isFirstAttemptDone = false;
64
+ isAllLevelsCompleted = false;
65
+ isHintMenuVisible = false;
66
+ isLoading = false;
67
+ isChecked = false;
68
+ error = '';
69
+
70
+ get isGenerateDisabled() { return !this.topic.trim() || this.isQuestionsGenerated; }
71
+ get isResetDisabled() { return !this.topic && !this.isQuestionsGenerated; }
72
+ get isTopicLocked() { return this.isQuestionsGenerated; }
73
+ get isDropdownDisabled() { return this.isQuestionsGenerated; }
74
+ get showLevelTooltip() { return this.isQuestionsGenerated; }
75
+ get activateLevelDot() { return this.isQuestionsGenerated; }
76
+
77
+ private hasShownHintIcon = false;
78
+ showHintIcon = false;
79
+ hasNewHints = false;
80
+
81
+ showGlobalCountdown = false;
82
+ globalCountdown = 0;
83
+ overlayCaption = '';
84
+ ringCircumference = 2 * Math.PI * RING_RADIUS;
85
+ ringDashoffset = this.ringCircumference;
86
 
87
+ private globalTimer: any;
88
+ private overlayTicker: any;
89
+ private overlayEndTs = 0;
90
+ private globalCountdownActive = false;
91
+ private confettiInterval: any;
92
 
93
  constructor(
94
+ private service: GenerateQuestionsService,
95
+ private router: Router
96
+ ) {}
97
+
98
+ ngOnDestroy(): void {
99
+ this.clearTimers();
100
+ this.stopConfettiAnimation();
101
+ }
102
+
103
+ private clearTimers(): void {
104
+ if (this.globalTimer) clearInterval(this.globalTimer);
105
+ if (this.overlayTicker) clearInterval(this.overlayTicker);
106
+ this.globalTimer = null;
107
+ this.overlayTicker = null;
108
+ this.showGlobalCountdown = false;
109
+ this.globalCountdownActive = false;
110
+ }
111
+
112
+ onTopicChange(): void {
113
+ this.showSuggestions = !this.topic.trim();
114
  }
115
 
116
  getProgressWidth(): string {
117
  const index = this.difficultyLevels.indexOf(this.currentDifficulty);
118
+ if (index === -1 || this.difficultyLevels.length <= 1) return '0%';
119
+ return `${(index / (this.difficultyLevels.length - 1)) * 100}%`;
 
120
  }
121
 
122
+ generateQuestions(): void {
 
 
 
 
 
123
  if (this.isAllLevelsCompleted) {
124
+ this.error = 'Please reset to start over.';
125
+ return;
126
+ }
127
+
128
+ this.resetQuestionState();
129
+
130
+ this.service.generateQuestions(this.topic, this.currentDifficulty)
131
+ .subscribe({
132
+ next: (response) => this.handleQuestionsResponse(response),
133
+ error: (error) => this.handleQuestionsError(error)
134
+ });
135
+ }
136
+
137
+ private handleQuestionsResponse(response: any): void {
138
+ const rawQuestions = (response?.text ?? response?.generations?.[0]?.text ?? '').trim();
139
+
140
+ if (!rawQuestions) {
141
+ this.error = 'No questions generated. Please try again.';
142
  return;
143
  }
144
 
145
+ this.isQuestionsGenerated = true;
146
+ this.parseAndInitializeQuestions(rawQuestions);
147
+ }
148
+
149
+ private handleQuestionsError(error: any): void {
150
+ this.error = error.status === 400 && error.error.message
151
+ ? error.error.message
152
+ : 'Failed to fetch questions. Please try again later.';
153
+ }
154
+
155
+ private parseAndInitializeQuestions(rawQuestions: string): void {
156
+ this.questionsWithAnswers = this.parseQuestions(rawQuestions);
157
+ this.questions = this.splitQuestionsIntoParts(this.questionsWithAnswers);
158
+ this.initializeAnswerArrays(this.questions.length);
159
+ }
160
+
161
+ private parseQuestions(text: string): QuestionWithAnswer[] {
162
+ const regex = /(\d+\.\s*.+?_______)\s*(.*?)\s*\(([^)]+)\)\s*$/gm;
163
+ const questions: QuestionWithAnswer[] = [];
164
+ let match;
165
+
166
+ while ((match = regex.exec(text)) !== null) {
167
+ const questionText = match[1]?.trim();
168
+ const afterBlank = match[2]?.replace(/^[_-]+/, '').replace(/^\s+/, ' ').replace(/\s{2,}/g, ' ');
169
+ const correctAnswer = match[3]?.trim();
170
+
171
+ if (questionText && correctAnswer) {
172
+ questions.push({
173
+ question: `${questionText}${afterBlank}`,
174
+ correctAnswer
175
+ });
176
+ }
177
+ }
178
+ return questions;
179
+ }
180
+
181
+ private splitQuestionsIntoParts(questions: QuestionWithAnswer[]): Question[] {
182
+ return questions.map((q, index) => {
183
+ const normalized = q.question.replace(/\s*_{3,}\s*/g, '_______');
184
+ const parts = normalized.split('_______').map(p => p.replace(/[_]+/g, '').trim());
185
+ return { parts, index };
186
+ });
187
+ }
188
+
189
+ private initializeAnswerArrays(length: number): void {
190
+ this.userAnswers = Array(length).fill('');
191
+ this.answerStatuses = Array(length).fill('pending');
192
+ this.feedback = Array(length).fill('');
193
+ this.readonlyAnswers = Array(length).fill(false);
194
+ this.hints = [];
195
+ this.attemptCounts = {};
196
+ for (let i = 0; i < length; i++) {
197
+ this.attemptCounts[i] = 0;
198
+ }
199
+ }
200
+
201
+ private resetQuestionState(): void {
202
+ this.hints = [];
203
  this.showSuggestions = false;
204
  this.readonlyAnswers = [];
205
+ }
206
+
207
+ resetTopic(): void {
208
+ this.topic = '';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
  this.questions = [];
210
  this.questionsWithAnswers = [];
211
  this.userAnswers = [];
212
+ this.answerStatuses = [];
213
  this.readonlyAnswers = [];
214
  this.feedback = [];
215
  this.hints = [];
216
+ this.attemptCounts = {};
217
+
218
+ this.isQuestionsGenerated = false;
219
  this.isValidationInProgress = false;
220
  this.isFirstAttemptDone = false;
221
  this.isAllLevelsCompleted = false;
222
  this.hasNewHints = false;
 
 
 
 
 
 
 
 
 
 
223
  this.showSuggestions = false;
224
+ this.isChecked = false;
225
+ this.currentDifficulty = 'basic';
226
+ this.error = '';
227
+
228
+ this.clearTimers();
229
  this.stopConfetti();
 
 
 
 
 
 
 
230
  }
231
 
232
  areAllAnswersFilled(): boolean {
233
+ return this.userAnswers.every(answer => answer?.trim());
234
  }
235
 
236
+ closeErrorPopup(): void {
237
+ this.error = '';
 
 
238
  }
239
 
240
+ private parseAIValidation(response: string): boolean {
241
+ if (!response) return false;
242
+
243
+ const lower = response.toLowerCase();
244
+
245
+ if (INCORRECT_INDICATORS.some(ind => lower.includes(ind))) return false;
246
+ if (CORRECT_INDICATORS.some(ind => lower.includes(ind))) return true;
247
 
248
+ return false;
249
+ }
250
+
251
+ checkAllAnswers(): void {
252
  this.isValidationInProgress = true;
253
  this.isLoading = true;
 
 
254
  this.isFirstAttemptDone = true;
255
 
256
+ const payload = this.questions.map((q, i) => ({
257
  topic: this.topic,
258
+ question: q.parts.join('_______'),
259
+ user_answer: this.userAnswers[i]
260
  }));
261
 
262
+ this.service.validateAllAnswers(payload).subscribe({
263
+ next: (response) => this.handleValidationResponse(response),
264
+ error: (error) => this.handleValidationError(error)
265
+ });
266
+ }
267
+
268
+ private handleValidationResponse(response: any): void {
269
+ if (!response.results?.length) {
270
+ this.error = 'Failed to validate answers. Please try again.';
271
+ this.resetValidationState();
272
+ return;
273
+ }
274
+
275
+ let hadFirstWrong = false;
276
+ let hadSecondWrong = false;
277
+
278
+ response.results.forEach((result: any, index: number) => {
279
+ this.attemptCounts[index]++;
280
+ const isCorrect = this.parseAIValidation(result.validation_response);
281
+
282
+ this.feedback[index] = result.validation_response || 'No feedback provided.';
283
+
284
+ if (isCorrect) {
285
+ this.markAsCorrect(index);
286
+ } else {
287
+ const attemptResult = this.handleIncorrectAttempt(index, result.hint);
288
+ hadFirstWrong = hadFirstWrong || attemptResult.first;
289
+ hadSecondWrong = hadSecondWrong || attemptResult.second;
290
+ }
291
+ });
292
+
293
+ this.handleHintsDisplay(hadFirstWrong || hadSecondWrong);
294
+
295
+ if (!hadSecondWrong) {
296
+ this.checkLevelCompletion();
297
+ }
298
+
299
+ this.resetValidationState();
300
+ }
301
+
302
+ private markAsCorrect(index: number): void {
303
+ this.readonlyAnswers[index] = true;
304
+ this.hints[index] = '';
305
+ this.answerStatuses[index] = 'correct';
306
+ this.userAnswers[index] = this.userAnswers[index].trim();
307
+ }
308
+
309
+ private handleIncorrectAttempt(index: number, hint: string): { first: boolean; second: boolean } {
310
+ this.answerStatuses[index] = 'incorrect';
311
+ this.hints[index] = hint?.trim() || '';
312
+
313
+ if (this.attemptCounts[index] === 1) {
314
+ this.scheduleAnswerClear(index);
315
+ return { first: true, second: false };
316
+ } else if (this.attemptCounts[index] >= 2) {
317
+ this.scheduleAnswerReveal(index);
318
+ return { first: false, second: true };
319
+ }
320
+
321
+ return { first: false, second: false };
322
+ }
323
+
324
+ private scheduleAnswerClear(index: number): void {
325
+ if (!this.globalCountdownActive) this.startOverlay('Try again in');
326
+
327
+ setTimeout(() => {
328
+ this.userAnswers[index] = '';
329
+ this.answerStatuses[index] = 'pending';
330
+ this.clearTimers();
331
+ }, COUNTDOWN_SECONDS * 1000);
332
+ }
333
+
334
+ private scheduleAnswerReveal(index: number): void {
335
+ if (!this.globalCountdownActive) this.startOverlay('Showing correct answer in');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
336
 
337
+ setTimeout(() => {
338
+ this.userAnswers[index] = this.questionsWithAnswers[index].correctAnswer;
339
+ this.answerStatuses[index] = 'correct';
340
+ this.readonlyAnswers[index] = true;
341
+ this.clearTimers();
342
+
343
+ if (this.areAllCorrectAnswersDisplayed()) {
344
+ if (this.isLastLevel()) {
345
+ this.scheduleCompletion();
346
  } else {
347
+ this.scheduleNextLevel();
348
  }
349
+ }
350
+ }, COUNTDOWN_SECONDS * 1000);
351
+ }
352
 
353
+ private scheduleCompletion(): void {
354
+ this.startOverlay('Finishing in');
355
+ setTimeout(() => {
356
+ this.clearTimers();
357
+ this.isAllLevelsCompleted = true;
358
+ this.triggerConfetti();
359
+ }, COUNTDOWN_SECONDS * 1000);
 
 
 
360
  }
361
 
362
+ private handleHintsDisplay(hasWrongAnswers: boolean): void {
363
+ this.hasNewHints = this.hints.some(h => h?.trim());
364
+
365
+ if (this.hasNewHints) {
366
+ setTimeout(() => { this.hasNewHints = false; }, HINT_AUTO_HIDE_MS);
367
+ }
368
+
369
+ if (!this.hasShownHintIcon && (this.hasNewHints || hasWrongAnswers)) {
370
+ this.showHintIcon = true;
371
+ this.hasShownHintIcon = true;
372
+ }
373
  }
374
 
375
+ private checkLevelCompletion(): void {
376
+ if (this.areAllCorrectAnswersDisplayed()) {
377
+ if (this.isLastLevel()) {
378
+ this.scheduleCompletion();
379
+ } else {
380
+ this.scheduleNextLevel();
381
+ }
382
+ }
383
+ }
384
+
385
+ private scheduleNextLevel(): void {
386
+ this.clearTimers();
387
+ this.startOverlay('Moving to next level in');
388
  setTimeout(() => {
389
+ this.clearTimers();
390
+ setTimeout(() => this.transitionDifficulty(), NEXT_LEVEL_DELAY_MS);
391
+ }, COUNTDOWN_SECONDS * 1000);
392
+ }
393
+
394
+ private handleValidationError(error: any): void {
395
+ this.error = 'Error validating answers. Please try again later.';
396
+ this.resetValidationState();
397
+ }
398
+
399
+ private resetValidationState(): void {
400
+ this.isValidationInProgress = false;
401
+ this.isLoading = false;
402
+ }
403
+
404
+ isLastLevel(): boolean {
405
+ return this.currentDifficulty === this.difficultyLevels[this.difficultyLevels.length - 1];
406
  }
407
 
408
  areAllCorrectAnswersDisplayed(): boolean {
409
+ return this.readonlyAnswers.every(readonly => readonly);
 
 
 
 
 
 
410
  }
411
 
412
+ transitionDifficulty(): void {
413
  const currentIndex = this.difficultyLevels.indexOf(this.currentDifficulty);
414
+
415
  if (currentIndex < this.difficultyLevels.length - 1) {
416
  this.currentDifficulty = this.difficultyLevels[currentIndex + 1];
 
417
  this.generateQuestions();
418
  } else {
419
  setTimeout(() => {
420
  this.questions = [];
421
  this.isAllLevelsCompleted = true;
 
 
 
 
422
  this.triggerConfetti();
423
+ }, COMPLETION_DELAY_MS);
424
  }
425
  }
426
 
427
+ onAnswerChange(index: number): void {
428
+ this.userAnswers[index] = this.userAnswers[index]?.trim() || '';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
429
  this.userAnswers = [...this.userAnswers];
430
  }
431
 
432
+ goToHome(): void {
433
+ this.router.navigate(['/home']);
434
  }
435
 
436
+ triggerConfetti(): void {
437
+ if (this.confettiInterval) clearInterval(this.confettiInterval);
 
 
 
 
438
 
439
  this.confettiInterval = setInterval(() => {
440
  confetti({
441
  startVelocity: 30,
442
  spread: 360,
443
  ticks: 60,
444
+ origin: { x: Math.random(), y: Math.random() - 0.2 }
 
 
 
445
  });
446
+ }, CONFETTI_INTERVAL_MS);
447
  }
448
 
449
+ stopConfetti(): void {
450
  this.isAllLevelsCompleted = false;
451
+ this.stopConfettiAnimation();
452
+ }
453
 
454
+ private stopConfettiAnimation(): void {
455
  if (this.confettiInterval) {
456
  clearInterval(this.confettiInterval);
457
  this.confettiInterval = null;
458
  }
459
+ document.querySelector('canvas.confetti-canvas')?.remove();
460
+ document.querySelectorAll('.confetti, .ts-confetti').forEach(el => el.remove());
 
 
 
 
 
 
461
  }
462
 
463
+ stopConfettiAndReset(): void {
464
  this.stopConfetti();
465
  this.resetTopic();
466
  }
 
468
  selectTopic(suggestion: string): void {
469
  this.topic = suggestion;
470
  this.showSuggestions = false;
 
 
 
471
  }
472
 
473
  hideSuggestions(): void {
474
+ setTimeout(() => { this.showSuggestions = false; }, 150);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
475
  }
476
 
477
  closeHints(): void {
 
479
  this.hasNewHints = false;
480
  }
481
 
482
+ toggleHintMenu(): void {
 
 
 
 
 
 
 
483
  this.isHintMenuVisible = !this.isHintMenuVisible;
484
+ if (this.isHintMenuVisible) this.showHintIcon = false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
485
  }
486
 
487
+ onInput(event: any, index: number): void {
488
+ const filtered = event.target.value.replace(/[^a-zA-Z]/g, '');
489
+ this.userAnswers[index] = filtered;
490
+ event.target.value = filtered;
 
 
 
 
491
  }
492
 
493
+ private startOverlay(caption: string, seconds = COUNTDOWN_SECONDS): void {
494
+ this.clearTimers();
 
 
 
 
495
 
496
  this.globalCountdownActive = true;
497
  this.showGlobalCountdown = true;
498
+ this.overlayCaption = caption;
 
499
  this.globalCountdown = seconds;
500
  this.overlayEndTs = Date.now() + seconds * 1000;
501
  this.ringDashoffset = this.ringCircumference;
 
506
  const elapsedMs = seconds * 1000 - remainingMs;
507
 
508
  this.globalCountdown = Math.ceil(remainingMs / 1000);
509
+ this.ringDashoffset = this.ringCircumference * (1 - Math.min(1, elapsedMs / (seconds * 1000)));
510
 
511
+ if (remainingMs <= 0) this.clearTimers();
512
+ }, 50);
 
 
 
 
 
 
 
 
 
 
513
 
514
+ this.globalTimer = setTimeout(() => this.clearTimers(), seconds * 1000);
 
 
 
 
515
  }
 
 
 
 
 
 
 
516
  }
src/app/generate-questions/generate-questions.service.ts CHANGED
@@ -1,38 +1,34 @@
1
  import { Injectable } from '@angular/core';
2
  import { HttpClient } from '@angular/common/http';
3
  import { Observable } from 'rxjs';
 
4
 
5
  @Injectable({
6
  providedIn: 'root'
7
  })
8
  export class GenerateQuestionsService {
 
9
 
10
-
11
- private baseUrl = location.hostname.endsWith('hf.space')
12
- ? 'https://pykara-py-learn-backend.hf.space/media'
13
- : 'http://localhost:5000/media';
14
 
15
- constructor(private http: HttpClient) { }
16
-
17
- // Method to get questions from the backend
 
 
 
 
18
  generateQuestions(topic: string, difficulty: string): Observable<any> {
19
  return this.http.post<any>(`${this.baseUrl}/generate-questions`, { topic, difficulty });
20
- }
21
-
22
-
23
-
24
- // Method to validate the user's answer for a specific question
25
- validateAnswer(question: string, userAnswer: string, topic: string): Observable<any> {
26
- return this.http.post<any>(`${this.baseUrl}/validate-answer`, {
27
- question,
28
- user_answer: userAnswer,
29
- topic
30
- });
31
  }
32
 
 
 
 
 
 
 
33
  validateAllAnswers(questions: { topic: string; question: string; user_answer: string }[]): Observable<any> {
34
  return this.http.post<any>(`${this.baseUrl}/validate-all-answers`, { questions });
35
  }
36
-
37
-
38
  }
 
1
  import { Injectable } from '@angular/core';
2
  import { HttpClient } from '@angular/common/http';
3
  import { Observable } from 'rxjs';
4
+ import { environment } from '../../environments/environment';
5
 
6
  @Injectable({
7
  providedIn: 'root'
8
  })
9
  export class GenerateQuestionsService {
10
+ private readonly baseUrl = environment.apiBaseUrl;
11
 
12
+ constructor(private http: HttpClient) {}
 
 
 
13
 
14
+ /**
15
+ * Generate grammar questions for a given topic and difficulty level.
16
+ *
17
+ * @param topic - The grammar topic
18
+ * @param difficulty - The difficulty level: 'basic', 'intermediate', or 'expert'
19
+ * @returns Observable with generated questions
20
+ */
21
  generateQuestions(topic: string, difficulty: string): Observable<any> {
22
  return this.http.post<any>(`${this.baseUrl}/generate-questions`, { topic, difficulty });
 
 
 
 
 
 
 
 
 
 
 
23
  }
24
 
25
+ /**
26
+ * Validate multiple user answers in a single batch request.
27
+ *
28
+ * @param questions - Array of questions with user answers
29
+ * @returns Observable with validation results and hints
30
+ */
31
  validateAllAnswers(questions: { topic: string; question: string; user_answer: string }[]): Observable<any> {
32
  return this.http.post<any>(`${this.baseUrl}/validate-all-answers`, { questions });
33
  }
 
 
34
  }
src/environments/environment.prod.ts ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ export const environment = {
2
+ production: true,
3
+ apiBaseUrl: 'https://pykara-py-learn-backend.hf.space/media'
4
+ };
src/environments/environment.ts ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ export const environment = {
2
+ production: false,
3
+ apiBaseUrl: 'http://localhost:5000/media'
4
+ };