Spaces:
Sleeping
Sleeping
pixel3user commited on
Commit ·
e46c4bd
1
Parent(s): 806144f
Update Farm2Market demo frontend
Browse files- package-lock.json +976 -1
- package.json +1 -0
- src/App.tsx +36 -15
- src/InvoiceDemoPanel.tsx +143 -6
- src/MarketingStudioPanel.tsx +185 -2
- src/MarketplaceDemoPanel.tsx +196 -1
- src/VoiceDemoPanel.tsx +185 -1
- src/liveAgent.ts +267 -0
- src/liveApi.ts +302 -0
- src/vite-env.d.ts +1 -0
package-lock.json
CHANGED
|
@@ -8,6 +8,7 @@
|
|
| 8 |
"name": "farm2market-hf-demo-frontend",
|
| 9 |
"version": "0.1.0",
|
| 10 |
"dependencies": {
|
|
|
|
| 11 |
"react": "^18.3.1",
|
| 12 |
"react-dom": "^18.3.1"
|
| 13 |
},
|
|
@@ -650,6 +651,599 @@
|
|
| 650 |
"node": ">=12"
|
| 651 |
}
|
| 652 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 653 |
"node_modules/@jridgewell/gen-mapping": {
|
| 654 |
"version": "0.3.13",
|
| 655 |
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
|
@@ -695,6 +1289,60 @@
|
|
| 695 |
"@jridgewell/sourcemap-codec": "^1.4.14"
|
| 696 |
}
|
| 697 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 698 |
"node_modules/@rolldown/pluginutils": {
|
| 699 |
"version": "1.0.0-beta.27",
|
| 700 |
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
|
|
@@ -1073,6 +1721,14 @@
|
|
| 1073 |
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
| 1074 |
"dev": true
|
| 1075 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1076 |
"node_modules/@types/prop-types": {
|
| 1077 |
"version": "15.7.15",
|
| 1078 |
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
|
@@ -1118,6 +1774,28 @@
|
|
| 1118 |
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
|
| 1119 |
}
|
| 1120 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1121 |
"node_modules/baseline-browser-mapping": {
|
| 1122 |
"version": "2.10.0",
|
| 1123 |
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz",
|
|
@@ -1183,6 +1861,35 @@
|
|
| 1183 |
}
|
| 1184 |
]
|
| 1185 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1186 |
"node_modules/convert-source-map": {
|
| 1187 |
"version": "2.0.0",
|
| 1188 |
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
|
@@ -1218,6 +1925,11 @@
|
|
| 1218 |
"integrity": "sha512-QBMrTWEf00GXZmJyx2lbYD45jpI3TUFnNIzJ5BBc8piGUDwMPa1GV6HJWTZVvY/eiN3fSopl7NRbgGp9sZ9LTA==",
|
| 1219 |
"dev": true
|
| 1220 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1221 |
"node_modules/esbuild": {
|
| 1222 |
"version": "0.21.5",
|
| 1223 |
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
|
|
@@ -1260,11 +1972,79 @@
|
|
| 1260 |
"version": "3.2.0",
|
| 1261 |
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
| 1262 |
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
| 1263 |
-
"dev": true,
|
| 1264 |
"engines": {
|
| 1265 |
"node": ">=6"
|
| 1266 |
}
|
| 1267 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1268 |
"node_modules/fsevents": {
|
| 1269 |
"version": "2.3.3",
|
| 1270 |
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
|
@@ -1288,6 +2068,32 @@
|
|
| 1288 |
"node": ">=6.9.0"
|
| 1289 |
}
|
| 1290 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1291 |
"node_modules/js-tokens": {
|
| 1292 |
"version": "4.0.0",
|
| 1293 |
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
|
@@ -1317,6 +2123,16 @@
|
|
| 1317 |
"node": ">=6"
|
| 1318 |
}
|
| 1319 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1320 |
"node_modules/loose-envify": {
|
| 1321 |
"version": "1.4.0",
|
| 1322 |
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
|
@@ -1401,6 +2217,29 @@
|
|
| 1401 |
"node": "^10 || ^12 || >=14"
|
| 1402 |
}
|
| 1403 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1404 |
"node_modules/react": {
|
| 1405 |
"version": "18.3.1",
|
| 1406 |
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
|
@@ -1433,6 +2272,14 @@
|
|
| 1433 |
"node": ">=0.10.0"
|
| 1434 |
}
|
| 1435 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1436 |
"node_modules/rollup": {
|
| 1437 |
"version": "4.59.0",
|
| 1438 |
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
|
|
@@ -1477,6 +2324,25 @@
|
|
| 1477 |
"fsevents": "~2.3.2"
|
| 1478 |
}
|
| 1479 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1480 |
"node_modules/scheduler": {
|
| 1481 |
"version": "0.23.2",
|
| 1482 |
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
|
@@ -1503,6 +2369,35 @@
|
|
| 1503 |
"node": ">=0.10.0"
|
| 1504 |
}
|
| 1505 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1506 |
"node_modules/typescript": {
|
| 1507 |
"version": "5.9.3",
|
| 1508 |
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
|
@@ -1516,6 +2411,11 @@
|
|
| 1516 |
"node": ">=14.17"
|
| 1517 |
}
|
| 1518 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1519 |
"node_modules/update-browserslist-db": {
|
| 1520 |
"version": "1.2.3",
|
| 1521 |
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
|
|
@@ -1605,11 +2505,86 @@
|
|
| 1605 |
}
|
| 1606 |
}
|
| 1607 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1608 |
"node_modules/yallist": {
|
| 1609 |
"version": "3.1.1",
|
| 1610 |
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
| 1611 |
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
| 1612 |
"dev": true
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1613 |
}
|
| 1614 |
}
|
| 1615 |
}
|
|
|
|
| 8 |
"name": "farm2market-hf-demo-frontend",
|
| 9 |
"version": "0.1.0",
|
| 10 |
"dependencies": {
|
| 11 |
+
"firebase": "^11.10.0",
|
| 12 |
"react": "^18.3.1",
|
| 13 |
"react-dom": "^18.3.1"
|
| 14 |
},
|
|
|
|
| 651 |
"node": ">=12"
|
| 652 |
}
|
| 653 |
},
|
| 654 |
+
"node_modules/@firebase/ai": {
|
| 655 |
+
"version": "1.4.1",
|
| 656 |
+
"resolved": "https://registry.npmjs.org/@firebase/ai/-/ai-1.4.1.tgz",
|
| 657 |
+
"integrity": "sha512-bcusQfA/tHjUjBTnMx6jdoPMpDl3r8K15Z+snHz9wq0Foox0F/V+kNLXucEOHoTL2hTc9l+onZCyBJs2QoIC3g==",
|
| 658 |
+
"dependencies": {
|
| 659 |
+
"@firebase/app-check-interop-types": "0.3.3",
|
| 660 |
+
"@firebase/component": "0.6.18",
|
| 661 |
+
"@firebase/logger": "0.4.4",
|
| 662 |
+
"@firebase/util": "1.12.1",
|
| 663 |
+
"tslib": "^2.1.0"
|
| 664 |
+
},
|
| 665 |
+
"engines": {
|
| 666 |
+
"node": ">=18.0.0"
|
| 667 |
+
},
|
| 668 |
+
"peerDependencies": {
|
| 669 |
+
"@firebase/app": "0.x",
|
| 670 |
+
"@firebase/app-types": "0.x"
|
| 671 |
+
}
|
| 672 |
+
},
|
| 673 |
+
"node_modules/@firebase/analytics": {
|
| 674 |
+
"version": "0.10.17",
|
| 675 |
+
"resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.17.tgz",
|
| 676 |
+
"integrity": "sha512-n5vfBbvzduMou/2cqsnKrIes4auaBjdhg8QNA2ZQZ59QgtO2QiwBaXQZQE4O4sgB0Ds1tvLgUUkY+pwzu6/xEg==",
|
| 677 |
+
"dependencies": {
|
| 678 |
+
"@firebase/component": "0.6.18",
|
| 679 |
+
"@firebase/installations": "0.6.18",
|
| 680 |
+
"@firebase/logger": "0.4.4",
|
| 681 |
+
"@firebase/util": "1.12.1",
|
| 682 |
+
"tslib": "^2.1.0"
|
| 683 |
+
},
|
| 684 |
+
"peerDependencies": {
|
| 685 |
+
"@firebase/app": "0.x"
|
| 686 |
+
}
|
| 687 |
+
},
|
| 688 |
+
"node_modules/@firebase/analytics-compat": {
|
| 689 |
+
"version": "0.2.23",
|
| 690 |
+
"resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.23.tgz",
|
| 691 |
+
"integrity": "sha512-3AdO10RN18G5AzREPoFgYhW6vWXr3u+OYQv6pl3CX6Fky8QRk0AHurZlY3Q1xkXO0TDxIsdhO3y65HF7PBOJDw==",
|
| 692 |
+
"dependencies": {
|
| 693 |
+
"@firebase/analytics": "0.10.17",
|
| 694 |
+
"@firebase/analytics-types": "0.8.3",
|
| 695 |
+
"@firebase/component": "0.6.18",
|
| 696 |
+
"@firebase/util": "1.12.1",
|
| 697 |
+
"tslib": "^2.1.0"
|
| 698 |
+
},
|
| 699 |
+
"peerDependencies": {
|
| 700 |
+
"@firebase/app-compat": "0.x"
|
| 701 |
+
}
|
| 702 |
+
},
|
| 703 |
+
"node_modules/@firebase/analytics-types": {
|
| 704 |
+
"version": "0.8.3",
|
| 705 |
+
"resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.3.tgz",
|
| 706 |
+
"integrity": "sha512-VrIp/d8iq2g501qO46uGz3hjbDb8xzYMrbu8Tp0ovzIzrvJZ2fvmj649gTjge/b7cCCcjT0H37g1gVtlNhnkbg=="
|
| 707 |
+
},
|
| 708 |
+
"node_modules/@firebase/app": {
|
| 709 |
+
"version": "0.13.2",
|
| 710 |
+
"resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.13.2.tgz",
|
| 711 |
+
"integrity": "sha512-jwtMmJa1BXXDCiDx1vC6SFN/+HfYG53UkfJa6qeN5ogvOunzbFDO3wISZy5n9xgYFUrEP6M7e8EG++riHNTv9w==",
|
| 712 |
+
"dependencies": {
|
| 713 |
+
"@firebase/component": "0.6.18",
|
| 714 |
+
"@firebase/logger": "0.4.4",
|
| 715 |
+
"@firebase/util": "1.12.1",
|
| 716 |
+
"idb": "7.1.1",
|
| 717 |
+
"tslib": "^2.1.0"
|
| 718 |
+
},
|
| 719 |
+
"engines": {
|
| 720 |
+
"node": ">=18.0.0"
|
| 721 |
+
}
|
| 722 |
+
},
|
| 723 |
+
"node_modules/@firebase/app-check": {
|
| 724 |
+
"version": "0.10.1",
|
| 725 |
+
"resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.10.1.tgz",
|
| 726 |
+
"integrity": "sha512-MgNdlms9Qb0oSny87pwpjKush9qUwCJhfmTJHDfrcKo4neLGiSeVE4qJkzP7EQTIUFKp84pbTxobSAXkiuQVYQ==",
|
| 727 |
+
"dependencies": {
|
| 728 |
+
"@firebase/component": "0.6.18",
|
| 729 |
+
"@firebase/logger": "0.4.4",
|
| 730 |
+
"@firebase/util": "1.12.1",
|
| 731 |
+
"tslib": "^2.1.0"
|
| 732 |
+
},
|
| 733 |
+
"engines": {
|
| 734 |
+
"node": ">=18.0.0"
|
| 735 |
+
},
|
| 736 |
+
"peerDependencies": {
|
| 737 |
+
"@firebase/app": "0.x"
|
| 738 |
+
}
|
| 739 |
+
},
|
| 740 |
+
"node_modules/@firebase/app-check-compat": {
|
| 741 |
+
"version": "0.3.26",
|
| 742 |
+
"resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.3.26.tgz",
|
| 743 |
+
"integrity": "sha512-PkX+XJMLDea6nmnopzFKlr+s2LMQGqdyT2DHdbx1v1dPSqOol2YzgpgymmhC67vitXVpNvS3m/AiWQWWhhRRPQ==",
|
| 744 |
+
"dependencies": {
|
| 745 |
+
"@firebase/app-check": "0.10.1",
|
| 746 |
+
"@firebase/app-check-types": "0.5.3",
|
| 747 |
+
"@firebase/component": "0.6.18",
|
| 748 |
+
"@firebase/logger": "0.4.4",
|
| 749 |
+
"@firebase/util": "1.12.1",
|
| 750 |
+
"tslib": "^2.1.0"
|
| 751 |
+
},
|
| 752 |
+
"engines": {
|
| 753 |
+
"node": ">=18.0.0"
|
| 754 |
+
},
|
| 755 |
+
"peerDependencies": {
|
| 756 |
+
"@firebase/app-compat": "0.x"
|
| 757 |
+
}
|
| 758 |
+
},
|
| 759 |
+
"node_modules/@firebase/app-check-interop-types": {
|
| 760 |
+
"version": "0.3.3",
|
| 761 |
+
"resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.3.tgz",
|
| 762 |
+
"integrity": "sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A=="
|
| 763 |
+
},
|
| 764 |
+
"node_modules/@firebase/app-check-types": {
|
| 765 |
+
"version": "0.5.3",
|
| 766 |
+
"resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.3.tgz",
|
| 767 |
+
"integrity": "sha512-hyl5rKSj0QmwPdsAxrI5x1otDlByQ7bvNvVt8G/XPO2CSwE++rmSVf3VEhaeOR4J8ZFaF0Z0NDSmLejPweZ3ng=="
|
| 768 |
+
},
|
| 769 |
+
"node_modules/@firebase/app-compat": {
|
| 770 |
+
"version": "0.4.2",
|
| 771 |
+
"resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.4.2.tgz",
|
| 772 |
+
"integrity": "sha512-LssbyKHlwLeiV8GBATyOyjmHcMpX/tFjzRUCS1jnwGAew1VsBB4fJowyS5Ud5LdFbYpJeS+IQoC+RQxpK7eH3Q==",
|
| 773 |
+
"dependencies": {
|
| 774 |
+
"@firebase/app": "0.13.2",
|
| 775 |
+
"@firebase/component": "0.6.18",
|
| 776 |
+
"@firebase/logger": "0.4.4",
|
| 777 |
+
"@firebase/util": "1.12.1",
|
| 778 |
+
"tslib": "^2.1.0"
|
| 779 |
+
},
|
| 780 |
+
"engines": {
|
| 781 |
+
"node": ">=18.0.0"
|
| 782 |
+
}
|
| 783 |
+
},
|
| 784 |
+
"node_modules/@firebase/app-types": {
|
| 785 |
+
"version": "0.9.3",
|
| 786 |
+
"resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz",
|
| 787 |
+
"integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw=="
|
| 788 |
+
},
|
| 789 |
+
"node_modules/@firebase/auth-compat": {
|
| 790 |
+
"version": "0.5.28",
|
| 791 |
+
"resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.5.28.tgz",
|
| 792 |
+
"integrity": "sha512-HpMSo/cc6Y8IX7bkRIaPPqT//Jt83iWy5rmDWeThXQCAImstkdNo3giFLORJwrZw2ptiGkOij64EH1ztNJzc7Q==",
|
| 793 |
+
"dependencies": {
|
| 794 |
+
"@firebase/auth": "1.10.8",
|
| 795 |
+
"@firebase/auth-types": "0.13.0",
|
| 796 |
+
"@firebase/component": "0.6.18",
|
| 797 |
+
"@firebase/util": "1.12.1",
|
| 798 |
+
"tslib": "^2.1.0"
|
| 799 |
+
},
|
| 800 |
+
"engines": {
|
| 801 |
+
"node": ">=18.0.0"
|
| 802 |
+
},
|
| 803 |
+
"peerDependencies": {
|
| 804 |
+
"@firebase/app-compat": "0.x"
|
| 805 |
+
}
|
| 806 |
+
},
|
| 807 |
+
"node_modules/@firebase/auth-compat/node_modules/@firebase/auth": {
|
| 808 |
+
"version": "1.10.8",
|
| 809 |
+
"resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.10.8.tgz",
|
| 810 |
+
"integrity": "sha512-GpuTz5ap8zumr/ocnPY57ZanX02COsXloY6Y/2LYPAuXYiaJRf6BAGDEdRq1BMjP93kqQnKNuKZUTMZbQ8MNYA==",
|
| 811 |
+
"dependencies": {
|
| 812 |
+
"@firebase/component": "0.6.18",
|
| 813 |
+
"@firebase/logger": "0.4.4",
|
| 814 |
+
"@firebase/util": "1.12.1",
|
| 815 |
+
"tslib": "^2.1.0"
|
| 816 |
+
},
|
| 817 |
+
"engines": {
|
| 818 |
+
"node": ">=18.0.0"
|
| 819 |
+
},
|
| 820 |
+
"peerDependencies": {
|
| 821 |
+
"@firebase/app": "0.x",
|
| 822 |
+
"@react-native-async-storage/async-storage": "^1.18.1"
|
| 823 |
+
},
|
| 824 |
+
"peerDependenciesMeta": {
|
| 825 |
+
"@react-native-async-storage/async-storage": {
|
| 826 |
+
"optional": true
|
| 827 |
+
}
|
| 828 |
+
}
|
| 829 |
+
},
|
| 830 |
+
"node_modules/@firebase/auth-interop-types": {
|
| 831 |
+
"version": "0.2.4",
|
| 832 |
+
"resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.4.tgz",
|
| 833 |
+
"integrity": "sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA=="
|
| 834 |
+
},
|
| 835 |
+
"node_modules/@firebase/auth-types": {
|
| 836 |
+
"version": "0.13.0",
|
| 837 |
+
"resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.13.0.tgz",
|
| 838 |
+
"integrity": "sha512-S/PuIjni0AQRLF+l9ck0YpsMOdE8GO2KU6ubmBB7P+7TJUCQDa3R1dlgYm9UzGbbePMZsp0xzB93f2b/CgxMOg==",
|
| 839 |
+
"peerDependencies": {
|
| 840 |
+
"@firebase/app-types": "0.x",
|
| 841 |
+
"@firebase/util": "1.x"
|
| 842 |
+
}
|
| 843 |
+
},
|
| 844 |
+
"node_modules/@firebase/component": {
|
| 845 |
+
"version": "0.6.18",
|
| 846 |
+
"resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.18.tgz",
|
| 847 |
+
"integrity": "sha512-n28kPCkE2dL2U28fSxZJjzPPVpKsQminJ6NrzcKXAI0E/lYC8YhfwpyllScqVEvAI3J2QgJZWYgrX+1qGI+SQQ==",
|
| 848 |
+
"dependencies": {
|
| 849 |
+
"@firebase/util": "1.12.1",
|
| 850 |
+
"tslib": "^2.1.0"
|
| 851 |
+
},
|
| 852 |
+
"engines": {
|
| 853 |
+
"node": ">=18.0.0"
|
| 854 |
+
}
|
| 855 |
+
},
|
| 856 |
+
"node_modules/@firebase/data-connect": {
|
| 857 |
+
"version": "0.3.10",
|
| 858 |
+
"resolved": "https://registry.npmjs.org/@firebase/data-connect/-/data-connect-0.3.10.tgz",
|
| 859 |
+
"integrity": "sha512-VMVk7zxIkgwlVQIWHOKFahmleIjiVFwFOjmakXPd/LDgaB/5vzwsB5DWIYo+3KhGxWpidQlR8geCIn39YflJIQ==",
|
| 860 |
+
"dependencies": {
|
| 861 |
+
"@firebase/auth-interop-types": "0.2.4",
|
| 862 |
+
"@firebase/component": "0.6.18",
|
| 863 |
+
"@firebase/logger": "0.4.4",
|
| 864 |
+
"@firebase/util": "1.12.1",
|
| 865 |
+
"tslib": "^2.1.0"
|
| 866 |
+
},
|
| 867 |
+
"peerDependencies": {
|
| 868 |
+
"@firebase/app": "0.x"
|
| 869 |
+
}
|
| 870 |
+
},
|
| 871 |
+
"node_modules/@firebase/database": {
|
| 872 |
+
"version": "1.0.20",
|
| 873 |
+
"resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.20.tgz",
|
| 874 |
+
"integrity": "sha512-H9Rpj1pQ1yc9+4HQOotFGLxqAXwOzCHsRSRjcQFNOr8lhUt6LeYjf0NSRL04sc4X0dWe8DsCvYKxMYvFG/iOJw==",
|
| 875 |
+
"dependencies": {
|
| 876 |
+
"@firebase/app-check-interop-types": "0.3.3",
|
| 877 |
+
"@firebase/auth-interop-types": "0.2.4",
|
| 878 |
+
"@firebase/component": "0.6.18",
|
| 879 |
+
"@firebase/logger": "0.4.4",
|
| 880 |
+
"@firebase/util": "1.12.1",
|
| 881 |
+
"faye-websocket": "0.11.4",
|
| 882 |
+
"tslib": "^2.1.0"
|
| 883 |
+
},
|
| 884 |
+
"engines": {
|
| 885 |
+
"node": ">=18.0.0"
|
| 886 |
+
}
|
| 887 |
+
},
|
| 888 |
+
"node_modules/@firebase/database-compat": {
|
| 889 |
+
"version": "2.0.11",
|
| 890 |
+
"resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.0.11.tgz",
|
| 891 |
+
"integrity": "sha512-itEsHARSsYS95+udF/TtIzNeQ0Uhx4uIna0sk4E0wQJBUnLc/G1X6D7oRljoOuwwCezRLGvWBRyNrugv/esOEw==",
|
| 892 |
+
"dependencies": {
|
| 893 |
+
"@firebase/component": "0.6.18",
|
| 894 |
+
"@firebase/database": "1.0.20",
|
| 895 |
+
"@firebase/database-types": "1.0.15",
|
| 896 |
+
"@firebase/logger": "0.4.4",
|
| 897 |
+
"@firebase/util": "1.12.1",
|
| 898 |
+
"tslib": "^2.1.0"
|
| 899 |
+
},
|
| 900 |
+
"engines": {
|
| 901 |
+
"node": ">=18.0.0"
|
| 902 |
+
}
|
| 903 |
+
},
|
| 904 |
+
"node_modules/@firebase/database-types": {
|
| 905 |
+
"version": "1.0.15",
|
| 906 |
+
"resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.15.tgz",
|
| 907 |
+
"integrity": "sha512-XWHJ0VUJ0k2E9HDMlKxlgy/ZuTa9EvHCGLjaKSUvrQnwhgZuRU5N3yX6SZ+ftf2hTzZmfRkv+b3QRvGg40bKNw==",
|
| 908 |
+
"dependencies": {
|
| 909 |
+
"@firebase/app-types": "0.9.3",
|
| 910 |
+
"@firebase/util": "1.12.1"
|
| 911 |
+
}
|
| 912 |
+
},
|
| 913 |
+
"node_modules/@firebase/firestore": {
|
| 914 |
+
"version": "4.8.0",
|
| 915 |
+
"resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.8.0.tgz",
|
| 916 |
+
"integrity": "sha512-QSRk+Q1/CaabKyqn3C32KSFiOdZpSqI9rpLK5BHPcooElumOBooPFa6YkDdiT+/KhJtel36LdAacha9BptMj2A==",
|
| 917 |
+
"dependencies": {
|
| 918 |
+
"@firebase/component": "0.6.18",
|
| 919 |
+
"@firebase/logger": "0.4.4",
|
| 920 |
+
"@firebase/util": "1.12.1",
|
| 921 |
+
"@firebase/webchannel-wrapper": "1.0.3",
|
| 922 |
+
"@grpc/grpc-js": "~1.9.0",
|
| 923 |
+
"@grpc/proto-loader": "^0.7.8",
|
| 924 |
+
"tslib": "^2.1.0"
|
| 925 |
+
},
|
| 926 |
+
"engines": {
|
| 927 |
+
"node": ">=18.0.0"
|
| 928 |
+
},
|
| 929 |
+
"peerDependencies": {
|
| 930 |
+
"@firebase/app": "0.x"
|
| 931 |
+
}
|
| 932 |
+
},
|
| 933 |
+
"node_modules/@firebase/firestore-compat": {
|
| 934 |
+
"version": "0.3.53",
|
| 935 |
+
"resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.53.tgz",
|
| 936 |
+
"integrity": "sha512-qI3yZL8ljwAYWrTousWYbemay2YZa+udLWugjdjju2KODWtLG94DfO4NALJgPLv8CVGcDHNFXoyQexdRA0Cz8Q==",
|
| 937 |
+
"dependencies": {
|
| 938 |
+
"@firebase/component": "0.6.18",
|
| 939 |
+
"@firebase/firestore": "4.8.0",
|
| 940 |
+
"@firebase/firestore-types": "3.0.3",
|
| 941 |
+
"@firebase/util": "1.12.1",
|
| 942 |
+
"tslib": "^2.1.0"
|
| 943 |
+
},
|
| 944 |
+
"engines": {
|
| 945 |
+
"node": ">=18.0.0"
|
| 946 |
+
},
|
| 947 |
+
"peerDependencies": {
|
| 948 |
+
"@firebase/app-compat": "0.x"
|
| 949 |
+
}
|
| 950 |
+
},
|
| 951 |
+
"node_modules/@firebase/firestore-types": {
|
| 952 |
+
"version": "3.0.3",
|
| 953 |
+
"resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-3.0.3.tgz",
|
| 954 |
+
"integrity": "sha512-hD2jGdiWRxB/eZWF89xcK9gF8wvENDJkzpVFb4aGkzfEaKxVRD1kjz1t1Wj8VZEp2LCB53Yx1zD8mrhQu87R6Q==",
|
| 955 |
+
"peerDependencies": {
|
| 956 |
+
"@firebase/app-types": "0.x",
|
| 957 |
+
"@firebase/util": "1.x"
|
| 958 |
+
}
|
| 959 |
+
},
|
| 960 |
+
"node_modules/@firebase/functions": {
|
| 961 |
+
"version": "0.12.9",
|
| 962 |
+
"resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.12.9.tgz",
|
| 963 |
+
"integrity": "sha512-FG95w6vjbUXN84Ehezc2SDjGmGq225UYbHrb/ptkRT7OTuCiQRErOQuyt1jI1tvcDekdNog+anIObihNFz79Lg==",
|
| 964 |
+
"dependencies": {
|
| 965 |
+
"@firebase/app-check-interop-types": "0.3.3",
|
| 966 |
+
"@firebase/auth-interop-types": "0.2.4",
|
| 967 |
+
"@firebase/component": "0.6.18",
|
| 968 |
+
"@firebase/messaging-interop-types": "0.2.3",
|
| 969 |
+
"@firebase/util": "1.12.1",
|
| 970 |
+
"tslib": "^2.1.0"
|
| 971 |
+
},
|
| 972 |
+
"engines": {
|
| 973 |
+
"node": ">=18.0.0"
|
| 974 |
+
},
|
| 975 |
+
"peerDependencies": {
|
| 976 |
+
"@firebase/app": "0.x"
|
| 977 |
+
}
|
| 978 |
+
},
|
| 979 |
+
"node_modules/@firebase/functions-compat": {
|
| 980 |
+
"version": "0.3.26",
|
| 981 |
+
"resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.3.26.tgz",
|
| 982 |
+
"integrity": "sha512-A798/6ff5LcG2LTWqaGazbFYnjBW8zc65YfID/en83ALmkhu2b0G8ykvQnLtakbV9ajrMYPn7Yc/XcYsZIUsjA==",
|
| 983 |
+
"dependencies": {
|
| 984 |
+
"@firebase/component": "0.6.18",
|
| 985 |
+
"@firebase/functions": "0.12.9",
|
| 986 |
+
"@firebase/functions-types": "0.6.3",
|
| 987 |
+
"@firebase/util": "1.12.1",
|
| 988 |
+
"tslib": "^2.1.0"
|
| 989 |
+
},
|
| 990 |
+
"engines": {
|
| 991 |
+
"node": ">=18.0.0"
|
| 992 |
+
},
|
| 993 |
+
"peerDependencies": {
|
| 994 |
+
"@firebase/app-compat": "0.x"
|
| 995 |
+
}
|
| 996 |
+
},
|
| 997 |
+
"node_modules/@firebase/functions-types": {
|
| 998 |
+
"version": "0.6.3",
|
| 999 |
+
"resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.3.tgz",
|
| 1000 |
+
"integrity": "sha512-EZoDKQLUHFKNx6VLipQwrSMh01A1SaL3Wg6Hpi//x6/fJ6Ee4hrAeswK99I5Ht8roiniKHw4iO0B1Oxj5I4plg=="
|
| 1001 |
+
},
|
| 1002 |
+
"node_modules/@firebase/installations": {
|
| 1003 |
+
"version": "0.6.18",
|
| 1004 |
+
"resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.18.tgz",
|
| 1005 |
+
"integrity": "sha512-NQ86uGAcvO8nBRwVltRL9QQ4Reidc/3whdAasgeWCPIcrhOKDuNpAALa6eCVryLnK14ua2DqekCOX5uC9XbU/A==",
|
| 1006 |
+
"dependencies": {
|
| 1007 |
+
"@firebase/component": "0.6.18",
|
| 1008 |
+
"@firebase/util": "1.12.1",
|
| 1009 |
+
"idb": "7.1.1",
|
| 1010 |
+
"tslib": "^2.1.0"
|
| 1011 |
+
},
|
| 1012 |
+
"peerDependencies": {
|
| 1013 |
+
"@firebase/app": "0.x"
|
| 1014 |
+
}
|
| 1015 |
+
},
|
| 1016 |
+
"node_modules/@firebase/installations-compat": {
|
| 1017 |
+
"version": "0.2.18",
|
| 1018 |
+
"resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.18.tgz",
|
| 1019 |
+
"integrity": "sha512-aLFohRpJO5kKBL/XYL4tN+GdwEB/Q6Vo9eZOM/6Kic7asSUgmSfGPpGUZO1OAaSRGwF4Lqnvi1f/f9VZnKzChw==",
|
| 1020 |
+
"dependencies": {
|
| 1021 |
+
"@firebase/component": "0.6.18",
|
| 1022 |
+
"@firebase/installations": "0.6.18",
|
| 1023 |
+
"@firebase/installations-types": "0.5.3",
|
| 1024 |
+
"@firebase/util": "1.12.1",
|
| 1025 |
+
"tslib": "^2.1.0"
|
| 1026 |
+
},
|
| 1027 |
+
"peerDependencies": {
|
| 1028 |
+
"@firebase/app-compat": "0.x"
|
| 1029 |
+
}
|
| 1030 |
+
},
|
| 1031 |
+
"node_modules/@firebase/installations-types": {
|
| 1032 |
+
"version": "0.5.3",
|
| 1033 |
+
"resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.3.tgz",
|
| 1034 |
+
"integrity": "sha512-2FJI7gkLqIE0iYsNQ1P751lO3hER+Umykel+TkLwHj6plzWVxqvfclPUZhcKFVQObqloEBTmpi2Ozn7EkCABAA==",
|
| 1035 |
+
"peerDependencies": {
|
| 1036 |
+
"@firebase/app-types": "0.x"
|
| 1037 |
+
}
|
| 1038 |
+
},
|
| 1039 |
+
"node_modules/@firebase/logger": {
|
| 1040 |
+
"version": "0.4.4",
|
| 1041 |
+
"resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.4.tgz",
|
| 1042 |
+
"integrity": "sha512-mH0PEh1zoXGnaR8gD1DeGeNZtWFKbnz9hDO91dIml3iou1gpOnLqXQ2dJfB71dj6dpmUjcQ6phY3ZZJbjErr9g==",
|
| 1043 |
+
"dependencies": {
|
| 1044 |
+
"tslib": "^2.1.0"
|
| 1045 |
+
},
|
| 1046 |
+
"engines": {
|
| 1047 |
+
"node": ">=18.0.0"
|
| 1048 |
+
}
|
| 1049 |
+
},
|
| 1050 |
+
"node_modules/@firebase/messaging": {
|
| 1051 |
+
"version": "0.12.22",
|
| 1052 |
+
"resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.22.tgz",
|
| 1053 |
+
"integrity": "sha512-GJcrPLc+Hu7nk+XQ70Okt3M1u1eRr2ZvpMbzbc54oTPJZySHcX9ccZGVFcsZbSZ6o1uqumm8Oc7OFkD3Rn1/og==",
|
| 1054 |
+
"dependencies": {
|
| 1055 |
+
"@firebase/component": "0.6.18",
|
| 1056 |
+
"@firebase/installations": "0.6.18",
|
| 1057 |
+
"@firebase/messaging-interop-types": "0.2.3",
|
| 1058 |
+
"@firebase/util": "1.12.1",
|
| 1059 |
+
"idb": "7.1.1",
|
| 1060 |
+
"tslib": "^2.1.0"
|
| 1061 |
+
},
|
| 1062 |
+
"peerDependencies": {
|
| 1063 |
+
"@firebase/app": "0.x"
|
| 1064 |
+
}
|
| 1065 |
+
},
|
| 1066 |
+
"node_modules/@firebase/messaging-compat": {
|
| 1067 |
+
"version": "0.2.22",
|
| 1068 |
+
"resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.22.tgz",
|
| 1069 |
+
"integrity": "sha512-5ZHtRnj6YO6f/QPa/KU6gryjmX4Kg33Kn4gRpNU6M1K47Gm8kcQwPkX7erRUYEH1mIWptfvjvXMHWoZaWjkU7A==",
|
| 1070 |
+
"dependencies": {
|
| 1071 |
+
"@firebase/component": "0.6.18",
|
| 1072 |
+
"@firebase/messaging": "0.12.22",
|
| 1073 |
+
"@firebase/util": "1.12.1",
|
| 1074 |
+
"tslib": "^2.1.0"
|
| 1075 |
+
},
|
| 1076 |
+
"peerDependencies": {
|
| 1077 |
+
"@firebase/app-compat": "0.x"
|
| 1078 |
+
}
|
| 1079 |
+
},
|
| 1080 |
+
"node_modules/@firebase/messaging-interop-types": {
|
| 1081 |
+
"version": "0.2.3",
|
| 1082 |
+
"resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.3.tgz",
|
| 1083 |
+
"integrity": "sha512-xfzFaJpzcmtDjycpDeCUj0Ge10ATFi/VHVIvEEjDNc3hodVBQADZ7BWQU7CuFpjSHE+eLuBI13z5F/9xOoGX8Q=="
|
| 1084 |
+
},
|
| 1085 |
+
"node_modules/@firebase/performance": {
|
| 1086 |
+
"version": "0.7.7",
|
| 1087 |
+
"resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.7.7.tgz",
|
| 1088 |
+
"integrity": "sha512-JTlTQNZKAd4+Q5sodpw6CN+6NmwbY72av3Lb6wUKTsL7rb3cuBIhQSrslWbVz0SwK3x0ZNcqX24qtRbwKiv+6w==",
|
| 1089 |
+
"dependencies": {
|
| 1090 |
+
"@firebase/component": "0.6.18",
|
| 1091 |
+
"@firebase/installations": "0.6.18",
|
| 1092 |
+
"@firebase/logger": "0.4.4",
|
| 1093 |
+
"@firebase/util": "1.12.1",
|
| 1094 |
+
"tslib": "^2.1.0",
|
| 1095 |
+
"web-vitals": "^4.2.4"
|
| 1096 |
+
},
|
| 1097 |
+
"peerDependencies": {
|
| 1098 |
+
"@firebase/app": "0.x"
|
| 1099 |
+
}
|
| 1100 |
+
},
|
| 1101 |
+
"node_modules/@firebase/performance-compat": {
|
| 1102 |
+
"version": "0.2.20",
|
| 1103 |
+
"resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.20.tgz",
|
| 1104 |
+
"integrity": "sha512-XkFK5NmOKCBuqOKWeRgBUFZZGz9SzdTZp4OqeUg+5nyjapTiZ4XoiiUL8z7mB2q+63rPmBl7msv682J3rcDXIQ==",
|
| 1105 |
+
"dependencies": {
|
| 1106 |
+
"@firebase/component": "0.6.18",
|
| 1107 |
+
"@firebase/logger": "0.4.4",
|
| 1108 |
+
"@firebase/performance": "0.7.7",
|
| 1109 |
+
"@firebase/performance-types": "0.2.3",
|
| 1110 |
+
"@firebase/util": "1.12.1",
|
| 1111 |
+
"tslib": "^2.1.0"
|
| 1112 |
+
},
|
| 1113 |
+
"peerDependencies": {
|
| 1114 |
+
"@firebase/app-compat": "0.x"
|
| 1115 |
+
}
|
| 1116 |
+
},
|
| 1117 |
+
"node_modules/@firebase/performance-types": {
|
| 1118 |
+
"version": "0.2.3",
|
| 1119 |
+
"resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.3.tgz",
|
| 1120 |
+
"integrity": "sha512-IgkyTz6QZVPAq8GSkLYJvwSLr3LS9+V6vNPQr0x4YozZJiLF5jYixj0amDtATf1X0EtYHqoPO48a9ija8GocxQ=="
|
| 1121 |
+
},
|
| 1122 |
+
"node_modules/@firebase/remote-config": {
|
| 1123 |
+
"version": "0.6.5",
|
| 1124 |
+
"resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.6.5.tgz",
|
| 1125 |
+
"integrity": "sha512-fU0c8HY0vrVHwC+zQ/fpXSqHyDMuuuglV94VF6Yonhz8Fg2J+KOowPGANM0SZkLvVOYpTeWp3ZmM+F6NjwWLnw==",
|
| 1126 |
+
"dependencies": {
|
| 1127 |
+
"@firebase/component": "0.6.18",
|
| 1128 |
+
"@firebase/installations": "0.6.18",
|
| 1129 |
+
"@firebase/logger": "0.4.4",
|
| 1130 |
+
"@firebase/util": "1.12.1",
|
| 1131 |
+
"tslib": "^2.1.0"
|
| 1132 |
+
},
|
| 1133 |
+
"peerDependencies": {
|
| 1134 |
+
"@firebase/app": "0.x"
|
| 1135 |
+
}
|
| 1136 |
+
},
|
| 1137 |
+
"node_modules/@firebase/remote-config-compat": {
|
| 1138 |
+
"version": "0.2.18",
|
| 1139 |
+
"resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.18.tgz",
|
| 1140 |
+
"integrity": "sha512-YiETpldhDy7zUrnS8e+3l7cNs0sL7+tVAxvVYU0lu7O+qLHbmdtAxmgY+wJqWdW2c9nDvBFec7QiF58pEUu0qQ==",
|
| 1141 |
+
"dependencies": {
|
| 1142 |
+
"@firebase/component": "0.6.18",
|
| 1143 |
+
"@firebase/logger": "0.4.4",
|
| 1144 |
+
"@firebase/remote-config": "0.6.5",
|
| 1145 |
+
"@firebase/remote-config-types": "0.4.0",
|
| 1146 |
+
"@firebase/util": "1.12.1",
|
| 1147 |
+
"tslib": "^2.1.0"
|
| 1148 |
+
},
|
| 1149 |
+
"peerDependencies": {
|
| 1150 |
+
"@firebase/app-compat": "0.x"
|
| 1151 |
+
}
|
| 1152 |
+
},
|
| 1153 |
+
"node_modules/@firebase/remote-config-types": {
|
| 1154 |
+
"version": "0.4.0",
|
| 1155 |
+
"resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.4.0.tgz",
|
| 1156 |
+
"integrity": "sha512-7p3mRE/ldCNYt8fmWMQ/MSGRmXYlJ15Rvs9Rk17t8p0WwZDbeK7eRmoI1tvCPaDzn9Oqh+yD6Lw+sGLsLg4kKg=="
|
| 1157 |
+
},
|
| 1158 |
+
"node_modules/@firebase/storage": {
|
| 1159 |
+
"version": "0.13.14",
|
| 1160 |
+
"resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.13.14.tgz",
|
| 1161 |
+
"integrity": "sha512-xTq5ixxORzx+bfqCpsh+o3fxOsGoDjC1nO0Mq2+KsOcny3l7beyBhP/y1u5T6mgsFQwI1j6oAkbT5cWdDBx87g==",
|
| 1162 |
+
"dependencies": {
|
| 1163 |
+
"@firebase/component": "0.6.18",
|
| 1164 |
+
"@firebase/util": "1.12.1",
|
| 1165 |
+
"tslib": "^2.1.0"
|
| 1166 |
+
},
|
| 1167 |
+
"engines": {
|
| 1168 |
+
"node": ">=18.0.0"
|
| 1169 |
+
},
|
| 1170 |
+
"peerDependencies": {
|
| 1171 |
+
"@firebase/app": "0.x"
|
| 1172 |
+
}
|
| 1173 |
+
},
|
| 1174 |
+
"node_modules/@firebase/storage-compat": {
|
| 1175 |
+
"version": "0.3.24",
|
| 1176 |
+
"resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.3.24.tgz",
|
| 1177 |
+
"integrity": "sha512-XHn2tLniiP7BFKJaPZ0P8YQXKiVJX+bMyE2j2YWjYfaddqiJnROJYqSomwW6L3Y+gZAga35ONXUJQju6MB6SOQ==",
|
| 1178 |
+
"dependencies": {
|
| 1179 |
+
"@firebase/component": "0.6.18",
|
| 1180 |
+
"@firebase/storage": "0.13.14",
|
| 1181 |
+
"@firebase/storage-types": "0.8.3",
|
| 1182 |
+
"@firebase/util": "1.12.1",
|
| 1183 |
+
"tslib": "^2.1.0"
|
| 1184 |
+
},
|
| 1185 |
+
"engines": {
|
| 1186 |
+
"node": ">=18.0.0"
|
| 1187 |
+
},
|
| 1188 |
+
"peerDependencies": {
|
| 1189 |
+
"@firebase/app-compat": "0.x"
|
| 1190 |
+
}
|
| 1191 |
+
},
|
| 1192 |
+
"node_modules/@firebase/storage-types": {
|
| 1193 |
+
"version": "0.8.3",
|
| 1194 |
+
"resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.3.tgz",
|
| 1195 |
+
"integrity": "sha512-+Muk7g9uwngTpd8xn9OdF/D48uiQ7I1Fae7ULsWPuKoCH3HU7bfFPhxtJYzyhjdniowhuDpQcfPmuNRAqZEfvg==",
|
| 1196 |
+
"peerDependencies": {
|
| 1197 |
+
"@firebase/app-types": "0.x",
|
| 1198 |
+
"@firebase/util": "1.x"
|
| 1199 |
+
}
|
| 1200 |
+
},
|
| 1201 |
+
"node_modules/@firebase/util": {
|
| 1202 |
+
"version": "1.12.1",
|
| 1203 |
+
"resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.12.1.tgz",
|
| 1204 |
+
"integrity": "sha512-zGlBn/9Dnya5ta9bX/fgEoNC3Cp8s6h+uYPYaDieZsFOAdHP/ExzQ/eaDgxD3GOROdPkLKpvKY0iIzr9adle0w==",
|
| 1205 |
+
"hasInstallScript": true,
|
| 1206 |
+
"dependencies": {
|
| 1207 |
+
"tslib": "^2.1.0"
|
| 1208 |
+
},
|
| 1209 |
+
"engines": {
|
| 1210 |
+
"node": ">=18.0.0"
|
| 1211 |
+
}
|
| 1212 |
+
},
|
| 1213 |
+
"node_modules/@firebase/webchannel-wrapper": {
|
| 1214 |
+
"version": "1.0.3",
|
| 1215 |
+
"resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.3.tgz",
|
| 1216 |
+
"integrity": "sha512-2xCRM9q9FlzGZCdgDMJwc0gyUkWFtkosy7Xxr6sFgQwn+wMNIWd7xIvYNauU1r64B5L5rsGKy/n9TKJ0aAFeqQ=="
|
| 1217 |
+
},
|
| 1218 |
+
"node_modules/@grpc/grpc-js": {
|
| 1219 |
+
"version": "1.9.15",
|
| 1220 |
+
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.15.tgz",
|
| 1221 |
+
"integrity": "sha512-nqE7Hc0AzI+euzUwDAy0aY5hCp10r734gMGRdU+qOPX0XSceI2ULrcXB5U2xSc5VkWwalCj4M7GzCAygZl2KoQ==",
|
| 1222 |
+
"dependencies": {
|
| 1223 |
+
"@grpc/proto-loader": "^0.7.8",
|
| 1224 |
+
"@types/node": ">=12.12.47"
|
| 1225 |
+
},
|
| 1226 |
+
"engines": {
|
| 1227 |
+
"node": "^8.13.0 || >=10.10.0"
|
| 1228 |
+
}
|
| 1229 |
+
},
|
| 1230 |
+
"node_modules/@grpc/proto-loader": {
|
| 1231 |
+
"version": "0.7.15",
|
| 1232 |
+
"resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz",
|
| 1233 |
+
"integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==",
|
| 1234 |
+
"dependencies": {
|
| 1235 |
+
"lodash.camelcase": "^4.3.0",
|
| 1236 |
+
"long": "^5.0.0",
|
| 1237 |
+
"protobufjs": "^7.2.5",
|
| 1238 |
+
"yargs": "^17.7.2"
|
| 1239 |
+
},
|
| 1240 |
+
"bin": {
|
| 1241 |
+
"proto-loader-gen-types": "build/bin/proto-loader-gen-types.js"
|
| 1242 |
+
},
|
| 1243 |
+
"engines": {
|
| 1244 |
+
"node": ">=6"
|
| 1245 |
+
}
|
| 1246 |
+
},
|
| 1247 |
"node_modules/@jridgewell/gen-mapping": {
|
| 1248 |
"version": "0.3.13",
|
| 1249 |
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
|
|
|
| 1289 |
"@jridgewell/sourcemap-codec": "^1.4.14"
|
| 1290 |
}
|
| 1291 |
},
|
| 1292 |
+
"node_modules/@protobufjs/aspromise": {
|
| 1293 |
+
"version": "1.1.2",
|
| 1294 |
+
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
|
| 1295 |
+
"integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="
|
| 1296 |
+
},
|
| 1297 |
+
"node_modules/@protobufjs/base64": {
|
| 1298 |
+
"version": "1.1.2",
|
| 1299 |
+
"resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
|
| 1300 |
+
"integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="
|
| 1301 |
+
},
|
| 1302 |
+
"node_modules/@protobufjs/codegen": {
|
| 1303 |
+
"version": "2.0.4",
|
| 1304 |
+
"resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
|
| 1305 |
+
"integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="
|
| 1306 |
+
},
|
| 1307 |
+
"node_modules/@protobufjs/eventemitter": {
|
| 1308 |
+
"version": "1.1.0",
|
| 1309 |
+
"resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
|
| 1310 |
+
"integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="
|
| 1311 |
+
},
|
| 1312 |
+
"node_modules/@protobufjs/fetch": {
|
| 1313 |
+
"version": "1.1.0",
|
| 1314 |
+
"resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
|
| 1315 |
+
"integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
|
| 1316 |
+
"dependencies": {
|
| 1317 |
+
"@protobufjs/aspromise": "^1.1.1",
|
| 1318 |
+
"@protobufjs/inquire": "^1.1.0"
|
| 1319 |
+
}
|
| 1320 |
+
},
|
| 1321 |
+
"node_modules/@protobufjs/float": {
|
| 1322 |
+
"version": "1.0.2",
|
| 1323 |
+
"resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
|
| 1324 |
+
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="
|
| 1325 |
+
},
|
| 1326 |
+
"node_modules/@protobufjs/inquire": {
|
| 1327 |
+
"version": "1.1.0",
|
| 1328 |
+
"resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
|
| 1329 |
+
"integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="
|
| 1330 |
+
},
|
| 1331 |
+
"node_modules/@protobufjs/path": {
|
| 1332 |
+
"version": "1.1.2",
|
| 1333 |
+
"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
|
| 1334 |
+
"integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="
|
| 1335 |
+
},
|
| 1336 |
+
"node_modules/@protobufjs/pool": {
|
| 1337 |
+
"version": "1.1.0",
|
| 1338 |
+
"resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
|
| 1339 |
+
"integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="
|
| 1340 |
+
},
|
| 1341 |
+
"node_modules/@protobufjs/utf8": {
|
| 1342 |
+
"version": "1.1.0",
|
| 1343 |
+
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
|
| 1344 |
+
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="
|
| 1345 |
+
},
|
| 1346 |
"node_modules/@rolldown/pluginutils": {
|
| 1347 |
"version": "1.0.0-beta.27",
|
| 1348 |
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
|
|
|
|
| 1721 |
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
| 1722 |
"dev": true
|
| 1723 |
},
|
| 1724 |
+
"node_modules/@types/node": {
|
| 1725 |
+
"version": "25.5.0",
|
| 1726 |
+
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz",
|
| 1727 |
+
"integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==",
|
| 1728 |
+
"dependencies": {
|
| 1729 |
+
"undici-types": "~7.18.0"
|
| 1730 |
+
}
|
| 1731 |
+
},
|
| 1732 |
"node_modules/@types/prop-types": {
|
| 1733 |
"version": "15.7.15",
|
| 1734 |
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
|
|
|
| 1774 |
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
|
| 1775 |
}
|
| 1776 |
},
|
| 1777 |
+
"node_modules/ansi-regex": {
|
| 1778 |
+
"version": "5.0.1",
|
| 1779 |
+
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
| 1780 |
+
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
| 1781 |
+
"engines": {
|
| 1782 |
+
"node": ">=8"
|
| 1783 |
+
}
|
| 1784 |
+
},
|
| 1785 |
+
"node_modules/ansi-styles": {
|
| 1786 |
+
"version": "4.3.0",
|
| 1787 |
+
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
| 1788 |
+
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
| 1789 |
+
"dependencies": {
|
| 1790 |
+
"color-convert": "^2.0.1"
|
| 1791 |
+
},
|
| 1792 |
+
"engines": {
|
| 1793 |
+
"node": ">=8"
|
| 1794 |
+
},
|
| 1795 |
+
"funding": {
|
| 1796 |
+
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
| 1797 |
+
}
|
| 1798 |
+
},
|
| 1799 |
"node_modules/baseline-browser-mapping": {
|
| 1800 |
"version": "2.10.0",
|
| 1801 |
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz",
|
|
|
|
| 1861 |
}
|
| 1862 |
]
|
| 1863 |
},
|
| 1864 |
+
"node_modules/cliui": {
|
| 1865 |
+
"version": "8.0.1",
|
| 1866 |
+
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
| 1867 |
+
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
|
| 1868 |
+
"dependencies": {
|
| 1869 |
+
"string-width": "^4.2.0",
|
| 1870 |
+
"strip-ansi": "^6.0.1",
|
| 1871 |
+
"wrap-ansi": "^7.0.0"
|
| 1872 |
+
},
|
| 1873 |
+
"engines": {
|
| 1874 |
+
"node": ">=12"
|
| 1875 |
+
}
|
| 1876 |
+
},
|
| 1877 |
+
"node_modules/color-convert": {
|
| 1878 |
+
"version": "2.0.1",
|
| 1879 |
+
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
| 1880 |
+
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
| 1881 |
+
"dependencies": {
|
| 1882 |
+
"color-name": "~1.1.4"
|
| 1883 |
+
},
|
| 1884 |
+
"engines": {
|
| 1885 |
+
"node": ">=7.0.0"
|
| 1886 |
+
}
|
| 1887 |
+
},
|
| 1888 |
+
"node_modules/color-name": {
|
| 1889 |
+
"version": "1.1.4",
|
| 1890 |
+
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
| 1891 |
+
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
| 1892 |
+
},
|
| 1893 |
"node_modules/convert-source-map": {
|
| 1894 |
"version": "2.0.0",
|
| 1895 |
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
|
|
|
| 1925 |
"integrity": "sha512-QBMrTWEf00GXZmJyx2lbYD45jpI3TUFnNIzJ5BBc8piGUDwMPa1GV6HJWTZVvY/eiN3fSopl7NRbgGp9sZ9LTA==",
|
| 1926 |
"dev": true
|
| 1927 |
},
|
| 1928 |
+
"node_modules/emoji-regex": {
|
| 1929 |
+
"version": "8.0.0",
|
| 1930 |
+
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
| 1931 |
+
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
|
| 1932 |
+
},
|
| 1933 |
"node_modules/esbuild": {
|
| 1934 |
"version": "0.21.5",
|
| 1935 |
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
|
|
|
|
| 1972 |
"version": "3.2.0",
|
| 1973 |
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
| 1974 |
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
|
|
|
| 1975 |
"engines": {
|
| 1976 |
"node": ">=6"
|
| 1977 |
}
|
| 1978 |
},
|
| 1979 |
+
"node_modules/faye-websocket": {
|
| 1980 |
+
"version": "0.11.4",
|
| 1981 |
+
"resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz",
|
| 1982 |
+
"integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==",
|
| 1983 |
+
"dependencies": {
|
| 1984 |
+
"websocket-driver": ">=0.5.1"
|
| 1985 |
+
},
|
| 1986 |
+
"engines": {
|
| 1987 |
+
"node": ">=0.8.0"
|
| 1988 |
+
}
|
| 1989 |
+
},
|
| 1990 |
+
"node_modules/firebase": {
|
| 1991 |
+
"version": "11.10.0",
|
| 1992 |
+
"resolved": "https://registry.npmjs.org/firebase/-/firebase-11.10.0.tgz",
|
| 1993 |
+
"integrity": "sha512-nKBXoDzF0DrXTBQJlZa+sbC5By99ysYU1D6PkMRYknm0nCW7rJly47q492Ht7Ndz5MeYSBuboKuhS1e6mFC03w==",
|
| 1994 |
+
"dependencies": {
|
| 1995 |
+
"@firebase/ai": "1.4.1",
|
| 1996 |
+
"@firebase/analytics": "0.10.17",
|
| 1997 |
+
"@firebase/analytics-compat": "0.2.23",
|
| 1998 |
+
"@firebase/app": "0.13.2",
|
| 1999 |
+
"@firebase/app-check": "0.10.1",
|
| 2000 |
+
"@firebase/app-check-compat": "0.3.26",
|
| 2001 |
+
"@firebase/app-compat": "0.4.2",
|
| 2002 |
+
"@firebase/app-types": "0.9.3",
|
| 2003 |
+
"@firebase/auth": "1.10.8",
|
| 2004 |
+
"@firebase/auth-compat": "0.5.28",
|
| 2005 |
+
"@firebase/data-connect": "0.3.10",
|
| 2006 |
+
"@firebase/database": "1.0.20",
|
| 2007 |
+
"@firebase/database-compat": "2.0.11",
|
| 2008 |
+
"@firebase/firestore": "4.8.0",
|
| 2009 |
+
"@firebase/firestore-compat": "0.3.53",
|
| 2010 |
+
"@firebase/functions": "0.12.9",
|
| 2011 |
+
"@firebase/functions-compat": "0.3.26",
|
| 2012 |
+
"@firebase/installations": "0.6.18",
|
| 2013 |
+
"@firebase/installations-compat": "0.2.18",
|
| 2014 |
+
"@firebase/messaging": "0.12.22",
|
| 2015 |
+
"@firebase/messaging-compat": "0.2.22",
|
| 2016 |
+
"@firebase/performance": "0.7.7",
|
| 2017 |
+
"@firebase/performance-compat": "0.2.20",
|
| 2018 |
+
"@firebase/remote-config": "0.6.5",
|
| 2019 |
+
"@firebase/remote-config-compat": "0.2.18",
|
| 2020 |
+
"@firebase/storage": "0.13.14",
|
| 2021 |
+
"@firebase/storage-compat": "0.3.24",
|
| 2022 |
+
"@firebase/util": "1.12.1"
|
| 2023 |
+
}
|
| 2024 |
+
},
|
| 2025 |
+
"node_modules/firebase/node_modules/@firebase/auth": {
|
| 2026 |
+
"version": "1.10.8",
|
| 2027 |
+
"resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.10.8.tgz",
|
| 2028 |
+
"integrity": "sha512-GpuTz5ap8zumr/ocnPY57ZanX02COsXloY6Y/2LYPAuXYiaJRf6BAGDEdRq1BMjP93kqQnKNuKZUTMZbQ8MNYA==",
|
| 2029 |
+
"dependencies": {
|
| 2030 |
+
"@firebase/component": "0.6.18",
|
| 2031 |
+
"@firebase/logger": "0.4.4",
|
| 2032 |
+
"@firebase/util": "1.12.1",
|
| 2033 |
+
"tslib": "^2.1.0"
|
| 2034 |
+
},
|
| 2035 |
+
"engines": {
|
| 2036 |
+
"node": ">=18.0.0"
|
| 2037 |
+
},
|
| 2038 |
+
"peerDependencies": {
|
| 2039 |
+
"@firebase/app": "0.x",
|
| 2040 |
+
"@react-native-async-storage/async-storage": "^1.18.1"
|
| 2041 |
+
},
|
| 2042 |
+
"peerDependenciesMeta": {
|
| 2043 |
+
"@react-native-async-storage/async-storage": {
|
| 2044 |
+
"optional": true
|
| 2045 |
+
}
|
| 2046 |
+
}
|
| 2047 |
+
},
|
| 2048 |
"node_modules/fsevents": {
|
| 2049 |
"version": "2.3.3",
|
| 2050 |
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
|
|
|
| 2068 |
"node": ">=6.9.0"
|
| 2069 |
}
|
| 2070 |
},
|
| 2071 |
+
"node_modules/get-caller-file": {
|
| 2072 |
+
"version": "2.0.5",
|
| 2073 |
+
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
| 2074 |
+
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
| 2075 |
+
"engines": {
|
| 2076 |
+
"node": "6.* || 8.* || >= 10.*"
|
| 2077 |
+
}
|
| 2078 |
+
},
|
| 2079 |
+
"node_modules/http-parser-js": {
|
| 2080 |
+
"version": "0.5.10",
|
| 2081 |
+
"resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz",
|
| 2082 |
+
"integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA=="
|
| 2083 |
+
},
|
| 2084 |
+
"node_modules/idb": {
|
| 2085 |
+
"version": "7.1.1",
|
| 2086 |
+
"resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz",
|
| 2087 |
+
"integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ=="
|
| 2088 |
+
},
|
| 2089 |
+
"node_modules/is-fullwidth-code-point": {
|
| 2090 |
+
"version": "3.0.0",
|
| 2091 |
+
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
| 2092 |
+
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
| 2093 |
+
"engines": {
|
| 2094 |
+
"node": ">=8"
|
| 2095 |
+
}
|
| 2096 |
+
},
|
| 2097 |
"node_modules/js-tokens": {
|
| 2098 |
"version": "4.0.0",
|
| 2099 |
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
|
|
|
| 2123 |
"node": ">=6"
|
| 2124 |
}
|
| 2125 |
},
|
| 2126 |
+
"node_modules/lodash.camelcase": {
|
| 2127 |
+
"version": "4.3.0",
|
| 2128 |
+
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
|
| 2129 |
+
"integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="
|
| 2130 |
+
},
|
| 2131 |
+
"node_modules/long": {
|
| 2132 |
+
"version": "5.3.2",
|
| 2133 |
+
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
|
| 2134 |
+
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="
|
| 2135 |
+
},
|
| 2136 |
"node_modules/loose-envify": {
|
| 2137 |
"version": "1.4.0",
|
| 2138 |
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
|
|
|
| 2217 |
"node": "^10 || ^12 || >=14"
|
| 2218 |
}
|
| 2219 |
},
|
| 2220 |
+
"node_modules/protobufjs": {
|
| 2221 |
+
"version": "7.5.4",
|
| 2222 |
+
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz",
|
| 2223 |
+
"integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==",
|
| 2224 |
+
"hasInstallScript": true,
|
| 2225 |
+
"dependencies": {
|
| 2226 |
+
"@protobufjs/aspromise": "^1.1.2",
|
| 2227 |
+
"@protobufjs/base64": "^1.1.2",
|
| 2228 |
+
"@protobufjs/codegen": "^2.0.4",
|
| 2229 |
+
"@protobufjs/eventemitter": "^1.1.0",
|
| 2230 |
+
"@protobufjs/fetch": "^1.1.0",
|
| 2231 |
+
"@protobufjs/float": "^1.0.2",
|
| 2232 |
+
"@protobufjs/inquire": "^1.1.0",
|
| 2233 |
+
"@protobufjs/path": "^1.1.2",
|
| 2234 |
+
"@protobufjs/pool": "^1.1.0",
|
| 2235 |
+
"@protobufjs/utf8": "^1.1.0",
|
| 2236 |
+
"@types/node": ">=13.7.0",
|
| 2237 |
+
"long": "^5.0.0"
|
| 2238 |
+
},
|
| 2239 |
+
"engines": {
|
| 2240 |
+
"node": ">=12.0.0"
|
| 2241 |
+
}
|
| 2242 |
+
},
|
| 2243 |
"node_modules/react": {
|
| 2244 |
"version": "18.3.1",
|
| 2245 |
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
|
|
|
| 2272 |
"node": ">=0.10.0"
|
| 2273 |
}
|
| 2274 |
},
|
| 2275 |
+
"node_modules/require-directory": {
|
| 2276 |
+
"version": "2.1.1",
|
| 2277 |
+
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
| 2278 |
+
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
|
| 2279 |
+
"engines": {
|
| 2280 |
+
"node": ">=0.10.0"
|
| 2281 |
+
}
|
| 2282 |
+
},
|
| 2283 |
"node_modules/rollup": {
|
| 2284 |
"version": "4.59.0",
|
| 2285 |
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
|
|
|
|
| 2324 |
"fsevents": "~2.3.2"
|
| 2325 |
}
|
| 2326 |
},
|
| 2327 |
+
"node_modules/safe-buffer": {
|
| 2328 |
+
"version": "5.2.1",
|
| 2329 |
+
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
| 2330 |
+
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
| 2331 |
+
"funding": [
|
| 2332 |
+
{
|
| 2333 |
+
"type": "github",
|
| 2334 |
+
"url": "https://github.com/sponsors/feross"
|
| 2335 |
+
},
|
| 2336 |
+
{
|
| 2337 |
+
"type": "patreon",
|
| 2338 |
+
"url": "https://www.patreon.com/feross"
|
| 2339 |
+
},
|
| 2340 |
+
{
|
| 2341 |
+
"type": "consulting",
|
| 2342 |
+
"url": "https://feross.org/support"
|
| 2343 |
+
}
|
| 2344 |
+
]
|
| 2345 |
+
},
|
| 2346 |
"node_modules/scheduler": {
|
| 2347 |
"version": "0.23.2",
|
| 2348 |
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
|
|
|
| 2369 |
"node": ">=0.10.0"
|
| 2370 |
}
|
| 2371 |
},
|
| 2372 |
+
"node_modules/string-width": {
|
| 2373 |
+
"version": "4.2.3",
|
| 2374 |
+
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
| 2375 |
+
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
| 2376 |
+
"dependencies": {
|
| 2377 |
+
"emoji-regex": "^8.0.0",
|
| 2378 |
+
"is-fullwidth-code-point": "^3.0.0",
|
| 2379 |
+
"strip-ansi": "^6.0.1"
|
| 2380 |
+
},
|
| 2381 |
+
"engines": {
|
| 2382 |
+
"node": ">=8"
|
| 2383 |
+
}
|
| 2384 |
+
},
|
| 2385 |
+
"node_modules/strip-ansi": {
|
| 2386 |
+
"version": "6.0.1",
|
| 2387 |
+
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
| 2388 |
+
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
| 2389 |
+
"dependencies": {
|
| 2390 |
+
"ansi-regex": "^5.0.1"
|
| 2391 |
+
},
|
| 2392 |
+
"engines": {
|
| 2393 |
+
"node": ">=8"
|
| 2394 |
+
}
|
| 2395 |
+
},
|
| 2396 |
+
"node_modules/tslib": {
|
| 2397 |
+
"version": "2.8.1",
|
| 2398 |
+
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
| 2399 |
+
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
|
| 2400 |
+
},
|
| 2401 |
"node_modules/typescript": {
|
| 2402 |
"version": "5.9.3",
|
| 2403 |
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
|
|
|
| 2411 |
"node": ">=14.17"
|
| 2412 |
}
|
| 2413 |
},
|
| 2414 |
+
"node_modules/undici-types": {
|
| 2415 |
+
"version": "7.18.2",
|
| 2416 |
+
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
|
| 2417 |
+
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="
|
| 2418 |
+
},
|
| 2419 |
"node_modules/update-browserslist-db": {
|
| 2420 |
"version": "1.2.3",
|
| 2421 |
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
|
|
|
|
| 2505 |
}
|
| 2506 |
}
|
| 2507 |
},
|
| 2508 |
+
"node_modules/web-vitals": {
|
| 2509 |
+
"version": "4.2.4",
|
| 2510 |
+
"resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz",
|
| 2511 |
+
"integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw=="
|
| 2512 |
+
},
|
| 2513 |
+
"node_modules/websocket-driver": {
|
| 2514 |
+
"version": "0.7.4",
|
| 2515 |
+
"resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz",
|
| 2516 |
+
"integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==",
|
| 2517 |
+
"dependencies": {
|
| 2518 |
+
"http-parser-js": ">=0.5.1",
|
| 2519 |
+
"safe-buffer": ">=5.1.0",
|
| 2520 |
+
"websocket-extensions": ">=0.1.1"
|
| 2521 |
+
},
|
| 2522 |
+
"engines": {
|
| 2523 |
+
"node": ">=0.8.0"
|
| 2524 |
+
}
|
| 2525 |
+
},
|
| 2526 |
+
"node_modules/websocket-extensions": {
|
| 2527 |
+
"version": "0.1.4",
|
| 2528 |
+
"resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz",
|
| 2529 |
+
"integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==",
|
| 2530 |
+
"engines": {
|
| 2531 |
+
"node": ">=0.8.0"
|
| 2532 |
+
}
|
| 2533 |
+
},
|
| 2534 |
+
"node_modules/wrap-ansi": {
|
| 2535 |
+
"version": "7.0.0",
|
| 2536 |
+
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
| 2537 |
+
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
| 2538 |
+
"dependencies": {
|
| 2539 |
+
"ansi-styles": "^4.0.0",
|
| 2540 |
+
"string-width": "^4.1.0",
|
| 2541 |
+
"strip-ansi": "^6.0.0"
|
| 2542 |
+
},
|
| 2543 |
+
"engines": {
|
| 2544 |
+
"node": ">=10"
|
| 2545 |
+
},
|
| 2546 |
+
"funding": {
|
| 2547 |
+
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
| 2548 |
+
}
|
| 2549 |
+
},
|
| 2550 |
+
"node_modules/y18n": {
|
| 2551 |
+
"version": "5.0.8",
|
| 2552 |
+
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
| 2553 |
+
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
|
| 2554 |
+
"engines": {
|
| 2555 |
+
"node": ">=10"
|
| 2556 |
+
}
|
| 2557 |
+
},
|
| 2558 |
"node_modules/yallist": {
|
| 2559 |
"version": "3.1.1",
|
| 2560 |
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
| 2561 |
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
| 2562 |
"dev": true
|
| 2563 |
+
},
|
| 2564 |
+
"node_modules/yargs": {
|
| 2565 |
+
"version": "17.7.2",
|
| 2566 |
+
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
|
| 2567 |
+
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
|
| 2568 |
+
"dependencies": {
|
| 2569 |
+
"cliui": "^8.0.1",
|
| 2570 |
+
"escalade": "^3.1.1",
|
| 2571 |
+
"get-caller-file": "^2.0.5",
|
| 2572 |
+
"require-directory": "^2.1.1",
|
| 2573 |
+
"string-width": "^4.2.3",
|
| 2574 |
+
"y18n": "^5.0.5",
|
| 2575 |
+
"yargs-parser": "^21.1.1"
|
| 2576 |
+
},
|
| 2577 |
+
"engines": {
|
| 2578 |
+
"node": ">=12"
|
| 2579 |
+
}
|
| 2580 |
+
},
|
| 2581 |
+
"node_modules/yargs-parser": {
|
| 2582 |
+
"version": "21.1.1",
|
| 2583 |
+
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
|
| 2584 |
+
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
|
| 2585 |
+
"engines": {
|
| 2586 |
+
"node": ">=12"
|
| 2587 |
+
}
|
| 2588 |
}
|
| 2589 |
}
|
| 2590 |
}
|
package.json
CHANGED
|
@@ -9,6 +9,7 @@
|
|
| 9 |
"preview": "vite preview"
|
| 10 |
},
|
| 11 |
"dependencies": {
|
|
|
|
| 12 |
"react": "^18.3.1",
|
| 13 |
"react-dom": "^18.3.1"
|
| 14 |
},
|
|
|
|
| 9 |
"preview": "vite preview"
|
| 10 |
},
|
| 11 |
"dependencies": {
|
| 12 |
+
"firebase": "^11.10.0",
|
| 13 |
"react": "^18.3.1",
|
| 14 |
"react-dom": "^18.3.1"
|
| 15 |
},
|
src/App.tsx
CHANGED
|
@@ -6,6 +6,7 @@ import MarketingStudioPanel from './MarketingStudioPanel';
|
|
| 6 |
import InvoiceDemoPanel from './InvoiceDemoPanel';
|
| 7 |
import MarketplaceDemoPanel from './MarketplaceDemoPanel';
|
| 8 |
import ChatFeatureDemo from './ChatFeatureDemo';
|
|
|
|
| 9 |
|
| 10 |
const TAB_DEFINITIONS: Record<DemoLocale, Array<{ id: DemoTabId; label: string; subtitle: string }>> = {
|
| 11 |
en: [
|
|
@@ -237,21 +238,37 @@ function App() {
|
|
| 237 |
setIsWorking(true);
|
| 238 |
|
| 239 |
try {
|
| 240 |
-
const result =
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
|
| 256 |
setMessages((prev) => [
|
| 257 |
...prev,
|
|
@@ -398,6 +415,7 @@ function App() {
|
|
| 398 |
|
| 399 |
{activeTab === 'voice' ? (
|
| 400 |
<VoiceDemoPanel
|
|
|
|
| 401 |
locale={locale}
|
| 402 |
onWorkingChange={setIsWorking}
|
| 403 |
onWorkflowInit={setProgressSteps}
|
|
@@ -409,6 +427,7 @@ function App() {
|
|
| 409 |
/>
|
| 410 |
) : activeTab === 'marketing' ? (
|
| 411 |
<MarketingStudioPanel
|
|
|
|
| 412 |
locale={locale}
|
| 413 |
onWorkingChange={setIsWorking}
|
| 414 |
onWorkflowInit={setProgressSteps}
|
|
@@ -420,6 +439,7 @@ function App() {
|
|
| 420 |
/>
|
| 421 |
) : activeTab === 'invoice' ? (
|
| 422 |
<InvoiceDemoPanel
|
|
|
|
| 423 |
locale={locale}
|
| 424 |
onWorkingChange={setIsWorking}
|
| 425 |
onWorkflowInit={setProgressSteps}
|
|
@@ -431,6 +451,7 @@ function App() {
|
|
| 431 |
/>
|
| 432 |
) : activeTab === 'marketplace' ? (
|
| 433 |
<MarketplaceDemoPanel
|
|
|
|
| 434 |
locale={locale}
|
| 435 |
onWorkingChange={setIsWorking}
|
| 436 |
onWorkflowInit={setProgressSteps}
|
|
|
|
| 6 |
import InvoiceDemoPanel from './InvoiceDemoPanel';
|
| 7 |
import MarketplaceDemoPanel from './MarketplaceDemoPanel';
|
| 8 |
import ChatFeatureDemo from './ChatFeatureDemo';
|
| 9 |
+
import { runLiveAgent } from './liveAgent';
|
| 10 |
|
| 11 |
const TAB_DEFINITIONS: Record<DemoLocale, Array<{ id: DemoTabId; label: string; subtitle: string }>> = {
|
| 12 |
en: [
|
|
|
|
| 238 |
setIsWorking(true);
|
| 239 |
|
| 240 |
try {
|
| 241 |
+
const result =
|
| 242 |
+
mode === 'live'
|
| 243 |
+
? await runLiveAgent({
|
| 244 |
+
tab: activeTab,
|
| 245 |
+
input: trimmed,
|
| 246 |
+
locale,
|
| 247 |
+
onWorkflowInit: (steps) => {
|
| 248 |
+
setProgressSteps(steps);
|
| 249 |
+
},
|
| 250 |
+
onStepStatus: (stepId, status, detail) => {
|
| 251 |
+
updateStepStatus(stepId, status, detail);
|
| 252 |
+
},
|
| 253 |
+
onTrace: (item) => {
|
| 254 |
+
pushTrace(item);
|
| 255 |
+
},
|
| 256 |
+
})
|
| 257 |
+
: await runMockAgent({
|
| 258 |
+
tab: activeTab,
|
| 259 |
+
input: trimmed,
|
| 260 |
+
locale,
|
| 261 |
+
mode,
|
| 262 |
+
onWorkflowInit: (steps) => {
|
| 263 |
+
setProgressSteps(steps);
|
| 264 |
+
},
|
| 265 |
+
onStepStatus: (stepId, status, detail) => {
|
| 266 |
+
updateStepStatus(stepId, status, detail);
|
| 267 |
+
},
|
| 268 |
+
onTrace: (item) => {
|
| 269 |
+
pushTrace(item);
|
| 270 |
+
},
|
| 271 |
+
});
|
| 272 |
|
| 273 |
setMessages((prev) => [
|
| 274 |
...prev,
|
|
|
|
| 415 |
|
| 416 |
{activeTab === 'voice' ? (
|
| 417 |
<VoiceDemoPanel
|
| 418 |
+
mode={mode}
|
| 419 |
locale={locale}
|
| 420 |
onWorkingChange={setIsWorking}
|
| 421 |
onWorkflowInit={setProgressSteps}
|
|
|
|
| 427 |
/>
|
| 428 |
) : activeTab === 'marketing' ? (
|
| 429 |
<MarketingStudioPanel
|
| 430 |
+
mode={mode}
|
| 431 |
locale={locale}
|
| 432 |
onWorkingChange={setIsWorking}
|
| 433 |
onWorkflowInit={setProgressSteps}
|
|
|
|
| 439 |
/>
|
| 440 |
) : activeTab === 'invoice' ? (
|
| 441 |
<InvoiceDemoPanel
|
| 442 |
+
mode={mode}
|
| 443 |
locale={locale}
|
| 444 |
onWorkingChange={setIsWorking}
|
| 445 |
onWorkflowInit={setProgressSteps}
|
|
|
|
| 451 |
/>
|
| 452 |
) : activeTab === 'marketplace' ? (
|
| 453 |
<MarketplaceDemoPanel
|
| 454 |
+
mode={mode}
|
| 455 |
locale={locale}
|
| 456 |
onWorkingChange={setIsWorking}
|
| 457 |
onWorkflowInit={setProgressSteps}
|
src/InvoiceDemoPanel.tsx
CHANGED
|
@@ -1,6 +1,8 @@
|
|
| 1 |
import { ChangeEvent, useEffect, useRef, useState } from 'react';
|
| 2 |
-
import type { DemoLocale, ProgressStep, ProgressStepStatus, TraceItem } from './types';
|
| 3 |
import InvoiceFeatureDemo from './InvoiceFeatureDemo';
|
|
|
|
|
|
|
| 4 |
|
| 5 |
type InvoiceDirection = 'sale' | 'purchase';
|
| 6 |
type InvoiceStatus = 'draft' | 'open' | 'paid' | 'overdue';
|
|
@@ -67,6 +69,7 @@ type InvoiceStepTemplate = {
|
|
| 67 |
};
|
| 68 |
|
| 69 |
type InvoiceDemoPanelProps = {
|
|
|
|
| 70 |
locale: DemoLocale;
|
| 71 |
onWorkingChange: (working: boolean) => void;
|
| 72 |
onWorkflowInit: (steps: ProgressStep[]) => void;
|
|
@@ -101,6 +104,7 @@ const COPY = {
|
|
| 101 |
outputTitle: 'Extraction Output',
|
| 102 |
outputSubtitle: 'JSON + human-friendly invoice preview',
|
| 103 |
emptyOutput: 'Upload or run a synthetic scan to view extracted invoice fields.',
|
|
|
|
| 104 |
stoppedByUser: 'Invoice scan was stopped by user.',
|
| 105 |
recordCreated: 'Draft invoice record created in demo history.',
|
| 106 |
reviewReady: 'Extraction completed. Review fields before creating a record.',
|
|
@@ -166,6 +170,7 @@ const COPY = {
|
|
| 166 |
outputTitle: '擷取結果',
|
| 167 |
outputSubtitle: 'JSON 與可閱讀發票預覽',
|
| 168 |
emptyOutput: '可先上傳圖片,或直接執行模擬掃描查看欄位結果。',
|
|
|
|
| 169 |
stoppedByUser: '掃描流程已由使用者停止。',
|
| 170 |
recordCreated: '已在示範歷史中建立草稿發票。',
|
| 171 |
reviewReady: '欄位擷取完成,請先檢查後再建立紀錄。',
|
|
@@ -487,7 +492,47 @@ function buildMockExtraction(locale: DemoLocale, role: WorkspaceRole, note: stri
|
|
| 487 |
};
|
| 488 |
}
|
| 489 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 490 |
export default function InvoiceDemoPanel({
|
|
|
|
| 491 |
locale,
|
| 492 |
onWorkingChange,
|
| 493 |
onWorkflowInit,
|
|
@@ -578,8 +623,8 @@ export default function InvoiceDemoPanel({
|
|
| 578 |
const runExtraction = async (forcedNote?: string) => {
|
| 579 |
const activeNote = (forcedNote ?? scanNote).trim();
|
| 580 |
const source = uploadedFile?.name || (locale === 'zh-TW' ? '模擬發票樣本' : 'Synthetic invoice sample');
|
| 581 |
-
const
|
| 582 |
-
const
|
| 583 |
const runToken = runTokenRef.current + 1;
|
| 584 |
runTokenRef.current = runToken;
|
| 585 |
const startedAtMs = Date.now();
|
|
@@ -616,6 +661,92 @@ export default function InvoiceDemoPanel({
|
|
| 616 |
);
|
| 617 |
|
| 618 |
try {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 619 |
setJobState(jobId, { status: 'scanning', progress: 16 });
|
| 620 |
const sourceReady = await runStep(runToken, steps[0], {
|
| 621 |
source: uploadedFile ? uploadedFile.mimeType : 'synthetic',
|
|
@@ -666,12 +797,12 @@ export default function InvoiceDemoPanel({
|
|
| 666 |
if (!validated) return;
|
| 667 |
|
| 668 |
const packaged = await runStep(runToken, steps[4], {
|
| 669 |
-
createRecord,
|
| 670 |
invoiceNumber: payload.invoice.invoiceNumber,
|
| 671 |
});
|
| 672 |
if (!packaged) return;
|
| 673 |
|
| 674 |
-
if (
|
| 675 |
const record: InvoiceRecord = {
|
| 676 |
id: makeId('record'),
|
| 677 |
invoiceNumber: payload.invoice.invoiceNumber,
|
|
@@ -763,6 +894,12 @@ export default function InvoiceDemoPanel({
|
|
| 763 |
};
|
| 764 |
}, []);
|
| 765 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 766 |
const balance = extraction ? extraction.invoice.total - extraction.invoice.paid : 0;
|
| 767 |
|
| 768 |
return (
|
|
@@ -834,7 +971,7 @@ export default function InvoiceDemoPanel({
|
|
| 834 |
type="checkbox"
|
| 835 |
checked={createRecord}
|
| 836 |
onChange={(event) => setCreateRecord(event.target.checked)}
|
| 837 |
-
disabled={isScanning}
|
| 838 |
/>
|
| 839 |
<span>{copy.createRecord}</span>
|
| 840 |
</label>
|
|
|
|
| 1 |
import { ChangeEvent, useEffect, useRef, useState } from 'react';
|
| 2 |
+
import type { DemoLocale, DemoMode, ProgressStep, ProgressStepStatus, TraceItem } from './types';
|
| 3 |
import InvoiceFeatureDemo from './InvoiceFeatureDemo';
|
| 4 |
+
import { parseDataUrl, scanInvoiceImage } from './liveApi';
|
| 5 |
+
import type { LiveInvoiceExtraction } from './liveApi';
|
| 6 |
|
| 7 |
type InvoiceDirection = 'sale' | 'purchase';
|
| 8 |
type InvoiceStatus = 'draft' | 'open' | 'paid' | 'overdue';
|
|
|
|
| 69 |
};
|
| 70 |
|
| 71 |
type InvoiceDemoPanelProps = {
|
| 72 |
+
mode: DemoMode;
|
| 73 |
locale: DemoLocale;
|
| 74 |
onWorkingChange: (working: boolean) => void;
|
| 75 |
onWorkflowInit: (steps: ProgressStep[]) => void;
|
|
|
|
| 104 |
outputTitle: 'Extraction Output',
|
| 105 |
outputSubtitle: 'JSON + human-friendly invoice preview',
|
| 106 |
emptyOutput: 'Upload or run a synthetic scan to view extracted invoice fields.',
|
| 107 |
+
liveUploadRequired: 'Live mode requires an uploaded invoice image.',
|
| 108 |
stoppedByUser: 'Invoice scan was stopped by user.',
|
| 109 |
recordCreated: 'Draft invoice record created in demo history.',
|
| 110 |
reviewReady: 'Extraction completed. Review fields before creating a record.',
|
|
|
|
| 170 |
outputTitle: '擷取結果',
|
| 171 |
outputSubtitle: 'JSON 與可閱讀發票預覽',
|
| 172 |
emptyOutput: '可先上傳圖片,或直接執行模擬掃描查看欄位結果。',
|
| 173 |
+
liveUploadRequired: '即時模式需要先上傳發票圖片。',
|
| 174 |
stoppedByUser: '掃描流程已由使用者停止。',
|
| 175 |
recordCreated: '已在示範歷史中建立草稿發票。',
|
| 176 |
reviewReady: '欄位擷取完成,請先檢查後再建立紀錄。',
|
|
|
|
| 492 |
};
|
| 493 |
}
|
| 494 |
|
| 495 |
+
function toNumber(value: unknown, fallback = 0): number {
|
| 496 |
+
return typeof value === 'number' && Number.isFinite(value) ? value : fallback;
|
| 497 |
+
}
|
| 498 |
+
|
| 499 |
+
function normalizeLiveExtraction(payload: LiveInvoiceExtraction | undefined, locale: DemoLocale): InvoiceExtraction {
|
| 500 |
+
const invoice = payload?.invoice || {};
|
| 501 |
+
const items = Array.isArray(invoice.items) ? invoice.items : [];
|
| 502 |
+
|
| 503 |
+
return {
|
| 504 |
+
provider: payload?.provider === 'gemini' || payload?.provider === 'mock' ? payload.provider : 'gemini',
|
| 505 |
+
model: payload?.model || (locale === 'zh-TW' ? 'gemini(live)' : 'gemini (live)'),
|
| 506 |
+
confidence: toNumber(payload?.confidence, 0.5),
|
| 507 |
+
warnings: Array.isArray(payload?.warnings) ? payload.warnings.map((item) => String(item)) : [],
|
| 508 |
+
rawText: typeof payload?.rawText === 'string' ? payload.rawText : '',
|
| 509 |
+
invoice: {
|
| 510 |
+
direction: invoice.direction === 'purchase' ? 'purchase' : 'sale',
|
| 511 |
+
status:
|
| 512 |
+
invoice.status === 'open' || invoice.status === 'paid' || invoice.status === 'overdue'
|
| 513 |
+
? invoice.status
|
| 514 |
+
: 'draft',
|
| 515 |
+
invoiceNumber: typeof invoice.invoiceNumber === 'string' ? invoice.invoiceNumber : '',
|
| 516 |
+
counterpartyName: typeof invoice.counterpartyName === 'string' ? invoice.counterpartyName : '',
|
| 517 |
+
description: typeof invoice.description === 'string' ? invoice.description : '',
|
| 518 |
+
currency: typeof invoice.currency === 'string' ? invoice.currency : 'TWD',
|
| 519 |
+
total: toNumber(invoice.total),
|
| 520 |
+
paid: toNumber(invoice.paid),
|
| 521 |
+
issueDate: typeof invoice.issueDate === 'string' ? invoice.issueDate : '',
|
| 522 |
+
dueDate: typeof invoice.dueDate === 'string' ? invoice.dueDate : '',
|
| 523 |
+
deliveryDate: typeof invoice.deliveryDate === 'string' ? invoice.deliveryDate : '',
|
| 524 |
+
items: items.map((item) => ({
|
| 525 |
+
description: typeof item.description === 'string' ? item.description : '',
|
| 526 |
+
quantity: toNumber(item.quantity),
|
| 527 |
+
unitPrice: toNumber(item.unitPrice),
|
| 528 |
+
lineTotal: toNumber(item.lineTotal),
|
| 529 |
+
})),
|
| 530 |
+
},
|
| 531 |
+
};
|
| 532 |
+
}
|
| 533 |
+
|
| 534 |
export default function InvoiceDemoPanel({
|
| 535 |
+
mode,
|
| 536 |
locale,
|
| 537 |
onWorkingChange,
|
| 538 |
onWorkflowInit,
|
|
|
|
| 623 |
const runExtraction = async (forcedNote?: string) => {
|
| 624 |
const activeNote = (forcedNote ?? scanNote).trim();
|
| 625 |
const source = uploadedFile?.name || (locale === 'zh-TW' ? '模擬發票樣本' : 'Synthetic invoice sample');
|
| 626 |
+
const liveCreateRecord = mode === 'live' ? false : createRecord;
|
| 627 |
+
const steps = buildSteps(locale, liveCreateRecord);
|
| 628 |
const runToken = runTokenRef.current + 1;
|
| 629 |
runTokenRef.current = runToken;
|
| 630 |
const startedAtMs = Date.now();
|
|
|
|
| 661 |
);
|
| 662 |
|
| 663 |
try {
|
| 664 |
+
if (mode === 'live') {
|
| 665 |
+
if (!uploadedFile) {
|
| 666 |
+
throw new Error(copy.liveUploadRequired);
|
| 667 |
+
}
|
| 668 |
+
|
| 669 |
+
setJobState(jobId, { status: 'scanning', progress: 16 });
|
| 670 |
+
const sourceReady = await runStep(runToken, steps[0], {
|
| 671 |
+
source: uploadedFile.mimeType,
|
| 672 |
+
});
|
| 673 |
+
if (!sourceReady) return;
|
| 674 |
+
|
| 675 |
+
const ocrStep = steps[1];
|
| 676 |
+
currentStepRef.current = ocrStep.id;
|
| 677 |
+
onStepStatus(ocrStep.id, 'in_progress', ocrStep.runningDetail);
|
| 678 |
+
onTrace(makeTrace(ocrStep.kind, ocrStep.label, ocrStep.runningDetail, 'running'));
|
| 679 |
+
|
| 680 |
+
const imagePayload = parseDataUrl(uploadedFile.previewUrl);
|
| 681 |
+
const response = await scanInvoiceImage({
|
| 682 |
+
dataBase64: imagePayload.base64,
|
| 683 |
+
mimeType: imagePayload.mimeType,
|
| 684 |
+
locale,
|
| 685 |
+
createInvoice: liveCreateRecord,
|
| 686 |
+
});
|
| 687 |
+
const payload = normalizeLiveExtraction(response.extraction, locale);
|
| 688 |
+
if (!mountedRef.current || runTokenRef.current !== runToken) return;
|
| 689 |
+
|
| 690 |
+
const chunks = splitTextChunks(payload.rawText || '');
|
| 691 |
+
for (let i = 0; i < chunks.length; i += 1) {
|
| 692 |
+
if (!mountedRef.current || runTokenRef.current !== runToken) return;
|
| 693 |
+
setRawTextStream((prev) => prev + chunks[i]);
|
| 694 |
+
if (i % 5 === 0) {
|
| 695 |
+
onTrace(
|
| 696 |
+
makeTrace(
|
| 697 |
+
'tool',
|
| 698 |
+
locale === 'zh-TW' ? 'OCR 片段' : 'OCR chunk',
|
| 699 |
+
locale === 'zh-TW' ? `已輸出第 ${i + 1} 段` : `Chunk ${i + 1} streamed`,
|
| 700 |
+
'ok'
|
| 701 |
+
)
|
| 702 |
+
);
|
| 703 |
+
}
|
| 704 |
+
await sleep(55);
|
| 705 |
+
}
|
| 706 |
+
|
| 707 |
+
onStepStatus(ocrStep.id, 'completed', ocrStep.doneDetail);
|
| 708 |
+
onTrace(makeTrace(ocrStep.kind, ocrStep.label, ocrStep.doneDetail, 'ok', { chars: payload.rawText.length }));
|
| 709 |
+
|
| 710 |
+
setJobState(jobId, { status: 'extracting', progress: 62 });
|
| 711 |
+
const extracted = await runStep(runToken, steps[2], {
|
| 712 |
+
fields: 12,
|
| 713 |
+
items: payload.invoice.items.length,
|
| 714 |
+
});
|
| 715 |
+
if (!extracted) return;
|
| 716 |
+
setExtraction(payload);
|
| 717 |
+
|
| 718 |
+
setJobState(jobId, { status: 'validating', progress: 82 });
|
| 719 |
+
const validated = await runStep(runToken, steps[3], {
|
| 720 |
+
total: payload.invoice.total,
|
| 721 |
+
paid: payload.invoice.paid,
|
| 722 |
+
warnings: payload.warnings.length,
|
| 723 |
+
});
|
| 724 |
+
if (!validated) return;
|
| 725 |
+
|
| 726 |
+
const packaged = await runStep(runToken, steps[4], {
|
| 727 |
+
createRecord: liveCreateRecord,
|
| 728 |
+
invoiceNumber: payload.invoice.invoiceNumber,
|
| 729 |
+
mode: 'live',
|
| 730 |
+
});
|
| 731 |
+
if (!packaged) return;
|
| 732 |
+
|
| 733 |
+
setNote(copy.reviewReady);
|
| 734 |
+
setJobState(jobId, {
|
| 735 |
+
status: 'ready',
|
| 736 |
+
progress: 100,
|
| 737 |
+
invoiceNumber: payload.invoice.invoiceNumber,
|
| 738 |
+
});
|
| 739 |
+
setMeta({
|
| 740 |
+
startedAt: new Date(startedAtMs).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }),
|
| 741 |
+
durationMs: Date.now() - startedAtMs,
|
| 742 |
+
model: payload.model,
|
| 743 |
+
provider: payload.provider,
|
| 744 |
+
source,
|
| 745 |
+
});
|
| 746 |
+
return;
|
| 747 |
+
}
|
| 748 |
+
|
| 749 |
+
const payload = buildMockExtraction(locale, role, activeNote);
|
| 750 |
setJobState(jobId, { status: 'scanning', progress: 16 });
|
| 751 |
const sourceReady = await runStep(runToken, steps[0], {
|
| 752 |
source: uploadedFile ? uploadedFile.mimeType : 'synthetic',
|
|
|
|
| 797 |
if (!validated) return;
|
| 798 |
|
| 799 |
const packaged = await runStep(runToken, steps[4], {
|
| 800 |
+
createRecord: liveCreateRecord,
|
| 801 |
invoiceNumber: payload.invoice.invoiceNumber,
|
| 802 |
});
|
| 803 |
if (!packaged) return;
|
| 804 |
|
| 805 |
+
if (liveCreateRecord) {
|
| 806 |
const record: InvoiceRecord = {
|
| 807 |
id: makeId('record'),
|
| 808 |
invoiceNumber: payload.invoice.invoiceNumber,
|
|
|
|
| 894 |
};
|
| 895 |
}, []);
|
| 896 |
|
| 897 |
+
useEffect(() => {
|
| 898 |
+
if (mode === 'live') {
|
| 899 |
+
setCreateRecord(false);
|
| 900 |
+
}
|
| 901 |
+
}, [mode]);
|
| 902 |
+
|
| 903 |
const balance = extraction ? extraction.invoice.total - extraction.invoice.paid : 0;
|
| 904 |
|
| 905 |
return (
|
|
|
|
| 971 |
type="checkbox"
|
| 972 |
checked={createRecord}
|
| 973 |
onChange={(event) => setCreateRecord(event.target.checked)}
|
| 974 |
+
disabled={isScanning || mode === 'live'}
|
| 975 |
/>
|
| 976 |
<span>{copy.createRecord}</span>
|
| 977 |
</label>
|
src/MarketingStudioPanel.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
import { ChangeEvent, useEffect, useMemo, useRef, useState } from 'react';
|
| 2 |
-
import type { DemoLocale, ProgressStep, ProgressStepStatus, TraceItem } from './types';
|
| 3 |
import MarketingFeatureDemo from './MarketingFeatureDemo';
|
|
|
|
| 4 |
|
| 5 |
type MarketingStepTemplate = {
|
| 6 |
id: string;
|
|
@@ -37,6 +38,7 @@ type GeneratedImage = {
|
|
| 37 |
};
|
| 38 |
|
| 39 |
type MarketingStudioPanelProps = {
|
|
|
|
| 40 |
locale: DemoLocale;
|
| 41 |
onWorkingChange: (working: boolean) => void;
|
| 42 |
onWorkflowInit: (steps: ProgressStep[]) => void;
|
|
@@ -337,6 +339,59 @@ function deckToMarkdown(locale: DemoLocale, deck: MarketingDeck): string {
|
|
| 337 |
].join('\n');
|
| 338 |
}
|
| 339 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 340 |
function makePalette(seed: number): [string, string, string] {
|
| 341 |
const palettes: Array<[string, string, string]> = [
|
| 342 |
['#1f8f4e', '#7dcf9b', '#edf8e6'],
|
|
@@ -409,6 +464,7 @@ function generateMockImageDataUrl(product: MarketingProduct, deck: MarketingDeck
|
|
| 409 |
}
|
| 410 |
|
| 411 |
export default function MarketingStudioPanel({
|
|
|
|
| 412 |
locale,
|
| 413 |
onWorkingChange,
|
| 414 |
onWorkflowInit,
|
|
@@ -437,6 +493,7 @@ export default function MarketingStudioPanel({
|
|
| 437 |
const [note, setNote] = useState<string | null>(null);
|
| 438 |
|
| 439 |
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
|
|
|
| 440 |
const runTokenRef = useRef(0);
|
| 441 |
const mountedRef = useRef(true);
|
| 442 |
const currentStepRef = useRef<string | null>(null);
|
|
@@ -471,6 +528,8 @@ export default function MarketingStudioPanel({
|
|
| 471 |
const stopGeneration = () => {
|
| 472 |
if (!isGenerating) return;
|
| 473 |
runTokenRef.current += 1;
|
|
|
|
|
|
|
| 474 |
if (currentStepRef.current) {
|
| 475 |
onStepStatus(currentStepRef.current, 'error', copy.stoppedByUser);
|
| 476 |
}
|
|
@@ -531,6 +590,122 @@ export default function MarketingStudioPanel({
|
|
| 531 |
});
|
| 532 |
if (!imageReady) return;
|
| 533 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 534 |
const deckPayload = createDeck(locale, selectedProduct, activePrompt, tone, channel);
|
| 535 |
const markdown = deckToMarkdown(locale, deckPayload);
|
| 536 |
const chunks = splitTextChunks(markdown);
|
|
@@ -602,7 +777,12 @@ export default function MarketingStudioPanel({
|
|
| 602 |
durationMs: Date.now() - startedAtMs,
|
| 603 |
});
|
| 604 |
} catch (runtimeError) {
|
| 605 |
-
const message =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 606 |
setError(message);
|
| 607 |
if (currentStepRef.current) {
|
| 608 |
onStepStatus(currentStepRef.current, 'error', message);
|
|
@@ -610,6 +790,7 @@ export default function MarketingStudioPanel({
|
|
| 610 |
onTrace(makeTrace('tool', locale === 'zh-TW' ? '生成錯誤' : 'Generation error', message, 'error'));
|
| 611 |
} finally {
|
| 612 |
if (runTokenRef.current === runToken) {
|
|
|
|
| 613 |
setIsGenerating(false);
|
| 614 |
onWorkingChange(false);
|
| 615 |
}
|
|
@@ -674,6 +855,8 @@ export default function MarketingStudioPanel({
|
|
| 674 |
return () => {
|
| 675 |
mountedRef.current = false;
|
| 676 |
runTokenRef.current += 1;
|
|
|
|
|
|
|
| 677 |
onWorkingChange(false);
|
| 678 |
};
|
| 679 |
}, []);
|
|
|
|
| 1 |
import { ChangeEvent, useEffect, useMemo, useRef, useState } from 'react';
|
| 2 |
+
import type { DemoLocale, DemoMode, ProgressStep, ProgressStepStatus, TraceItem } from './types';
|
| 3 |
import MarketingFeatureDemo from './MarketingFeatureDemo';
|
| 4 |
+
import { parseDataUrl, streamMarketingGeneration } from './liveApi';
|
| 5 |
|
| 6 |
type MarketingStepTemplate = {
|
| 7 |
id: string;
|
|
|
|
| 38 |
};
|
| 39 |
|
| 40 |
type MarketingStudioPanelProps = {
|
| 41 |
+
mode: DemoMode;
|
| 42 |
locale: DemoLocale;
|
| 43 |
onWorkingChange: (working: boolean) => void;
|
| 44 |
onWorkflowInit: (steps: ProgressStep[]) => void;
|
|
|
|
| 339 |
].join('\n');
|
| 340 |
}
|
| 341 |
|
| 342 |
+
function parseDeckFromLiveText(locale: DemoLocale, text: string): MarketingDeck {
|
| 343 |
+
const fallback = locale === 'zh-TW'
|
| 344 |
+
? {
|
| 345 |
+
headline: '已生成行銷內容',
|
| 346 |
+
caption: text.trim().slice(0, 220) || '已從後端串流取得文案結果。',
|
| 347 |
+
cta: '立即查看並套用',
|
| 348 |
+
hashtags: ['#Farm2Market'],
|
| 349 |
+
designNotes: ['已使用後端即時生成結果。'],
|
| 350 |
+
}
|
| 351 |
+
: {
|
| 352 |
+
headline: 'Marketing content generated',
|
| 353 |
+
caption: text.trim().slice(0, 220) || 'Live backend copy has been streamed.',
|
| 354 |
+
cta: 'Review and apply now',
|
| 355 |
+
hashtags: ['#Farm2Market'],
|
| 356 |
+
designNotes: ['Generated from live backend stream.'],
|
| 357 |
+
};
|
| 358 |
+
|
| 359 |
+
const normalized = text
|
| 360 |
+
.split('\n')
|
| 361 |
+
.map((line) => line.trim())
|
| 362 |
+
.filter(Boolean);
|
| 363 |
+
|
| 364 |
+
if (!normalized.length) return fallback;
|
| 365 |
+
|
| 366 |
+
const hashTags = Array.from(
|
| 367 |
+
new Set(
|
| 368 |
+
(text.match(/#[\p{L}\p{N}_-]+/gu) || [])
|
| 369 |
+
.map((tag) => tag.trim())
|
| 370 |
+
.filter(Boolean)
|
| 371 |
+
)
|
| 372 |
+
);
|
| 373 |
+
|
| 374 |
+
const headline = normalized[0]?.replace(/^#+\s*/, '') || fallback.headline;
|
| 375 |
+
const caption = normalized.slice(1, 4).join(' ') || fallback.caption;
|
| 376 |
+
|
| 377 |
+
const ctaLine = normalized.find((line) =>
|
| 378 |
+
locale === 'zh-TW' ? /(立即|現在|下單|預購|CTA|行動)/.test(line) : /(cta|order|buy now|reserve|shop)/i.test(line)
|
| 379 |
+
);
|
| 380 |
+
|
| 381 |
+
const noteLines = normalized
|
| 382 |
+
.filter((line) => line.startsWith('-') || line.startsWith('•'))
|
| 383 |
+
.map((line) => line.replace(/^[-•]\s*/, ''))
|
| 384 |
+
.slice(0, 3);
|
| 385 |
+
|
| 386 |
+
return {
|
| 387 |
+
headline,
|
| 388 |
+
caption,
|
| 389 |
+
cta: ctaLine || fallback.cta,
|
| 390 |
+
hashtags: hashTags.length ? hashTags.slice(0, 6) : fallback.hashtags,
|
| 391 |
+
designNotes: noteLines.length ? noteLines : fallback.designNotes,
|
| 392 |
+
};
|
| 393 |
+
}
|
| 394 |
+
|
| 395 |
function makePalette(seed: number): [string, string, string] {
|
| 396 |
const palettes: Array<[string, string, string]> = [
|
| 397 |
['#1f8f4e', '#7dcf9b', '#edf8e6'],
|
|
|
|
| 464 |
}
|
| 465 |
|
| 466 |
export default function MarketingStudioPanel({
|
| 467 |
+
mode,
|
| 468 |
locale,
|
| 469 |
onWorkingChange,
|
| 470 |
onWorkflowInit,
|
|
|
|
| 493 |
const [note, setNote] = useState<string | null>(null);
|
| 494 |
|
| 495 |
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
| 496 |
+
const abortRef = useRef<AbortController | null>(null);
|
| 497 |
const runTokenRef = useRef(0);
|
| 498 |
const mountedRef = useRef(true);
|
| 499 |
const currentStepRef = useRef<string | null>(null);
|
|
|
|
| 528 |
const stopGeneration = () => {
|
| 529 |
if (!isGenerating) return;
|
| 530 |
runTokenRef.current += 1;
|
| 531 |
+
abortRef.current?.abort();
|
| 532 |
+
abortRef.current = null;
|
| 533 |
if (currentStepRef.current) {
|
| 534 |
onStepStatus(currentStepRef.current, 'error', copy.stoppedByUser);
|
| 535 |
}
|
|
|
|
| 590 |
});
|
| 591 |
if (!imageReady) return;
|
| 592 |
|
| 593 |
+
if (mode === 'live') {
|
| 594 |
+
const textStep = steps[2];
|
| 595 |
+
const imageStep = steps[3];
|
| 596 |
+
const controller = new AbortController();
|
| 597 |
+
abortRef.current = controller;
|
| 598 |
+
currentStepRef.current = textStep.id;
|
| 599 |
+
onStepStatus(textStep.id, 'in_progress', textStep.runningDetail);
|
| 600 |
+
onTrace(makeTrace(textStep.kind, textStep.label, textStep.runningDetail, 'running'));
|
| 601 |
+
|
| 602 |
+
let imageStepStarted = false;
|
| 603 |
+
let streamedImages = 0;
|
| 604 |
+
let streamedText = '';
|
| 605 |
+
|
| 606 |
+
await streamMarketingGeneration({
|
| 607 |
+
prompt: activePrompt,
|
| 608 |
+
locale,
|
| 609 |
+
product: {
|
| 610 |
+
name: selectedProduct.name,
|
| 611 |
+
category: selectedProduct.category,
|
| 612 |
+
description: selectedProduct.name,
|
| 613 |
+
},
|
| 614 |
+
image: uploadedImage ? parseDataUrl(uploadedImage.previewUrl) : undefined,
|
| 615 |
+
signal: controller.signal,
|
| 616 |
+
onEvent: (event) => {
|
| 617 |
+
if (!mountedRef.current || runTokenRef.current !== runToken) return;
|
| 618 |
+
|
| 619 |
+
if (event.type === 'text') {
|
| 620 |
+
streamedText += event.text;
|
| 621 |
+
setStreamText((prev) => prev + event.text);
|
| 622 |
+
return;
|
| 623 |
+
}
|
| 624 |
+
|
| 625 |
+
if (event.type === 'image') {
|
| 626 |
+
streamedImages += 1;
|
| 627 |
+
if (!imageStepStarted) {
|
| 628 |
+
imageStepStarted = true;
|
| 629 |
+
currentStepRef.current = imageStep.id;
|
| 630 |
+
onStepStatus(imageStep.id, 'in_progress', imageStep.runningDetail);
|
| 631 |
+
onTrace(makeTrace(imageStep.kind, imageStep.label, imageStep.runningDetail, 'running'));
|
| 632 |
+
}
|
| 633 |
+
const dataUrl = `data:${event.mimeType || 'image/png'};base64,${event.base64}`;
|
| 634 |
+
setImages((prev) => [
|
| 635 |
+
...prev,
|
| 636 |
+
{
|
| 637 |
+
id: makeId('img'),
|
| 638 |
+
dataUrl,
|
| 639 |
+
base64: event.base64,
|
| 640 |
+
mimeType: event.mimeType || 'image/png',
|
| 641 |
+
label:
|
| 642 |
+
locale === 'zh-TW'
|
| 643 |
+
? streamedImages === 1
|
| 644 |
+
? '主視覺'
|
| 645 |
+
: `社群版型 ${streamedImages - 1}`
|
| 646 |
+
: streamedImages === 1
|
| 647 |
+
? 'Hero visual'
|
| 648 |
+
: `Social variation ${streamedImages - 1}`,
|
| 649 |
+
},
|
| 650 |
+
]);
|
| 651 |
+
onTrace(
|
| 652 |
+
makeTrace(
|
| 653 |
+
'tool',
|
| 654 |
+
locale === 'zh-TW' ? '圖片片段' : 'Image chunk',
|
| 655 |
+
locale === 'zh-TW' ? `已接收第 ${streamedImages} 張圖片` : `Received image ${streamedImages}`,
|
| 656 |
+
'ok'
|
| 657 |
+
)
|
| 658 |
+
);
|
| 659 |
+
return;
|
| 660 |
+
}
|
| 661 |
+
|
| 662 |
+
if (event.type === 'error') {
|
| 663 |
+
throw new Error(event.message || copy.streamError);
|
| 664 |
+
}
|
| 665 |
+
},
|
| 666 |
+
});
|
| 667 |
+
|
| 668 |
+
if (!mountedRef.current || runTokenRef.current !== runToken) return;
|
| 669 |
+
abortRef.current = null;
|
| 670 |
+
|
| 671 |
+
onStepStatus(textStep.id, 'completed', textStep.doneDetail);
|
| 672 |
+
onTrace(makeTrace(textStep.kind, textStep.label, textStep.doneDetail, 'ok', { chars: streamedText.length }));
|
| 673 |
+
|
| 674 |
+
if (imageStepStarted) {
|
| 675 |
+
onStepStatus(imageStep.id, 'completed', imageStep.doneDetail);
|
| 676 |
+
onTrace(makeTrace(imageStep.kind, imageStep.label, imageStep.doneDetail, 'ok', { images: streamedImages }));
|
| 677 |
+
} else {
|
| 678 |
+
onStepStatus(imageStep.id, 'completed', locale === 'zh-TW' ? '本次未回傳圖片。' : 'No image chunks in this run.');
|
| 679 |
+
onTrace(
|
| 680 |
+
makeTrace(
|
| 681 |
+
imageStep.kind,
|
| 682 |
+
imageStep.label,
|
| 683 |
+
locale === 'zh-TW' ? '本次只回傳文字。' : 'Text-only response returned.',
|
| 684 |
+
'ok'
|
| 685 |
+
)
|
| 686 |
+
);
|
| 687 |
+
}
|
| 688 |
+
|
| 689 |
+
const finalDeck = parseDeckFromLiveText(locale, streamedText);
|
| 690 |
+
setDeck(finalDeck);
|
| 691 |
+
|
| 692 |
+
const packaged = await runStep(runToken, steps[4], {
|
| 693 |
+
output: {
|
| 694 |
+
text: true,
|
| 695 |
+
images: streamedImages,
|
| 696 |
+
mode: 'live',
|
| 697 |
+
},
|
| 698 |
+
});
|
| 699 |
+
if (!packaged) return;
|
| 700 |
+
|
| 701 |
+
setMeta({
|
| 702 |
+
model: 'gemini-2.5-flash-image (live)',
|
| 703 |
+
startedAt: new Date(startedAtMs).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }),
|
| 704 |
+
durationMs: Date.now() - startedAtMs,
|
| 705 |
+
});
|
| 706 |
+
return;
|
| 707 |
+
}
|
| 708 |
+
|
| 709 |
const deckPayload = createDeck(locale, selectedProduct, activePrompt, tone, channel);
|
| 710 |
const markdown = deckToMarkdown(locale, deckPayload);
|
| 711 |
const chunks = splitTextChunks(markdown);
|
|
|
|
| 777 |
durationMs: Date.now() - startedAtMs,
|
| 778 |
});
|
| 779 |
} catch (runtimeError) {
|
| 780 |
+
const message =
|
| 781 |
+
runtimeError instanceof Error
|
| 782 |
+
? runtimeError.name === 'AbortError'
|
| 783 |
+
? copy.stoppedByUser
|
| 784 |
+
: runtimeError.message
|
| 785 |
+
: copy.streamError;
|
| 786 |
setError(message);
|
| 787 |
if (currentStepRef.current) {
|
| 788 |
onStepStatus(currentStepRef.current, 'error', message);
|
|
|
|
| 790 |
onTrace(makeTrace('tool', locale === 'zh-TW' ? '生成錯誤' : 'Generation error', message, 'error'));
|
| 791 |
} finally {
|
| 792 |
if (runTokenRef.current === runToken) {
|
| 793 |
+
abortRef.current = null;
|
| 794 |
setIsGenerating(false);
|
| 795 |
onWorkingChange(false);
|
| 796 |
}
|
|
|
|
| 855 |
return () => {
|
| 856 |
mountedRef.current = false;
|
| 857 |
runTokenRef.current += 1;
|
| 858 |
+
abortRef.current?.abort();
|
| 859 |
+
abortRef.current = null;
|
| 860 |
onWorkingChange(false);
|
| 861 |
};
|
| 862 |
}, []);
|
src/MarketplaceDemoPanel.tsx
CHANGED
|
@@ -1,6 +1,8 @@
|
|
| 1 |
import { useEffect, useMemo, useRef, useState } from 'react';
|
| 2 |
-
import type { DemoLocale, ProgressStep, ProgressStepStatus, TraceItem } from './types';
|
| 3 |
import MarketplaceFeatureDemo from './MarketplaceFeatureDemo';
|
|
|
|
|
|
|
| 4 |
|
| 5 |
type MarketplaceMode = 'hybrid' | 'products' | 'stores' | 'stats';
|
| 6 |
|
|
@@ -50,6 +52,7 @@ type MarketplaceStepTemplate = {
|
|
| 50 |
};
|
| 51 |
|
| 52 |
type MarketplaceDemoPanelProps = {
|
|
|
|
| 53 |
locale: DemoLocale;
|
| 54 |
onWorkingChange: (working: boolean) => void;
|
| 55 |
onWorkflowInit: (steps: ProgressStep[]) => void;
|
|
@@ -322,6 +325,71 @@ function computeMarketplaceStats(products: ProductRecord[], stores: StoreRecord[
|
|
| 322 |
};
|
| 323 |
}
|
| 324 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 325 |
function scoreProduct(
|
| 326 |
record: ProductRecord,
|
| 327 |
queryTokens: string[],
|
|
@@ -575,6 +643,7 @@ const COPY = {
|
|
| 575 |
} as const;
|
| 576 |
|
| 577 |
export default function MarketplaceDemoPanel({
|
|
|
|
| 578 |
locale,
|
| 579 |
onWorkingChange,
|
| 580 |
onWorkflowInit,
|
|
@@ -705,6 +774,132 @@ export default function MarketplaceDemoPanel({
|
|
| 705 |
});
|
| 706 |
if (!parsed) return;
|
| 707 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 708 |
const productStep = steps.find((step) => step.id === 'market-step-2');
|
| 709 |
if (productStep) {
|
| 710 |
toolCalls.push('product_search_public');
|
|
|
|
| 1 |
import { useEffect, useMemo, useRef, useState } from 'react';
|
| 2 |
+
import type { DemoLocale, DemoMode, ProgressStep, ProgressStepStatus, TraceItem } from './types';
|
| 3 |
import MarketplaceFeatureDemo from './MarketplaceFeatureDemo';
|
| 4 |
+
import { callVoiceTool } from './liveApi';
|
| 5 |
+
import type { LiveVoiceToolUi } from './liveApi';
|
| 6 |
|
| 7 |
type MarketplaceMode = 'hybrid' | 'products' | 'stores' | 'stats';
|
| 8 |
|
|
|
|
| 52 |
};
|
| 53 |
|
| 54 |
type MarketplaceDemoPanelProps = {
|
| 55 |
+
mode: DemoMode;
|
| 56 |
locale: DemoLocale;
|
| 57 |
onWorkingChange: (working: boolean) => void;
|
| 58 |
onWorkflowInit: (steps: ProgressStep[]) => void;
|
|
|
|
| 325 |
};
|
| 326 |
}
|
| 327 |
|
| 328 |
+
function toRankedProductsFromUi(ui: LiveVoiceToolUi | undefined, limit: number): RankedProduct[] {
|
| 329 |
+
const rows = Array.isArray(ui?.items) ? ui.items : [];
|
| 330 |
+
return rows.slice(0, limit).map((item, index) => {
|
| 331 |
+
const row = typeof item === 'object' && item ? (item as Record<string, unknown>) : {};
|
| 332 |
+
return {
|
| 333 |
+
id: typeof row.id === 'string' ? row.id : `live-product-${index}`,
|
| 334 |
+
name: typeof row.name === 'string' ? row.name : `Product ${index + 1}`,
|
| 335 |
+
category: typeof row.category === 'string' ? row.category : 'General',
|
| 336 |
+
price: typeof row.price === 'number' ? row.price : 0,
|
| 337 |
+
unit: typeof row.unit === 'string' ? row.unit : 'unit',
|
| 338 |
+
stock: typeof row.stock === 'number' ? row.stock : 0,
|
| 339 |
+
tags: [],
|
| 340 |
+
location: typeof row.location === 'string' ? row.location : '-',
|
| 341 |
+
seller: typeof row.seller === 'string' ? row.seller : '-',
|
| 342 |
+
trend: Math.max(10, 100 - index * 8),
|
| 343 |
+
score: Math.max(18, 100 - index * 12),
|
| 344 |
+
};
|
| 345 |
+
});
|
| 346 |
+
}
|
| 347 |
+
|
| 348 |
+
function toRankedStoresFromUi(ui: LiveVoiceToolUi | undefined, limit: number): RankedStore[] {
|
| 349 |
+
const rows = Array.isArray(ui?.items) ? ui.items : [];
|
| 350 |
+
return rows.slice(0, limit).map((item, index) => {
|
| 351 |
+
const row = typeof item === 'object' && item ? (item as Record<string, unknown>) : {};
|
| 352 |
+
const sourceType =
|
| 353 |
+
typeof row.type === 'string' &&
|
| 354 |
+
(row.type === 'retail' || row.type === 'restaurant' || row.type === 'wholesale' || row.type === 'co-op')
|
| 355 |
+
? row.type
|
| 356 |
+
: 'retail';
|
| 357 |
+
return {
|
| 358 |
+
id: typeof row.id === 'string' ? row.id : `live-store-${index}`,
|
| 359 |
+
name: typeof row.name === 'string' ? row.name : `Store ${index + 1}`,
|
| 360 |
+
type: sourceType,
|
| 361 |
+
location: typeof row.location === 'string' ? row.location : '-',
|
| 362 |
+
buyingFocus: [],
|
| 363 |
+
responseRate: 0.8,
|
| 364 |
+
activeListings: typeof row.activeListings === 'number' ? row.activeListings : 0,
|
| 365 |
+
rating: typeof row.rating === 'number' ? row.rating : 4,
|
| 366 |
+
score: Math.max(20, 100 - index * 12),
|
| 367 |
+
};
|
| 368 |
+
});
|
| 369 |
+
}
|
| 370 |
+
|
| 371 |
+
function toMarketplaceStatsFromUi(ui: LiveVoiceToolUi | undefined): MarketplaceStats | null {
|
| 372 |
+
const rows = Array.isArray(ui?.items) ? ui.items : [];
|
| 373 |
+
if (!rows.length) return null;
|
| 374 |
+
const map: Record<string, number> = {};
|
| 375 |
+
for (const row of rows) {
|
| 376 |
+
if (typeof row !== 'object' || !row) continue;
|
| 377 |
+
const item = row as Record<string, unknown>;
|
| 378 |
+
const label = typeof item.label === 'string' ? item.label.toLowerCase() : '';
|
| 379 |
+
const value = typeof item.value === 'number' ? item.value : typeof item.value === 'string' ? Number(item.value) : NaN;
|
| 380 |
+
if (!label || Number.isNaN(value)) continue;
|
| 381 |
+
map[label] = value;
|
| 382 |
+
}
|
| 383 |
+
return {
|
| 384 |
+
sellers: map.sellers ?? map['賣家'] ?? 0,
|
| 385 |
+
buyers: map.buyers ?? map['買家'] ?? 0,
|
| 386 |
+
products: map.products ?? map['產品'] ?? map['商品'] ?? 0,
|
| 387 |
+
stores: map.stores ?? map['店家'] ?? 0,
|
| 388 |
+
avgResponseRate: 0,
|
| 389 |
+
weeklyDemandIndex: 0,
|
| 390 |
+
};
|
| 391 |
+
}
|
| 392 |
+
|
| 393 |
function scoreProduct(
|
| 394 |
record: ProductRecord,
|
| 395 |
queryTokens: string[],
|
|
|
|
| 643 |
} as const;
|
| 644 |
|
| 645 |
export default function MarketplaceDemoPanel({
|
| 646 |
+
mode: demoMode,
|
| 647 |
locale,
|
| 648 |
onWorkingChange,
|
| 649 |
onWorkflowInit,
|
|
|
|
| 774 |
});
|
| 775 |
if (!parsed) return;
|
| 776 |
|
| 777 |
+
if (demoMode === 'live') {
|
| 778 |
+
let liveProducts: RankedProduct[] = [];
|
| 779 |
+
let liveStores: RankedStore[] = [];
|
| 780 |
+
let liveStats: MarketplaceStats | null = null;
|
| 781 |
+
|
| 782 |
+
const productStep = steps.find((step) => step.id === 'market-step-2');
|
| 783 |
+
if (productStep) {
|
| 784 |
+
toolCalls.push('product_search_public');
|
| 785 |
+
const productReady = await stepRunner(runToken, productStep, {
|
| 786 |
+
args: {
|
| 787 |
+
nameContains: activeQuery || undefined,
|
| 788 |
+
categoryEquals: category !== 'all' ? category : undefined,
|
| 789 |
+
minPrice,
|
| 790 |
+
maxPrice,
|
| 791 |
+
limit,
|
| 792 |
+
},
|
| 793 |
+
});
|
| 794 |
+
if (!productReady) return;
|
| 795 |
+
const tool = await callVoiceTool({
|
| 796 |
+
name: 'product_search_public',
|
| 797 |
+
locale,
|
| 798 |
+
args: {
|
| 799 |
+
nameContains: activeQuery || undefined,
|
| 800 |
+
categoryEquals: category !== 'all' ? category : undefined,
|
| 801 |
+
minPrice,
|
| 802 |
+
maxPrice,
|
| 803 |
+
limit,
|
| 804 |
+
},
|
| 805 |
+
});
|
| 806 |
+
liveProducts = toRankedProductsFromUi(tool.ui, limit);
|
| 807 |
+
setProducts(liveProducts);
|
| 808 |
+
onTrace(
|
| 809 |
+
buildTrace(
|
| 810 |
+
'tool',
|
| 811 |
+
locale === 'zh-TW' ? '商品結果' : 'Product results',
|
| 812 |
+
locale === 'zh-TW' ? `回傳 ${liveProducts.length} 筆商品` : `${liveProducts.length} products returned`,
|
| 813 |
+
'ok',
|
| 814 |
+
{ count: liveProducts.length, uiKind: tool.ui?.kind || null }
|
| 815 |
+
)
|
| 816 |
+
);
|
| 817 |
+
}
|
| 818 |
+
|
| 819 |
+
const storeStep = steps.find((step) => step.id === 'market-step-3');
|
| 820 |
+
if (storeStep) {
|
| 821 |
+
toolCalls.push('store_search_public');
|
| 822 |
+
const storeReady = await stepRunner(runToken, storeStep, {
|
| 823 |
+
args: {
|
| 824 |
+
nameContains: activeQuery || undefined,
|
| 825 |
+
typeEquals: storeType !== 'all' ? storeType : undefined,
|
| 826 |
+
locationContains: location || undefined,
|
| 827 |
+
limit,
|
| 828 |
+
},
|
| 829 |
+
});
|
| 830 |
+
if (!storeReady) return;
|
| 831 |
+
const tool = await callVoiceTool({
|
| 832 |
+
name: 'store_search_public',
|
| 833 |
+
locale,
|
| 834 |
+
args: {
|
| 835 |
+
nameContains: activeQuery || undefined,
|
| 836 |
+
typeEquals: storeType !== 'all' ? storeType : undefined,
|
| 837 |
+
locationContains: location || undefined,
|
| 838 |
+
limit,
|
| 839 |
+
},
|
| 840 |
+
});
|
| 841 |
+
liveStores = toRankedStoresFromUi(tool.ui, limit);
|
| 842 |
+
setStores(liveStores);
|
| 843 |
+
onTrace(
|
| 844 |
+
buildTrace(
|
| 845 |
+
'tool',
|
| 846 |
+
locale === 'zh-TW' ? '店家結果' : 'Store results',
|
| 847 |
+
locale === 'zh-TW' ? `回傳 ${liveStores.length} 筆店家` : `${liveStores.length} stores returned`,
|
| 848 |
+
'ok',
|
| 849 |
+
{ count: liveStores.length, uiKind: tool.ui?.kind || null }
|
| 850 |
+
)
|
| 851 |
+
);
|
| 852 |
+
}
|
| 853 |
+
|
| 854 |
+
const statsStep = steps.find((step) => step.id === 'market-step-4');
|
| 855 |
+
if (statsStep) {
|
| 856 |
+
toolCalls.push('marketplace_get_stats');
|
| 857 |
+
const statsReady = await stepRunner(runToken, statsStep);
|
| 858 |
+
if (!statsReady) return;
|
| 859 |
+
const tool = await callVoiceTool({
|
| 860 |
+
name: 'marketplace_get_stats',
|
| 861 |
+
locale,
|
| 862 |
+
});
|
| 863 |
+
liveStats = toMarketplaceStatsFromUi(tool.ui) || computeMarketplaceStats(liveProducts, liveStores);
|
| 864 |
+
setStats(liveStats);
|
| 865 |
+
onTrace(
|
| 866 |
+
buildTrace(
|
| 867 |
+
'tool',
|
| 868 |
+
locale === 'zh-TW' ? '市集統計' : 'Marketplace stats',
|
| 869 |
+
locale === 'zh-TW' ? '已取得即時統計。' : 'Fetched live marketplace stats.',
|
| 870 |
+
'ok',
|
| 871 |
+
{ uiKind: tool.ui?.kind || null }
|
| 872 |
+
)
|
| 873 |
+
);
|
| 874 |
+
}
|
| 875 |
+
|
| 876 |
+
const renderStep = steps.find((step) => step.id === 'market-step-5');
|
| 877 |
+
if (renderStep) {
|
| 878 |
+
const rendered = await stepRunner(runToken, renderStep, {
|
| 879 |
+
products: liveProducts.length,
|
| 880 |
+
stores: liveStores.length,
|
| 881 |
+
stats: Boolean(liveStats),
|
| 882 |
+
mode: 'live',
|
| 883 |
+
});
|
| 884 |
+
if (!rendered) return;
|
| 885 |
+
}
|
| 886 |
+
|
| 887 |
+
const summaryText =
|
| 888 |
+
locale === 'zh-TW'
|
| 889 |
+
? `已完成即時市集搜尋:商品 ${liveProducts.length} 筆,店家 ${liveStores.length} 筆。`
|
| 890 |
+
: `Live marketplace search completed: ${liveProducts.length} products and ${liveStores.length} stores.`;
|
| 891 |
+
setSummary(summaryText);
|
| 892 |
+
|
| 893 |
+
setMeta({
|
| 894 |
+
query: activeQuery || '*',
|
| 895 |
+
mode,
|
| 896 |
+
startedAt: new Date(startMs).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }),
|
| 897 |
+
durationMs: Date.now() - startMs,
|
| 898 |
+
toolCalls,
|
| 899 |
+
});
|
| 900 |
+
return;
|
| 901 |
+
}
|
| 902 |
+
|
| 903 |
const productStep = steps.find((step) => step.id === 'market-step-2');
|
| 904 |
if (productStep) {
|
| 905 |
toolCalls.push('product_search_public');
|
src/VoiceDemoPanel.tsx
CHANGED
|
@@ -1,6 +1,8 @@
|
|
| 1 |
import { useEffect, useMemo, useRef, useState } from 'react';
|
| 2 |
-
import type { DemoLocale, ProgressStep, ProgressStepStatus, TraceItem } from './types';
|
| 3 |
import VoiceFeatureDemo from './VoiceFeatureDemo';
|
|
|
|
|
|
|
| 4 |
|
| 5 |
type VoiceStatus = 'idle' | 'connecting' | 'ready' | 'listening' | 'processing' | 'error';
|
| 6 |
type VoiceRole = 'assistant' | 'user' | 'system';
|
|
@@ -51,6 +53,7 @@ type WorkflowStepTemplate = {
|
|
| 51 |
};
|
| 52 |
|
| 53 |
type VoiceDemoPanelProps = {
|
|
|
|
| 54 |
locale: DemoLocale;
|
| 55 |
onWorkingChange: (working: boolean) => void;
|
| 56 |
onWorkflowInit: (steps: ProgressStep[]) => void;
|
|
@@ -61,6 +64,92 @@ type VoiceDemoPanelProps = {
|
|
| 61 |
onConsumeQueuedPrompt: () => void;
|
| 62 |
};
|
| 63 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
const COPY = {
|
| 65 |
en: {
|
| 66 |
title: 'Voice Assistant',
|
|
@@ -347,6 +436,7 @@ function buildStatsCard(locale: DemoLocale): VoiceToolCard {
|
|
| 347 |
}
|
| 348 |
|
| 349 |
export default function VoiceDemoPanel({
|
|
|
|
| 350 |
locale,
|
| 351 |
onWorkingChange,
|
| 352 |
onWorkflowInit,
|
|
@@ -490,10 +580,100 @@ export default function VoiceDemoPanel({
|
|
| 490 |
onWorkingChange(false);
|
| 491 |
};
|
| 492 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 493 |
const simulateVoiceTurn = async (transcript?: string) => {
|
| 494 |
const sample = transcript || VOICE_SAMPLE_INPUTS[locale][Math.floor(Math.random() * VOICE_SAMPLE_INPUTS[locale].length)];
|
| 495 |
appendEntry({ role: 'user', text: sample });
|
| 496 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 497 |
const steps = buildTurnSteps(locale);
|
| 498 |
const summary =
|
| 499 |
locale === 'zh-TW'
|
|
@@ -537,6 +717,10 @@ export default function VoiceDemoPanel({
|
|
| 537 |
await openAssistant();
|
| 538 |
}
|
| 539 |
appendEntry({ role: 'user', text: copy.tutorialRequest });
|
|
|
|
|
|
|
|
|
|
|
|
|
| 540 |
await runWorkflow(buildTutorialSteps(locale), {
|
| 541 |
text:
|
| 542 |
locale === 'zh-TW'
|
|
|
|
| 1 |
import { useEffect, useMemo, useRef, useState } from 'react';
|
| 2 |
+
import type { DemoLocale, DemoMode, ProgressStep, ProgressStepStatus, TraceItem } from './types';
|
| 3 |
import VoiceFeatureDemo from './VoiceFeatureDemo';
|
| 4 |
+
import { sendVoiceAssistantMessage } from './liveApi';
|
| 5 |
+
import type { LiveVoiceToolUi } from './liveApi';
|
| 6 |
|
| 7 |
type VoiceStatus = 'idle' | 'connecting' | 'ready' | 'listening' | 'processing' | 'error';
|
| 8 |
type VoiceRole = 'assistant' | 'user' | 'system';
|
|
|
|
| 53 |
};
|
| 54 |
|
| 55 |
type VoiceDemoPanelProps = {
|
| 56 |
+
mode: DemoMode;
|
| 57 |
locale: DemoLocale;
|
| 58 |
onWorkingChange: (working: boolean) => void;
|
| 59 |
onWorkflowInit: (steps: ProgressStep[]) => void;
|
|
|
|
| 64 |
onConsumeQueuedPrompt: () => void;
|
| 65 |
};
|
| 66 |
|
| 67 |
+
function uiActionLabels(ui: LiveVoiceToolUi | undefined): string[] {
|
| 68 |
+
if (!ui || !Array.isArray(ui.actions)) return [];
|
| 69 |
+
return ui.actions
|
| 70 |
+
.map((action) => (action && typeof action.label === 'string' ? action.label : ''))
|
| 71 |
+
.filter(Boolean)
|
| 72 |
+
.slice(0, 4);
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
function mapLiveUiToCard(ui: LiveVoiceToolUi | undefined, locale: DemoLocale): VoiceToolCard | undefined {
|
| 76 |
+
if (!ui) return undefined;
|
| 77 |
+
const kind = typeof ui.kind === 'string' ? ui.kind : '';
|
| 78 |
+
const fallbackTitle = locale === 'zh-TW' ? '工具結果' : 'Tool Result';
|
| 79 |
+
const title = typeof ui.title === 'string' ? ui.title : fallbackTitle;
|
| 80 |
+
const description = typeof ui.description === 'string' ? ui.description : '';
|
| 81 |
+
const actions = uiActionLabels(ui);
|
| 82 |
+
|
| 83 |
+
if (kind === 'product_created' || kind === 'product_updated') {
|
| 84 |
+
const product = typeof ui.product === 'object' && ui.product ? (ui.product as Record<string, unknown>) : {};
|
| 85 |
+
return {
|
| 86 |
+
kind: 'product_updated',
|
| 87 |
+
title,
|
| 88 |
+
description,
|
| 89 |
+
product: {
|
| 90 |
+
name: typeof product.name === 'string' ? product.name : locale === 'zh-TW' ? '未命名產品' : 'Unnamed product',
|
| 91 |
+
price:
|
| 92 |
+
typeof product.price === 'number'
|
| 93 |
+
? `NT$ ${product.price}${typeof product.unit === 'string' && product.unit ? ` / ${product.unit}` : ''}`
|
| 94 |
+
: '-',
|
| 95 |
+
stock: typeof product.stock === 'number' ? String(product.stock) : '-',
|
| 96 |
+
category: typeof product.category === 'string' ? product.category : '-',
|
| 97 |
+
},
|
| 98 |
+
actions: actions.length ? actions : locale === 'zh-TW' ? ['查看產品'] : ['Open product'],
|
| 99 |
+
};
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
if (kind === 'stats' || kind === 'marketplace_stats') {
|
| 103 |
+
const items = Array.isArray(ui.items) ? ui.items : [];
|
| 104 |
+
const stats = items
|
| 105 |
+
.map((item) => {
|
| 106 |
+
if (typeof item !== 'object' || !item) return null;
|
| 107 |
+
const row = item as Record<string, unknown>;
|
| 108 |
+
if (typeof row.label !== 'string') return null;
|
| 109 |
+
return {
|
| 110 |
+
label: row.label,
|
| 111 |
+
value:
|
| 112 |
+
typeof row.value === 'number'
|
| 113 |
+
? String(row.value)
|
| 114 |
+
: typeof row.value === 'string'
|
| 115 |
+
? row.value
|
| 116 |
+
: '-',
|
| 117 |
+
};
|
| 118 |
+
})
|
| 119 |
+
.filter((row): row is { label: string; value: string } => Boolean(row))
|
| 120 |
+
.slice(0, 6);
|
| 121 |
+
return {
|
| 122 |
+
kind: 'stats',
|
| 123 |
+
title,
|
| 124 |
+
description,
|
| 125 |
+
stats,
|
| 126 |
+
actions: actions.length ? actions : locale === 'zh-TW' ? ['查看詳情'] : ['View details'],
|
| 127 |
+
};
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
const items = Array.isArray(ui.items) ? ui.items : [];
|
| 131 |
+
const textItems = items
|
| 132 |
+
.map((item) => {
|
| 133 |
+
if (typeof item !== 'object' || !item) return '';
|
| 134 |
+
const row = item as Record<string, unknown>;
|
| 135 |
+
if (typeof row.title === 'string') return row.title;
|
| 136 |
+
if (typeof row.name === 'string') return row.name;
|
| 137 |
+
if (typeof row.label === 'string') return row.label;
|
| 138 |
+
return '';
|
| 139 |
+
})
|
| 140 |
+
.filter(Boolean)
|
| 141 |
+
.slice(0, 5);
|
| 142 |
+
|
| 143 |
+
if (!textItems.length && !description) return undefined;
|
| 144 |
+
return {
|
| 145 |
+
kind: 'toolkit',
|
| 146 |
+
title,
|
| 147 |
+
description: description || (locale === 'zh-TW' ? '已收到後端工具輸出。' : 'Backend tool output received.'),
|
| 148 |
+
items: textItems.length ? textItems : [locale === 'zh-TW' ? '已完成即時工具執行' : 'Live tool execution completed'],
|
| 149 |
+
actions: actions.length ? actions : locale === 'zh-TW' ? ['下一步'] : ['Next step'],
|
| 150 |
+
};
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
const COPY = {
|
| 154 |
en: {
|
| 155 |
title: 'Voice Assistant',
|
|
|
|
| 436 |
}
|
| 437 |
|
| 438 |
export default function VoiceDemoPanel({
|
| 439 |
+
mode,
|
| 440 |
locale,
|
| 441 |
onWorkingChange,
|
| 442 |
onWorkflowInit,
|
|
|
|
| 580 |
onWorkingChange(false);
|
| 581 |
};
|
| 582 |
|
| 583 |
+
const runLiveVoiceTurn = async (transcript: string) => {
|
| 584 |
+
runRef.current += 1;
|
| 585 |
+
const runId = runRef.current;
|
| 586 |
+
onWorkingChange(true);
|
| 587 |
+
setStatus('processing');
|
| 588 |
+
|
| 589 |
+
const steps = buildTurnSteps(locale);
|
| 590 |
+
onWorkflowInit(
|
| 591 |
+
steps.map((step) => ({
|
| 592 |
+
id: step.id,
|
| 593 |
+
label: step.label,
|
| 594 |
+
detail: step.runningDetail,
|
| 595 |
+
status: 'pending',
|
| 596 |
+
}))
|
| 597 |
+
);
|
| 598 |
+
|
| 599 |
+
try {
|
| 600 |
+
onStepStatus(steps[0].id, 'in_progress', steps[0].runningDetail);
|
| 601 |
+
onTrace(buildTrace(steps[0].kind, steps[0].label, steps[0].runningDetail, 'running'));
|
| 602 |
+
await sleep(160);
|
| 603 |
+
if (!mountedRef.current || runRef.current !== runId) return;
|
| 604 |
+
onStepStatus(steps[0].id, 'completed', steps[0].doneDetail);
|
| 605 |
+
onTrace(buildTrace(steps[0].kind, steps[0].label, steps[0].doneDetail, 'ok'));
|
| 606 |
+
|
| 607 |
+
onStepStatus(steps[1].id, 'in_progress', steps[1].runningDetail);
|
| 608 |
+
onTrace(buildTrace(steps[1].kind, steps[1].label, steps[1].runningDetail, 'running'));
|
| 609 |
+
const response = await sendVoiceAssistantMessage({
|
| 610 |
+
message: transcript,
|
| 611 |
+
locale,
|
| 612 |
+
pageRoute: '/hf-demo/voice',
|
| 613 |
+
pageContext: locale === 'zh-TW' ? 'Hugging Face Voice Agent Demo' : 'Hugging Face Voice Agent Demo',
|
| 614 |
+
});
|
| 615 |
+
if (!mountedRef.current || runRef.current !== runId) return;
|
| 616 |
+
onStepStatus(steps[1].id, 'completed', steps[1].doneDetail);
|
| 617 |
+
onTrace(
|
| 618 |
+
buildTrace(steps[1].kind, steps[1].label, steps[1].doneDetail, 'ok', {
|
| 619 |
+
messages: response.messages?.length ?? 0,
|
| 620 |
+
uiKind: response.ui?.kind || null,
|
| 621 |
+
})
|
| 622 |
+
);
|
| 623 |
+
|
| 624 |
+
onStepStatus(steps[2].id, 'in_progress', steps[2].runningDetail);
|
| 625 |
+
onTrace(buildTrace(steps[2].kind, steps[2].label, steps[2].runningDetail, 'running'));
|
| 626 |
+
const assistantTexts = (response.messages || [])
|
| 627 |
+
.filter((message) => message.role === 'assistant' && message.text)
|
| 628 |
+
.map((message) => message.text);
|
| 629 |
+
if (assistantTexts.length) {
|
| 630 |
+
for (const text of assistantTexts) {
|
| 631 |
+
appendEntry({ role: 'assistant', text });
|
| 632 |
+
}
|
| 633 |
+
} else {
|
| 634 |
+
appendEntry({
|
| 635 |
+
role: 'assistant',
|
| 636 |
+
text: locale === 'zh-TW' ? '已完成後端語音工具流程。' : 'Live voice tool flow completed.',
|
| 637 |
+
});
|
| 638 |
+
}
|
| 639 |
+
const liveCard = mapLiveUiToCard(response.ui, locale);
|
| 640 |
+
if (liveCard) {
|
| 641 |
+
appendEntry({ role: 'assistant', card: liveCard });
|
| 642 |
+
}
|
| 643 |
+
onStepStatus(steps[2].id, 'completed', steps[2].doneDetail);
|
| 644 |
+
onTrace(
|
| 645 |
+
buildTrace(steps[2].kind, steps[2].label, steps[2].doneDetail, 'ok', {
|
| 646 |
+
card: liveCard?.kind || null,
|
| 647 |
+
})
|
| 648 |
+
);
|
| 649 |
+
|
| 650 |
+
onStepStatus(steps[3].id, 'in_progress', steps[3].runningDetail);
|
| 651 |
+
onTrace(buildTrace(steps[3].kind, steps[3].label, steps[3].runningDetail, 'running'));
|
| 652 |
+
await sleep(120);
|
| 653 |
+
if (!mountedRef.current || runRef.current !== runId) return;
|
| 654 |
+
onStepStatus(steps[3].id, 'completed', steps[3].doneDetail);
|
| 655 |
+
onTrace(buildTrace(steps[3].kind, steps[3].label, steps[3].doneDetail, 'ok'));
|
| 656 |
+
|
| 657 |
+
setStatus('ready');
|
| 658 |
+
onWorkingChange(false);
|
| 659 |
+
} catch (error) {
|
| 660 |
+
const message = error instanceof Error ? error.message : locale === 'zh-TW' ? '語音流程發生錯誤。' : 'Voice flow failed.';
|
| 661 |
+
onStepStatus(steps[1].id, 'error', message);
|
| 662 |
+
onTrace(buildTrace('tool', locale === 'zh-TW' ? '語音錯誤' : 'Voice error', message, 'error'));
|
| 663 |
+
setStatus('error');
|
| 664 |
+
onWorkingChange(false);
|
| 665 |
+
}
|
| 666 |
+
};
|
| 667 |
+
|
| 668 |
const simulateVoiceTurn = async (transcript?: string) => {
|
| 669 |
const sample = transcript || VOICE_SAMPLE_INPUTS[locale][Math.floor(Math.random() * VOICE_SAMPLE_INPUTS[locale].length)];
|
| 670 |
appendEntry({ role: 'user', text: sample });
|
| 671 |
|
| 672 |
+
if (mode === 'live') {
|
| 673 |
+
await runLiveVoiceTurn(sample);
|
| 674 |
+
return;
|
| 675 |
+
}
|
| 676 |
+
|
| 677 |
const steps = buildTurnSteps(locale);
|
| 678 |
const summary =
|
| 679 |
locale === 'zh-TW'
|
|
|
|
| 717 |
await openAssistant();
|
| 718 |
}
|
| 719 |
appendEntry({ role: 'user', text: copy.tutorialRequest });
|
| 720 |
+
if (mode === 'live') {
|
| 721 |
+
await runLiveVoiceTurn(copy.tutorialRequest);
|
| 722 |
+
return;
|
| 723 |
+
}
|
| 724 |
await runWorkflow(buildTutorialSteps(locale), {
|
| 725 |
text:
|
| 726 |
locale === 'zh-TW'
|
src/liveAgent.ts
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { callVoiceTool, sendVoiceAssistantMessage } from './liveApi';
|
| 2 |
+
import type { LiveVoiceToolResponse } from './liveApi';
|
| 3 |
+
import type { DemoCard, DemoLocale, DemoTabId, MockAgentResult, ProgressStep, ProgressStepStatus, TraceItem } from './types';
|
| 4 |
+
|
| 5 |
+
type RunLiveAgentParams = {
|
| 6 |
+
tab: DemoTabId;
|
| 7 |
+
input: string;
|
| 8 |
+
locale: DemoLocale;
|
| 9 |
+
onWorkflowInit: (steps: ProgressStep[]) => void;
|
| 10 |
+
onStepStatus: (stepId: string, status: ProgressStepStatus, detail?: string) => void;
|
| 11 |
+
onTrace: (item: TraceItem) => void;
|
| 12 |
+
};
|
| 13 |
+
|
| 14 |
+
function nowLabel(): string {
|
| 15 |
+
return new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
let sequence = 0;
|
| 19 |
+
function nextId(prefix: string): string {
|
| 20 |
+
sequence += 1;
|
| 21 |
+
return `${prefix}-${Date.now().toString(36)}-${sequence.toString(36)}`;
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
function trace(kind: TraceItem['kind'], title: string, detail: string, status: TraceItem['status'], payload?: Record<string, unknown>): TraceItem {
|
| 25 |
+
return {
|
| 26 |
+
id: nextId('trace'),
|
| 27 |
+
kind,
|
| 28 |
+
title,
|
| 29 |
+
detail,
|
| 30 |
+
status,
|
| 31 |
+
payload,
|
| 32 |
+
timestamp: nowLabel(),
|
| 33 |
+
};
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
function uiActions(ui: Record<string, unknown>): string[] {
|
| 37 |
+
const actions = Array.isArray(ui.actions) ? ui.actions : [];
|
| 38 |
+
return actions
|
| 39 |
+
.map((entry) => {
|
| 40 |
+
if (typeof entry !== 'object' || !entry) return '';
|
| 41 |
+
const label = (entry as { label?: unknown }).label;
|
| 42 |
+
return typeof label === 'string' ? label : '';
|
| 43 |
+
})
|
| 44 |
+
.filter(Boolean)
|
| 45 |
+
.slice(0, 4);
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
function toMetric(label: string, value: unknown): { label: string; value: string } {
|
| 49 |
+
return { label, value: typeof value === 'number' ? String(value) : typeof value === 'string' ? value : '-' };
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
function uiToCards(ui: Record<string, unknown> | undefined, locale: DemoLocale): DemoCard[] {
|
| 53 |
+
if (!ui) return [];
|
| 54 |
+
const kind = typeof ui.kind === 'string' ? ui.kind : '';
|
| 55 |
+
const title = typeof ui.title === 'string' ? ui.title : locale === 'zh-TW' ? '工具結果' : 'Tool Output';
|
| 56 |
+
const description = typeof ui.description === 'string' ? ui.description : undefined;
|
| 57 |
+
const actions = uiActions(ui);
|
| 58 |
+
|
| 59 |
+
if (kind === 'stats' || kind === 'marketplace_stats') {
|
| 60 |
+
const items = Array.isArray(ui.items) ? ui.items : [];
|
| 61 |
+
const metrics = items
|
| 62 |
+
.map((item) => {
|
| 63 |
+
if (typeof item !== 'object' || !item) return null;
|
| 64 |
+
const label = (item as { label?: unknown }).label;
|
| 65 |
+
const value = (item as { value?: unknown }).value;
|
| 66 |
+
return typeof label === 'string' ? toMetric(label, value) : null;
|
| 67 |
+
})
|
| 68 |
+
.filter((metric): metric is { label: string; value: string } => Boolean(metric))
|
| 69 |
+
.slice(0, 6);
|
| 70 |
+
|
| 71 |
+
return [{ title, subtitle: description, metrics, actions }];
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
if (kind === 'product_created' || kind === 'product_updated') {
|
| 75 |
+
const product = typeof ui.product === 'object' && ui.product ? (ui.product as Record<string, unknown>) : {};
|
| 76 |
+
const metrics = [
|
| 77 |
+
toMetric(locale === 'zh-TW' ? '價格' : 'Price', product.price),
|
| 78 |
+
toMetric(locale === 'zh-TW' ? '庫存' : 'Stock', product.stock),
|
| 79 |
+
toMetric(locale === 'zh-TW' ? '分類' : 'Category', product.category),
|
| 80 |
+
];
|
| 81 |
+
return [
|
| 82 |
+
{
|
| 83 |
+
title,
|
| 84 |
+
subtitle: typeof product.name === 'string' ? product.name : description,
|
| 85 |
+
metrics,
|
| 86 |
+
actions,
|
| 87 |
+
},
|
| 88 |
+
];
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
if (kind === 'product_list' || kind === 'store_list' || kind === 'marketing_list') {
|
| 92 |
+
const items = Array.isArray(ui.items) ? ui.items : [];
|
| 93 |
+
const cards = items.slice(0, 3).map((item, index) => {
|
| 94 |
+
if (typeof item !== 'object' || !item) {
|
| 95 |
+
return { title: `${title} ${index + 1}` } as DemoCard;
|
| 96 |
+
}
|
| 97 |
+
const row = item as Record<string, unknown>;
|
| 98 |
+
const rowTitle =
|
| 99 |
+
typeof row.name === 'string'
|
| 100 |
+
? row.name
|
| 101 |
+
: typeof row.title === 'string'
|
| 102 |
+
? row.title
|
| 103 |
+
: `${title} ${index + 1}`;
|
| 104 |
+
const metrics: Array<{ label: string; value: string }> = [];
|
| 105 |
+
if (row.price !== undefined) metrics.push(toMetric(locale === 'zh-TW' ? '價格' : 'Price', row.price));
|
| 106 |
+
if (row.stock !== undefined) metrics.push(toMetric(locale === 'zh-TW' ? '庫存' : 'Stock', row.stock));
|
| 107 |
+
if (row.location !== undefined) metrics.push(toMetric(locale === 'zh-TW' ? '地區' : 'Location', row.location));
|
| 108 |
+
if (row.type !== undefined) metrics.push(toMetric(locale === 'zh-TW' ? '類型' : 'Type', row.type));
|
| 109 |
+
return {
|
| 110 |
+
title: rowTitle,
|
| 111 |
+
subtitle: typeof row.description === 'string' ? row.description : undefined,
|
| 112 |
+
metrics: metrics.length ? metrics : undefined,
|
| 113 |
+
} as DemoCard;
|
| 114 |
+
});
|
| 115 |
+
return cards.length ? cards : [{ title, subtitle: description, actions }];
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
return [{ title, subtitle: description, actions }];
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
function assistantSummary(messages: Array<{ role?: unknown; text?: unknown }>, locale: DemoLocale): string {
|
| 122 |
+
const textList = messages
|
| 123 |
+
.filter((message) => message && message.role === 'assistant' && typeof message.text === 'string')
|
| 124 |
+
.map((message) => String(message.text).trim())
|
| 125 |
+
.filter(Boolean);
|
| 126 |
+
if (!textList.length) {
|
| 127 |
+
return locale === 'zh-TW' ? '工具流程已完成。' : 'Tool workflow finished.';
|
| 128 |
+
}
|
| 129 |
+
return textList[textList.length - 1];
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
export async function runLiveAgent(params: RunLiveAgentParams): Promise<MockAgentResult> {
|
| 133 |
+
const { tab, input, locale, onWorkflowInit, onStepStatus, onTrace } = params;
|
| 134 |
+
|
| 135 |
+
if (tab !== 'chat') {
|
| 136 |
+
throw new Error('Live agent is currently supported for chat tab only.');
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
const steps: ProgressStep[] = [
|
| 140 |
+
{
|
| 141 |
+
id: 'live-step-1',
|
| 142 |
+
label: locale === 'zh-TW' ? '呼叫語音助理後端' : 'Call voice assistant backend',
|
| 143 |
+
detail: locale === 'zh-TW' ? '傳送需求並等待回覆。' : 'Submitting request and waiting for response.',
|
| 144 |
+
status: 'pending',
|
| 145 |
+
},
|
| 146 |
+
{
|
| 147 |
+
id: 'live-step-2',
|
| 148 |
+
label: locale === 'zh-TW' ? '補充工具資料' : 'Fetch supplemental tool data',
|
| 149 |
+
detail: locale === 'zh-TW' ? '取得即時市集統計。' : 'Fetching live marketplace stats.',
|
| 150 |
+
status: 'pending',
|
| 151 |
+
},
|
| 152 |
+
{
|
| 153 |
+
id: 'live-step-3',
|
| 154 |
+
label: locale === 'zh-TW' ? '渲染回覆卡片' : 'Render response cards',
|
| 155 |
+
detail: locale === 'zh-TW' ? '整理訊息與結構化輸出。' : 'Formatting messages and structured payload.',
|
| 156 |
+
status: 'pending',
|
| 157 |
+
},
|
| 158 |
+
];
|
| 159 |
+
|
| 160 |
+
onWorkflowInit(steps);
|
| 161 |
+
|
| 162 |
+
onStepStatus(steps[0].id, 'in_progress', steps[0].detail);
|
| 163 |
+
onTrace(
|
| 164 |
+
trace(
|
| 165 |
+
'planner',
|
| 166 |
+
locale === 'zh-TW' ? '聊天請求' : 'Chat request',
|
| 167 |
+
locale === 'zh-TW' ? '正在呼叫 /api/voice/assistant。' : 'Calling /api/voice/assistant.',
|
| 168 |
+
'running'
|
| 169 |
+
)
|
| 170 |
+
);
|
| 171 |
+
|
| 172 |
+
const assistant = await sendVoiceAssistantMessage({
|
| 173 |
+
message: input,
|
| 174 |
+
locale,
|
| 175 |
+
pageRoute: '/hf-demo/chat',
|
| 176 |
+
pageContext: locale === 'zh-TW' ? 'Hugging Face Chat Agent Demo' : 'Hugging Face Chat Agent Demo',
|
| 177 |
+
});
|
| 178 |
+
|
| 179 |
+
onStepStatus(steps[0].id, 'completed', locale === 'zh-TW' ? '已收到助理回覆。' : 'Assistant response received.');
|
| 180 |
+
onTrace(
|
| 181 |
+
trace(
|
| 182 |
+
'planner',
|
| 183 |
+
locale === 'zh-TW' ? '聊天回覆' : 'Chat response',
|
| 184 |
+
locale === 'zh-TW' ? '語音助理回覆已到達。' : 'Voice assistant returned output.',
|
| 185 |
+
'ok',
|
| 186 |
+
{ messageCount: assistant.messages?.length ?? 0, uiKind: assistant.ui?.kind || null }
|
| 187 |
+
)
|
| 188 |
+
);
|
| 189 |
+
|
| 190 |
+
onStepStatus(steps[1].id, 'in_progress', steps[1].detail);
|
| 191 |
+
onTrace(
|
| 192 |
+
trace(
|
| 193 |
+
'tool',
|
| 194 |
+
locale === 'zh-TW' ? '補充工具查詢' : 'Supplemental tool query',
|
| 195 |
+
locale === 'zh-TW' ? '呼叫 marketplace_get_stats。' : 'Calling marketplace_get_stats.',
|
| 196 |
+
'running'
|
| 197 |
+
)
|
| 198 |
+
);
|
| 199 |
+
|
| 200 |
+
let statsPayload: LiveVoiceToolResponse | null = null;
|
| 201 |
+
try {
|
| 202 |
+
statsPayload = await callVoiceTool({
|
| 203 |
+
name: 'marketplace_get_stats',
|
| 204 |
+
locale,
|
| 205 |
+
});
|
| 206 |
+
onStepStatus(steps[1].id, 'completed', locale === 'zh-TW' ? '即時統計已取得。' : 'Live stats fetched.');
|
| 207 |
+
onTrace(
|
| 208 |
+
trace(
|
| 209 |
+
'tool',
|
| 210 |
+
locale === 'zh-TW' ? '統計工具結果' : 'Stats tool result',
|
| 211 |
+
locale === 'zh-TW' ? '市集統計已附加。' : 'Marketplace stats attached.',
|
| 212 |
+
'ok',
|
| 213 |
+
{ uiKind: statsPayload.ui?.kind || null }
|
| 214 |
+
)
|
| 215 |
+
);
|
| 216 |
+
} catch (error) {
|
| 217 |
+
const message = error instanceof Error ? error.message : 'Unknown live tool error';
|
| 218 |
+
onStepStatus(steps[1].id, 'error', message);
|
| 219 |
+
onTrace(
|
| 220 |
+
trace(
|
| 221 |
+
'tool',
|
| 222 |
+
locale === 'zh-TW' ? '統計工具失敗' : 'Stats tool failed',
|
| 223 |
+
message,
|
| 224 |
+
'error'
|
| 225 |
+
)
|
| 226 |
+
);
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
onStepStatus(steps[2].id, 'in_progress', steps[2].detail);
|
| 230 |
+
onTrace(
|
| 231 |
+
trace(
|
| 232 |
+
'renderer',
|
| 233 |
+
locale === 'zh-TW' ? '結果渲染' : 'Result rendering',
|
| 234 |
+
locale === 'zh-TW' ? '正在整理卡片與輸出。' : 'Formatting cards and payload.',
|
| 235 |
+
'running'
|
| 236 |
+
)
|
| 237 |
+
);
|
| 238 |
+
|
| 239 |
+
const cards = [
|
| 240 |
+
...uiToCards(assistant.ui as Record<string, unknown> | undefined, locale),
|
| 241 |
+
...uiToCards(statsPayload?.ui as Record<string, unknown> | undefined, locale),
|
| 242 |
+
];
|
| 243 |
+
|
| 244 |
+
const summary = assistantSummary(assistant.messages || [], locale);
|
| 245 |
+
const payload: Record<string, unknown> = {
|
| 246 |
+
assistant,
|
| 247 |
+
statsPayload,
|
| 248 |
+
source: 'live-backend',
|
| 249 |
+
};
|
| 250 |
+
|
| 251 |
+
onStepStatus(steps[2].id, 'completed', locale === 'zh-TW' ? '即時回覆已完成。' : 'Live response rendered.');
|
| 252 |
+
onTrace(
|
| 253 |
+
trace(
|
| 254 |
+
'renderer',
|
| 255 |
+
locale === 'zh-TW' ? '渲染完成' : 'Rendering complete',
|
| 256 |
+
locale === 'zh-TW' ? '回覆內容與載荷已就緒。' : 'Response and payload are ready.',
|
| 257 |
+
'ok',
|
| 258 |
+
{ cards: cards.length }
|
| 259 |
+
)
|
| 260 |
+
);
|
| 261 |
+
|
| 262 |
+
return {
|
| 263 |
+
summary,
|
| 264 |
+
cards,
|
| 265 |
+
payload,
|
| 266 |
+
};
|
| 267 |
+
}
|
src/liveApi.ts
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { getApp, getApps, initializeApp } from 'firebase/app';
|
| 2 |
+
import { getAuth, signInAnonymously } from 'firebase/auth';
|
| 3 |
+
import type { DemoLocale } from './types';
|
| 4 |
+
|
| 5 |
+
type FirebaseEnvConfig = {
|
| 6 |
+
apiKey: string;
|
| 7 |
+
authDomain: string;
|
| 8 |
+
projectId: string;
|
| 9 |
+
appId: string;
|
| 10 |
+
messagingSenderId?: string;
|
| 11 |
+
storageBucket?: string;
|
| 12 |
+
};
|
| 13 |
+
|
| 14 |
+
type VoiceRole = 'assistant' | 'system';
|
| 15 |
+
|
| 16 |
+
export type LiveVoiceMessage = {
|
| 17 |
+
role: VoiceRole;
|
| 18 |
+
text: string;
|
| 19 |
+
};
|
| 20 |
+
|
| 21 |
+
export type LiveVoiceToolAction = {
|
| 22 |
+
label?: string;
|
| 23 |
+
prompt?: string;
|
| 24 |
+
href?: string;
|
| 25 |
+
auto?: boolean;
|
| 26 |
+
};
|
| 27 |
+
|
| 28 |
+
export type LiveVoiceToolUi = {
|
| 29 |
+
kind?: string;
|
| 30 |
+
title?: string;
|
| 31 |
+
description?: string;
|
| 32 |
+
items?: Array<Record<string, unknown>>;
|
| 33 |
+
actions?: LiveVoiceToolAction[];
|
| 34 |
+
[key: string]: unknown;
|
| 35 |
+
};
|
| 36 |
+
|
| 37 |
+
export type LiveVoiceAssistantResponse = {
|
| 38 |
+
messages?: LiveVoiceMessage[];
|
| 39 |
+
ui?: LiveVoiceToolUi;
|
| 40 |
+
};
|
| 41 |
+
|
| 42 |
+
export type LiveVoiceToolResponse = {
|
| 43 |
+
messages?: LiveVoiceMessage[];
|
| 44 |
+
ui?: LiveVoiceToolUi;
|
| 45 |
+
};
|
| 46 |
+
|
| 47 |
+
export type LiveMarketingStreamEvent =
|
| 48 |
+
| { type: 'text'; text: string }
|
| 49 |
+
| { type: 'image'; base64: string; mimeType?: string }
|
| 50 |
+
| { type: 'done' }
|
| 51 |
+
| { type: 'error'; message: string };
|
| 52 |
+
|
| 53 |
+
export type LiveInvoiceItem = {
|
| 54 |
+
description?: string;
|
| 55 |
+
quantity?: number;
|
| 56 |
+
unitPrice?: number;
|
| 57 |
+
lineTotal?: number;
|
| 58 |
+
};
|
| 59 |
+
|
| 60 |
+
export type LiveInvoiceExtraction = {
|
| 61 |
+
provider?: 'gemini' | 'mock' | string;
|
| 62 |
+
model?: string;
|
| 63 |
+
confidence?: number;
|
| 64 |
+
warnings?: string[];
|
| 65 |
+
rawText?: string;
|
| 66 |
+
invoice?: {
|
| 67 |
+
direction?: 'sale' | 'purchase' | string;
|
| 68 |
+
status?: 'draft' | 'open' | 'paid' | 'overdue' | string;
|
| 69 |
+
invoiceNumber?: string;
|
| 70 |
+
counterpartyName?: string;
|
| 71 |
+
description?: string;
|
| 72 |
+
currency?: string;
|
| 73 |
+
total?: number;
|
| 74 |
+
paid?: number;
|
| 75 |
+
issueDate?: string;
|
| 76 |
+
dueDate?: string;
|
| 77 |
+
deliveryDate?: string;
|
| 78 |
+
items?: LiveInvoiceItem[];
|
| 79 |
+
};
|
| 80 |
+
};
|
| 81 |
+
|
| 82 |
+
export type LiveInvoiceScanResponse = {
|
| 83 |
+
extraction?: LiveInvoiceExtraction;
|
| 84 |
+
created?: { id?: string } | null;
|
| 85 |
+
};
|
| 86 |
+
|
| 87 |
+
const DEMO_USER_ID = (import.meta.env.VITE_DEMO_USER_ID as string | undefined)?.trim() || '';
|
| 88 |
+
const HF_DEMO_API_KEY = (import.meta.env.VITE_HF_DEMO_API_KEY as string | undefined)?.trim() || '';
|
| 89 |
+
|
| 90 |
+
const firebaseConfig: FirebaseEnvConfig = {
|
| 91 |
+
apiKey: (import.meta.env.VITE_FIREBASE_API_KEY as string | undefined)?.trim() || '',
|
| 92 |
+
authDomain: (import.meta.env.VITE_FIREBASE_AUTH_DOMAIN as string | undefined)?.trim() || '',
|
| 93 |
+
projectId: (import.meta.env.VITE_FIREBASE_PROJECT_ID as string | undefined)?.trim() || '',
|
| 94 |
+
appId: (import.meta.env.VITE_FIREBASE_APP_ID as string | undefined)?.trim() || '',
|
| 95 |
+
messagingSenderId: (import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID as string | undefined)?.trim() || undefined,
|
| 96 |
+
storageBucket: (import.meta.env.VITE_FIREBASE_STORAGE_BUCKET as string | undefined)?.trim() || undefined,
|
| 97 |
+
};
|
| 98 |
+
|
| 99 |
+
let authTokenPromise: Promise<string | null> | null = null;
|
| 100 |
+
|
| 101 |
+
function getLiveApiBaseUrl(): string {
|
| 102 |
+
const raw =
|
| 103 |
+
(import.meta.env.VITE_DEMO_API_BASE_URL as string | undefined)?.trim() ||
|
| 104 |
+
(import.meta.env.VITE_API_BASE_URL as string | undefined)?.trim() ||
|
| 105 |
+
'';
|
| 106 |
+
|
| 107 |
+
if (!raw) {
|
| 108 |
+
throw new Error('Missing `VITE_DEMO_API_BASE_URL` for live mode.');
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
return raw.endsWith('/api') ? raw.slice(0, -4) : raw.replace(/\/$/, '');
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
function hasFirebaseConfig(config: FirebaseEnvConfig): boolean {
|
| 115 |
+
return Boolean(config.apiKey && config.authDomain && config.projectId && config.appId);
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
async function getIdToken(): Promise<string | null> {
|
| 119 |
+
if (!hasFirebaseConfig(firebaseConfig)) return null;
|
| 120 |
+
if (!authTokenPromise) {
|
| 121 |
+
authTokenPromise = (async () => {
|
| 122 |
+
const app = getApps().some((entry) => entry.name === 'hf-demo-app')
|
| 123 |
+
? getApp('hf-demo-app')
|
| 124 |
+
: initializeApp(firebaseConfig, 'hf-demo-app');
|
| 125 |
+
const auth = getAuth(app);
|
| 126 |
+
if (!auth.currentUser) {
|
| 127 |
+
await signInAnonymously(auth);
|
| 128 |
+
}
|
| 129 |
+
return auth.currentUser ? await auth.currentUser.getIdToken(true) : null;
|
| 130 |
+
})();
|
| 131 |
+
}
|
| 132 |
+
return authTokenPromise;
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
async function parseErrorResponse(response: Response): Promise<string> {
|
| 136 |
+
try {
|
| 137 |
+
const json = (await response.json()) as { error?: string; message?: string; hint?: string };
|
| 138 |
+
return json.message || json.error || `Request failed with status ${response.status}`;
|
| 139 |
+
} catch {
|
| 140 |
+
const text = await response.text().catch(() => '');
|
| 141 |
+
return text || `Request failed with status ${response.status}`;
|
| 142 |
+
}
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
async function requestJson<T>(path: string, init: RequestInit = {}): Promise<T> {
|
| 146 |
+
const baseUrl = getLiveApiBaseUrl();
|
| 147 |
+
const token = await getIdToken();
|
| 148 |
+
const headers = new Headers(init.headers || {});
|
| 149 |
+
if (!headers.has('Content-Type')) {
|
| 150 |
+
headers.set('Content-Type', 'application/json');
|
| 151 |
+
}
|
| 152 |
+
if (token) {
|
| 153 |
+
headers.set('Authorization', `Bearer ${token}`);
|
| 154 |
+
}
|
| 155 |
+
if (HF_DEMO_API_KEY) {
|
| 156 |
+
headers.set('x-hf-demo-key', HF_DEMO_API_KEY);
|
| 157 |
+
}
|
| 158 |
+
const response = await fetch(`${baseUrl}${path}`, {
|
| 159 |
+
...init,
|
| 160 |
+
headers,
|
| 161 |
+
});
|
| 162 |
+
if (!response.ok) {
|
| 163 |
+
const message = await parseErrorResponse(response);
|
| 164 |
+
throw new Error(message);
|
| 165 |
+
}
|
| 166 |
+
return (await response.json()) as T;
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
function withDemoUser<T extends Record<string, unknown>>(body: T): T {
|
| 170 |
+
if (!DEMO_USER_ID) return body;
|
| 171 |
+
return { ...body, userId: DEMO_USER_ID };
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
export function parseDataUrl(dataUrl: string): { base64: string; mimeType: string } {
|
| 175 |
+
const trimmed = dataUrl.trim();
|
| 176 |
+
const match = trimmed.match(/^data:([^;]+);base64,(.+)$/);
|
| 177 |
+
if (!match) {
|
| 178 |
+
return { base64: trimmed, mimeType: 'image/png' };
|
| 179 |
+
}
|
| 180 |
+
return { mimeType: match[1], base64: match[2] };
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
export async function sendVoiceAssistantMessage(params: {
|
| 184 |
+
message: string;
|
| 185 |
+
locale: DemoLocale;
|
| 186 |
+
pageRoute?: string;
|
| 187 |
+
pageContext?: string;
|
| 188 |
+
}): Promise<LiveVoiceAssistantResponse> {
|
| 189 |
+
return await requestJson<LiveVoiceAssistantResponse>(
|
| 190 |
+
'/api/voice/assistant',
|
| 191 |
+
{
|
| 192 |
+
method: 'POST',
|
| 193 |
+
body: JSON.stringify(
|
| 194 |
+
withDemoUser({
|
| 195 |
+
message: params.message,
|
| 196 |
+
locale: params.locale,
|
| 197 |
+
pageRoute: params.pageRoute,
|
| 198 |
+
pageContext: params.pageContext,
|
| 199 |
+
})
|
| 200 |
+
),
|
| 201 |
+
}
|
| 202 |
+
);
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
export async function callVoiceTool(params: {
|
| 206 |
+
name: string;
|
| 207 |
+
args?: Record<string, unknown>;
|
| 208 |
+
locale: DemoLocale;
|
| 209 |
+
}): Promise<LiveVoiceToolResponse> {
|
| 210 |
+
return await requestJson<LiveVoiceToolResponse>(
|
| 211 |
+
'/api/voice/tool',
|
| 212 |
+
{
|
| 213 |
+
method: 'POST',
|
| 214 |
+
body: JSON.stringify(
|
| 215 |
+
withDemoUser({
|
| 216 |
+
name: params.name,
|
| 217 |
+
args: params.args || {},
|
| 218 |
+
locale: params.locale,
|
| 219 |
+
})
|
| 220 |
+
),
|
| 221 |
+
}
|
| 222 |
+
);
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
export async function scanInvoiceImage(params: {
|
| 226 |
+
dataBase64: string;
|
| 227 |
+
mimeType?: string;
|
| 228 |
+
locale: DemoLocale;
|
| 229 |
+
createInvoice: boolean;
|
| 230 |
+
}): Promise<LiveInvoiceScanResponse> {
|
| 231 |
+
return await requestJson<LiveInvoiceScanResponse>(
|
| 232 |
+
'/api/invoices/scan',
|
| 233 |
+
{
|
| 234 |
+
method: 'POST',
|
| 235 |
+
body: JSON.stringify({
|
| 236 |
+
dataBase64: params.dataBase64,
|
| 237 |
+
mimeType: params.mimeType,
|
| 238 |
+
locale: params.locale,
|
| 239 |
+
createInvoice: params.createInvoice,
|
| 240 |
+
}),
|
| 241 |
+
}
|
| 242 |
+
);
|
| 243 |
+
}
|
| 244 |
+
|
| 245 |
+
export async function streamMarketingGeneration(params: {
|
| 246 |
+
prompt: string;
|
| 247 |
+
locale: DemoLocale;
|
| 248 |
+
product: { name: string; category: string; description?: string };
|
| 249 |
+
image?: { base64: string; mimeType: string };
|
| 250 |
+
signal?: AbortSignal;
|
| 251 |
+
onEvent: (event: LiveMarketingStreamEvent) => void;
|
| 252 |
+
}): Promise<void> {
|
| 253 |
+
const baseUrl = getLiveApiBaseUrl();
|
| 254 |
+
const token = await getIdToken();
|
| 255 |
+
const headers = new Headers({ 'Content-Type': 'application/json' });
|
| 256 |
+
if (token) {
|
| 257 |
+
headers.set('Authorization', `Bearer ${token}`);
|
| 258 |
+
}
|
| 259 |
+
if (HF_DEMO_API_KEY) {
|
| 260 |
+
headers.set('x-hf-demo-key', HF_DEMO_API_KEY);
|
| 261 |
+
}
|
| 262 |
+
|
| 263 |
+
const response = await fetch(`${baseUrl}/api/marketing/generate-stream`, {
|
| 264 |
+
method: 'POST',
|
| 265 |
+
headers,
|
| 266 |
+
signal: params.signal,
|
| 267 |
+
body: JSON.stringify({
|
| 268 |
+
prompt: params.prompt,
|
| 269 |
+
language: params.locale,
|
| 270 |
+
product: params.product,
|
| 271 |
+
image: params.image,
|
| 272 |
+
}),
|
| 273 |
+
});
|
| 274 |
+
|
| 275 |
+
if (!response.ok || !response.body) {
|
| 276 |
+
const message = await parseErrorResponse(response);
|
| 277 |
+
throw new Error(message);
|
| 278 |
+
}
|
| 279 |
+
|
| 280 |
+
const reader = response.body.getReader();
|
| 281 |
+
const decoder = new TextDecoder('utf-8');
|
| 282 |
+
let buffer = '';
|
| 283 |
+
|
| 284 |
+
while (true) {
|
| 285 |
+
const { value, done } = await reader.read();
|
| 286 |
+
if (done) break;
|
| 287 |
+
buffer += decoder.decode(value, { stream: true });
|
| 288 |
+
const lines = buffer.split('\n');
|
| 289 |
+
buffer = lines.pop() ?? '';
|
| 290 |
+
|
| 291 |
+
for (const line of lines) {
|
| 292 |
+
const trimmed = line.trim();
|
| 293 |
+
if (!trimmed) continue;
|
| 294 |
+
try {
|
| 295 |
+
const event = JSON.parse(trimmed) as LiveMarketingStreamEvent;
|
| 296 |
+
params.onEvent(event);
|
| 297 |
+
} catch {
|
| 298 |
+
// Ignore malformed chunks from network boundaries.
|
| 299 |
+
}
|
| 300 |
+
}
|
| 301 |
+
}
|
| 302 |
+
}
|
src/vite-env.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
/// <reference types="vite/client" />
|