From a4d90299f04baede76eb5f89adeb21fc101c3dfc Mon Sep 17 00:00:00 2001 From: javiermengual Date: Wed, 13 May 2026 20:03:58 +0200 Subject: [PATCH] Refactor voice assistant commands and improve silence detection in audio recording --- package-lock.json | 224 +++++++++++++++---------------- src/components/VoiceAssistant.js | 149 +++++++++++--------- src/pages/pagesRegistry.js | 7 +- src/services/voiceInput.js | 64 ++++++++- 4 files changed, 264 insertions(+), 180 deletions(-) diff --git a/package-lock.json b/package-lock.json index f48554d..d79fe0f 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1036,9 +1036,9 @@ "license": "BSD-3-Clause" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz", - "integrity": "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.3.tgz", + "integrity": "sha512-x35CNW/ANXG3hE/EZpRU8MXX1JDN86hBb2wMGAtltkz7pc6cxgjpy1OMMfDosOQ+2hWqIkag/fGok1Yady9nGw==", "cpu": [ "arm" ], @@ -1050,9 +1050,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz", - "integrity": "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.3.tgz", + "integrity": "sha512-xw3xtkDApIOGayehp2+Rz4zimfkaX65r4t47iy+ymQB2G4iJCBBfj0ogVg5jpvjpn8UWn/+q9tprxleYeNp3Hw==", "cpu": [ "arm64" ], @@ -1064,9 +1064,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz", - "integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.3.tgz", + "integrity": "sha512-vo6Y5Qfpx7/5EaamIwi0WqW2+zfiusVihKatLvtN1VFVy3D13uERk/6gZLU1UiHRL6fDXqj/ELIeVRGnvcTE1g==", "cpu": [ "arm64" ], @@ -1078,9 +1078,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz", - "integrity": "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.3.tgz", + "integrity": "sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw==", "cpu": [ "x64" ], @@ -1092,9 +1092,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz", - "integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.3.tgz", + "integrity": "sha512-6HnvHCT7fDyj6R0Ph7A6x8dQS/S38MClRWeDLqc0MdfWkxjiu1HSDYrdPhqSILzjTIC/pnXbbJbo+ft+gy/9hQ==", "cpu": [ "arm64" ], @@ -1106,9 +1106,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz", - "integrity": "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.3.tgz", + "integrity": "sha512-KHLgC3WKlUYW3ShFKnnosZDOJ0xjg9zp7au3sIm2bs/tGBeC2ipmvRh/N7JKi0t9Ue20C0dpEshi8WUubg+cnA==", "cpu": [ "x64" ], @@ -1120,9 +1120,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz", - "integrity": "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.3.tgz", + "integrity": "sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g==", "cpu": [ "arm" ], @@ -1134,9 +1134,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz", - "integrity": "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.3.tgz", + "integrity": "sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==", "cpu": [ "arm" ], @@ -1148,9 +1148,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz", - "integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.3.tgz", + "integrity": "sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==", "cpu": [ "arm64" ], @@ -1162,9 +1162,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz", - "integrity": "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.3.tgz", + "integrity": "sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg==", "cpu": [ "arm64" ], @@ -1176,9 +1176,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz", - "integrity": "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.3.tgz", + "integrity": "sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA==", "cpu": [ "loong64" ], @@ -1190,9 +1190,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz", - "integrity": "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.3.tgz", + "integrity": "sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg==", "cpu": [ "loong64" ], @@ -1204,9 +1204,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz", - "integrity": "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.3.tgz", + "integrity": "sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ==", "cpu": [ "ppc64" ], @@ -1218,9 +1218,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz", - "integrity": "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.3.tgz", + "integrity": "sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA==", "cpu": [ "ppc64" ], @@ -1232,9 +1232,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz", - "integrity": "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.3.tgz", + "integrity": "sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw==", "cpu": [ "riscv64" ], @@ -1246,9 +1246,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz", - "integrity": "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.3.tgz", + "integrity": "sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ==", "cpu": [ "riscv64" ], @@ -1260,9 +1260,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz", - "integrity": "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.3.tgz", + "integrity": "sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig==", "cpu": [ "s390x" ], @@ -1274,9 +1274,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.1.tgz", - "integrity": "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.3.tgz", + "integrity": "sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA==", "cpu": [ "x64" ], @@ -1288,9 +1288,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz", - "integrity": "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.3.tgz", + "integrity": "sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA==", "cpu": [ "x64" ], @@ -1302,9 +1302,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.1.tgz", - "integrity": "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.3.tgz", + "integrity": "sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q==", "cpu": [ "x64" ], @@ -1316,9 +1316,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz", - "integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.3.tgz", + "integrity": "sha512-AaXwSvUi3QIPtroAUw1t5yHGIyqKEXwH54WUocFolZhpGDruJcs8c+xPNDRn4XiQsS7MEwnYsHW2l0MBLDMkWg==", "cpu": [ "arm64" ], @@ -1330,9 +1330,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz", - "integrity": "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.3.tgz", + "integrity": "sha512-65LAKM/bAWDqKNEelHlcHvm2V+Vfb8C6INFxQXRHCvaVN1rJfwr4NvdP4FyzUaLqWfaCGaadf6UbTm8xJeYfEg==", "cpu": [ "arm64" ], @@ -1344,9 +1344,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz", - "integrity": "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.3.tgz", + "integrity": "sha512-EEM2gyhBF5MFnI6vMKdX1LAosE627RGBzIoGMdLloPZkXrUN0Ckqgr2Qi8+J3zip/8NVVro3/FjB+tjhZUgUHA==", "cpu": [ "ia32" ], @@ -1358,9 +1358,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.1.tgz", - "integrity": "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.3.tgz", + "integrity": "sha512-E5Eb5H/DpxaoXH++Qkv28RcUJboMopmdDUALBczvHMf7hNIxaDZqwY5lK12UK1BHacSmvupoEWGu+n993Z0y1A==", "cpu": [ "x64" ], @@ -1372,9 +1372,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.1.tgz", - "integrity": "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.3.tgz", + "integrity": "sha512-hPt/bgL5cE+Qp+/TPHBqptcAgPzgj46mPcg/16zNUmbQk0j+mOEQV/+Lqu8QRtDV3Ek95Q6FeFITpuhl6OTsAA==", "cpu": [ "x64" ], @@ -1782,9 +1782,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -1801,9 +1801,9 @@ "license": "MIT" }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", + "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", "dev": true, "funding": [ { @@ -1871,9 +1871,9 @@ } }, "node_modules/rollup": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz", - "integrity": "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.3.tgz", + "integrity": "sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==", "dev": true, "license": "MIT", "dependencies": { @@ -1887,31 +1887,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.55.1", - "@rollup/rollup-android-arm64": "4.55.1", - "@rollup/rollup-darwin-arm64": "4.55.1", - "@rollup/rollup-darwin-x64": "4.55.1", - "@rollup/rollup-freebsd-arm64": "4.55.1", - "@rollup/rollup-freebsd-x64": "4.55.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.55.1", - "@rollup/rollup-linux-arm-musleabihf": "4.55.1", - "@rollup/rollup-linux-arm64-gnu": "4.55.1", - "@rollup/rollup-linux-arm64-musl": "4.55.1", - "@rollup/rollup-linux-loong64-gnu": "4.55.1", - "@rollup/rollup-linux-loong64-musl": "4.55.1", - "@rollup/rollup-linux-ppc64-gnu": "4.55.1", - "@rollup/rollup-linux-ppc64-musl": "4.55.1", - "@rollup/rollup-linux-riscv64-gnu": "4.55.1", - "@rollup/rollup-linux-riscv64-musl": "4.55.1", - "@rollup/rollup-linux-s390x-gnu": "4.55.1", - "@rollup/rollup-linux-x64-gnu": "4.55.1", - "@rollup/rollup-linux-x64-musl": "4.55.1", - "@rollup/rollup-openbsd-x64": "4.55.1", - "@rollup/rollup-openharmony-arm64": "4.55.1", - "@rollup/rollup-win32-arm64-msvc": "4.55.1", - "@rollup/rollup-win32-ia32-msvc": "4.55.1", - "@rollup/rollup-win32-x64-gnu": "4.55.1", - "@rollup/rollup-win32-x64-msvc": "4.55.1", + "@rollup/rollup-android-arm-eabi": "4.60.3", + "@rollup/rollup-android-arm64": "4.60.3", + "@rollup/rollup-darwin-arm64": "4.60.3", + "@rollup/rollup-darwin-x64": "4.60.3", + "@rollup/rollup-freebsd-arm64": "4.60.3", + "@rollup/rollup-freebsd-x64": "4.60.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.3", + "@rollup/rollup-linux-arm-musleabihf": "4.60.3", + "@rollup/rollup-linux-arm64-gnu": "4.60.3", + "@rollup/rollup-linux-arm64-musl": "4.60.3", + "@rollup/rollup-linux-loong64-gnu": "4.60.3", + "@rollup/rollup-linux-loong64-musl": "4.60.3", + "@rollup/rollup-linux-ppc64-gnu": "4.60.3", + "@rollup/rollup-linux-ppc64-musl": "4.60.3", + "@rollup/rollup-linux-riscv64-gnu": "4.60.3", + "@rollup/rollup-linux-riscv64-musl": "4.60.3", + "@rollup/rollup-linux-s390x-gnu": "4.60.3", + "@rollup/rollup-linux-x64-gnu": "4.60.3", + "@rollup/rollup-linux-x64-musl": "4.60.3", + "@rollup/rollup-openbsd-x64": "4.60.3", + "@rollup/rollup-openharmony-arm64": "4.60.3", + "@rollup/rollup-win32-arm64-msvc": "4.60.3", + "@rollup/rollup-win32-ia32-msvc": "4.60.3", + "@rollup/rollup-win32-x64-gnu": "4.60.3", + "@rollup/rollup-win32-x64-msvc": "4.60.3", "fsevents": "~2.3.2" } }, @@ -2067,9 +2067,9 @@ "license": "MIT" }, "node_modules/vite": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", - "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.3.tgz", + "integrity": "sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==", "dev": true, "license": "MIT", "dependencies": { diff --git a/src/components/VoiceAssistant.js b/src/components/VoiceAssistant.js index 4c94581..b34cecf 100644 --- a/src/components/VoiceAssistant.js +++ b/src/components/VoiceAssistant.js @@ -1,4 +1,5 @@ import { pipeline } from '@huggingface/transformers'; +import { pagesRegistry } from '../pages/pagesRegistry.js'; // ── Modelo (singleton entre sesiones) ───────────────────────────────────────── // Xenova/whisper-tiny + transformers.js v3 = WASM puro, sin WebGPU, todos los navegadores @@ -8,65 +9,37 @@ function loadModel(onProgress) { if (!transcriberPromise) { transcriberPromise = pipeline( 'automatic-speech-recognition', - 'Xenova/whisper-tiny', + 'Xenova/whisper-base', { progress_callback: onProgress } ).catch(err => { transcriberPromise = null; throw err; }); } return transcriberPromise; } -// ── Comandos de voz ─────────────────────────────────────────────────────────── -const COMMANDS = [ - { - label: 'Nueva factura', - hint: '"nueva factura" · "crear factura"', - icon: ``, - patterns: ['nueva factura', 'crear factura', 'factura nueva', 'añadir factura', 'nueva facturas'], - action: () => { window.location.hash = '#create-invoice'; }, - }, - { - label: 'Ver facturas', - hint: '"facturas" · "ver facturas"', - icon: ``, - patterns: ['facturas', 'ver facturas', 'ir a facturas', 'lista facturas', 'mis facturas', 'listado facturas'], - action: () => { window.location.hash = '#invoices'; }, - }, - { - label: 'Ver clientes', - hint: '"clientes" · "ver clientes"', - icon: ``, - patterns: ['clientes', 'ver clientes', 'ir a clientes', 'lista clientes', 'mis clientes'], - action: () => { window.location.hash = '#clients'; }, - }, - { - label: 'Dashboard', - hint: '"dashboard" · "inicio" · "resumen"', - icon: ``, - patterns: ['dashboard', 'inicio', 'ir al inicio', 'ir al dashboard', 'resumen', 'home', 'panel', 'principal'], - action: () => { window.location.hash = '#dashboard'; }, - }, - { - label: 'Configuración', - hint: '"configuración" · "ajustes"', - icon: ``, - patterns: ['configuración', 'configuracion', 'ajustes', 'settings', 'preferencias'], - action: () => { window.location.hash = '#settings'; }, - }, -]; +// ── Comandos de voz (generados desde pagesRegistry) ────────────────────────── +const COMMANDS = pagesRegistry + .filter(p => p.voicePatterns?.length) + .map(p => ({ + label: p.name, + hint: `"${p.voicePatterns.slice(0, 2).join('" · "')}"`, + icon: p.icon, + patterns: p.voicePatterns, + action: () => { window.location.hash = '#' + p.route; }, + })); function matchCommand(text) { - const norm = text.toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '').trim(); + const norm = text.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '').trim(); for (const cmd of COMMANDS) { for (const pattern of cmd.patterns) { - const normPattern = pattern.normalize('NFD').replace(/[̀-ͯ]/g, ''); - if (norm.includes(normPattern)) return cmd; + const normP = pattern.normalize('NFD').replace(/[\u0300-\u036f]/g, ''); + if (norm.includes(normP)) return cmd; } } return null; } -// ── Grabación ───────────────────────────────────────────────────────────────── -async function startRecording() { +// ── Grabación con detección de silencio ─────────────────────────────────────── +async function startRecording(onSilence) { const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false }); const mimeType = ['audio/webm;codecs=opus', 'audio/webm', 'audio/ogg;codecs=opus'] .find(t => MediaRecorder.isTypeSupported(t)) || ''; @@ -74,8 +47,56 @@ async function startRecording() { const chunks = []; recorder.ondataavailable = e => { if (e.data?.size > 0) chunks.push(e.data); }; recorder.start(200); + + // Detección de silencio via Web Audio API + const audioCtx = new AudioContext(); + const analyser = audioCtx.createAnalyser(); + analyser.fftSize = 512; + const source = audioCtx.createMediaStreamSource(stream); + source.connect(analyser); + const dataArray = new Uint8Array(analyser.frequencyBinCount); + + const SILENCE_THRESHOLD = 12; // RMS 0-128 (sala silenciosa ≈ 2-5) + const SILENCE_DELAY_MS = 1800; // ms de silencio para auto-parar + const MIN_RECORD_MS = 700; // no parar antes de este tiempo + + const startTime = Date.now(); + let silenceStart = null; + let rafId = null; + let stopped = false; + + function tick() { + if (stopped) return; + analyser.getByteTimeDomainData(dataArray); + let sum = 0; + for (let i = 0; i < dataArray.length; i++) { + const v = dataArray[i] / 128.0 - 1.0; + sum += v * v; + } + const rms = Math.sqrt(sum / dataArray.length) * 128; + + if (Date.now() - startTime >= MIN_RECORD_MS) { + if (rms < SILENCE_THRESHOLD) { + if (!silenceStart) silenceStart = Date.now(); + else if (Date.now() - silenceStart >= SILENCE_DELAY_MS) { + stopped = true; + audioCtx.close().catch(() => { }); + onSilence?.(); + return; + } + } else { + silenceStart = null; + } + } + rafId = requestAnimationFrame(tick); + } + rafId = requestAnimationFrame(tick); + return { stop: () => new Promise(resolve => { + stopped = true; + if (rafId) cancelAnimationFrame(rafId); + audioCtx.close().catch(() => { }); recorder.onstop = () => { stream.getTracks().forEach(t => t.stop()); resolve(new Blob(chunks, { type: mimeType || 'audio/webm' })); @@ -188,17 +209,17 @@ export function mountVoiceAssistant() { }); // ── Referencias ──────────────────────────────────────────────────────────── - const fab = assistantEl.querySelector('#va-fab'); - const panel = assistantEl.querySelector('#va-panel'); - const closeBtn = assistantEl.querySelector('#va-close'); + const fab = assistantEl.querySelector('#va-fab'); + const panel = assistantEl.querySelector('#va-panel'); + const closeBtn = assistantEl.querySelector('#va-close'); const statusIcon = assistantEl.querySelector('#va-status-icon'); const statusText = assistantEl.querySelector('#va-status-text'); - const statusSub = assistantEl.querySelector('#va-status-sub'); - const resultBox = assistantEl.querySelector('#va-result'); + const statusSub = assistantEl.querySelector('#va-status-sub'); + const resultBox = assistantEl.querySelector('#va-result'); const resultText = assistantEl.querySelector('#va-result-text'); const resultAction = assistantEl.querySelector('#va-result-action'); - const fabMic = fab.querySelector('.va-fab-mic'); - const fabStop = fab.querySelector('.va-fab-stop'); + const fabMic = fab.querySelector('.va-fab-mic'); + const fabStop = fab.querySelector('.va-fab-stop'); // ── Estado ───────────────────────────────────────────────────────────────── let state = 'idle'; // idle | recording | processing @@ -233,7 +254,7 @@ export function mountVoiceAssistant() { panel.hidden = true; panelOpen = false; if (state === 'recording' && recorder) { - recorder.stop().catch(() => {}); + recorder.stop().catch(() => { }); recorder = null; } setStatus('idle', 'Di un comando…'); @@ -244,7 +265,7 @@ export function mountVoiceAssistant() { if (!panelOpen) { openPanel(); return; } if (state === 'recording') { - // Detener grabación + // Detener grabación manualmente setStatus('processing', 'Procesando audio…'); const blob = await recorder.stop(); recorder = null; @@ -254,10 +275,16 @@ export function mountVoiceAssistant() { if (state !== 'idle') return; - // Iniciar grabación + // Iniciar grabación con auto-stop por silencio try { - recorder = await startRecording(); - setStatus('recording', 'Escuchando…', 'Clic para detener'); + recorder = await startRecording(async () => { + if (state !== 'recording') return; + setStatus('processing', 'Procesando audio…'); + const blob = await recorder.stop(); + recorder = null; + await transcribe(blob); + }); + setStatus('recording', 'Escuchando…', 'Para solo al detectar silencio'); resultBox.hidden = true; // Pre-carga el modelo mientras graba (sin bloquear) if (!transcriberPromise) loadModel(); @@ -272,7 +299,7 @@ export function mountVoiceAssistant() { async function transcribe(blob) { try { // Carga el modelo si aún no está listo - setStatus('processing', 'Cargando modelo…', 'Solo la primera vez (~40 MB)'); + setStatus('processing', 'Cargando modelo…', 'Solo la primera vez (~140 MB)'); const transcriber = await loadModel(prog => { if (prog.status === 'progress') { const pct = Math.round(prog.progress ?? 0); @@ -313,10 +340,10 @@ export function mountVoiceAssistant() { } } catch (e) { console.error('[VA] Error en transcripción:', e?.message ?? e); - const msg = e?.message?.includes('vacío') ? 'Audio vacío, graba más tiempo' - : e?.message?.includes('session') ? 'Error cargando el modelo' - : e?.message?.includes('decodeAudioData') ? 'Formato de audio no soportado' - : 'Error al procesar'; + const msg = e?.message?.includes('vacío') ? 'Audio vacío, graba más tiempo' + : e?.message?.includes('session') ? 'Error cargando el modelo' + : e?.message?.includes('decodeAudioData') ? 'Formato de audio no soportado' + : 'Error al procesar'; setStatus('idle', msg); } } diff --git a/src/pages/pagesRegistry.js b/src/pages/pagesRegistry.js index 2c6da94..e1ef6c2 100755 --- a/src/pages/pagesRegistry.js +++ b/src/pages/pagesRegistry.js @@ -10,6 +10,7 @@ export const pagesRegistry = [ route: 'dashboard', name: 'Dashboard', icon: '', + voicePatterns: ['dashboard', 'inicio', 'ir al inicio', 'ir al dashboard', 'resumen', 'home', 'panel', 'principal'], requiresAuth: true, showInSidebar: true, render: renderDashboard @@ -18,6 +19,7 @@ export const pagesRegistry = [ route: 'invoices', name: 'Facturas', icon: '', + voicePatterns: ['facturas', 'ver facturas', 'ir a facturas', 'lista facturas', 'mis facturas', 'listado facturas'], requiresAuth: true, showInSidebar: true, render: renderFacturasPage @@ -26,6 +28,7 @@ export const pagesRegistry = [ route: 'create-invoice', name: 'Nueva Factura', icon: '+', + voicePatterns: ['nueva factura', 'crear factura', 'factura nueva', 'añadir factura'], requiresAuth: true, showInSidebar: false, // No mostrar en sidebar render: renderCreateInvoicePage @@ -34,6 +37,7 @@ export const pagesRegistry = [ route: 'clients', name: 'Clientes', icon: '', + voicePatterns: ['clientes', 'ver clientes', 'ir a clientes', 'lista clientes', 'mis clientes'], requiresAuth: true, showInSidebar: true, render: renderClientesPage @@ -41,8 +45,7 @@ export const pagesRegistry = [ { route: 'settings', name: 'Configuración', - icon: '', - requiresAuth: true, + icon: '', voicePatterns: ['configuración', 'configuracion', 'ajustes', 'settings', 'preferencias'], requiresAuth: true, showInSidebar: true, render: renderSettingsPage } diff --git a/src/services/voiceInput.js b/src/services/voiceInput.js index 077375b..9ed79e7 100644 --- a/src/services/voiceInput.js +++ b/src/services/voiceInput.js @@ -28,7 +28,7 @@ function loadModel(onProgress) { if (!transcriberPromise) { transcriberPromise = pipeline( 'automatic-speech-recognition', - 'Xenova/whisper-tiny', + 'Xenova/whisper-base', { progress_callback: onProgress } ).catch(err => { transcriberPromise = null; // permitir reintento @@ -50,7 +50,7 @@ function showTip(btn, text, duration = 3000) { return tip; } -async function startMediaRecorder() { +async function startMediaRecorder(onSilence) { const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false }); const mimeType = ['audio/webm;codecs=opus', 'audio/webm', 'audio/ogg;codecs=opus'] .find(t => MediaRecorder.isTypeSupported(t)) || ''; @@ -60,8 +60,55 @@ async function startMediaRecorder() { recorder.ondataavailable = e => { if (e.data?.size > 0) chunks.push(e.data); }; recorder.start(200); + // Detección de silencio via Web Audio API + const audioCtx = new AudioContext(); + const analyser = audioCtx.createAnalyser(); + analyser.fftSize = 512; + const source = audioCtx.createMediaStreamSource(stream); + source.connect(analyser); + const dataArray = new Uint8Array(analyser.frequencyBinCount); + + const SILENCE_THRESHOLD = 12; + const SILENCE_DELAY_MS = 1800; + const MIN_RECORD_MS = 700; + + const startTime = Date.now(); + let silenceStart = null; + let rafId = null; + let stopped = false; + + function tick() { + if (stopped) return; + analyser.getByteTimeDomainData(dataArray); + let sum = 0; + for (let i = 0; i < dataArray.length; i++) { + const v = dataArray[i] / 128.0 - 1.0; + sum += v * v; + } + const rms = Math.sqrt(sum / dataArray.length) * 128; + + if (Date.now() - startTime >= MIN_RECORD_MS) { + if (rms < SILENCE_THRESHOLD) { + if (!silenceStart) silenceStart = Date.now(); + else if (Date.now() - silenceStart >= SILENCE_DELAY_MS) { + stopped = true; + audioCtx.close().catch(() => {}); + onSilence?.(); + return; + } + } else { + silenceStart = null; + } + } + rafId = requestAnimationFrame(tick); + } + rafId = requestAnimationFrame(tick); + return { stop: () => new Promise(resolve => { + stopped = true; + if (rafId) cancelAnimationFrame(rafId); + audioCtx.close().catch(() => {}); recorder.onstop = () => { stream.getTracks().forEach(t => t.stop()); resolve(new Blob(chunks, { type: mimeType || 'audio/webm' })); @@ -125,9 +172,16 @@ export function attachVoiceInput(inputEl, { lang = 'es' } = {}) { // ── Iniciar grabación ────────────────────────────────────────────────── try { - mediaRecorder = await startMediaRecorder(); + mediaRecorder = await startMediaRecorder(async () => { + if (state !== 'recording') return; + showTip(btn, 'Procesando…', 0); + setState('processing'); + const blob = await mediaRecorder.stop(); + mediaRecorder = null; + await transcribe(blob); + }); setState('recording'); - showTip(btn, 'Hablando… clic para detener'); + showTip(btn, 'Hablando… para solo'); // Pre-carga el modelo en paralelo mientras el usuario habla if (!transcriberPromise) loadModel(); @@ -145,7 +199,7 @@ export function attachVoiceInput(inputEl, { lang = 'es' } = {}) { const transcriber = await loadModel(progress => { if (progress.status === 'progress') { const pct = Math.round(progress.progress ?? 0); - btn.title = `Cargando modelo: ${pct}%`; + btn.title = `Cargando modelo: ${pct}% (~140 MB primera vez)`; } }); tipLoad.remove();