Oviya commited on
Commit ·
6534ea1
1
Parent(s): 2434088
add staticchat
Browse files- dist/gramm-ai/3rdpartylicenses.txt +14 -0
- dist/gramm-ai/browser/index.html +1 -1
- dist/gramm-ai/browser/main-OWHUQN5B.js +0 -0
- dist/gramm-ai/browser/main-P2CB6RHX.js +0 -0
- package-lock.json +37 -65
- package.json +1 -0
- src/app/app-routing.module.ts +21 -13
- src/app/app.module.ts +5 -2
- src/app/chat/api.service.ts +1 -1
- src/app/pronunciation/pronunciation.service.ts +1 -1
- src/app/staticchat/staticchat.component.css +495 -0
- src/app/staticchat/staticchat.component.html +115 -0
- src/app/staticchat/staticchat.component.ts +756 -0
- src/app/staticchat/staticchat.service.ts +155 -0
dist/gramm-ai/3rdpartylicenses.txt
CHANGED
|
@@ -304,6 +304,20 @@ Package: @angular/animations
|
|
| 304 |
License: "MIT"
|
| 305 |
|
| 306 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 307 |
--------------------------------------------------------------------------------
|
| 308 |
Package: zone.js
|
| 309 |
License: "MIT"
|
|
|
|
| 304 |
License: "MIT"
|
| 305 |
|
| 306 |
|
| 307 |
+
--------------------------------------------------------------------------------
|
| 308 |
+
Package: uuid
|
| 309 |
+
License: "MIT"
|
| 310 |
+
|
| 311 |
+
The MIT License (MIT)
|
| 312 |
+
|
| 313 |
+
Copyright (c) 2010-2020 Robert Kieffer and other contributors
|
| 314 |
+
|
| 315 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
| 316 |
+
|
| 317 |
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
| 318 |
+
|
| 319 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 320 |
+
|
| 321 |
--------------------------------------------------------------------------------
|
| 322 |
Package: zone.js
|
| 323 |
License: "MIT"
|
dist/gramm-ai/browser/index.html
CHANGED
|
@@ -16,5 +16,5 @@
|
|
| 16 |
</style><link rel="stylesheet" href="styles-TX4PRGSR.css" media="print" onload="this.media='all'"><noscript><link rel="stylesheet" href="styles-TX4PRGSR.css"></noscript></head>
|
| 17 |
<body>
|
| 18 |
<app-root></app-root>
|
| 19 |
-
<script src="polyfills-FFHMD2TL.js" type="module"></script><script src="main-
|
| 20 |
</html>
|
|
|
|
| 16 |
</style><link rel="stylesheet" href="styles-TX4PRGSR.css" media="print" onload="this.media='all'"><noscript><link rel="stylesheet" href="styles-TX4PRGSR.css"></noscript></head>
|
| 17 |
<body>
|
| 18 |
<app-root></app-root>
|
| 19 |
+
<script src="polyfills-FFHMD2TL.js" type="module"></script><script src="main-OWHUQN5B.js" type="module"></script></body>
|
| 20 |
</html>
|
dist/gramm-ai/browser/main-OWHUQN5B.js
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
dist/gramm-ai/browser/main-P2CB6RHX.js
DELETED
|
The diff for this file is too large to render.
See raw diff
|
|
|
package-lock.json
CHANGED
|
@@ -27,6 +27,7 @@
|
|
| 27 |
"rxjs": "~7.8.0",
|
| 28 |
"tailwindcss": "^3.4.17",
|
| 29 |
"tslib": "^2.3.0",
|
|
|
|
| 30 |
"wavesurfer.js": "^7.11.1",
|
| 31 |
"zone.js": "~0.14.3"
|
| 32 |
},
|
|
@@ -271,7 +272,6 @@
|
|
| 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,7 +355,6 @@
|
|
| 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,7 +421,6 @@
|
|
| 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,7 +436,6 @@
|
|
| 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,7 +456,6 @@
|
|
| 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,7 +528,6 @@
|
|
| 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,7 +543,6 @@
|
|
| 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,7 +625,6 @@
|
|
| 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,7 +701,6 @@
|
|
| 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,7 +5335,6 @@
|
|
| 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,7 +5405,6 @@
|
|
| 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,7 +5888,6 @@
|
|
| 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,8 +8885,7 @@
|
|
| 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,7 +9595,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,7 +9801,6 @@
|
|
| 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,7 +11415,6 @@
|
|
| 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,7 +12246,6 @@
|
|
| 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,7 +12304,6 @@
|
|
| 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",
|
|
@@ -12896,6 +12879,16 @@
|
|
| 12896 |
"websocket-driver": "^0.7.4"
|
| 12897 |
}
|
| 12898 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12899 |
"node_modules/socks": {
|
| 12900 |
"version": "2.8.3",
|
| 12901 |
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz",
|
|
@@ -13438,7 +13431,6 @@
|
|
| 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,7 +13669,6 @@
|
|
| 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"
|
|
@@ -13870,12 +13861,16 @@
|
|
| 13870 |
}
|
| 13871 |
},
|
| 13872 |
"node_modules/uuid": {
|
| 13873 |
-
"version": "
|
| 13874 |
-
"resolved": "https://registry.npmjs.org/uuid/-/uuid-
|
| 13875 |
-
"integrity": "sha512
|
| 13876 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13877 |
"bin": {
|
| 13878 |
-
"uuid": "dist/bin/uuid"
|
| 13879 |
}
|
| 13880 |
},
|
| 13881 |
"node_modules/validate-npm-package-license": {
|
|
@@ -13911,7 +13906,6 @@
|
|
| 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,7 +14421,6 @@
|
|
| 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,7 +14495,6 @@
|
|
| 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,7 +14621,6 @@
|
|
| 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,8 +14872,7 @@
|
|
| 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,7 +14991,6 @@
|
|
| 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,7 +15046,6 @@
|
|
| 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,7 +15090,6 @@
|
|
| 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,7 +15098,6 @@
|
|
| 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,7 +15107,6 @@
|
|
| 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,7 +15161,6 @@
|
|
| 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,7 +15169,6 @@
|
|
| 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,7 +15232,6 @@
|
|
| 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,7 +15271,6 @@
|
|
| 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,8 +18730,7 @@
|
|
| 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,7 +18783,6 @@
|
|
| 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,7 +19127,6 @@
|
|
| 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,8 +21324,7 @@
|
|
| 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,7 +21867,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,7 +22034,6 @@
|
|
| 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,7 +23208,6 @@
|
|
| 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,7 +23780,6 @@
|
|
| 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,7 +23817,6 @@
|
|
| 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",
|
|
@@ -24266,6 +24238,14 @@
|
|
| 24266 |
"faye-websocket": "^0.11.3",
|
| 24267 |
"uuid": "^8.3.2",
|
| 24268 |
"websocket-driver": "^0.7.4"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24269 |
}
|
| 24270 |
},
|
| 24271 |
"socks": {
|
|
@@ -24686,7 +24666,6 @@
|
|
| 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,8 +24833,7 @@
|
|
| 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",
|
|
@@ -24970,10 +24948,9 @@
|
|
| 24970 |
"dev": true
|
| 24971 |
},
|
| 24972 |
"uuid": {
|
| 24973 |
-
"version": "
|
| 24974 |
-
"resolved": "https://registry.npmjs.org/uuid/-/uuid-
|
| 24975 |
-
"integrity": "sha512
|
| 24976 |
-
"dev": true
|
| 24977 |
},
|
| 24978 |
"validate-npm-package-license": {
|
| 24979 |
"version": "3.0.4",
|
|
@@ -25002,7 +24979,6 @@
|
|
| 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,7 +25232,6 @@
|
|
| 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,7 +25263,6 @@
|
|
| 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,7 +25330,6 @@
|
|
| 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,8 +25531,7 @@
|
|
| 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 |
}
|
|
|
|
| 27 |
"rxjs": "~7.8.0",
|
| 28 |
"tailwindcss": "^3.4.17",
|
| 29 |
"tslib": "^2.3.0",
|
| 30 |
+
"uuid": "^13.0.0",
|
| 31 |
"wavesurfer.js": "^7.11.1",
|
| 32 |
"zone.js": "~0.14.3"
|
| 33 |
},
|
|
|
|
| 272 |
"url": "https://github.com/sponsors/ai"
|
| 273 |
}
|
| 274 |
],
|
|
|
|
| 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 |
"dependencies": {
|
| 359 |
"tslib": "^2.3.0"
|
| 360 |
},
|
|
|
|
| 421 |
"version": "17.3.12",
|
| 422 |
"resolved": "https://registry.npmjs.org/@angular/common/-/common-17.3.12.tgz",
|
| 423 |
"integrity": "sha512-vabJzvrx76XXFrm1RJZ6o/CyG32piTB/1sfFfKHdlH1QrmArb8It4gyk9oEjZ1IkAD0HvBWlfWmn+T6Vx3pdUw==",
|
|
|
|
| 424 |
"dependencies": {
|
| 425 |
"tslib": "^2.3.0"
|
| 426 |
},
|
|
|
|
| 436 |
"version": "17.3.12",
|
| 437 |
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-17.3.12.tgz",
|
| 438 |
"integrity": "sha512-vwI8oOL/gM+wPnptOVeBbMfZYwzRxQsovojZf+Zol9szl0k3SZ3FycWlxxXZGFu3VIEfrP6pXplTmyODS/Lt1w==",
|
|
|
|
| 439 |
"dependencies": {
|
| 440 |
"tslib": "^2.3.0"
|
| 441 |
},
|
|
|
|
| 456 |
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-17.3.12.tgz",
|
| 457 |
"integrity": "sha512-1F8M7nWfChzurb7obbvuE7mJXlHtY1UG58pcwcomVtpPb+kPavgAO8OEvJHYBMV+bzSxkXt5UIwL9lt9jHUxZA==",
|
| 458 |
"dev": true,
|
|
|
|
| 459 |
"dependencies": {
|
| 460 |
"@babel/core": "7.23.9",
|
| 461 |
"@jridgewell/sourcemap-codec": "^1.4.14",
|
|
|
|
| 528 |
"version": "17.3.12",
|
| 529 |
"resolved": "https://registry.npmjs.org/@angular/core/-/core-17.3.12.tgz",
|
| 530 |
"integrity": "sha512-MuFt5yKi161JmauUta4Dh0m8ofwoq6Ino+KoOtkYMBGsSx+A7dSm+DUxxNwdj7+DNyg3LjVGCFgBFnq4g8z06A==",
|
|
|
|
| 531 |
"dependencies": {
|
| 532 |
"tslib": "^2.3.0"
|
| 533 |
},
|
|
|
|
| 543 |
"version": "17.3.12",
|
| 544 |
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-17.3.12.tgz",
|
| 545 |
"integrity": "sha512-tV6r12Q3yEUlXwpVko4E+XscunTIpPkLbaiDn/MTL3Vxi2LZnsLgHyd/i38HaHN+e/H3B0a1ToSOhV5wf3ay4Q==",
|
|
|
|
| 546 |
"dependencies": {
|
| 547 |
"tslib": "^2.3.0"
|
| 548 |
},
|
|
|
|
| 625 |
"version": "17.3.12",
|
| 626 |
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-17.3.12.tgz",
|
| 627 |
"integrity": "sha512-DYY04ptWh/ulMHzd+y52WCE8QnEYGeIiW3hEIFjCN8z0kbIdFdUtEB0IK5vjNL3ejyhUmphcpeT5PYf3YXtqWQ==",
|
|
|
|
| 628 |
"dependencies": {
|
| 629 |
"tslib": "^2.3.0"
|
| 630 |
},
|
|
|
|
| 701 |
"version": "7.24.0",
|
| 702 |
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.0.tgz",
|
| 703 |
"integrity": "sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw==",
|
|
|
|
| 704 |
"dependencies": {
|
| 705 |
"@ampproject/remapping": "^2.2.0",
|
| 706 |
"@babel/code-frame": "^7.23.5",
|
|
|
|
| 5335 |
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
|
| 5336 |
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
|
| 5337 |
"dev": true,
|
|
|
|
| 5338 |
"bin": {
|
| 5339 |
"acorn": "bin/acorn"
|
| 5340 |
},
|
|
|
|
| 5405 |
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
|
| 5406 |
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
|
| 5407 |
"dev": true,
|
|
|
|
| 5408 |
"dependencies": {
|
| 5409 |
"fast-deep-equal": "^3.1.1",
|
| 5410 |
"json-schema-traverse": "^1.0.0",
|
|
|
|
| 5888 |
"url": "https://github.com/sponsors/ai"
|
| 5889 |
}
|
| 5890 |
],
|
|
|
|
| 5891 |
"dependencies": {
|
| 5892 |
"caniuse-lite": "^1.0.30001688",
|
| 5893 |
"electron-to-chromium": "^1.5.73",
|
|
|
|
| 8885 |
"version": "5.1.2",
|
| 8886 |
"resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.1.2.tgz",
|
| 8887 |
"integrity": "sha512-2oIUMGn00FdUiqz6epiiJr7xcFyNYj3rDcfmnzfkBnHyBQ3cBQUs4mmyGsOb7TTLb9kxk7dBcmEmqhDKkBoDyA==",
|
| 8888 |
+
"dev": true
|
|
|
|
| 8889 |
},
|
| 8890 |
"node_modules/jest-diff": {
|
| 8891 |
"version": "30.0.0-alpha.6",
|
|
|
|
| 9595 |
"resolved": "https://registry.npmjs.org/karma/-/karma-6.4.4.tgz",
|
| 9596 |
"integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==",
|
| 9597 |
"dev": true,
|
|
|
|
| 9598 |
"dependencies": {
|
| 9599 |
"@colors/colors": "1.5.0",
|
| 9600 |
"body-parser": "^1.19.0",
|
|
|
|
| 9801 |
"resolved": "https://registry.npmjs.org/less/-/less-4.2.0.tgz",
|
| 9802 |
"integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==",
|
| 9803 |
"dev": true,
|
|
|
|
| 9804 |
"dependencies": {
|
| 9805 |
"copy-anything": "^2.0.1",
|
| 9806 |
"parse-node-version": "^1.0.1",
|
|
|
|
| 11415 |
"url": "https://github.com/sponsors/ai"
|
| 11416 |
}
|
| 11417 |
],
|
|
|
|
| 11418 |
"dependencies": {
|
| 11419 |
"nanoid": "^3.3.7",
|
| 11420 |
"picocolors": "^1.1.1",
|
|
|
|
| 12246 |
"version": "7.8.1",
|
| 12247 |
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
|
| 12248 |
"integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
|
|
|
|
| 12249 |
"dependencies": {
|
| 12250 |
"tslib": "^2.1.0"
|
| 12251 |
}
|
|
|
|
| 12304 |
"resolved": "https://registry.npmjs.org/sass/-/sass-1.71.1.tgz",
|
| 12305 |
"integrity": "sha512-wovtnV2PxzteLlfNzbgm1tFXPLoZILYAMJtvoXXkD7/+1uP41eKkIt1ypWq5/q2uT94qHjXehEYfmjKOvjL9sg==",
|
| 12306 |
"dev": true,
|
|
|
|
| 12307 |
"dependencies": {
|
| 12308 |
"chokidar": ">=3.0.0 <4.0.0",
|
| 12309 |
"immutable": "^4.0.0",
|
|
|
|
| 12879 |
"websocket-driver": "^0.7.4"
|
| 12880 |
}
|
| 12881 |
},
|
| 12882 |
+
"node_modules/sockjs/node_modules/uuid": {
|
| 12883 |
+
"version": "8.3.2",
|
| 12884 |
+
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
| 12885 |
+
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
| 12886 |
+
"dev": true,
|
| 12887 |
+
"license": "MIT",
|
| 12888 |
+
"bin": {
|
| 12889 |
+
"uuid": "dist/bin/uuid"
|
| 12890 |
+
}
|
| 12891 |
+
},
|
| 12892 |
"node_modules/socks": {
|
| 12893 |
"version": "2.8.3",
|
| 12894 |
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz",
|
|
|
|
| 13431 |
"resolved": "https://registry.npmjs.org/terser/-/terser-5.29.1.tgz",
|
| 13432 |
"integrity": "sha512-lZQ/fyaIGxsbGxApKmoPTODIzELy3++mXhS5hOqaAWZjQtpq/hFHAc+rm29NND1rYRxRWKcjuARNwULNXa5RtQ==",
|
| 13433 |
"dev": true,
|
|
|
|
| 13434 |
"dependencies": {
|
| 13435 |
"@jridgewell/source-map": "^0.3.3",
|
| 13436 |
"acorn": "^8.8.2",
|
|
|
|
| 13669 |
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
|
| 13670 |
"integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
|
| 13671 |
"dev": true,
|
|
|
|
| 13672 |
"bin": {
|
| 13673 |
"tsc": "bin/tsc",
|
| 13674 |
"tsserver": "bin/tsserver"
|
|
|
|
| 13861 |
}
|
| 13862 |
},
|
| 13863 |
"node_modules/uuid": {
|
| 13864 |
+
"version": "13.0.0",
|
| 13865 |
+
"resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz",
|
| 13866 |
+
"integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==",
|
| 13867 |
+
"funding": [
|
| 13868 |
+
"https://github.com/sponsors/broofa",
|
| 13869 |
+
"https://github.com/sponsors/ctavan"
|
| 13870 |
+
],
|
| 13871 |
+
"license": "MIT",
|
| 13872 |
"bin": {
|
| 13873 |
+
"uuid": "dist-node/bin/uuid"
|
| 13874 |
}
|
| 13875 |
},
|
| 13876 |
"node_modules/validate-npm-package-license": {
|
|
|
|
| 13906 |
"resolved": "https://registry.npmjs.org/vite/-/vite-5.1.8.tgz",
|
| 13907 |
"integrity": "sha512-mB8ToUuSmzODSpENgvpFk2fTiU/YQ1tmcVJJ4WZbq4fPdGJkFNVcmVL5k7iDug6xzWjjuGDKAuSievIsD6H7Xw==",
|
| 13908 |
"dev": true,
|
|
|
|
| 13909 |
"dependencies": {
|
| 13910 |
"esbuild": "^0.19.3",
|
| 13911 |
"postcss": "^8.4.35",
|
|
|
|
| 14421 |
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz",
|
| 14422 |
"integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==",
|
| 14423 |
"dev": true,
|
|
|
|
| 14424 |
"dependencies": {
|
| 14425 |
"@types/estree": "^1.0.5",
|
| 14426 |
"@webassemblyjs/ast": "^1.12.1",
|
|
|
|
| 14495 |
"resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz",
|
| 14496 |
"integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==",
|
| 14497 |
"dev": true,
|
|
|
|
| 14498 |
"dependencies": {
|
| 14499 |
"@types/bonjour": "^3.5.9",
|
| 14500 |
"@types/connect-history-api-fallback": "^1.3.5",
|
|
|
|
| 14621 |
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
| 14622 |
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
| 14623 |
"dev": true,
|
|
|
|
| 14624 |
"dependencies": {
|
| 14625 |
"fast-deep-equal": "^3.1.1",
|
| 14626 |
"fast-json-stable-stringify": "^2.0.0",
|
|
|
|
| 14872 |
"node_modules/zone.js": {
|
| 14873 |
"version": "0.14.10",
|
| 14874 |
"resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.10.tgz",
|
| 14875 |
+
"integrity": "sha512-YGAhaO7J5ywOXW6InXNlLmfU194F8lVgu7bRntUF3TiG8Y3nBK0x1UJJuHUP/e8IyihkjCYqhCScpSwnlaSRkQ=="
|
|
|
|
| 14876 |
}
|
| 14877 |
},
|
| 14878 |
"dependencies": {
|
|
|
|
| 14991 |
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
|
| 14992 |
"integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
|
| 14993 |
"dev": true,
|
|
|
|
| 14994 |
"requires": {
|
| 14995 |
"nanoid": "^3.3.7",
|
| 14996 |
"picocolors": "^1.0.0",
|
|
|
|
| 15046 |
"version": "17.3.12",
|
| 15047 |
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-17.3.12.tgz",
|
| 15048 |
"integrity": "sha512-9hsdWF4gRRcVJtPcCcYLaX1CIyM9wUu6r+xRl6zU5hq8qhl35hig6ounz7CXFAzLf0WDBdM16bPHouVGaG76lg==",
|
|
|
|
| 15049 |
"requires": {
|
| 15050 |
"tslib": "^2.3.0"
|
| 15051 |
}
|
|
|
|
| 15090 |
"version": "17.3.12",
|
| 15091 |
"resolved": "https://registry.npmjs.org/@angular/common/-/common-17.3.12.tgz",
|
| 15092 |
"integrity": "sha512-vabJzvrx76XXFrm1RJZ6o/CyG32piTB/1sfFfKHdlH1QrmArb8It4gyk9oEjZ1IkAD0HvBWlfWmn+T6Vx3pdUw==",
|
|
|
|
| 15093 |
"requires": {
|
| 15094 |
"tslib": "^2.3.0"
|
| 15095 |
}
|
|
|
|
| 15098 |
"version": "17.3.12",
|
| 15099 |
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-17.3.12.tgz",
|
| 15100 |
"integrity": "sha512-vwI8oOL/gM+wPnptOVeBbMfZYwzRxQsovojZf+Zol9szl0k3SZ3FycWlxxXZGFu3VIEfrP6pXplTmyODS/Lt1w==",
|
|
|
|
| 15101 |
"requires": {
|
| 15102 |
"tslib": "^2.3.0"
|
| 15103 |
}
|
|
|
|
| 15107 |
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-17.3.12.tgz",
|
| 15108 |
"integrity": "sha512-1F8M7nWfChzurb7obbvuE7mJXlHtY1UG58pcwcomVtpPb+kPavgAO8OEvJHYBMV+bzSxkXt5UIwL9lt9jHUxZA==",
|
| 15109 |
"dev": true,
|
|
|
|
| 15110 |
"requires": {
|
| 15111 |
"@babel/core": "7.23.9",
|
| 15112 |
"@jridgewell/sourcemap-codec": "^1.4.14",
|
|
|
|
| 15161 |
"version": "17.3.12",
|
| 15162 |
"resolved": "https://registry.npmjs.org/@angular/core/-/core-17.3.12.tgz",
|
| 15163 |
"integrity": "sha512-MuFt5yKi161JmauUta4Dh0m8ofwoq6Ino+KoOtkYMBGsSx+A7dSm+DUxxNwdj7+DNyg3LjVGCFgBFnq4g8z06A==",
|
|
|
|
| 15164 |
"requires": {
|
| 15165 |
"tslib": "^2.3.0"
|
| 15166 |
}
|
|
|
|
| 15169 |
"version": "17.3.12",
|
| 15170 |
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-17.3.12.tgz",
|
| 15171 |
"integrity": "sha512-tV6r12Q3yEUlXwpVko4E+XscunTIpPkLbaiDn/MTL3Vxi2LZnsLgHyd/i38HaHN+e/H3B0a1ToSOhV5wf3ay4Q==",
|
|
|
|
| 15172 |
"requires": {
|
| 15173 |
"tslib": "^2.3.0"
|
| 15174 |
}
|
|
|
|
| 15232 |
"version": "17.3.12",
|
| 15233 |
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-17.3.12.tgz",
|
| 15234 |
"integrity": "sha512-DYY04ptWh/ulMHzd+y52WCE8QnEYGeIiW3hEIFjCN8z0kbIdFdUtEB0IK5vjNL3ejyhUmphcpeT5PYf3YXtqWQ==",
|
|
|
|
| 15235 |
"requires": {
|
| 15236 |
"tslib": "^2.3.0"
|
| 15237 |
}
|
|
|
|
| 15271 |
"version": "7.24.0",
|
| 15272 |
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.0.tgz",
|
| 15273 |
"integrity": "sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw==",
|
|
|
|
| 15274 |
"requires": {
|
| 15275 |
"@ampproject/remapping": "^2.2.0",
|
| 15276 |
"@babel/code-frame": "^7.23.5",
|
|
|
|
| 18730 |
"version": "8.14.0",
|
| 18731 |
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
|
| 18732 |
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
|
| 18733 |
+
"dev": true
|
|
|
|
| 18734 |
},
|
| 18735 |
"acorn-import-attributes": {
|
| 18736 |
"version": "1.9.5",
|
|
|
|
| 18783 |
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
|
| 18784 |
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
|
| 18785 |
"dev": true,
|
|
|
|
| 18786 |
"requires": {
|
| 18787 |
"fast-deep-equal": "^3.1.1",
|
| 18788 |
"json-schema-traverse": "^1.0.0",
|
|
|
|
| 19127 |
"version": "4.24.3",
|
| 19128 |
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.3.tgz",
|
| 19129 |
"integrity": "sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==",
|
|
|
|
| 19130 |
"requires": {
|
| 19131 |
"caniuse-lite": "^1.0.30001688",
|
| 19132 |
"electron-to-chromium": "^1.5.73",
|
|
|
|
| 21324 |
"version": "5.1.2",
|
| 21325 |
"resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.1.2.tgz",
|
| 21326 |
"integrity": "sha512-2oIUMGn00FdUiqz6epiiJr7xcFyNYj3rDcfmnzfkBnHyBQ3cBQUs4mmyGsOb7TTLb9kxk7dBcmEmqhDKkBoDyA==",
|
| 21327 |
+
"dev": true
|
|
|
|
| 21328 |
},
|
| 21329 |
"jest-diff": {
|
| 21330 |
"version": "30.0.0-alpha.6",
|
|
|
|
| 21867 |
"resolved": "https://registry.npmjs.org/karma/-/karma-6.4.4.tgz",
|
| 21868 |
"integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==",
|
| 21869 |
"dev": true,
|
|
|
|
| 21870 |
"requires": {
|
| 21871 |
"@colors/colors": "1.5.0",
|
| 21872 |
"body-parser": "^1.19.0",
|
|
|
|
| 22034 |
"resolved": "https://registry.npmjs.org/less/-/less-4.2.0.tgz",
|
| 22035 |
"integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==",
|
| 22036 |
"dev": true,
|
|
|
|
| 22037 |
"requires": {
|
| 22038 |
"copy-anything": "^2.0.1",
|
| 22039 |
"errno": "^0.1.1",
|
|
|
|
| 23208 |
"version": "8.4.49",
|
| 23209 |
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
|
| 23210 |
"integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
|
|
|
|
| 23211 |
"requires": {
|
| 23212 |
"nanoid": "^3.3.7",
|
| 23213 |
"picocolors": "^1.1.1",
|
|
|
|
| 23780 |
"version": "7.8.1",
|
| 23781 |
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
|
| 23782 |
"integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
|
|
|
|
| 23783 |
"requires": {
|
| 23784 |
"tslib": "^2.1.0"
|
| 23785 |
}
|
|
|
|
| 23817 |
"resolved": "https://registry.npmjs.org/sass/-/sass-1.71.1.tgz",
|
| 23818 |
"integrity": "sha512-wovtnV2PxzteLlfNzbgm1tFXPLoZILYAMJtvoXXkD7/+1uP41eKkIt1ypWq5/q2uT94qHjXehEYfmjKOvjL9sg==",
|
| 23819 |
"dev": true,
|
|
|
|
| 23820 |
"requires": {
|
| 23821 |
"chokidar": ">=3.0.0 <4.0.0",
|
| 23822 |
"immutable": "^4.0.0",
|
|
|
|
| 24238 |
"faye-websocket": "^0.11.3",
|
| 24239 |
"uuid": "^8.3.2",
|
| 24240 |
"websocket-driver": "^0.7.4"
|
| 24241 |
+
},
|
| 24242 |
+
"dependencies": {
|
| 24243 |
+
"uuid": {
|
| 24244 |
+
"version": "8.3.2",
|
| 24245 |
+
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
| 24246 |
+
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
| 24247 |
+
"dev": true
|
| 24248 |
+
}
|
| 24249 |
}
|
| 24250 |
},
|
| 24251 |
"socks": {
|
|
|
|
| 24666 |
"resolved": "https://registry.npmjs.org/terser/-/terser-5.29.1.tgz",
|
| 24667 |
"integrity": "sha512-lZQ/fyaIGxsbGxApKmoPTODIzELy3++mXhS5hOqaAWZjQtpq/hFHAc+rm29NND1rYRxRWKcjuARNwULNXa5RtQ==",
|
| 24668 |
"dev": true,
|
|
|
|
| 24669 |
"requires": {
|
| 24670 |
"@jridgewell/source-map": "^0.3.3",
|
| 24671 |
"acorn": "^8.8.2",
|
|
|
|
| 24833 |
"version": "5.3.3",
|
| 24834 |
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
|
| 24835 |
"integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
|
| 24836 |
+
"dev": true
|
|
|
|
| 24837 |
},
|
| 24838 |
"ua-parser-js": {
|
| 24839 |
"version": "0.7.40",
|
|
|
|
| 24948 |
"dev": true
|
| 24949 |
},
|
| 24950 |
"uuid": {
|
| 24951 |
+
"version": "13.0.0",
|
| 24952 |
+
"resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz",
|
| 24953 |
+
"integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w=="
|
|
|
|
| 24954 |
},
|
| 24955 |
"validate-npm-package-license": {
|
| 24956 |
"version": "3.0.4",
|
|
|
|
| 24979 |
"resolved": "https://registry.npmjs.org/vite/-/vite-5.1.8.tgz",
|
| 24980 |
"integrity": "sha512-mB8ToUuSmzODSpENgvpFk2fTiU/YQ1tmcVJJ4WZbq4fPdGJkFNVcmVL5k7iDug6xzWjjuGDKAuSievIsD6H7Xw==",
|
| 24981 |
"dev": true,
|
|
|
|
| 24982 |
"requires": {
|
| 24983 |
"esbuild": "^0.19.3",
|
| 24984 |
"fsevents": "~2.3.3",
|
|
|
|
| 25232 |
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz",
|
| 25233 |
"integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==",
|
| 25234 |
"dev": true,
|
|
|
|
| 25235 |
"requires": {
|
| 25236 |
"@types/estree": "^1.0.5",
|
| 25237 |
"@webassemblyjs/ast": "^1.12.1",
|
|
|
|
| 25263 |
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
| 25264 |
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
| 25265 |
"dev": true,
|
|
|
|
| 25266 |
"requires": {
|
| 25267 |
"fast-deep-equal": "^3.1.1",
|
| 25268 |
"fast-json-stable-stringify": "^2.0.0",
|
|
|
|
| 25330 |
"resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz",
|
| 25331 |
"integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==",
|
| 25332 |
"dev": true,
|
|
|
|
| 25333 |
"requires": {
|
| 25334 |
"@types/bonjour": "^3.5.9",
|
| 25335 |
"@types/connect-history-api-fallback": "^1.3.5",
|
|
|
|
| 25531 |
"zone.js": {
|
| 25532 |
"version": "0.14.10",
|
| 25533 |
"resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.10.tgz",
|
| 25534 |
+
"integrity": "sha512-YGAhaO7J5ywOXW6InXNlLmfU194F8lVgu7bRntUF3TiG8Y3nBK0x1UJJuHUP/e8IyihkjCYqhCScpSwnlaSRkQ=="
|
|
|
|
| 25535 |
}
|
| 25536 |
}
|
| 25537 |
}
|
package.json
CHANGED
|
@@ -30,6 +30,7 @@
|
|
| 30 |
"rxjs": "~7.8.0",
|
| 31 |
"tailwindcss": "^3.4.17",
|
| 32 |
"tslib": "^2.3.0",
|
|
|
|
| 33 |
"wavesurfer.js": "^7.11.1",
|
| 34 |
"zone.js": "~0.14.3"
|
| 35 |
},
|
|
|
|
| 30 |
"rxjs": "~7.8.0",
|
| 31 |
"tailwindcss": "^3.4.17",
|
| 32 |
"tslib": "^2.3.0",
|
| 33 |
+
"uuid": "^13.0.0",
|
| 34 |
"wavesurfer.js": "^7.11.1",
|
| 35 |
"zone.js": "~0.14.3"
|
| 36 |
},
|
src/app/app-routing.module.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { ChatComponent } from './chat/chat.component';
|
|
| 4 |
import { HomeComponent } from './home/home.component';
|
| 5 |
import { SignInComponent } from './sign-in/sign-in.component';
|
| 6 |
import { authGuard } from './core/guards/auth.guard';
|
|
|
|
| 7 |
|
| 8 |
/**
|
| 9 |
* Application routing configuration
|
|
@@ -14,38 +15,45 @@ import { authGuard } from './core/guards/auth.guard';
|
|
| 14 |
*/
|
| 15 |
export const routes: Routes = [
|
| 16 |
// Public routes
|
| 17 |
-
{
|
| 18 |
-
path: '',
|
| 19 |
-
component: HomeComponent,
|
| 20 |
pathMatch: 'full',
|
| 21 |
data: { title: 'Home' }
|
| 22 |
},
|
| 23 |
-
{
|
| 24 |
-
path: 'home',
|
| 25 |
component: HomeComponent,
|
| 26 |
data: { title: 'Home' }
|
| 27 |
},
|
| 28 |
-
{
|
| 29 |
-
path: 'login',
|
| 30 |
component: SignInComponent,
|
| 31 |
data: { title: 'Sign In' }
|
| 32 |
},
|
| 33 |
|
| 34 |
// Chat routes
|
| 35 |
-
{
|
| 36 |
-
path: 'chat',
|
| 37 |
component: ChatComponent,
|
| 38 |
data: { title: 'Chat' }
|
| 39 |
},
|
| 40 |
-
{
|
| 41 |
-
path: 'chat/:id',
|
| 42 |
component: ChatComponent,
|
| 43 |
data: { title: 'Chat' }
|
| 44 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
|
| 46 |
// Fallback route
|
| 47 |
-
{
|
| 48 |
-
path: '**',
|
| 49 |
redirectTo: '',
|
| 50 |
data: { title: 'Page Not Found' }
|
| 51 |
}
|
|
|
|
| 4 |
import { HomeComponent } from './home/home.component';
|
| 5 |
import { SignInComponent } from './sign-in/sign-in.component';
|
| 6 |
import { authGuard } from './core/guards/auth.guard';
|
| 7 |
+
import { StaticChatComponent } from './staticchat/staticchat.component';
|
| 8 |
|
| 9 |
/**
|
| 10 |
* Application routing configuration
|
|
|
|
| 15 |
*/
|
| 16 |
export const routes: Routes = [
|
| 17 |
// Public routes
|
| 18 |
+
{
|
| 19 |
+
path: '',
|
| 20 |
+
component: HomeComponent,
|
| 21 |
pathMatch: 'full',
|
| 22 |
data: { title: 'Home' }
|
| 23 |
},
|
| 24 |
+
{
|
| 25 |
+
path: 'home',
|
| 26 |
component: HomeComponent,
|
| 27 |
data: { title: 'Home' }
|
| 28 |
},
|
| 29 |
+
{
|
| 30 |
+
path: 'login',
|
| 31 |
component: SignInComponent,
|
| 32 |
data: { title: 'Sign In' }
|
| 33 |
},
|
| 34 |
|
| 35 |
// Chat routes
|
| 36 |
+
{
|
| 37 |
+
path: 'chat',
|
| 38 |
component: ChatComponent,
|
| 39 |
data: { title: 'Chat' }
|
| 40 |
},
|
| 41 |
+
{
|
| 42 |
+
path: 'chat/:id',
|
| 43 |
component: ChatComponent,
|
| 44 |
data: { title: 'Chat' }
|
| 45 |
},
|
| 46 |
+
{
|
| 47 |
+
path: 'mj-chat',
|
| 48 |
+
component: StaticChatComponent,
|
| 49 |
+
data: { title: 'MJ-CHAT' }
|
| 50 |
+
},
|
| 51 |
+
|
| 52 |
+
|
| 53 |
|
| 54 |
// Fallback route
|
| 55 |
+
{
|
| 56 |
+
path: '**',
|
| 57 |
redirectTo: '',
|
| 58 |
data: { title: 'Page Not Found' }
|
| 59 |
}
|
src/app/app.module.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
import { NgModule } from '@angular/core';
|
| 2 |
import { BrowserModule } from '@angular/platform-browser';
|
| 3 |
-
import { FormsModule } from '@angular/forms';
|
| 4 |
import { HttpClientModule } from '@angular/common/http';
|
| 5 |
import { CommonModule } from '@angular/common';
|
| 6 |
import { AppRoutingModule } from './app-routing.module';
|
|
@@ -19,18 +19,21 @@ import { SignInComponent } from './sign-in/sign-in.component';
|
|
| 19 |
import { PronunciationComponent } from './pronunciation/pronunciation.component';
|
| 20 |
import { FooterComponent } from './footer/footer.component';
|
| 21 |
import { ButtonComponent } from './shared/button/button.component';
|
|
|
|
| 22 |
|
| 23 |
|
| 24 |
@NgModule({
|
| 25 |
declarations: [
|
| 26 |
AppComponent,
|
| 27 |
HomeComponent,
|
| 28 |
-
PronunciationComponent
|
|
|
|
| 29 |
],
|
| 30 |
imports: [
|
| 31 |
BrowserModule,
|
| 32 |
AppRoutingModule,
|
| 33 |
FormsModule,
|
|
|
|
| 34 |
HttpClientModule,
|
| 35 |
CommonModule,
|
| 36 |
MatDialogModule,
|
|
|
|
| 1 |
import { NgModule } from '@angular/core';
|
| 2 |
import { BrowserModule } from '@angular/platform-browser';
|
| 3 |
+
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
| 4 |
import { HttpClientModule } from '@angular/common/http';
|
| 5 |
import { CommonModule } from '@angular/common';
|
| 6 |
import { AppRoutingModule } from './app-routing.module';
|
|
|
|
| 19 |
import { PronunciationComponent } from './pronunciation/pronunciation.component';
|
| 20 |
import { FooterComponent } from './footer/footer.component';
|
| 21 |
import { ButtonComponent } from './shared/button/button.component';
|
| 22 |
+
import { StaticChatComponent } from './staticchat/staticchat.component';
|
| 23 |
|
| 24 |
|
| 25 |
@NgModule({
|
| 26 |
declarations: [
|
| 27 |
AppComponent,
|
| 28 |
HomeComponent,
|
| 29 |
+
PronunciationComponent,
|
| 30 |
+
StaticChatComponent
|
| 31 |
],
|
| 32 |
imports: [
|
| 33 |
BrowserModule,
|
| 34 |
AppRoutingModule,
|
| 35 |
FormsModule,
|
| 36 |
+
ReactiveFormsModule,
|
| 37 |
HttpClientModule,
|
| 38 |
CommonModule,
|
| 39 |
MatDialogModule,
|
src/app/chat/api.service.ts
CHANGED
|
@@ -7,7 +7,7 @@ type DbLevel = 'low' | 'mid' | 'high';
|
|
| 7 |
|
| 8 |
function resolveBaseUrl(): string {
|
| 9 |
const isHF = location.hostname.endsWith('hf.space');
|
| 10 |
-
if (isHF) return 'https://
|
| 11 |
if (location.hostname === 'localhost' || location.hostname === '127.0.0.1') return 'http://localhost:5000/rag';
|
| 12 |
return '/rag';
|
| 13 |
}
|
|
|
|
| 7 |
|
| 8 |
function resolveBaseUrl(): string {
|
| 9 |
const isHF = location.hostname.endsWith('hf.space');
|
| 10 |
+
if (isHF) return 'https://pykara-py-learn-backend.hf.space/rag';
|
| 11 |
if (location.hostname === 'localhost' || location.hostname === '127.0.0.1') return 'http://localhost:5000/rag';
|
| 12 |
return '/rag';
|
| 13 |
}
|
src/app/pronunciation/pronunciation.service.ts
CHANGED
|
@@ -13,7 +13,7 @@ export interface ScoreResponse {
|
|
| 13 |
export class PronunciationService {
|
| 14 |
private readonly apiBase =
|
| 15 |
location.hostname.endsWith('hf.space')
|
| 16 |
-
? 'https://
|
| 17 |
: 'http://localhost:5000';
|
| 18 |
|
| 19 |
private readonly scoreEndpoint = `${this.apiBase}/pronunciation/score`;
|
|
|
|
| 13 |
export class PronunciationService {
|
| 14 |
private readonly apiBase =
|
| 15 |
location.hostname.endsWith('hf.space')
|
| 16 |
+
? 'https://pykara-py-learn-backend.hf.space'
|
| 17 |
: 'http://localhost:5000';
|
| 18 |
|
| 19 |
private readonly scoreEndpoint = `${this.apiBase}/pronunciation/score`;
|
src/app/staticchat/staticchat.component.css
ADDED
|
@@ -0,0 +1,495 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* ===== GLOBAL FONT FOR ENTIRE CHAT ===== */
|
| 2 |
+
.chat-container,
|
| 3 |
+
.chat-container * {
|
| 4 |
+
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", sans-serif !important;
|
| 5 |
+
}
|
| 6 |
+
|
| 7 |
+
/* ===== MAIN CONTAINER ===== */
|
| 8 |
+
.chat-container {
|
| 9 |
+
display: flex;
|
| 10 |
+
justify-content: center;
|
| 11 |
+
align-items: center;
|
| 12 |
+
height: 91vh;
|
| 13 |
+
padding: 2vw;
|
| 14 |
+
gap: 2vw;
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
.chat-window {
|
| 18 |
+
width: 100%;
|
| 19 |
+
max-width: 1000px;
|
| 20 |
+
height: 80vh;
|
| 21 |
+
display: flex;
|
| 22 |
+
flex-direction: column;
|
| 23 |
+
border-radius: 20px;
|
| 24 |
+
backdrop-filter: blur(20px);
|
| 25 |
+
background: rgba(255,255,255,0.08);
|
| 26 |
+
box-shadow: 0 10px 40px rgba(0,0,0,0.4);
|
| 27 |
+
overflow: hidden;
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
/* ===== MESSAGES AREA ===== */
|
| 31 |
+
.chat-messages {
|
| 32 |
+
flex: 1;
|
| 33 |
+
/* padding: 25px;*/
|
| 34 |
+
/* prevent input overlap: keep some bottom padding */
|
| 35 |
+
/*padding-bottom: 120px;*/
|
| 36 |
+
overflow-y: auto;
|
| 37 |
+
display: flex;
|
| 38 |
+
flex-direction: column;
|
| 39 |
+
gap: 7vw;
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
/* ===== MESSAGE ROW (AVATAR + BUBBLE) ===== */
|
| 43 |
+
.message-row {
|
| 44 |
+
display: flex;
|
| 45 |
+
align-items: flex-end;
|
| 46 |
+
gap: 10px;
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
.user-row {
|
| 50 |
+
justify-content: flex-end;
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
.bot-row {
|
| 54 |
+
justify-content: flex-start;
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
/* ===== AVATAR ===== */
|
| 58 |
+
.avatar {
|
| 59 |
+
width: 38px;
|
| 60 |
+
height: 38px;
|
| 61 |
+
border-radius: 50%;
|
| 62 |
+
object-fit: cover;
|
| 63 |
+
box-shadow: 0 2px 6px rgba(0,0,0,0.3);
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
/* ===== MESSAGE BUBBLE ===== */
|
| 67 |
+
.message-bubble {
|
| 68 |
+
max-width: 60%;
|
| 69 |
+
padding: 14px 18px 8px 18px;
|
| 70 |
+
border-radius: 18px;
|
| 71 |
+
font-size: clamp(14px, 1vw, 18px);
|
| 72 |
+
line-height: 1.6;
|
| 73 |
+
display: flex;
|
| 74 |
+
flex-direction: column;
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
/* User bubble */
|
| 78 |
+
.user-message {
|
| 79 |
+
background: linear-gradient(135deg, #0099ff, #0066ff);
|
| 80 |
+
color: white;
|
| 81 |
+
border-bottom-right-radius: 6px;
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
/* Bot bubble */
|
| 85 |
+
.bot-message {
|
| 86 |
+
background: rgba(255,255,255,0.95);
|
| 87 |
+
color: #333;
|
| 88 |
+
border-bottom-left-radius: 6px;
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
/* ===== MESSAGE TIME (BOTTOM RIGHT) ===== */
|
| 92 |
+
.message-time {
|
| 93 |
+
font-size: clamp(10px, 0.7vw, 12px);
|
| 94 |
+
opacity: 0.6;
|
| 95 |
+
margin-top: 6px;
|
| 96 |
+
align-self: flex-end;
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
/* ===== TYPING INDICATOR ===== */
|
| 100 |
+
.typing-indicator {
|
| 101 |
+
display: flex;
|
| 102 |
+
gap: 5px;
|
| 103 |
+
align-items: center;
|
| 104 |
+
color: white;
|
| 105 |
+
padding-left: 50px;
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
.dot {
|
| 109 |
+
width: 6px;
|
| 110 |
+
height: 6px;
|
| 111 |
+
background: #ccc;
|
| 112 |
+
border-radius: 50%;
|
| 113 |
+
animation: blink 1.4s infinite both;
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
.dot:nth-child(2) {
|
| 117 |
+
animation-delay: 0.2s;
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
.dot:nth-child(3) {
|
| 121 |
+
animation-delay: 0.4s;
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
@keyframes blink {
|
| 125 |
+
0% {
|
| 126 |
+
opacity: .2;
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
20% {
|
| 130 |
+
opacity: 1;
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
100% {
|
| 134 |
+
opacity: .2;
|
| 135 |
+
}
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
/* ===== SUGGESTIONS DROPDOWN ===== */
|
| 139 |
+
.suggestions-box {
|
| 140 |
+
/* background: rgba(0,0,0,0.75);*/
|
| 141 |
+
background: linear-gradient(135deg, #0072ff, #00c6ff );
|
| 142 |
+
max-height: 250px;
|
| 143 |
+
overflow-y: auto;
|
| 144 |
+
border-radius: 12px;
|
| 145 |
+
margin: 0 20px 10px 20px;
|
| 146 |
+
width: 47vw;
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
.suggestion-item {
|
| 150 |
+
padding: 10px 14px;
|
| 151 |
+
cursor: pointer;
|
| 152 |
+
color: white;
|
| 153 |
+
font-size:1vw;
|
| 154 |
+
font-weight:bold;
|
| 155 |
+
border-bottom: 1px solid rgba(255,255,255,0.1);
|
| 156 |
+
transition: background 0.2s ease;
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
.suggestion-item:hover {
|
| 160 |
+
background: rgba(255,255,255,0.15);
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
/* ===== INPUT BAR ===== */
|
| 164 |
+
.chat-input {
|
| 165 |
+
display: flex;
|
| 166 |
+
padding: 16px;
|
| 167 |
+
background: rgba(255,255,255,0.15);
|
| 168 |
+
backdrop-filter: blur(12px);
|
| 169 |
+
border-top: 1px solid rgba(255,255,255,0.2);
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
.chat-input input {
|
| 173 |
+
flex: 1;
|
| 174 |
+
padding: 12px 16px;
|
| 175 |
+
border-radius: 25px;
|
| 176 |
+
border: none;
|
| 177 |
+
outline: none;
|
| 178 |
+
font-size: 14px;
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
.chat-input button {
|
| 182 |
+
margin-left: 10px;
|
| 183 |
+
width: 48px;
|
| 184 |
+
height: 48px;
|
| 185 |
+
border-radius: 50%;
|
| 186 |
+
border: none;
|
| 187 |
+
background: linear-gradient(135deg, #00c6ff, #0072ff);
|
| 188 |
+
color: white;
|
| 189 |
+
font-size: 30px;
|
| 190 |
+
cursor: pointer;
|
| 191 |
+
transition: transform 0.2s ease;
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
.chat-input button:hover {
|
| 195 |
+
transform: scale(1.08);
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
.chat-input button:disabled {
|
| 199 |
+
opacity: 0.5;
|
| 200 |
+
cursor: not-allowed;
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
/* ===== SCROLLBAR ===== */
|
| 204 |
+
.chat-messages::-webkit-scrollbar {
|
| 205 |
+
width: 6px;
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
.chat-messages::-webkit-scrollbar-thumb {
|
| 209 |
+
background: rgba(255,255,255,0.3);
|
| 210 |
+
border-radius: 10px;
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
.video-window {
|
| 214 |
+
width: 100%;
|
| 215 |
+
max-width: 640px;
|
| 216 |
+
height: 80vh;
|
| 217 |
+
margin-left: 20px;
|
| 218 |
+
border-radius: 20px;
|
| 219 |
+
overflow: hidden;
|
| 220 |
+
background: rgba(255,255,255,0.05);
|
| 221 |
+
backdrop-filter: blur(10px);
|
| 222 |
+
box-shadow: 0 10px 30px rgba(0,0,0,0.4);
|
| 223 |
+
display: flex;
|
| 224 |
+
flex-direction: column;
|
| 225 |
+
align-items: center;
|
| 226 |
+
gap: 2vw;
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
/* Video fills top area */
|
| 230 |
+
.video-window video {
|
| 231 |
+
width: 100%;
|
| 232 |
+
height: 100%;
|
| 233 |
+
flex: 1;
|
| 234 |
+
object-fit: cover;
|
| 235 |
+
background: black;
|
| 236 |
+
}
|
| 237 |
+
|
| 238 |
+
.play-pause-btn {
|
| 239 |
+
position: relative;
|
| 240 |
+
top: -1vw;
|
| 241 |
+
width: 4vw;
|
| 242 |
+
min-width: 40px;
|
| 243 |
+
height: 4vw;
|
| 244 |
+
min-height: 40px;
|
| 245 |
+
cursor: pointer;
|
| 246 |
+
border: 2px solid #3f61ad;
|
| 247 |
+
border-radius: 50px;
|
| 248 |
+
box-shadow: 0 8px 20px rgba(63,97,173,0.18), 0 2px 6px rgba(0,0,0,0.25);
|
| 249 |
+
transition: transform 160ms ease, box-shadow 160ms ease;
|
| 250 |
+
background: linear-gradient(180deg, rgba(255,255,255,0.02), rgba(0,0,0,0.03));
|
| 251 |
+
}
|
| 252 |
+
|
| 253 |
+
.play-pause-btn:hover {
|
| 254 |
+
transform: translateY(-3px);
|
| 255 |
+
box-shadow: 0 16px 36px rgba(63,97,173,0.22), 0 6px 12px rgba(0,0,0,0.28);
|
| 256 |
+
}
|
| 257 |
+
|
| 258 |
+
.play-pause-btn:active {
|
| 259 |
+
transform: translateY(0);
|
| 260 |
+
box-shadow: 0 6px 12px rgba(0,0,0,0.22);
|
| 261 |
+
}
|
| 262 |
+
|
| 263 |
+
.bot-answer {
|
| 264 |
+
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", sans-serif !important;
|
| 265 |
+
font-size: clamp(14px, 1vw, 18px);
|
| 266 |
+
line-height: 1.6;
|
| 267 |
+
}
|
| 268 |
+
|
| 269 |
+
.micBtn {
|
| 270 |
+
width: 44px;
|
| 271 |
+
height: 44px;
|
| 272 |
+
border: 0;
|
| 273 |
+
border-radius: 999px;
|
| 274 |
+
cursor: pointer;
|
| 275 |
+
display: inline-flex;
|
| 276 |
+
align-items: center;
|
| 277 |
+
justify-content: center;
|
| 278 |
+
background: #f3f4f6;
|
| 279 |
+
font-size: 30px !important;
|
| 280 |
+
}
|
| 281 |
+
|
| 282 |
+
.micBtn:disabled {
|
| 283 |
+
opacity: 0.5;
|
| 284 |
+
cursor: not-allowed;
|
| 285 |
+
}
|
| 286 |
+
|
| 287 |
+
.micBtn.active {
|
| 288 |
+
background: #111827;
|
| 289 |
+
}
|
| 290 |
+
|
| 291 |
+
.micIcon {
|
| 292 |
+
width: 22px;
|
| 293 |
+
height: 22px;
|
| 294 |
+
fill: #111827;
|
| 295 |
+
}
|
| 296 |
+
|
| 297 |
+
.micBtn.active .micIcon {
|
| 298 |
+
fill: #ffffff;
|
| 299 |
+
}
|
| 300 |
+
|
| 301 |
+
.actions {
|
| 302 |
+
display: inline-flex;
|
| 303 |
+
gap: 8px;
|
| 304 |
+
margin-left: 8px;
|
| 305 |
+
align-items: center;
|
| 306 |
+
}
|
| 307 |
+
|
| 308 |
+
.okBtn, .noBtn {
|
| 309 |
+
font-size: 18px;
|
| 310 |
+
width: 38px;
|
| 311 |
+
height: 38px;
|
| 312 |
+
border-radius: 999px;
|
| 313 |
+
border: 0;
|
| 314 |
+
cursor: pointer;
|
| 315 |
+
background: #f3f4f6;
|
| 316 |
+
}
|
| 317 |
+
|
| 318 |
+
.okBtn:disabled {
|
| 319 |
+
opacity: 0.5;
|
| 320 |
+
cursor: not-allowed;
|
| 321 |
+
}
|
| 322 |
+
|
| 323 |
+
.okBtn:hover, .noBtn:hover {
|
| 324 |
+
background: #e5e7eb;
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
+
.mediaRow {
|
| 328 |
+
display: flex;
|
| 329 |
+
gap: 1vw;
|
| 330 |
+
font-size: 1vw;
|
| 331 |
+
padding-top: 2vw;
|
| 332 |
+
}
|
| 333 |
+
|
| 334 |
+
.chip {
|
| 335 |
+
display: inline-flex;
|
| 336 |
+
align-items: center;
|
| 337 |
+
justify-content: center;
|
| 338 |
+
min-width: 48px;
|
| 339 |
+
height: clamp(30px, 2vw, 36px);
|
| 340 |
+
padding: 0 12px;
|
| 341 |
+
background: linear-gradient(135deg, #00c6ff, #0072ff);
|
| 342 |
+
color: #ffffff;
|
| 343 |
+
font-weight: 600;
|
| 344 |
+
font-size: 0.9rem;
|
| 345 |
+
border: none;
|
| 346 |
+
border-radius: 999px;
|
| 347 |
+
box-shadow: 0 6px 20px rgba(0, 114, 255, 0.18), 0 2px 6px rgba(0, 0, 0, 0.25);
|
| 348 |
+
cursor: pointer;
|
| 349 |
+
transition: transform 0.15s ease, box-shadow 0.15s ease, opacity 0.15s ease;
|
| 350 |
+
user-select: none;
|
| 351 |
+
}
|
| 352 |
+
|
| 353 |
+
.chip:hover {
|
| 354 |
+
transform: translateY(-3px);
|
| 355 |
+
box-shadow: 0 14px 30px rgba(0, 114, 255, 0.20), 0 4px 10px rgba(0, 0, 0, 0.28);
|
| 356 |
+
}
|
| 357 |
+
|
| 358 |
+
.chip:active {
|
| 359 |
+
transform: translateY(0);
|
| 360 |
+
box-shadow: 0 6px 14px rgba(0, 0, 0, 0.22);
|
| 361 |
+
opacity: 0.98;
|
| 362 |
+
}
|
| 363 |
+
|
| 364 |
+
.chip:focus {
|
| 365 |
+
outline: 3px solid rgba(0, 114, 255, 0.18);
|
| 366 |
+
outline-offset: 3px;
|
| 367 |
+
}
|
| 368 |
+
|
| 369 |
+
.arrow-btn {
|
| 370 |
+
position: fixed;
|
| 371 |
+
right: 16px;
|
| 372 |
+
width: 51px;
|
| 373 |
+
height: 51px;
|
| 374 |
+
border-radius: 50%;
|
| 375 |
+
border: none;
|
| 376 |
+
background: linear-gradient(135deg, #00c6ff, #0072ff);
|
| 377 |
+
/* background: #56cdc2;*/
|
| 378 |
+
color: #fff;
|
| 379 |
+
font-size: 31px;
|
| 380 |
+
box-shadow: 0 6px 16px #00000040;
|
| 381 |
+
cursor: pointer;
|
| 382 |
+
transition: transform .15s ease, box-shadow .2s ease, background .2s ease;
|
| 383 |
+
z-index: 20;
|
| 384 |
+
}
|
| 385 |
+
|
| 386 |
+
.up {
|
| 387 |
+
top: 1vw;
|
| 388 |
+
}
|
| 389 |
+
|
| 390 |
+
.down {
|
| 391 |
+
bottom: 5vw;
|
| 392 |
+
}
|
| 393 |
+
|
| 394 |
+
/* Add scroll-snap behavior so each pair fills the visible area and scrolls one-by-one */
|
| 395 |
+
.chat-messages {
|
| 396 |
+
overflow-y: auto;
|
| 397 |
+
scroll-snap-type: y mandatory;
|
| 398 |
+
-webkit-overflow-scrolling: touch;
|
| 399 |
+
}
|
| 400 |
+
|
| 401 |
+
/* Each pair is treated as a snap point */
|
| 402 |
+
/* Use 100% of the messages area so one pair is visible at a time; align content start so user appears at top */
|
| 403 |
+
.pair {
|
| 404 |
+
scroll-snap-align: start;
|
| 405 |
+
min-height: 100%;
|
| 406 |
+
display: flex;
|
| 407 |
+
flex-direction: column;
|
| 408 |
+
justify-content: flex-start;
|
| 409 |
+
padding: 4.5rem 1rem;
|
| 410 |
+
box-sizing: border-box;
|
| 411 |
+
gap: 2vw;
|
| 412 |
+
}
|
| 413 |
+
|
| 414 |
+
/* small visual spacing between pairs */
|
| 415 |
+
.pair + .pair {
|
| 416 |
+
margin-top: 8px;
|
| 417 |
+
}
|
| 418 |
+
|
| 419 |
+
/* preserve existing message-row styling assumed by template;
|
| 420 |
+
adjust these rules if your project already defines them */
|
| 421 |
+
.message-row {
|
| 422 |
+
display: flex;
|
| 423 |
+
align-items: flex-end;
|
| 424 |
+
gap: 0.6rem;
|
| 425 |
+
}
|
| 426 |
+
|
| 427 |
+
.bot-row {
|
| 428 |
+
justify-content: flex-start;
|
| 429 |
+
}
|
| 430 |
+
|
| 431 |
+
.user-row {
|
| 432 |
+
justify-content: flex-end;
|
| 433 |
+
}
|
| 434 |
+
|
| 435 |
+
.avatar {
|
| 436 |
+
width: 3.2rem;
|
| 437 |
+
height: 3.2rem;
|
| 438 |
+
border-radius: 50%;
|
| 439 |
+
object-fit: cover;
|
| 440 |
+
}
|
| 441 |
+
|
| 442 |
+
.message-bubble {
|
| 443 |
+
max-width: 70%;
|
| 444 |
+
padding: 0.6rem 0.8rem;
|
| 445 |
+
border-radius: 0.6rem;
|
| 446 |
+
position: relative;
|
| 447 |
+
}
|
| 448 |
+
|
| 449 |
+
.bot-message {
|
| 450 |
+
background: #f1f1f1;
|
| 451 |
+
color: #111;
|
| 452 |
+
}
|
| 453 |
+
|
| 454 |
+
.user-message {
|
| 455 |
+
/* background: #4caf50;*/
|
| 456 |
+
color: #fff;
|
| 457 |
+
background: linear-gradient(135deg, #00c6ff, #0072ff);
|
| 458 |
+
}
|
| 459 |
+
|
| 460 |
+
.message-time {
|
| 461 |
+
font-size: 0.7rem;
|
| 462 |
+
color: rgba(0,0,0,1);
|
| 463 |
+
margin-top: 6px;
|
| 464 |
+
text-align: right;
|
| 465 |
+
}
|
| 466 |
+
|
| 467 |
+
/* typing indicator stays at the end of the list */
|
| 468 |
+
.typing-indicator {
|
| 469 |
+
display: flex;
|
| 470 |
+
gap: 0.4rem;
|
| 471 |
+
padding: 0.5rem;
|
| 472 |
+
align-items: center;
|
| 473 |
+
}
|
| 474 |
+
|
| 475 |
+
.typing-indicator .dot {
|
| 476 |
+
width: 8px;
|
| 477 |
+
height: 8px;
|
| 478 |
+
border-radius: 50%;
|
| 479 |
+
background: #bbb;
|
| 480 |
+
animation: blink 1.2s infinite;
|
| 481 |
+
}
|
| 482 |
+
|
| 483 |
+
@keyframes blink {
|
| 484 |
+
0% {
|
| 485 |
+
opacity: 0.2;
|
| 486 |
+
}
|
| 487 |
+
|
| 488 |
+
50% {
|
| 489 |
+
opacity: 1;
|
| 490 |
+
}
|
| 491 |
+
|
| 492 |
+
100% {
|
| 493 |
+
opacity: 0.2;
|
| 494 |
+
}
|
| 495 |
+
}
|
src/app/staticchat/staticchat.component.html
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<div class="chat-container">
|
| 2 |
+
<div class="chat-window">
|
| 3 |
+
<button class="arrow-btn up" title="Prev message" (click)="showPreviousPair()">↑</button>
|
| 4 |
+
|
| 5 |
+
<!-- MESSAGES -->
|
| 6 |
+
<div class="chat-messages" #chatContainer>
|
| 7 |
+
<div *ngFor="let pair of pairedMessages; let i = index" class="pair" [attr.data-index]="i">
|
| 8 |
+
|
| 9 |
+
<!-- If user exists in pair (now shown first/top) -->
|
| 10 |
+
<div *ngIf="pair.user" class="message-row user-row">
|
| 11 |
+
<div class="message-bubble user-message">
|
| 12 |
+
<span style="font-size:1.5vw;">{{ pair.user.text }}</span>
|
| 13 |
+
<div class="message-time">
|
| 14 |
+
{{ pair.user.timestamp | date:'shortTime' }}
|
| 15 |
+
</div>
|
| 16 |
+
</div>
|
| 17 |
+
<img src="assets/staticchat/student.png" class="avatar" />
|
| 18 |
+
</div>
|
| 19 |
+
|
| 20 |
+
<!-- If bot exists in pair (now shown below user) -->
|
| 21 |
+
<div *ngIf="pair.bot" class="message-row bot-row">
|
| 22 |
+
<img src="assets/staticchat/teacher.png" class="avatar" />
|
| 23 |
+
<div class="message-bubble bot-message">
|
| 24 |
+
<span style="font-size:1.5vw;">{{ pair.bot.text }}</span>
|
| 25 |
+
|
| 26 |
+
<div class="mediaRow" *ngIf="pair.bot.rawData && (pair.bot.rawData?.audio_url || pair.bot.rawData?.video_url)">
|
| 27 |
+
<button class="chip" *ngIf="pair.bot.rawData?.audio_url" (click)="playAudio(pair.bot.rawData?.audio_url)">
|
| 28 |
+
🔊 Audio
|
| 29 |
+
</button>
|
| 30 |
+
<button class="chip" *ngIf="pair.bot.rawData?.video_url" (click)="playResponseVideo(pair.bot.rawData?.video_url)">
|
| 31 |
+
🎬 Video
|
| 32 |
+
</button>
|
| 33 |
+
<button class="chip" *ngIf="pair.bot.rawData?.detail_url" (click)="playResponseVideo(pair.bot.rawData?.detail_url)">
|
| 34 |
+
💡 Detail
|
| 35 |
+
</button>
|
| 36 |
+
<button class="chip" *ngIf="pair.bot.rawData?.story_url" (click)="playResponseVideo(pair.bot.rawData?.story_url)">
|
| 37 |
+
📖 Story
|
| 38 |
+
</button>
|
| 39 |
+
<button class="chip" *ngIf="pair.bot.rawData?.example_url" (click)="playResponseVideo(pair.bot.rawData?.example_url)">
|
| 40 |
+
🧪 Example
|
| 41 |
+
</button>
|
| 42 |
+
</div>
|
| 43 |
+
|
| 44 |
+
<div class="message-time">
|
| 45 |
+
{{ pair.bot.timestamp | date:'shortTime' }}
|
| 46 |
+
</div>
|
| 47 |
+
</div>
|
| 48 |
+
</div>
|
| 49 |
+
|
| 50 |
+
</div>
|
| 51 |
+
|
| 52 |
+
<!-- Typing indicator -->
|
| 53 |
+
<div *ngIf="isTyping" class="typing-indicator">
|
| 54 |
+
<div class="dot"></div>
|
| 55 |
+
<div class="dot"></div>
|
| 56 |
+
<div class="dot"></div>
|
| 57 |
+
</div>
|
| 58 |
+
</div>
|
| 59 |
+
<button class="arrow-btn down" title="Next message" (click)="showNextPair()">↓</button>
|
| 60 |
+
|
| 61 |
+
<!-- SUGGESTIONS DROPDOWN -->
|
| 62 |
+
<div *ngIf="showSuggestions && suggestedQuestions.length > 0" class="suggestions-box">
|
| 63 |
+
<div *ngFor="let question of suggestedQuestions"
|
| 64 |
+
class="suggestion-item"
|
| 65 |
+
(click)="selectQuestion(question)">
|
| 66 |
+
{{ question }}
|
| 67 |
+
</div>
|
| 68 |
+
</div>
|
| 69 |
+
|
| 70 |
+
<!-- INPUT BAR -->
|
| 71 |
+
<form [formGroup]="chatForm" (ngSubmit)="sendMessage()" class="chat-input">
|
| 72 |
+
<!--<input type="text"
|
| 73 |
+
formControlName="message"
|
| 74 |
+
#messageInput
|
| 75 |
+
placeholder="Type your message..."
|
| 76 |
+
(focus)="onInputFocus()"
|
| 77 |
+
(input)="onInputChange()"
|
| 78 |
+
[readonly]="isSpeechProcessing" />-->
|
| 79 |
+
<input type="text"
|
| 80 |
+
formControlName="message"
|
| 81 |
+
#messageInput
|
| 82 |
+
[placeholder]="isListening ? '⏺ Listening...' : 'Type your message...'"
|
| 83 |
+
(focus)="onInputFocus()"
|
| 84 |
+
(input)="onInputChange()"
|
| 85 |
+
[readonly]="isSpeechProcessing || isListening" />
|
| 86 |
+
|
| 87 |
+
<button type="button"
|
| 88 |
+
class="micBtn"
|
| 89 |
+
[disabled]="!supported || isListening"
|
| 90 |
+
(click)="toggleMic()">
|
| 91 |
+
🎤
|
| 92 |
+
</button>
|
| 93 |
+
|
| 94 |
+
<div class="actions" *ngIf="showActions">
|
| 95 |
+
<button class="reject" (click)="reject()">❌</button>
|
| 96 |
+
<button class="accept" (click)="accept()">✅</button>
|
| 97 |
+
|
| 98 |
+
</div>
|
| 99 |
+
|
| 100 |
+
<button type="submit" [disabled]="!chatForm.valid">
|
| 101 |
+
➤
|
| 102 |
+
</button>
|
| 103 |
+
</form>
|
| 104 |
+
|
| 105 |
+
</div>
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
<div class="video-window">
|
| 109 |
+
|
| 110 |
+
<video #videoPlayer autoplay muted playsinline></video>
|
| 111 |
+
<img class="play-pause-btn"
|
| 112 |
+
[src]="(currentVideoType !== 'blink' && isVideoPlaying) ? 'assets/staticchat/pause.png' : 'assets/staticchat/play.png'"
|
| 113 |
+
(click)="togglePlayPause()" />
|
| 114 |
+
</div>
|
| 115 |
+
</div>
|
src/app/staticchat/staticchat.component.ts
ADDED
|
@@ -0,0 +1,756 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// (file header unchanged)
|
| 2 |
+
import { Component, OnInit, ViewChild, ElementRef, HostListener, AfterViewInit } from '@angular/core';
|
| 3 |
+
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
| 4 |
+
import { ChatService, ChatMessage, SearchResponse, Question } from './staticchat.service';
|
| 5 |
+
import { Subject } from 'rxjs';
|
| 6 |
+
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
|
| 7 |
+
import { isPlatformBrowser } from '@angular/common';
|
| 8 |
+
import {
|
| 9 |
+
Inject,
|
| 10 |
+
NgZone,
|
| 11 |
+
PLATFORM_ID,
|
| 12 |
+
} from '@angular/core';
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
@Component({
|
| 16 |
+
selector: 'app-staticchat',
|
| 17 |
+
templateUrl: './staticchat.component.html',
|
| 18 |
+
styleUrls: ['./staticchat.component.css']
|
| 19 |
+
})
|
| 20 |
+
export class StaticChatComponent implements OnInit, AfterViewInit {
|
| 21 |
+
|
| 22 |
+
@ViewChild('chatContainer') private chatContainer!: ElementRef;
|
| 23 |
+
@ViewChild('messageInput') private messageInput!: ElementRef;
|
| 24 |
+
@ViewChild('videoPlayer') videoRef!: ElementRef<HTMLVideoElement>;
|
| 25 |
+
|
| 26 |
+
chatForm: FormGroup;
|
| 27 |
+
messages: (ChatMessage & { suggestions?: string[] })[] = [];
|
| 28 |
+
isTyping = false;
|
| 29 |
+
suggestedQuestions: string[] = [];
|
| 30 |
+
showSuggestions = false;
|
| 31 |
+
allQuestions: Question[] = [];
|
| 32 |
+
searchQuery = new Subject<string>();
|
| 33 |
+
selectedQuestions: Set<string> = new Set();
|
| 34 |
+
|
| 35 |
+
// navigation index for pair view
|
| 36 |
+
currentPairIndex = 0;
|
| 37 |
+
|
| 38 |
+
// 🎬 Video sources
|
| 39 |
+
blinkVideoSrc = 'assets/staticchat/blink.mp4';
|
| 40 |
+
introVideoSrc = 'assets/staticchat/intro.mp4';
|
| 41 |
+
|
| 42 |
+
// Video state
|
| 43 |
+
// 'blink' | 'intro' | 'response' to indicate currently loaded video type
|
| 44 |
+
currentVideoType: 'blink' | 'intro' | 'response' = 'blink';
|
| 45 |
+
currentResponseVideoUrl: string | null = null;
|
| 46 |
+
// whether the currently loaded non-idle video is playing
|
| 47 |
+
isVideoPlaying = false;
|
| 48 |
+
|
| 49 |
+
// audio player for response audio
|
| 50 |
+
private audioPlayer: HTMLAudioElement | null = null;
|
| 51 |
+
hasChatStarted = false;
|
| 52 |
+
lastResponseVideoUrl: string | null = null;
|
| 53 |
+
|
| 54 |
+
supported = false;
|
| 55 |
+
isListening = false; // we will treat this as "isRecording"
|
| 56 |
+
showActions = false;
|
| 57 |
+
|
| 58 |
+
private isBrowser = false;
|
| 59 |
+
|
| 60 |
+
private mediaStream: MediaStream | null = null;
|
| 61 |
+
private recorder: MediaRecorder | null = null;
|
| 62 |
+
private chunks: BlobPart[] = [];
|
| 63 |
+
|
| 64 |
+
private uploadInProgress = false;
|
| 65 |
+
isSpeechProcessing = false;
|
| 66 |
+
|
| 67 |
+
constructor(
|
| 68 |
+
private fb: FormBuilder,
|
| 69 |
+
private chatService: ChatService,
|
| 70 |
+
@Inject(PLATFORM_ID) platformId: object, private zone: NgZone
|
| 71 |
+
) {
|
| 72 |
+
this.chatForm = this.fb.group({
|
| 73 |
+
message: ['', Validators.required]
|
| 74 |
+
});
|
| 75 |
+
|
| 76 |
+
this.searchQuery.pipe(
|
| 77 |
+
debounceTime(300),
|
| 78 |
+
distinctUntilChanged()
|
| 79 |
+
).subscribe(query => {
|
| 80 |
+
this.searchQuestions(query);
|
| 81 |
+
});
|
| 82 |
+
this.isBrowser = isPlatformBrowser(platformId);
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
ngOnInit() {
|
| 86 |
+
this.messages.push({
|
| 87 |
+
id: 1,
|
| 88 |
+
text: 'Hello children! Today we will learn tenses in a simple and fun way.',
|
| 89 |
+
sender: 'bot',
|
| 90 |
+
timestamp: new Date()
|
| 91 |
+
});
|
| 92 |
+
|
| 93 |
+
if (!this.isBrowser) return;
|
| 94 |
+
|
| 95 |
+
const hasGetUserMedia = !!navigator.mediaDevices?.getUserMedia;
|
| 96 |
+
const hasMediaRecorder = typeof (window as any).MediaRecorder !== 'undefined';
|
| 97 |
+
this.supported = hasGetUserMedia && hasMediaRecorder;
|
| 98 |
+
|
| 99 |
+
this.loadAllQuestions();
|
| 100 |
+
|
| 101 |
+
// start at last pair by default
|
| 102 |
+
setTimeout(() => this.scrollToLastPair(), 0);
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
ngAfterViewInit() {
|
| 106 |
+
this.playBlinkVideo();
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
/* ================= VIDEO CONTROL HELPERS ================= */
|
| 110 |
+
|
| 111 |
+
private safeVideo(): HTMLVideoElement | null {
|
| 112 |
+
try { return this.videoRef.nativeElement; } catch { return null; }
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
// Idle blink loop — keep playing but show PLAY icon in UI
|
| 116 |
+
playBlinkVideo() {
|
| 117 |
+
const video = this.safeVideo();
|
| 118 |
+
if (!video) return;
|
| 119 |
+
|
| 120 |
+
video.onended = null;
|
| 121 |
+
video.src = this.blinkVideoSrc;
|
| 122 |
+
video.loop = true;
|
| 123 |
+
video.muted = true;
|
| 124 |
+
video.currentTime = 0;
|
| 125 |
+
video.play().catch(() => { /* ignore autoplay failure for idle */ });
|
| 126 |
+
|
| 127 |
+
this.currentVideoType = 'blink';
|
| 128 |
+
this.currentResponseVideoUrl = null;
|
| 129 |
+
// For blink we show the Play icon, so set isVideoPlaying = false
|
| 130 |
+
this.isVideoPlaying = false;
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
// Load and start intro (user intends to watch intro)
|
| 134 |
+
playIntroVideo() {
|
| 135 |
+
const video = this.safeVideo();
|
| 136 |
+
if (!video) return;
|
| 137 |
+
|
| 138 |
+
// stop any audio
|
| 139 |
+
if (this.audioPlayer && !this.audioPlayer.paused) { this.audioPlayer.pause(); }
|
| 140 |
+
|
| 141 |
+
video.onended = () => {
|
| 142 |
+
this.playBlinkVideo();
|
| 143 |
+
};
|
| 144 |
+
|
| 145 |
+
video.src = this.introVideoSrc;
|
| 146 |
+
video.loop = false;
|
| 147 |
+
video.muted = false;
|
| 148 |
+
video.currentTime = 0;
|
| 149 |
+
video.play().catch(() => {
|
| 150 |
+
// fallback muted play if autoplay blocked
|
| 151 |
+
video.muted = true;
|
| 152 |
+
video.play().catch(() => { /* ignore */ });
|
| 153 |
+
});
|
| 154 |
+
|
| 155 |
+
this.currentVideoType = 'intro';
|
| 156 |
+
this.currentResponseVideoUrl = null;
|
| 157 |
+
this.isVideoPlaying = true;
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
// Play a response video (from chat) in the same player.
|
| 161 |
+
// After the response ends return to blink.
|
| 162 |
+
playResponseVideo(url?: string) {
|
| 163 |
+
if (!url) return;
|
| 164 |
+
const video = this.safeVideo();
|
| 165 |
+
if (!video) return;
|
| 166 |
+
|
| 167 |
+
// stop any audio
|
| 168 |
+
if (this.audioPlayer && !this.audioPlayer.paused) { this.audioPlayer.pause(); }
|
| 169 |
+
|
| 170 |
+
video.onended = () => {
|
| 171 |
+
this.playBlinkVideo();
|
| 172 |
+
};
|
| 173 |
+
|
| 174 |
+
this.currentResponseVideoUrl = url;
|
| 175 |
+
this.currentVideoType = 'response';
|
| 176 |
+
|
| 177 |
+
video.src = url;
|
| 178 |
+
video.loop = false;
|
| 179 |
+
video.muted = false;
|
| 180 |
+
video.currentTime = 0;
|
| 181 |
+
video.play().then(() => {
|
| 182 |
+
this.isVideoPlaying = true;
|
| 183 |
+
}).catch(() => {
|
| 184 |
+
// If autoplay blocked, try muted play as fallback
|
| 185 |
+
video.muted = true;
|
| 186 |
+
video.play().catch(() => { /* ignore */ });
|
| 187 |
+
// set state according to actual playing state
|
| 188 |
+
this.isVideoPlaying = !video.paused;
|
| 189 |
+
});
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
// Top-right button behavior:
|
| 193 |
+
// - If blink is running → start intro.
|
| 194 |
+
// - If intro/response loaded → toggle play/pause for that loaded video.
|
| 195 |
+
togglePlayPause() {
|
| 196 |
+
const video = this.safeVideo();
|
| 197 |
+
if (!video) return;
|
| 198 |
+
|
| 199 |
+
if (this.currentVideoType === 'blink') {
|
| 200 |
+
// Before first question → intro
|
| 201 |
+
if (!this.hasChatStarted) {
|
| 202 |
+
this.playIntroVideo();
|
| 203 |
+
return;
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
// After chat started → do NOT play intro again
|
| 207 |
+
// Replay last response video if available
|
| 208 |
+
if (this.lastResponseVideoUrl) {
|
| 209 |
+
this.playResponseVideo(this.lastResponseVideoUrl);
|
| 210 |
+
}
|
| 211 |
+
return;
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
// If user is starting/resuming a video, pause any playing audio first
|
| 215 |
+
if (video.paused) {
|
| 216 |
+
if (this.audioPlayer && !this.audioPlayer.paused) {
|
| 217 |
+
this.audioPlayer.pause();
|
| 218 |
+
}
|
| 219 |
+
video.play().catch(() => { /* ignore */ });
|
| 220 |
+
this.isVideoPlaying = true;
|
| 221 |
+
} else {
|
| 222 |
+
video.pause();
|
| 223 |
+
this.isVideoPlaying = false;
|
| 224 |
+
}
|
| 225 |
+
}
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
/* ================= CHAT SYSTEM ================= */
|
| 229 |
+
|
| 230 |
+
loadAllQuestions() {
|
| 231 |
+
this.chatService.getAllQuestions().subscribe({
|
| 232 |
+
next: (response) => {
|
| 233 |
+
if (response.success) {
|
| 234 |
+
this.allQuestions = response.questions;
|
| 235 |
+
}
|
| 236 |
+
},
|
| 237 |
+
error: (error) => console.error('Error loading questions:', error)
|
| 238 |
+
});
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
onInputFocus() { this.showQuestionSuggestions(); }
|
| 242 |
+
onInputClick() { this.showQuestionSuggestions(); }
|
| 243 |
+
|
| 244 |
+
showQuestionSuggestions() {
|
| 245 |
+
if (this.allQuestions.length === 0) {
|
| 246 |
+
this.loadAllQuestions();
|
| 247 |
+
return;
|
| 248 |
+
}
|
| 249 |
+
|
| 250 |
+
if (this.messages.length <= 1) {
|
| 251 |
+
this.suggestedQuestions = this.allQuestions.slice(0, 5).map(q => q.question);
|
| 252 |
+
this.showSuggestions = true;
|
| 253 |
+
return;
|
| 254 |
+
}
|
| 255 |
+
|
| 256 |
+
const unselected = this.allQuestions.filter(q => !this.selectedQuestions.has(q.question));
|
| 257 |
+
|
| 258 |
+
if (unselected.length === 0) {
|
| 259 |
+
const shuffled = [...this.allQuestions].sort(() => 0.5 - Math.random());
|
| 260 |
+
this.suggestedQuestions = shuffled.slice(0, 5).map(q => q.question);
|
| 261 |
+
} else {
|
| 262 |
+
this.suggestedQuestions = unselected.slice(0, 5).map(q => q.question);
|
| 263 |
+
}
|
| 264 |
+
|
| 265 |
+
this.showSuggestions = true;
|
| 266 |
+
}
|
| 267 |
+
|
| 268 |
+
searchQuestions(query: string) {
|
| 269 |
+
if (query.length > 0) {
|
| 270 |
+
const filtered = this.allQuestions
|
| 271 |
+
.filter(q => q.question.toLowerCase().includes(query.toLowerCase()))
|
| 272 |
+
.slice(0, 5);
|
| 273 |
+
|
| 274 |
+
this.suggestedQuestions = filtered.map(q => q.question);
|
| 275 |
+
this.showSuggestions = this.suggestedQuestions.length > 0;
|
| 276 |
+
} else {
|
| 277 |
+
this.showQuestionSuggestions();
|
| 278 |
+
}
|
| 279 |
+
}
|
| 280 |
+
|
| 281 |
+
onInputChange() {
|
| 282 |
+
const query = this.chatForm.get('message')?.value;
|
| 283 |
+
query ? this.searchQuery.next(query) : this.showQuestionSuggestions();
|
| 284 |
+
}
|
| 285 |
+
|
| 286 |
+
selectQuestion(question: string) {
|
| 287 |
+
this.selectedQuestions.add(question);
|
| 288 |
+
this.chatForm.get('message')?.setValue(question);
|
| 289 |
+
this.showSuggestions = false;
|
| 290 |
+
this.suggestedQuestions = this.suggestedQuestions.filter(q => q !== question);
|
| 291 |
+
this.sendMessage();
|
| 292 |
+
}
|
| 293 |
+
|
| 294 |
+
sendMessage() {
|
| 295 |
+
const message = this.chatForm.get('message')?.value.trim();
|
| 296 |
+
if (!message) return;
|
| 297 |
+
|
| 298 |
+
this.messages.push({
|
| 299 |
+
id: this.messages.length + 1,
|
| 300 |
+
text: message,
|
| 301 |
+
sender: 'user',
|
| 302 |
+
timestamp: new Date()
|
| 303 |
+
});
|
| 304 |
+
this.hasChatStarted = true;
|
| 305 |
+
this.chatForm.reset();
|
| 306 |
+
this.showSuggestions = false;
|
| 307 |
+
this.isTyping = true;
|
| 308 |
+
|
| 309 |
+
// show the pair containing this user message (may be a user-only pair until bot replies)
|
| 310 |
+
setTimeout(() => this.scrollToLastPair(), 50);
|
| 311 |
+
|
| 312 |
+
this.chatService.searchQuestion(message).subscribe({
|
| 313 |
+
next: (response: SearchResponse) => {
|
| 314 |
+
this.isTyping = false;
|
| 315 |
+
|
| 316 |
+
const botText = response.answer
|
| 317 |
+
? response.answer.replace(/\n/g, ' ')
|
| 318 |
+
: (response.message || 'Sorry, I could not find an answer.');
|
| 319 |
+
|
| 320 |
+
this.messages.push({
|
| 321 |
+
id: this.messages.length + 1,
|
| 322 |
+
text: botText,
|
| 323 |
+
sender: 'bot',
|
| 324 |
+
timestamp: new Date(),
|
| 325 |
+
rawData: response
|
| 326 |
+
});
|
| 327 |
+
|
| 328 |
+
// scroll to the new pair (user+bot)
|
| 329 |
+
setTimeout(() => this.scrollToLastPair(), 50);
|
| 330 |
+
|
| 331 |
+
// Play audio/video returned by the response
|
| 332 |
+
if (response.audio_url) {
|
| 333 |
+
this.playAudio(response.audio_url);
|
| 334 |
+
}
|
| 335 |
+
if (response.video_url) {
|
| 336 |
+
this.lastResponseVideoUrl = response.video_url; // ✅ remember it
|
| 337 |
+
this.playResponseVideo(response.video_url);
|
| 338 |
+
}
|
| 339 |
+
|
| 340 |
+
},
|
| 341 |
+
error: () => {
|
| 342 |
+
this.isTyping = false;
|
| 343 |
+
this.messages.push({
|
| 344 |
+
id: this.messages.length + 1,
|
| 345 |
+
text: 'Sorry, I encountered an error. Please try again.',
|
| 346 |
+
sender: 'bot',
|
| 347 |
+
timestamp: new Date()
|
| 348 |
+
});
|
| 349 |
+
setTimeout(() => this.scrollToLastPair(), 50);
|
| 350 |
+
}
|
| 351 |
+
});
|
| 352 |
+
}
|
| 353 |
+
|
| 354 |
+
// Play audio directly (uses a single HTMLAudioElement instance)
|
| 355 |
+
playAudio(url?: string) {
|
| 356 |
+
if (!url) return;
|
| 357 |
+
try {
|
| 358 |
+
const video = this.safeVideo();
|
| 359 |
+
// If a non-idle video is currently playing, pause it before starting audio
|
| 360 |
+
if (video && this.currentVideoType !== 'blink' && !video.paused) {
|
| 361 |
+
video.pause();
|
| 362 |
+
this.isVideoPlaying = false;
|
| 363 |
+
}
|
| 364 |
+
|
| 365 |
+
if (!this.audioPlayer) {
|
| 366 |
+
this.audioPlayer = new Audio();
|
| 367 |
+
} else {
|
| 368 |
+
this.audioPlayer.pause();
|
| 369 |
+
}
|
| 370 |
+
this.audioPlayer.src = url;
|
| 371 |
+
this.audioPlayer.currentTime = 0;
|
| 372 |
+
this.audioPlayer.play().catch(() => { /* ignore autoplay errors */ });
|
| 373 |
+
} catch (e) {
|
| 374 |
+
console.error('Audio play failed', e);
|
| 375 |
+
}
|
| 376 |
+
}
|
| 377 |
+
|
| 378 |
+
// helper used by template to play video for a chat item
|
| 379 |
+
playVideoFromChat(url?: string) {
|
| 380 |
+
if (!url) return;
|
| 381 |
+
this.playResponseVideo(url);
|
| 382 |
+
}
|
| 383 |
+
|
| 384 |
+
formatAnswer(response: SearchResponse): string {
|
| 385 |
+
let html = '';
|
| 386 |
+
|
| 387 |
+
const answerText = response.answer?.replace(/\n/g, '<br>') ?? 'No answer available.';
|
| 388 |
+
html += `<div class="bot-answer">${answerText}</div>`;
|
| 389 |
+
|
| 390 |
+
if (response.audio_url || response.video_url) {
|
| 391 |
+
html += `<div class="media-row">`;
|
| 392 |
+
|
| 393 |
+
if (response.audio_url) {
|
| 394 |
+
html += `
|
| 395 |
+
<span class="media-icon"
|
| 396 |
+
onclick="window.dispatchEvent(new CustomEvent('playAudio', { detail: '${response.audio_url}' }))">
|
| 397 |
+
🎧
|
| 398 |
+
</span>`;
|
| 399 |
+
}
|
| 400 |
+
|
| 401 |
+
if (response.video_url) {
|
| 402 |
+
html += `
|
| 403 |
+
<span class="media-icon"
|
| 404 |
+
onclick="window.dispatchEvent(new CustomEvent('playVideo', { detail: '${response.video_url}' }))">
|
| 405 |
+
📺
|
| 406 |
+
</span>`;
|
| 407 |
+
}
|
| 408 |
+
|
| 409 |
+
html += `</div>`;
|
| 410 |
+
}
|
| 411 |
+
|
| 412 |
+
return html;
|
| 413 |
+
}
|
| 414 |
+
|
| 415 |
+
formatErrorMessage(response: SearchResponse): string {
|
| 416 |
+
let message = response.message || "I couldn't find an exact match.";
|
| 417 |
+
|
| 418 |
+
if (response.sample_questions?.length) {
|
| 419 |
+
message += '<br><br><strong>Try asking:</strong><ul>';
|
| 420 |
+
response.sample_questions.forEach(q => message += `<li>${q}</li>`);
|
| 421 |
+
message += '</ul>';
|
| 422 |
+
}
|
| 423 |
+
|
| 424 |
+
return message;
|
| 425 |
+
}
|
| 426 |
+
|
| 427 |
+
// Build pairs: each pair is { user?: ChatMessage, bot?: ChatMessage }
|
| 428 |
+
get pairedMessages(): Array<{ user?: ChatMessage, bot?: ChatMessage }> {
|
| 429 |
+
const pairs: Array<{ user?: ChatMessage, bot?: ChatMessage }> = [];
|
| 430 |
+
const msgs = this.messages || [];
|
| 431 |
+
let i = 0;
|
| 432 |
+
while (i < msgs.length) {
|
| 433 |
+
const current = msgs[i];
|
| 434 |
+
if (current.sender === 'user') {
|
| 435 |
+
const pair: { user?: ChatMessage, bot?: ChatMessage } = { user: current };
|
| 436 |
+
const next = msgs[i + 1];
|
| 437 |
+
if (next && next.sender === 'bot') {
|
| 438 |
+
pair.bot = next;
|
| 439 |
+
i += 2;
|
| 440 |
+
} else {
|
| 441 |
+
i += 1;
|
| 442 |
+
}
|
| 443 |
+
pairs.push(pair);
|
| 444 |
+
} else if (current.sender === 'bot') {
|
| 445 |
+
// bot message without preceding user (welcome message, errors, etc.)
|
| 446 |
+
pairs.push({ bot: current });
|
| 447 |
+
i += 1;
|
| 448 |
+
} else {
|
| 449 |
+
// fallback: add as single
|
| 450 |
+
pairs.push({ bot: current });
|
| 451 |
+
i += 1;
|
| 452 |
+
}
|
| 453 |
+
}
|
| 454 |
+
return pairs;
|
| 455 |
+
}
|
| 456 |
+
|
| 457 |
+
// Scroll helpers for pair navigation
|
| 458 |
+
showNextPair() {
|
| 459 |
+
const total = this.pairedMessages.length;
|
| 460 |
+
if (total === 0) return;
|
| 461 |
+
const next = Math.min(this.currentPairIndex + 1, total - 1);
|
| 462 |
+
this.scrollToPair(next);
|
| 463 |
+
}
|
| 464 |
+
|
| 465 |
+
showPreviousPair() {
|
| 466 |
+
const total = this.pairedMessages.length;
|
| 467 |
+
if (total === 0) return;
|
| 468 |
+
const prev = Math.max(this.currentPairIndex - 1, 0);
|
| 469 |
+
this.scrollToPair(prev);
|
| 470 |
+
}
|
| 471 |
+
|
| 472 |
+
private scrollToPair(index: number) {
|
| 473 |
+
setTimeout(() => {
|
| 474 |
+
try {
|
| 475 |
+
const container = this.chatContainer.nativeElement as HTMLElement;
|
| 476 |
+
const pairs = container.querySelectorAll('.pair');
|
| 477 |
+
if (!pairs || pairs.length === 0) return;
|
| 478 |
+
if (index < 0) index = 0;
|
| 479 |
+
if (index >= pairs.length) index = pairs.length - 1;
|
| 480 |
+
const target = pairs[index] as HTMLElement;
|
| 481 |
+
if (!target) return;
|
| 482 |
+
container.scrollTo({ top: target.offsetTop, behavior: 'smooth' });
|
| 483 |
+
this.currentPairIndex = index;
|
| 484 |
+
} catch (e) {
|
| 485 |
+
// fallback: scroll to bottom/top
|
| 486 |
+
try {
|
| 487 |
+
const container = this.chatContainer.nativeElement as HTMLElement;
|
| 488 |
+
if (index === 0) container.scrollTop = 0;
|
| 489 |
+
else container.scrollTop = container.scrollHeight;
|
| 490 |
+
} catch { }
|
| 491 |
+
}
|
| 492 |
+
}, 50);
|
| 493 |
+
}
|
| 494 |
+
|
| 495 |
+
private scrollToLastPair(): void {
|
| 496 |
+
setTimeout(() => {
|
| 497 |
+
try {
|
| 498 |
+
const total = this.pairedMessages.length;
|
| 499 |
+
if (total === 0) return;
|
| 500 |
+
this.scrollToPair(total - 1);
|
| 501 |
+
} catch { }
|
| 502 |
+
}, 50);
|
| 503 |
+
}
|
| 504 |
+
|
| 505 |
+
scrollToTop(): void {
|
| 506 |
+
setTimeout(() => {
|
| 507 |
+
try {
|
| 508 |
+
const el = this.chatContainer.nativeElement as HTMLElement;
|
| 509 |
+
// Use smooth scroll if supported, otherwise fall back to direct assignment
|
| 510 |
+
if (typeof el.scrollTo === 'function') {
|
| 511 |
+
el.scrollTo({ top: 0, behavior: 'smooth' });
|
| 512 |
+
} else {
|
| 513 |
+
el.scrollTop = 0;
|
| 514 |
+
}
|
| 515 |
+
} catch { }
|
| 516 |
+
}, 100);
|
| 517 |
+
}
|
| 518 |
+
|
| 519 |
+
clearChat() {
|
| 520 |
+
this.messages = [];
|
| 521 |
+
this.selectedQuestions.clear();
|
| 522 |
+
|
| 523 |
+
this.hasChatStarted = false;
|
| 524 |
+
this.lastResponseVideoUrl = null;
|
| 525 |
+
|
| 526 |
+
this.ngOnInit();
|
| 527 |
+
this.playBlinkVideo();
|
| 528 |
+
}
|
| 529 |
+
|
| 530 |
+
|
| 531 |
+
private pickMimeType(): string {
|
| 532 |
+
const w: any = window;
|
| 533 |
+
|
| 534 |
+
// Try in order. Different browsers support different types.
|
| 535 |
+
const types = [
|
| 536 |
+
'audio/webm;codecs=opus', // Chrome/Edge/Firefox (best)
|
| 537 |
+
'audio/webm',
|
| 538 |
+
'audio/mp4', // Safari (sometimes)
|
| 539 |
+
'audio/m4a',
|
| 540 |
+
];
|
| 541 |
+
|
| 542 |
+
if (!w.MediaRecorder?.isTypeSupported) return '';
|
| 543 |
+
for (const t of types) {
|
| 544 |
+
if (w.MediaRecorder.isTypeSupported(t)) return t;
|
| 545 |
+
}
|
| 546 |
+
return '';
|
| 547 |
+
}
|
| 548 |
+
|
| 549 |
+
async toggleMic() {
|
| 550 |
+
if (!this.supported || this.isListening || this.uploadInProgress) return;
|
| 551 |
+
|
| 552 |
+
try {
|
| 553 |
+
this.mediaStream = await navigator.mediaDevices.getUserMedia({
|
| 554 |
+
audio: {
|
| 555 |
+
echoCancellation: true,
|
| 556 |
+
noiseSuppression: true,
|
| 557 |
+
},
|
| 558 |
+
});
|
| 559 |
+
|
| 560 |
+
const mimeType = this.pickMimeType();
|
| 561 |
+
this.chunks = [];
|
| 562 |
+
|
| 563 |
+
this.recorder = mimeType
|
| 564 |
+
? new MediaRecorder(this.mediaStream, { mimeType })
|
| 565 |
+
: new MediaRecorder(this.mediaStream);
|
| 566 |
+
|
| 567 |
+
this.recorder.ondataavailable = (e: BlobEvent) => {
|
| 568 |
+
if (e.data && e.data.size > 0) this.chunks.push(e.data);
|
| 569 |
+
};
|
| 570 |
+
|
| 571 |
+
this.recorder.onerror = () => {
|
| 572 |
+
this.zone.run(() => {
|
| 573 |
+
this.handleTranscriptionError('Audio recording error.');
|
| 574 |
+
this.cleanupRecorder();
|
| 575 |
+
});
|
| 576 |
+
};
|
| 577 |
+
|
| 578 |
+
this.zone.run(() => {
|
| 579 |
+
this.isListening = true;
|
| 580 |
+
this.showActions = true;
|
| 581 |
+
});
|
| 582 |
+
|
| 583 |
+
this.recorder.start();
|
| 584 |
+
} catch (e: any) {
|
| 585 |
+
this.zone.run(() => {
|
| 586 |
+
this.handleTranscriptionError('Microphone permission denied or not available.');
|
| 587 |
+
this.cleanupRecorder();
|
| 588 |
+
});
|
| 589 |
+
}
|
| 590 |
+
}
|
| 591 |
+
|
| 592 |
+
// ✅ Stop + transcribe
|
| 593 |
+
accept() {
|
| 594 |
+
if (!this.recorder || this.uploadInProgress) return;
|
| 595 |
+
|
| 596 |
+
this.uploadInProgress = true;
|
| 597 |
+
|
| 598 |
+
// We need the blob only after "stop" finishes
|
| 599 |
+
this.recorder.onstop = async () => {
|
| 600 |
+
try {
|
| 601 |
+
const mime = this.recorder?.mimeType || 'audio/webm';
|
| 602 |
+
const blob = new Blob(this.chunks, { type: mime });
|
| 603 |
+
|
| 604 |
+
// 🔄 Show loading state in input
|
| 605 |
+
this.zone.run(() => {
|
| 606 |
+
this.isSpeechProcessing = true;
|
| 607 |
+
this.showActions = false;
|
| 608 |
+
this.isListening = false;
|
| 609 |
+
this.chatForm.get('message')?.setValue('⏳ Converting speech to text...');
|
| 610 |
+
});
|
| 611 |
+
|
| 612 |
+
const text = await this.sendToBackendForTranscription(blob);
|
| 613 |
+
|
| 614 |
+
this.zone.run(() => {
|
| 615 |
+
this.isSpeechProcessing = false;
|
| 616 |
+
if (text && text.trim()) {
|
| 617 |
+
this.handleTranscriptionAccepted(text.trim());
|
| 618 |
+
} else {
|
| 619 |
+
this.chatForm.get('message')?.setValue('');
|
| 620 |
+
}
|
| 621 |
+
});
|
| 622 |
+
|
| 623 |
+
} catch (err: any) {
|
| 624 |
+
this.zone.run(() => {
|
| 625 |
+
this.handleTranscriptionError(
|
| 626 |
+
typeof err?.message === 'string' ? err.message : 'Transcription failed.'
|
| 627 |
+
);
|
| 628 |
+
this.showActions = false;
|
| 629 |
+
this.isListening = false;
|
| 630 |
+
});
|
| 631 |
+
} finally {
|
| 632 |
+
this.uploadInProgress = false;
|
| 633 |
+
this.cleanupRecorder();
|
| 634 |
+
}
|
| 635 |
+
};
|
| 636 |
+
|
| 637 |
+
try {
|
| 638 |
+
this.recorder.stop();
|
| 639 |
+
} catch {
|
| 640 |
+
// If stop fails, still cleanup
|
| 641 |
+
this.uploadInProgress = false;
|
| 642 |
+
this.cleanupRecorder();
|
| 643 |
+
}
|
| 644 |
+
}
|
| 645 |
+
|
| 646 |
+
// ❌ Stop + discard
|
| 647 |
+
reject() {
|
| 648 |
+
if (this.uploadInProgress) return;
|
| 649 |
+
|
| 650 |
+
try {
|
| 651 |
+
this.recorder?.stop();
|
| 652 |
+
} catch { }
|
| 653 |
+
|
| 654 |
+
this.zone.run(() => {
|
| 655 |
+
this.handleTranscriptionRejected();
|
| 656 |
+
this.showActions = false;
|
| 657 |
+
this.isListening = false;
|
| 658 |
+
});
|
| 659 |
+
|
| 660 |
+
this.cleanupRecorder();
|
| 661 |
+
}
|
| 662 |
+
|
| 663 |
+
private async sendToBackendForTranscription(blob: Blob): Promise<string> {
|
| 664 |
+
// Change this URL if your backend route is different
|
| 665 |
+
const url = 'http://localhost:5000/api/transcribe';
|
| 666 |
+
|
| 667 |
+
const form = new FormData();
|
| 668 |
+
// Keep extension generic; backend can read mimetype
|
| 669 |
+
form.append('file', blob, 'speech.webm');
|
| 670 |
+
|
| 671 |
+
const res = await fetch(url, {
|
| 672 |
+
method: 'POST',
|
| 673 |
+
body: form,
|
| 674 |
+
});
|
| 675 |
+
|
| 676 |
+
if (!res.ok) {
|
| 677 |
+
const msg = await res.text().catch(() => '');
|
| 678 |
+
throw new Error(msg || `Transcribe API failed (${res.status}).`);
|
| 679 |
+
}
|
| 680 |
+
|
| 681 |
+
const data = await res.json();
|
| 682 |
+
// Expect { text: "..." }
|
| 683 |
+
return (data?.text || '').toString();
|
| 684 |
+
}
|
| 685 |
+
|
| 686 |
+
private cleanupRecorder() {
|
| 687 |
+
try {
|
| 688 |
+
this.recorder?.removeEventListener?.('dataavailable', () => { });
|
| 689 |
+
} catch { }
|
| 690 |
+
|
| 691 |
+
this.recorder = null;
|
| 692 |
+
this.chunks = [];
|
| 693 |
+
|
| 694 |
+
if (this.mediaStream) {
|
| 695 |
+
try {
|
| 696 |
+
this.mediaStream.getTracks().forEach((t) => t.stop());
|
| 697 |
+
} catch { }
|
| 698 |
+
this.mediaStream = null;
|
| 699 |
+
}
|
| 700 |
+
}
|
| 701 |
+
|
| 702 |
+
|
| 703 |
+
@HostListener('document:click', ['$event'])
|
| 704 |
+
handleClickOutside(event: Event) {
|
| 705 |
+
if (this.showSuggestions && this.messageInput) {
|
| 706 |
+
const clickedInside = this.messageInput.nativeElement.contains(event.target);
|
| 707 |
+
if (!clickedInside) this.showSuggestions = false;
|
| 708 |
+
}
|
| 709 |
+
}
|
| 710 |
+
|
| 711 |
+
/* ========== Internal handlers (replace Outputs) ========== */
|
| 712 |
+
|
| 713 |
+
private handleTranscriptionAccepted(text: string) {
|
| 714 |
+
try {
|
| 715 |
+
// Put recognized text into input field ONLY
|
| 716 |
+
this.chatForm.get('message')?.setValue(text);
|
| 717 |
+
|
| 718 |
+
// Keep cursor at end (optional but good UX)
|
| 719 |
+
setTimeout(() => {
|
| 720 |
+
this.messageInput?.nativeElement.focus();
|
| 721 |
+
}, 0);
|
| 722 |
+
|
| 723 |
+
} catch (e) {
|
| 724 |
+
console.error('handleTranscriptionAccepted error', e);
|
| 725 |
+
}
|
| 726 |
+
}
|
| 727 |
+
|
| 728 |
+
|
| 729 |
+
private handleTranscriptionRejected() {
|
| 730 |
+
// Clear input and keep UI consistent
|
| 731 |
+
try {
|
| 732 |
+
this.chatForm.get('message')?.setValue('');
|
| 733 |
+
} catch (e) {
|
| 734 |
+
console.error('handleTranscriptionRejected error', e);
|
| 735 |
+
}
|
| 736 |
+
}
|
| 737 |
+
|
| 738 |
+
private handleTranscriptionError(msg: string) {
|
| 739 |
+
try {
|
| 740 |
+
this.isSpeechProcessing = false;
|
| 741 |
+
this.chatForm.get('message')?.setValue('');
|
| 742 |
+
|
| 743 |
+
this.messages.push({
|
| 744 |
+
id: this.messages.length + 1,
|
| 745 |
+
text: `Transcription error: ${msg}`,
|
| 746 |
+
sender: 'bot',
|
| 747 |
+
timestamp: new Date()
|
| 748 |
+
});
|
| 749 |
+
setTimeout(() => this.scrollToLastPair(), 50);
|
| 750 |
+
} catch (e) {
|
| 751 |
+
console.error('handleTranscriptionError error', e);
|
| 752 |
+
}
|
| 753 |
+
}
|
| 754 |
+
|
| 755 |
+
|
| 756 |
+
}
|
src/app/staticchat/staticchat.service.ts
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Injectable } from '@angular/core';
|
| 2 |
+
import { HttpClient } from '@angular/common/http';
|
| 3 |
+
import { Observable } from 'rxjs';
|
| 4 |
+
import { v4 as uuidv4 } from 'uuid';
|
| 5 |
+
|
| 6 |
+
export interface ChatMessage {
|
| 7 |
+
id?: number;
|
| 8 |
+
text: string;
|
| 9 |
+
sender: 'user' | 'bot';
|
| 10 |
+
timestamp: Date;
|
| 11 |
+
isTyping?: boolean;
|
| 12 |
+
rawData?: any;
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
export interface SearchResponse {
|
| 16 |
+
success: boolean;
|
| 17 |
+
matched_question?: string;
|
| 18 |
+
answer?: string;
|
| 19 |
+
sno?: number;
|
| 20 |
+
audio_url?: string;
|
| 21 |
+
video_url?: string;
|
| 22 |
+
story_url?: string;
|
| 23 |
+
detail_url?: string;
|
| 24 |
+
example_url?: string;
|
| 25 |
+
confidence_score?: number;
|
| 26 |
+
user_question?: string;
|
| 27 |
+
message?: string;
|
| 28 |
+
suggestion?: string;
|
| 29 |
+
sample_questions?: string[];
|
| 30 |
+
total_questions_available?: number;
|
| 31 |
+
matching_method?: string;
|
| 32 |
+
is_follow_up?: boolean;
|
| 33 |
+
enhanced_question?: string;
|
| 34 |
+
scenario?: string;
|
| 35 |
+
context_info?: {
|
| 36 |
+
current_topic: string | null;
|
| 37 |
+
current_intent: string | null;
|
| 38 |
+
has_context: boolean;
|
| 39 |
+
history_length: number;
|
| 40 |
+
};
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
export interface Question {
|
| 44 |
+
sno: number;
|
| 45 |
+
question: string;
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
@Injectable({
|
| 49 |
+
providedIn: 'root'
|
| 50 |
+
})
|
| 51 |
+
export class ChatService {
|
| 52 |
+
|
| 53 |
+
private readonly apiBase =
|
| 54 |
+
location.hostname.endsWith('hf.space')
|
| 55 |
+
? 'https://pykara-py-learn-backend.hf.space'
|
| 56 |
+
: 'http://localhost:5000/staticchat';
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
// =====================================================
|
| 61 |
+
// Static user_id: generated once per browser session.
|
| 62 |
+
// Persists across page navigations within the same tab,
|
| 63 |
+
// but resets when the tab/browser is closed.
|
| 64 |
+
//
|
| 65 |
+
// Options:
|
| 66 |
+
// sessionStorage — resets when tab closes (recommended)
|
| 67 |
+
// localStorage — persists even after browser restart
|
| 68 |
+
// =====================================================
|
| 69 |
+
private userId: string;
|
| 70 |
+
|
| 71 |
+
constructor(private http: HttpClient) {
|
| 72 |
+
// Try to restore existing session ID
|
| 73 |
+
const stored = sessionStorage.getItem('chat_user_id');
|
| 74 |
+
if (stored) {
|
| 75 |
+
this.userId = stored;
|
| 76 |
+
} else {
|
| 77 |
+
// Generate a new one for this session
|
| 78 |
+
this.userId = uuidv4();
|
| 79 |
+
sessionStorage.setItem('chat_user_id', this.userId);
|
| 80 |
+
}
|
| 81 |
+
console.log('Chat session user_id:', this.userId);
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
/**
|
| 85 |
+
* Get the current user ID (useful for context endpoints)
|
| 86 |
+
*/
|
| 87 |
+
getUserId(): string {
|
| 88 |
+
return this.userId;
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
/**
|
| 92 |
+
* Reset the session — clears context on backend and generates a new user_id
|
| 93 |
+
*/
|
| 94 |
+
resetSession(): Observable<{ success: boolean; message: string }> {
|
| 95 |
+
const oldUserId = this.userId;
|
| 96 |
+
|
| 97 |
+
// Generate new user_id
|
| 98 |
+
this.userId = uuidv4();
|
| 99 |
+
sessionStorage.setItem('chat_user_id', this.userId);
|
| 100 |
+
console.log('Session reset. New user_id:', this.userId);
|
| 101 |
+
|
| 102 |
+
// Clear old context on the backend
|
| 103 |
+
return this.http.post<{ success: boolean; message: string }>(
|
| 104 |
+
`${this.apiBase}/context/${oldUserId}/clear`, {}
|
| 105 |
+
);
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
/**
|
| 109 |
+
* Search — sends user_id with every request for context carry-forward
|
| 110 |
+
*/
|
| 111 |
+
searchQuestion(question: string): Observable<SearchResponse> {
|
| 112 |
+
return this.http.post<SearchResponse>(
|
| 113 |
+
`${this.apiBase}/search`,
|
| 114 |
+
{
|
| 115 |
+
question,
|
| 116 |
+
user_id: this.userId // <-- This is the key fix
|
| 117 |
+
}
|
| 118 |
+
);
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
/**
|
| 122 |
+
* Get all questions for reference / autocomplete
|
| 123 |
+
*/
|
| 124 |
+
getAllQuestions(): Observable<{ success: boolean; questions: Question[]; count: number }> {
|
| 125 |
+
return this.http.get<{ success: boolean; questions: Question[]; count: number }>(
|
| 126 |
+
`${this.apiBase}/questions`
|
| 127 |
+
);
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
/**
|
| 131 |
+
* Get random suggestions
|
| 132 |
+
*/
|
| 133 |
+
getRandomSuggestions(count: number = 5): Observable<{ success: boolean; suggestions: string[] }> {
|
| 134 |
+
return this.http.get<{ success: boolean; suggestions: string[] }>(
|
| 135 |
+
`${this.apiBase}/suggestions`,
|
| 136 |
+
{ params: { count: count.toString() } }
|
| 137 |
+
);
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
/**
|
| 141 |
+
* Get context-aware follow-up suggestions based on conversation history
|
| 142 |
+
*/
|
| 143 |
+
getContextSuggestions(): Observable<{ success: boolean; suggestions: string[]; current_topic: string | null }> {
|
| 144 |
+
return this.http.get<{ success: boolean; suggestions: string[]; current_topic: string | null }>(
|
| 145 |
+
`${this.apiBase}/context/suggestions/${this.userId}`
|
| 146 |
+
);
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
/**
|
| 150 |
+
* Get current conversation context (for debugging or UI display)
|
| 151 |
+
*/
|
| 152 |
+
getContext(): Observable<any> {
|
| 153 |
+
return this.http.get(`${this.apiBase}/context/${this.userId}`);
|
| 154 |
+
}
|
| 155 |
+
}
|