update 优化录音功能模块的代码和火山模块
This commit is contained in:
552
pnpm-lock.yaml
generated
552
pnpm-lock.yaml
generated
@@ -75,6 +75,15 @@ importers:
|
|||||||
'@kevisual/registry':
|
'@kevisual/registry':
|
||||||
specifier: ^0.0.1
|
specifier: ^0.0.1
|
||||||
version: 0.0.1(typescript@5.9.3)
|
version: 0.0.1(typescript@5.9.3)
|
||||||
|
'@radix-ui/react-alert-dialog':
|
||||||
|
specifier: ^1.1.15
|
||||||
|
version: 1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
|
'@radix-ui/react-dialog':
|
||||||
|
specifier: ^1.1.15
|
||||||
|
version: 1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
|
'@radix-ui/react-slot':
|
||||||
|
specifier: ^1.2.3
|
||||||
|
version: 1.2.3(@types/react@19.2.2)(react@19.2.0)
|
||||||
'@ricky0123/vad-web':
|
'@ricky0123/vad-web':
|
||||||
specifier: ^0.0.28
|
specifier: ^0.0.28
|
||||||
version: 0.0.28
|
version: 0.0.28
|
||||||
@@ -84,6 +93,12 @@ importers:
|
|||||||
'@tailwindcss/vite':
|
'@tailwindcss/vite':
|
||||||
specifier: ^4.1.14
|
specifier: ^4.1.14
|
||||||
version: 4.1.14(vite@6.3.7(@types/node@24.7.2)(jiti@2.6.1)(lightningcss@1.30.1))
|
version: 4.1.14(vite@6.3.7(@types/node@24.7.2)(jiti@2.6.1)(lightningcss@1.30.1))
|
||||||
|
'@tanstack/react-form':
|
||||||
|
specifier: ^1.23.7
|
||||||
|
version: 1.23.7(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
|
'@tanstack/react-query':
|
||||||
|
specifier: ^5.90.5
|
||||||
|
version: 5.90.5(react@19.2.0)
|
||||||
astro:
|
astro:
|
||||||
specifier: ^5.14.4
|
specifier: ^5.14.4
|
||||||
version: 5.14.5(@types/node@24.7.2)(idb-keyval@6.2.2)(jiti@2.6.1)(lightningcss@1.30.1)(rollup@4.52.4)(typescript@5.9.3)
|
version: 5.14.5(@types/node@24.7.2)(idb-keyval@6.2.2)(jiti@2.6.1)(lightningcss@1.30.1)(rollup@4.52.4)(typescript@5.9.3)
|
||||||
@@ -161,7 +176,7 @@ importers:
|
|||||||
version: 7.11.0
|
version: 7.11.0
|
||||||
zustand:
|
zustand:
|
||||||
specifier: ^5.0.8
|
specifier: ^5.0.8
|
||||||
version: 5.0.8(@types/react@19.2.2)(react@19.2.0)
|
version: 5.0.8(@types/react@19.2.2)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0))
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@kevisual/types':
|
'@kevisual/types':
|
||||||
specifier: ^0.0.10
|
specifier: ^0.0.10
|
||||||
@@ -760,6 +775,190 @@ packages:
|
|||||||
'@protobufjs/utf8@1.1.0':
|
'@protobufjs/utf8@1.1.0':
|
||||||
resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==}
|
resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==}
|
||||||
|
|
||||||
|
'@radix-ui/primitive@1.1.3':
|
||||||
|
resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==}
|
||||||
|
|
||||||
|
'@radix-ui/react-alert-dialog@1.1.15':
|
||||||
|
resolution: {integrity: sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-compose-refs@1.1.2':
|
||||||
|
resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-context@1.1.2':
|
||||||
|
resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-dialog@1.1.15':
|
||||||
|
resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-dismissable-layer@1.1.11':
|
||||||
|
resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-focus-guards@1.1.3':
|
||||||
|
resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-focus-scope@1.1.7':
|
||||||
|
resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-id@1.1.1':
|
||||||
|
resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-portal@1.1.9':
|
||||||
|
resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-presence@1.1.5':
|
||||||
|
resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-primitive@2.1.3':
|
||||||
|
resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-slot@1.2.3':
|
||||||
|
resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-use-callback-ref@1.1.1':
|
||||||
|
resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-use-controllable-state@1.2.2':
|
||||||
|
resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-use-effect-event@0.0.2':
|
||||||
|
resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-use-escape-keydown@1.1.1':
|
||||||
|
resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-use-layout-effect@1.1.1':
|
||||||
|
resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@ricky0123/vad-web@0.0.28':
|
'@ricky0123/vad-web@0.0.28':
|
||||||
resolution: {integrity: sha512-Hvw8jN3r1SBxmjJa89HITxRcwlT6dc7CQPVtVQLrqfY8EeQcx41QeqKUol4lw8ZCeAIHKwYndHnB1K/4SAQJgQ==}
|
resolution: {integrity: sha512-Hvw8jN3r1SBxmjJa89HITxRcwlT6dc7CQPVtVQLrqfY8EeQcx41QeqKUol4lw8ZCeAIHKwYndHnB1K/4SAQJgQ==}
|
||||||
|
|
||||||
@@ -1051,6 +1250,39 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
vite: ^5.2.0 || ^6 || ^7
|
vite: ^5.2.0 || ^6 || ^7
|
||||||
|
|
||||||
|
'@tanstack/devtools-event-client@0.3.3':
|
||||||
|
resolution: {integrity: sha512-RfV+OPV/M3CGryYqTue684u10jUt55PEqeBOnOtCe6tAmHI9Iqyc8nHeDhWPEV9715gShuauFVaMc9RiUVNdwg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
'@tanstack/form-core@1.24.3':
|
||||||
|
resolution: {integrity: sha512-e+HzSD49NWr4aIqJWtPPzmi+/phBJAP3nSPN8dvxwmJWqAxuB/cH138EcmCFf3+oA7j3BXvwvTY0I+8UweGPjQ==}
|
||||||
|
|
||||||
|
'@tanstack/query-core@5.90.5':
|
||||||
|
resolution: {integrity: sha512-wLamYp7FaDq6ZnNehypKI5fNvxHPfTYylE0m/ZpuuzJfJqhR5Pxg9gvGBHZx4n7J+V5Rg5mZxHHTlv25Zt5u+w==}
|
||||||
|
|
||||||
|
'@tanstack/react-form@1.23.7':
|
||||||
|
resolution: {integrity: sha512-p/j9Gi2+s135sOjj48RjM+6xZQr1FVpliQlETLYBEGmmmxWHgYYs2b62mTDSnuv7AqtuZhpQ+t0CRFVfbQLsFA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@tanstack/react-start': ^1.130.10
|
||||||
|
react: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@tanstack/react-start':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@tanstack/react-query@5.90.5':
|
||||||
|
resolution: {integrity: sha512-pN+8UWpxZkEJ/Rnnj2v2Sxpx1WFlaa9L6a4UO89p6tTQbeo+m0MS8oYDjbggrR8QcTyjKoYWKS3xJQGr3ExT8Q==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^18 || ^19
|
||||||
|
|
||||||
|
'@tanstack/react-store@0.7.7':
|
||||||
|
resolution: {integrity: sha512-qqT0ufegFRDGSof9D/VqaZgjNgp4tRPHZIJq2+QIHkMUtHjaJ0lYrrXjeIUJvjnTbgPfSD1XgOMEt0lmANn6Zg==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
|
||||||
|
'@tanstack/store@0.7.7':
|
||||||
|
resolution: {integrity: sha512-xa6pTan1bcaqYDS9BDpSiS63qa6EoDkPN9RsRaxHuDdVDNntzq3xNwR5YKTU/V3SkSyC9T4YVOPh2zRQN0nhIQ==}
|
||||||
|
|
||||||
'@tweenjs/tween.js@23.1.3':
|
'@tweenjs/tween.js@23.1.3':
|
||||||
resolution: {integrity: sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==}
|
resolution: {integrity: sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==}
|
||||||
|
|
||||||
@@ -1263,6 +1495,10 @@ packages:
|
|||||||
argparse@2.0.1:
|
argparse@2.0.1:
|
||||||
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
||||||
|
|
||||||
|
aria-hidden@1.2.6:
|
||||||
|
resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
aria-query@5.3.2:
|
aria-query@5.3.2:
|
||||||
resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==}
|
resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -1439,6 +1675,9 @@ packages:
|
|||||||
supports-color:
|
supports-color:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
decode-formdata@0.9.0:
|
||||||
|
resolution: {integrity: sha512-q5uwOjR3Um5YD+ZWPOF/1sGHVW9A5rCrRwITQChRXlmPkxDFBqCm4jNTIVdGHNH9OnR+V9MoZVgRhsFb+ARbUw==}
|
||||||
|
|
||||||
decode-named-character-reference@1.2.0:
|
decode-named-character-reference@1.2.0:
|
||||||
resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==}
|
resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==}
|
||||||
|
|
||||||
@@ -1469,6 +1708,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
detect-node-es@1.1.0:
|
||||||
|
resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
|
||||||
|
|
||||||
deterministic-object-hash@2.0.2:
|
deterministic-object-hash@2.0.2:
|
||||||
resolution: {integrity: sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==}
|
resolution: {integrity: sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@@ -1677,6 +1919,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==}
|
resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
get-nonce@1.0.1:
|
||||||
|
resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
github-slugger@2.0.0:
|
github-slugger@2.0.0:
|
||||||
resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==}
|
resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==}
|
||||||
|
|
||||||
@@ -2555,12 +2801,42 @@ packages:
|
|||||||
resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
|
resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
react-remove-scroll-bar@2.3.8:
|
||||||
|
resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
react-remove-scroll@2.7.1:
|
||||||
|
resolution: {integrity: sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
react-resizable-panels@3.0.6:
|
react-resizable-panels@3.0.6:
|
||||||
resolution: {integrity: sha512-b3qKHQ3MLqOgSS+FRYKapNkJZf5EQzuf6+RLiq1/IlTHw99YrZ2NJZLk4hQIzTnnIkRg2LUqyVinu6YWWpUYew==}
|
resolution: {integrity: sha512-b3qKHQ3MLqOgSS+FRYKapNkJZf5EQzuf6+RLiq1/IlTHw99YrZ2NJZLk4hQIzTnnIkRg2LUqyVinu6YWWpUYew==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
|
react: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
|
||||||
react-dom: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
|
react-dom: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
|
||||||
|
|
||||||
|
react-style-singleton@2.2.3:
|
||||||
|
resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
react-toastify@11.0.5:
|
react-toastify@11.0.5:
|
||||||
resolution: {integrity: sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA==}
|
resolution: {integrity: sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -3058,6 +3334,31 @@ packages:
|
|||||||
url-parse@1.5.10:
|
url-parse@1.5.10:
|
||||||
resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==}
|
resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==}
|
||||||
|
|
||||||
|
use-callback-ref@1.3.3:
|
||||||
|
resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
use-sidecar@1.1.3:
|
||||||
|
resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
use-sync-external-store@1.6.0:
|
||||||
|
resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
|
||||||
util-deprecate@1.0.2:
|
util-deprecate@1.0.2:
|
||||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||||
|
|
||||||
@@ -3799,6 +4100,163 @@ snapshots:
|
|||||||
|
|
||||||
'@protobufjs/utf8@1.1.0': {}
|
'@protobufjs/utf8@1.1.0': {}
|
||||||
|
|
||||||
|
'@radix-ui/primitive@1.1.3': {}
|
||||||
|
|
||||||
|
'@radix-ui/react-alert-dialog@1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/primitive': 1.1.3
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0)
|
||||||
|
'@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0)
|
||||||
|
'@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
|
'@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0)
|
||||||
|
react: 19.2.0
|
||||||
|
react-dom: 19.2.0(react@19.2.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.2
|
||||||
|
'@types/react-dom': 19.2.2(@types/react@19.2.2)
|
||||||
|
|
||||||
|
'@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.2)(react@19.2.0)':
|
||||||
|
dependencies:
|
||||||
|
react: 19.2.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.2
|
||||||
|
|
||||||
|
'@radix-ui/react-context@1.1.2(@types/react@19.2.2)(react@19.2.0)':
|
||||||
|
dependencies:
|
||||||
|
react: 19.2.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.2
|
||||||
|
|
||||||
|
'@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/primitive': 1.1.3
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0)
|
||||||
|
'@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0)
|
||||||
|
'@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
|
'@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.2)(react@19.2.0)
|
||||||
|
'@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
|
'@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0)
|
||||||
|
'@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
|
'@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
|
'@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0)
|
||||||
|
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0)
|
||||||
|
aria-hidden: 1.2.6
|
||||||
|
react: 19.2.0
|
||||||
|
react-dom: 19.2.0(react@19.2.0)
|
||||||
|
react-remove-scroll: 2.7.1(@types/react@19.2.2)(react@19.2.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.2
|
||||||
|
'@types/react-dom': 19.2.2(@types/react@19.2.2)
|
||||||
|
|
||||||
|
'@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/primitive': 1.1.3
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0)
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
|
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0)
|
||||||
|
'@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.2)(react@19.2.0)
|
||||||
|
react: 19.2.0
|
||||||
|
react-dom: 19.2.0(react@19.2.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.2
|
||||||
|
'@types/react-dom': 19.2.2(@types/react@19.2.2)
|
||||||
|
|
||||||
|
'@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.2)(react@19.2.0)':
|
||||||
|
dependencies:
|
||||||
|
react: 19.2.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.2
|
||||||
|
|
||||||
|
'@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0)
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
|
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0)
|
||||||
|
react: 19.2.0
|
||||||
|
react-dom: 19.2.0(react@19.2.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.2
|
||||||
|
'@types/react-dom': 19.2.2(@types/react@19.2.2)
|
||||||
|
|
||||||
|
'@radix-ui/react-id@1.1.1(@types/react@19.2.2)(react@19.2.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0)
|
||||||
|
react: 19.2.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.2
|
||||||
|
|
||||||
|
'@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0)
|
||||||
|
react: 19.2.0
|
||||||
|
react-dom: 19.2.0(react@19.2.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.2
|
||||||
|
'@types/react-dom': 19.2.2(@types/react@19.2.2)
|
||||||
|
|
||||||
|
'@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0)
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0)
|
||||||
|
react: 19.2.0
|
||||||
|
react-dom: 19.2.0(react@19.2.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.2
|
||||||
|
'@types/react-dom': 19.2.2(@types/react@19.2.2)
|
||||||
|
|
||||||
|
'@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0)
|
||||||
|
react: 19.2.0
|
||||||
|
react-dom: 19.2.0(react@19.2.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.2
|
||||||
|
'@types/react-dom': 19.2.2(@types/react@19.2.2)
|
||||||
|
|
||||||
|
'@radix-ui/react-slot@1.2.3(@types/react@19.2.2)(react@19.2.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0)
|
||||||
|
react: 19.2.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.2
|
||||||
|
|
||||||
|
'@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.2)(react@19.2.0)':
|
||||||
|
dependencies:
|
||||||
|
react: 19.2.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.2
|
||||||
|
|
||||||
|
'@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.2)(react@19.2.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.2)(react@19.2.0)
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0)
|
||||||
|
react: 19.2.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.2
|
||||||
|
|
||||||
|
'@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.2)(react@19.2.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0)
|
||||||
|
react: 19.2.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.2
|
||||||
|
|
||||||
|
'@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.2)(react@19.2.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0)
|
||||||
|
react: 19.2.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.2
|
||||||
|
|
||||||
|
'@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.2)(react@19.2.0)':
|
||||||
|
dependencies:
|
||||||
|
react: 19.2.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.2
|
||||||
|
|
||||||
'@ricky0123/vad-web@0.0.28':
|
'@ricky0123/vad-web@0.0.28':
|
||||||
dependencies:
|
dependencies:
|
||||||
onnxruntime-web: 1.23.0
|
onnxruntime-web: 1.23.0
|
||||||
@@ -4024,6 +4482,39 @@ snapshots:
|
|||||||
tailwindcss: 4.1.14
|
tailwindcss: 4.1.14
|
||||||
vite: 6.3.7(@types/node@24.7.2)(jiti@2.6.1)(lightningcss@1.30.1)
|
vite: 6.3.7(@types/node@24.7.2)(jiti@2.6.1)(lightningcss@1.30.1)
|
||||||
|
|
||||||
|
'@tanstack/devtools-event-client@0.3.3': {}
|
||||||
|
|
||||||
|
'@tanstack/form-core@1.24.3':
|
||||||
|
dependencies:
|
||||||
|
'@tanstack/devtools-event-client': 0.3.3
|
||||||
|
'@tanstack/store': 0.7.7
|
||||||
|
|
||||||
|
'@tanstack/query-core@5.90.5': {}
|
||||||
|
|
||||||
|
'@tanstack/react-form@1.23.7(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
|
||||||
|
dependencies:
|
||||||
|
'@tanstack/form-core': 1.24.3
|
||||||
|
'@tanstack/react-store': 0.7.7(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
|
decode-formdata: 0.9.0
|
||||||
|
devalue: 5.3.2
|
||||||
|
react: 19.2.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- react-dom
|
||||||
|
|
||||||
|
'@tanstack/react-query@5.90.5(react@19.2.0)':
|
||||||
|
dependencies:
|
||||||
|
'@tanstack/query-core': 5.90.5
|
||||||
|
react: 19.2.0
|
||||||
|
|
||||||
|
'@tanstack/react-store@0.7.7(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
|
||||||
|
dependencies:
|
||||||
|
'@tanstack/store': 0.7.7
|
||||||
|
react: 19.2.0
|
||||||
|
react-dom: 19.2.0(react@19.2.0)
|
||||||
|
use-sync-external-store: 1.6.0(react@19.2.0)
|
||||||
|
|
||||||
|
'@tanstack/store@0.7.7': {}
|
||||||
|
|
||||||
'@tweenjs/tween.js@23.1.3': {}
|
'@tweenjs/tween.js@23.1.3': {}
|
||||||
|
|
||||||
'@types/babel__core@7.20.5':
|
'@types/babel__core@7.20.5':
|
||||||
@@ -4301,6 +4792,10 @@ snapshots:
|
|||||||
|
|
||||||
argparse@2.0.1: {}
|
argparse@2.0.1: {}
|
||||||
|
|
||||||
|
aria-hidden@1.2.6:
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
aria-query@5.3.2: {}
|
aria-query@5.3.2: {}
|
||||||
|
|
||||||
array-iterate@2.0.1: {}
|
array-iterate@2.0.1: {}
|
||||||
@@ -4539,6 +5034,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ms: 2.1.3
|
ms: 2.1.3
|
||||||
|
|
||||||
|
decode-formdata@0.9.0: {}
|
||||||
|
|
||||||
decode-named-character-reference@1.2.0:
|
decode-named-character-reference@1.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
character-entities: 2.0.2
|
character-entities: 2.0.2
|
||||||
@@ -4560,6 +5057,8 @@ snapshots:
|
|||||||
|
|
||||||
detect-libc@2.1.2: {}
|
detect-libc@2.1.2: {}
|
||||||
|
|
||||||
|
detect-node-es@1.1.0: {}
|
||||||
|
|
||||||
deterministic-object-hash@2.0.2:
|
deterministic-object-hash@2.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
base-64: 1.0.0
|
base-64: 1.0.0
|
||||||
@@ -4782,6 +5281,8 @@ snapshots:
|
|||||||
|
|
||||||
get-east-asian-width@1.4.0: {}
|
get-east-asian-width@1.4.0: {}
|
||||||
|
|
||||||
|
get-nonce@1.0.1: {}
|
||||||
|
|
||||||
github-slugger@2.0.0: {}
|
github-slugger@2.0.0: {}
|
||||||
|
|
||||||
glob-parent@5.1.2:
|
glob-parent@5.1.2:
|
||||||
@@ -6002,11 +6503,38 @@ snapshots:
|
|||||||
|
|
||||||
react-refresh@0.17.0: {}
|
react-refresh@0.17.0: {}
|
||||||
|
|
||||||
|
react-remove-scroll-bar@2.3.8(@types/react@19.2.2)(react@19.2.0):
|
||||||
|
dependencies:
|
||||||
|
react: 19.2.0
|
||||||
|
react-style-singleton: 2.2.3(@types/react@19.2.2)(react@19.2.0)
|
||||||
|
tslib: 2.8.1
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.2
|
||||||
|
|
||||||
|
react-remove-scroll@2.7.1(@types/react@19.2.2)(react@19.2.0):
|
||||||
|
dependencies:
|
||||||
|
react: 19.2.0
|
||||||
|
react-remove-scroll-bar: 2.3.8(@types/react@19.2.2)(react@19.2.0)
|
||||||
|
react-style-singleton: 2.2.3(@types/react@19.2.2)(react@19.2.0)
|
||||||
|
tslib: 2.8.1
|
||||||
|
use-callback-ref: 1.3.3(@types/react@19.2.2)(react@19.2.0)
|
||||||
|
use-sidecar: 1.1.3(@types/react@19.2.2)(react@19.2.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.2
|
||||||
|
|
||||||
react-resizable-panels@3.0.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
|
react-resizable-panels@3.0.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 19.2.0
|
react: 19.2.0
|
||||||
react-dom: 19.2.0(react@19.2.0)
|
react-dom: 19.2.0(react@19.2.0)
|
||||||
|
|
||||||
|
react-style-singleton@2.2.3(@types/react@19.2.2)(react@19.2.0):
|
||||||
|
dependencies:
|
||||||
|
get-nonce: 1.0.1
|
||||||
|
react: 19.2.0
|
||||||
|
tslib: 2.8.1
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.2
|
||||||
|
|
||||||
react-toastify@11.0.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
|
react-toastify@11.0.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
clsx: 2.1.1
|
clsx: 2.1.1
|
||||||
@@ -6597,6 +7125,25 @@ snapshots:
|
|||||||
querystringify: 2.2.0
|
querystringify: 2.2.0
|
||||||
requires-port: 1.0.0
|
requires-port: 1.0.0
|
||||||
|
|
||||||
|
use-callback-ref@1.3.3(@types/react@19.2.2)(react@19.2.0):
|
||||||
|
dependencies:
|
||||||
|
react: 19.2.0
|
||||||
|
tslib: 2.8.1
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.2
|
||||||
|
|
||||||
|
use-sidecar@1.1.3(@types/react@19.2.2)(react@19.2.0):
|
||||||
|
dependencies:
|
||||||
|
detect-node-es: 1.1.0
|
||||||
|
react: 19.2.0
|
||||||
|
tslib: 2.8.1
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.2
|
||||||
|
|
||||||
|
use-sync-external-store@1.6.0(react@19.2.0):
|
||||||
|
dependencies:
|
||||||
|
react: 19.2.0
|
||||||
|
|
||||||
util-deprecate@1.0.2: {}
|
util-deprecate@1.0.2: {}
|
||||||
|
|
||||||
uuid@8.3.2: {}
|
uuid@8.3.2: {}
|
||||||
@@ -6698,9 +7245,10 @@ snapshots:
|
|||||||
|
|
||||||
zod@3.25.76: {}
|
zod@3.25.76: {}
|
||||||
|
|
||||||
zustand@5.0.8(@types/react@19.2.2)(react@19.2.0):
|
zustand@5.0.8(@types/react@19.2.2)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)):
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.2.2
|
'@types/react': 19.2.2
|
||||||
react: 19.2.0
|
react: 19.2.0
|
||||||
|
use-sync-external-store: 1.6.0(react@19.2.0)
|
||||||
|
|
||||||
zwitch@2.0.4: {}
|
zwitch@2.0.4: {}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
"build": "astro build",
|
"build": "astro build",
|
||||||
"preview": "astro preview",
|
"preview": "astro preview",
|
||||||
"pub": "envision deploy ./dist -k light-code-center -v 0.0.1 -u",
|
"pub": "envision deploy ./dist -k light-code-center -v 0.0.1 -u",
|
||||||
|
"ui": "pnpm dlx shadcn@latest add ",
|
||||||
"sn": "pnpm dlx shadcn@latest add "
|
"sn": "pnpm dlx shadcn@latest add "
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -25,9 +26,14 @@
|
|||||||
"@kevisual/query": "^0.0.29",
|
"@kevisual/query": "^0.0.29",
|
||||||
"@kevisual/query-login": "^0.0.6",
|
"@kevisual/query-login": "^0.0.6",
|
||||||
"@kevisual/registry": "^0.0.1",
|
"@kevisual/registry": "^0.0.1",
|
||||||
|
"@radix-ui/react-alert-dialog": "^1.1.15",
|
||||||
|
"@radix-ui/react-dialog": "^1.1.15",
|
||||||
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
"@ricky0123/vad-web": "^0.0.28",
|
"@ricky0123/vad-web": "^0.0.28",
|
||||||
"@szhsin/react-menu": "^4.5.0",
|
"@szhsin/react-menu": "^4.5.0",
|
||||||
"@tailwindcss/vite": "^4.1.14",
|
"@tailwindcss/vite": "^4.1.14",
|
||||||
|
"@tanstack/react-form": "^1.23.7",
|
||||||
|
"@tanstack/react-query": "^5.90.5",
|
||||||
"astro": "^5.14.4",
|
"astro": "^5.14.4",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
|||||||
@@ -333,8 +333,8 @@ export const Table: React.FC<TableProps> = ({
|
|||||||
|
|
||||||
// Ctrl/Cmd + A 全选
|
// Ctrl/Cmd + A 全选
|
||||||
if ((event.ctrlKey || event.metaKey) && event.key === 'a') {
|
if ((event.ctrlKey || event.metaKey) && event.key === 'a') {
|
||||||
event.preventDefault();
|
// event.preventDefault();
|
||||||
handleSelectAll(true);
|
// handleSelectAll(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
46
web/src/apps/muse/components/PasswordInput.tsx
Normal file
46
web/src/apps/muse/components/PasswordInput.tsx
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Eye, EyeOff } from 'lucide-react';
|
||||||
|
|
||||||
|
interface PasswordInputProps {
|
||||||
|
value: string;
|
||||||
|
onChange: (value: string) => void;
|
||||||
|
placeholder?: string;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PasswordInput: React.FC<PasswordInputProps> = ({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
placeholder,
|
||||||
|
className = '',
|
||||||
|
}) => {
|
||||||
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
|
||||||
|
const togglePasswordVisibility = () => {
|
||||||
|
setShowPassword(!showPassword);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative">
|
||||||
|
<input
|
||||||
|
type={showPassword ? 'text' : 'password'}
|
||||||
|
value={value}
|
||||||
|
onChange={(e) => onChange(e.target.value)}
|
||||||
|
placeholder={placeholder}
|
||||||
|
className={`pr-10 ${className}`}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={togglePasswordVisibility}
|
||||||
|
className="absolute right-3 top-1/2 transform -translate-y-1/2 p-1 hover:bg-gray-100 rounded transition-colors"
|
||||||
|
title={showPassword ? '隐藏密码' : '显示密码'}
|
||||||
|
>
|
||||||
|
{showPassword ? (
|
||||||
|
<EyeOff className="w-4 h-4 text-gray-500" />
|
||||||
|
) : (
|
||||||
|
<Eye className="w-4 h-4 text-gray-500" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -3,7 +3,7 @@ import { AuthProvider } from '../login/AuthProvider';
|
|||||||
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
|
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
|
||||||
import { useState, useRef } from 'react';
|
import { useState, useRef } from 'react';
|
||||||
|
|
||||||
import { App as Voice } from './videos/index.tsx';
|
import { App as Voice } from './voice/index.tsx';
|
||||||
import { ChatInterface } from './prompts/index.tsx';
|
import { ChatInterface } from './prompts/index.tsx';
|
||||||
import { BaseApp } from './base/index.tsx';
|
import { BaseApp } from './base/index.tsx';
|
||||||
import { exampleUsage, markService } from './modules/mark-service.ts';
|
import { exampleUsage, markService } from './modules/mark-service.ts';
|
||||||
@@ -201,7 +201,7 @@ export const App: React.FC = () => {
|
|||||||
<MuseApp />
|
<MuseApp />
|
||||||
<ToastContainer
|
<ToastContainer
|
||||||
position="top-right"
|
position="top-right"
|
||||||
autoClose={5000}
|
autoClose={1000}
|
||||||
hideProgressBar={false}
|
hideProgressBar={false}
|
||||||
newestOnTop={false}
|
newestOnTop={false}
|
||||||
closeOnClick
|
closeOnClick
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
@import 'tailwindcss';
|
|
||||||
|
|
||||||
.low-energy-spin {
|
|
||||||
animation: 2.5s linear 0s infinite normal forwards running spin;
|
|
||||||
}
|
|
||||||
148
web/src/apps/muse/voice/modules/SettingModal.tsx
Normal file
148
web/src/apps/muse/voice/modules/SettingModal.tsx
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "../../../../components/ui/dialog";
|
||||||
|
import { useSettingStore } from '../store/settingStore';
|
||||||
|
import { PasswordInput } from '../../components/PasswordInput';
|
||||||
|
import { X, RotateCcw } from 'lucide-react';
|
||||||
|
|
||||||
|
export const SettingModal: React.FC = () => {
|
||||||
|
const {
|
||||||
|
isOpen,
|
||||||
|
autoRecognize,
|
||||||
|
listen,
|
||||||
|
volcengineAucAppId,
|
||||||
|
volcengineAucToken,
|
||||||
|
closeModal,
|
||||||
|
setAutoRecognize,
|
||||||
|
setListen,
|
||||||
|
setVolcengineAucAppId,
|
||||||
|
setVolcengineAucToken,
|
||||||
|
resetToDefault,
|
||||||
|
} = useSettingStore();
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
closeModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReset = () => {
|
||||||
|
if (window.confirm('确定要重置所有设置为默认值吗?')) {
|
||||||
|
resetToDefault();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={isOpen} onOpenChange={handleClose} >
|
||||||
|
<DialogContent className="max-w-md max-h-[80vh] overflow-y-auto" showCloseButton={false}>
|
||||||
|
<DialogHeader>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<DialogTitle>语音设置</DialogTitle>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<button
|
||||||
|
onClick={handleClose}
|
||||||
|
className="p-1 hover:bg-gray-100 rounded transition-colors cursor-pointer"
|
||||||
|
>
|
||||||
|
<X className="w-4 h-4 text-gray-500" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<DialogDescription>
|
||||||
|
配置语音录制和识别相关设置
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
{/* 语音识别设置 */}
|
||||||
|
<div className="space-y-4">
|
||||||
|
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<label className="text-sm text-gray-700">自动识别语音</label>
|
||||||
|
<div className="relative">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={autoRecognize}
|
||||||
|
onChange={(e) => setAutoRecognize(e.target.checked)}
|
||||||
|
className="sr-only"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
onClick={() => setAutoRecognize(!autoRecognize)}
|
||||||
|
className={`w-11 h-6 rounded-full cursor-pointer transition-colors ${autoRecognize ? 'bg-blue-600' : 'bg-gray-200'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`w-5 h-5 bg-white rounded-full shadow-md transform transition-transform ${autoRecognize ? 'translate-x-5' : 'translate-x-0.5'
|
||||||
|
} mt-0.5`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<label className="text-sm text-gray-700">自动启动监听</label>
|
||||||
|
<div className="relative">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={listen}
|
||||||
|
onChange={(e) => setListen(e.target.checked)}
|
||||||
|
className="sr-only"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
onClick={() => setListen(!listen)}
|
||||||
|
className={`w-11 h-6 rounded-full cursor-pointer transition-colors ${listen ? 'bg-blue-600' : 'bg-gray-200'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`w-5 h-5 bg-white rounded-full shadow-md transform transition-transform ${listen ? 'translate-x-5' : 'translate-x-0.5'
|
||||||
|
} mt-0.5`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 火山引擎配置 */}
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h3 className="text-sm font-medium text-gray-900">火山引擎配置</h3>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm text-gray-700 mb-1">App ID</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={volcengineAucAppId}
|
||||||
|
onChange={(e) => setVolcengineAucAppId(e.target.value)}
|
||||||
|
placeholder="请输入火山引擎 App ID"
|
||||||
|
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm text-gray-700 mb-1">Token</label>
|
||||||
|
<PasswordInput
|
||||||
|
value={volcengineAucToken}
|
||||||
|
onChange={setVolcengineAucToken}
|
||||||
|
placeholder="请输入火山引擎 Token"
|
||||||
|
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end space-x-3 pt-4 border-t">
|
||||||
|
<button
|
||||||
|
onClick={handleClose}
|
||||||
|
className="px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-md transition-colors"
|
||||||
|
>
|
||||||
|
关闭
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -2,13 +2,26 @@ import { MicVAD, utils } from "@ricky0123/vad-web"
|
|||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { useState, useEffect, useRef } from "react";
|
import { useState, useEffect, useRef } from "react";
|
||||||
import './style.css'
|
import './style.css'
|
||||||
import { MoreHorizontal, Play, Pause } from "lucide-react";
|
import { MoreHorizontal, Play, Pause, Settings, FileAudio, StopCircle, Loader } from "lucide-react";
|
||||||
import { Menu, MenuItem, MenuButton, } from '@szhsin/react-menu';
|
import { Menu, MenuItem, MenuButton, } from '@szhsin/react-menu';
|
||||||
import '@szhsin/react-menu/dist/index.css';
|
import '@szhsin/react-menu/dist/index.css';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { Speak } from "./speak-db/speak";
|
import { Speak } from "./speak-db/speak";
|
||||||
import { useVoiceStore } from "../store/voiceStore";
|
import { useVoiceStore } from "../store/voiceStore";
|
||||||
|
import { useSettingStore } from "../store/settingStore";
|
||||||
|
import { SettingModal } from "./SettingModal";
|
||||||
|
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
AlertDialogTrigger,
|
||||||
|
} from "../../../../components/ui/alert-dialog";
|
||||||
|
|
||||||
type VadVoiceProps = {
|
type VadVoiceProps = {
|
||||||
data: Speak;
|
data: Speak;
|
||||||
@@ -136,19 +149,51 @@ const VoicePlayer = ({ data }: VadVoiceProps) => {
|
|||||||
<span>识别语音</span>
|
<span>识别语音</span>
|
||||||
</div>
|
</div>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem onClick={handleDelete}>
|
{data.text && data.text.trim() ? (
|
||||||
<div className="flex items-center space-x-2">
|
<AlertDialog>
|
||||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<AlertDialogTrigger asChild>
|
||||||
<path
|
<MenuItem>
|
||||||
strokeLinecap="round"
|
<div className="flex items-center space-x-2">
|
||||||
strokeLinejoin="round"
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
strokeWidth={2}
|
<path
|
||||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1-1H8a1 1 0 00-1 1v3M4 7h16"
|
strokeLinecap="round"
|
||||||
/>
|
strokeLinejoin="round"
|
||||||
</svg>
|
strokeWidth={2}
|
||||||
<span>删除</span>
|
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1-1H8a1 1 0 00-1 1v3M4 7h16"
|
||||||
</div>
|
/>
|
||||||
</MenuItem>
|
</svg>
|
||||||
|
<span>删除</span>
|
||||||
|
</div>
|
||||||
|
</MenuItem>
|
||||||
|
</AlertDialogTrigger>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>确认删除</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
此语音包含文字内容:"{data.text}"。删除后将无法恢复,确定要删除吗?
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>取消</AlertDialogCancel>
|
||||||
|
<AlertDialogAction onClick={handleDelete}>确认删除</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
) : (
|
||||||
|
<MenuItem onClick={handleDelete}>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1-1H8a1 1 0 00-1 1v3M4 7h16"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span>删除</span>
|
||||||
|
</div>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
</Menu>
|
</Menu>
|
||||||
{/* 播放/暂停按钮 */}
|
{/* 播放/暂停按钮 */}
|
||||||
{!isPlaying ? (
|
{!isPlaying ? (
|
||||||
@@ -187,11 +232,92 @@ export const ShowVoicePlayer = ({ data }: { data: Speak[] }) => {
|
|||||||
<VoicePlayer data={item} />
|
<VoicePlayer data={item} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="text-xs text-gray-400 truncate">
|
<div className="flex items-center justify-between space-x-2">
|
||||||
{new Date(item.timestamp).toLocaleTimeString()}
|
<div>
|
||||||
</div>
|
<div className="text-xs text-gray-400 truncate">
|
||||||
<div className="text-xs text-gray-300">
|
{new Date(item.timestamp).toLocaleTimeString()}
|
||||||
#{item.no}
|
</div>
|
||||||
|
<div className="text-xs text-gray-300">
|
||||||
|
#{item.no}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-1">
|
||||||
|
{item.text && item.text.trim() ? (
|
||||||
|
<AlertDialog>
|
||||||
|
<AlertDialogTrigger asChild>
|
||||||
|
<button
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
className="w-5 h-5 hover:bg-red-100 rounded flex items-center justify-center text-red-500 transition-colors cursor-pointer"
|
||||||
|
title="删除"
|
||||||
|
>
|
||||||
|
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1-1H8a1 1 0 00-1 1v3M4 7h16"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</AlertDialogTrigger>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>确认删除</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
此语音包含文字内容:"{item.text}"。删除后将无法恢复,确定要删除吗?
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>取消</AlertDialogCancel>
|
||||||
|
<AlertDialogAction
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
const { deleteVoice } = useVoiceStore.getState();
|
||||||
|
deleteVoice(item.id)
|
||||||
|
.then(() => {
|
||||||
|
console.log('语音记录删除成功');
|
||||||
|
toast.success('删除成功', { autoClose: 200 });
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('删除语音记录失败:', error);
|
||||||
|
toast.error('删除失败: ' + (error instanceof Error ? error.message : '未知错误'));
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
确认删除
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
const { deleteVoice } = useVoiceStore.getState();
|
||||||
|
deleteVoice(item.id)
|
||||||
|
.then(() => {
|
||||||
|
console.log('语音记录删除成功');
|
||||||
|
toast.success('删除成功', { autoClose: 200 });
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('删除语音记录失败:', error);
|
||||||
|
toast.error('删除失败: ' + (error instanceof Error ? error.message : '未知错误'));
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
className="w-5 h-5 hover:bg-red-100 rounded flex items-center justify-center text-red-500 transition-colors cursor-pointer"
|
||||||
|
title="删除"
|
||||||
|
>
|
||||||
|
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1-1H8a1 1 0 00-1 1v3M4 7h16"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{item.text && (
|
{item.text && (
|
||||||
<div
|
<div
|
||||||
@@ -230,7 +356,15 @@ export const VadVoice = () => {
|
|||||||
setError: setStoreError
|
setError: setStoreError
|
||||||
} = useVoiceStore();
|
} = useVoiceStore();
|
||||||
|
|
||||||
const [listen, setListen] = useState<boolean>(false);
|
// 使用设置 store
|
||||||
|
const {
|
||||||
|
openModal: openSettingModal,
|
||||||
|
listen,
|
||||||
|
setListen,
|
||||||
|
autoRecognize,
|
||||||
|
setAutoRecognize
|
||||||
|
} = useSettingStore();
|
||||||
|
|
||||||
const [vadStatus, setVadStatus] = useState<'idle' | 'initializing' | 'ready' | 'error'>('idle');
|
const [vadStatus, setVadStatus] = useState<'idle' | 'initializing' | 'ready' | 'error'>('idle');
|
||||||
const [realListen, setRealListen] = useState<boolean>(false);
|
const [realListen, setRealListen] = useState<boolean>(false);
|
||||||
const [errorMessage, setErrorMessage] = useState<string>('');
|
const [errorMessage, setErrorMessage] = useState<string>('');
|
||||||
@@ -238,7 +372,8 @@ export const VadVoice = () => {
|
|||||||
const ref = useRef<MicVAD | null>(null);
|
const ref = useRef<MicVAD | null>(null);
|
||||||
const initializingRef = useRef<boolean>(false);
|
const initializingRef = useRef<boolean>(false);
|
||||||
|
|
||||||
async function initializeVAD() {
|
async function initializeVAD(ls: boolean = true) {
|
||||||
|
if (!ls) { return }
|
||||||
if (ref.current || initializingRef.current) return;
|
if (ref.current || initializingRef.current) return;
|
||||||
|
|
||||||
initializingRef.current = true;
|
initializingRef.current = true;
|
||||||
@@ -247,7 +382,6 @@ export const VadVoice = () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
console.log('Starting VAD initialization...');
|
console.log('Starting VAD initialization...');
|
||||||
|
|
||||||
// 添加延迟确保资源加载完成
|
// 添加延迟确保资源加载完成
|
||||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||||
|
|
||||||
@@ -310,7 +444,7 @@ export const VadVoice = () => {
|
|||||||
const handleUserInteraction = async () => {
|
const handleUserInteraction = async () => {
|
||||||
if (!userInteracted) {
|
if (!userInteracted) {
|
||||||
setUserInteracted(true);
|
setUserInteracted(true);
|
||||||
await initializeVAD();
|
// await initializeVAD();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -318,7 +452,6 @@ export const VadVoice = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initializeStore();
|
initializeStore();
|
||||||
}, [initializeStore]);
|
}, [initializeStore]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 页面加载时不自动初始化,等待用户交互
|
// 页面加载时不自动初始化,等待用户交互
|
||||||
const handleFirstClick = () => {
|
const handleFirstClick = () => {
|
||||||
@@ -329,16 +462,23 @@ export const VadVoice = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener('click', handleFirstClick);
|
document.addEventListener('click', handleFirstClick);
|
||||||
// handleUserInteraction()
|
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener('click', handleFirstClick);
|
document.removeEventListener('click', handleFirstClick);
|
||||||
// 清理 VAD 资源
|
|
||||||
if (ref.current) {
|
|
||||||
ref.current.destroy();
|
|
||||||
ref.current = null;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}, [])
|
}, [])
|
||||||
|
useEffect(() => {
|
||||||
|
if (!userInteracted) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log('VadVoice listen changed:', listen, userInteracted);
|
||||||
|
initializeVAD(listen);
|
||||||
|
return () => {
|
||||||
|
ref.current?.destroy?.();
|
||||||
|
ref.current = null;
|
||||||
|
setVadStatus('idle');
|
||||||
|
}
|
||||||
|
|
||||||
|
}, [listen, userInteracted]);
|
||||||
const close = () => {
|
const close = () => {
|
||||||
if (ref.current) {
|
if (ref.current) {
|
||||||
ref.current.destroy();
|
ref.current.destroy();
|
||||||
@@ -369,20 +509,26 @@ export const VadVoice = () => {
|
|||||||
return <div className="h-full flex flex-col">
|
return <div className="h-full flex flex-col">
|
||||||
{/* Audio Recordings List */}
|
{/* Audio Recordings List */}
|
||||||
<div className="flex-1 overflow-y-auto px-2 py-3 min-h-0 max-h-200">
|
<div className="flex-1 overflow-y-auto px-2 py-3 min-h-0 max-h-200">
|
||||||
{!userInteracted && vadStatus === 'idle' ? (
|
{
|
||||||
<div className="text-center text-gray-400 text-sm py-8">
|
voiceList.length === 0 ? (
|
||||||
|
<div className="text-center text-gray-400 text-sm py-8">
|
||||||
|
<div className="mb-2">🎤</div>
|
||||||
|
<div>No recordings yet</div>
|
||||||
|
<div className="text-xs mt-1">Start talking to record</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<ShowVoicePlayer data={voiceList} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{!userInteracted && vadStatus === 'idle' && listen && (
|
||||||
|
<div className="text-center text-gray-400 text-sm py-8" onClick={() => {
|
||||||
|
setUserInteracted(true);
|
||||||
|
initializeVAD(listen)
|
||||||
|
}}>
|
||||||
<div className="mb-2">🎤</div>
|
<div className="mb-2">🎤</div>
|
||||||
<div>Click anywhere to initialize microphone</div>
|
<div>Click anywhere to initialize microphone</div>
|
||||||
<div className="text-xs mt-1">Browser requires user interaction for microphone access</div>
|
<div className="text-xs mt-1">Browser requires user interaction for microphone access</div>
|
||||||
</div>
|
</div>
|
||||||
) : voiceList.length === 0 ? (
|
|
||||||
<div className="text-center text-gray-400 text-sm py-8">
|
|
||||||
<div className="mb-2">🎤</div>
|
|
||||||
<div>No recordings yet</div>
|
|
||||||
<div className="text-xs mt-1">Start talking to record</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<ShowVoicePlayer data={voiceList} />
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -401,7 +547,7 @@ export const VadVoice = () => {
|
|||||||
<div className="absolute -top-1 -right-1 w-3 h-3 bg-blue-500 rounded-full animate-pulse"></div>
|
<div className="absolute -top-1 -right-1 w-3 h-3 bg-blue-500 rounded-full animate-pulse"></div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{realListen && (
|
{listen && realListen && (
|
||||||
<div className="absolute -top-1 -right-1 w-3 h-3 bg-red-500 rounded-full animate-pulse"></div>
|
<div className="absolute -top-1 -right-1 w-3 h-3 bg-red-500 rounded-full animate-pulse"></div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -433,17 +579,41 @@ export const VadVoice = () => {
|
|||||||
onClick={handleStartStop}
|
onClick={handleStartStop}
|
||||||
disabled={vadStatus === 'initializing'}
|
disabled={vadStatus === 'initializing'}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"px-3 py-1.5 text-xs font-medium rounded-md transition-colors",
|
"w-8 h-8 text-xs font-medium rounded-full flex items-center justify-center transition-colors cursor-pointer",
|
||||||
vadStatus === 'initializing' && "opacity-50 cursor-not-allowed",
|
vadStatus === 'initializing' && "opacity-50 cursor-not-allowed",
|
||||||
listen
|
listen
|
||||||
? "bg-red-100 text-red-700 hover:bg-red-200"
|
? "bg-red-100 text-red-700 hover:bg-red-200"
|
||||||
: "bg-green-100 text-green-700 hover:bg-green-200"
|
: "bg-green-100 text-green-700 hover:bg-green-200"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{vadStatus === 'initializing' ? 'Initializing...' : (listen ? 'Stop' : 'Start')}
|
{vadStatus === 'initializing' ? <Loader className="w-4 h-4 inline-block animate-spin" /> : (listen ? <StopCircle className="w-4 h-4 inline-block" /> :
|
||||||
|
<Play className="w-4 h-4 inline-block" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
<button onClick={() => {
|
||||||
|
const newStatus = !autoRecognize;
|
||||||
|
setAutoRecognize(newStatus);
|
||||||
|
}}
|
||||||
|
className={clsx(
|
||||||
|
"w-8 h-8 hover:bg-gray-200 rounded-full flex items-center justify-center text-gray-700 transition-colors cursor-pointer",
|
||||||
|
{ "bg-blue-200": autoRecognize }
|
||||||
|
)}
|
||||||
|
|
||||||
|
title={autoRecognize ? '自动转文字中' : '转文字禁用中'}>
|
||||||
|
<FileAudio className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={openSettingModal}
|
||||||
|
className="w-8 h-8 hover:bg-gray-200 rounded-full flex items-center justify-center text-gray-700 transition-colors cursor-pointer"
|
||||||
|
title="设置"
|
||||||
|
>
|
||||||
|
<Settings className="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 设置弹窗 */}
|
||||||
|
<SettingModal />
|
||||||
</div >
|
</div >
|
||||||
}
|
}
|
||||||
@@ -10,7 +10,9 @@ export const getConfig = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
// 火山引擎APPID
|
||||||
VOLCENGINE_AUC_APPID: getFromLocalStorage('VOLCENGINE_AUC_APPID', ''),
|
VOLCENGINE_AUC_APPID: getFromLocalStorage('VOLCENGINE_AUC_APPID', ''),
|
||||||
|
// 火山引擎Access Token
|
||||||
VOLCENGINE_AUC_TOKEN: getFromLocalStorage('VOLCENGINE_AUC_TOKEN', ''),
|
VOLCENGINE_AUC_TOKEN: getFromLocalStorage('VOLCENGINE_AUC_TOKEN', ''),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
58
web/src/apps/muse/voice/modules/style.css
Normal file
58
web/src/apps/muse/voice/modules/style.css
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
@import 'tailwindcss';
|
||||||
|
|
||||||
|
.low-energy-spin {
|
||||||
|
animation: 2.5s linear 0s infinite normal forwards running spin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 自定义滑块样式 */
|
||||||
|
.slider {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider::-webkit-slider-track {
|
||||||
|
background: #e5e7eb;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
background: #3b82f6;
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: -6px;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider::-webkit-slider-thumb:hover {
|
||||||
|
background: #2563eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider::-moz-range-track {
|
||||||
|
background: #e5e7eb;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider::-moz-range-thumb {
|
||||||
|
background: #3b82f6;
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider::-moz-range-thumb:hover {
|
||||||
|
background: #2563eb;
|
||||||
|
}
|
||||||
133
web/src/apps/muse/voice/store/settingStore.ts
Normal file
133
web/src/apps/muse/voice/store/settingStore.ts
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
import { persist } from 'zustand/middleware';
|
||||||
|
|
||||||
|
interface SettingState {
|
||||||
|
// 弹窗状态
|
||||||
|
isOpen: boolean;
|
||||||
|
mount: boolean;
|
||||||
|
// 语音设置
|
||||||
|
autoRecognize: boolean;
|
||||||
|
listen: boolean;
|
||||||
|
recognitionLanguage: string;
|
||||||
|
|
||||||
|
// 火山引擎配置
|
||||||
|
volcengineAucAppId: string;
|
||||||
|
volcengineAucToken: string;
|
||||||
|
|
||||||
|
// 操作方法
|
||||||
|
openModal: () => void;
|
||||||
|
closeModal: () => void;
|
||||||
|
setAutoRecognize: (value: boolean) => void;
|
||||||
|
setListen: (value: boolean) => void;
|
||||||
|
setRecognitionLanguage: (language: string) => void;
|
||||||
|
setVolcengineAucAppId: (appId: string) => void;
|
||||||
|
setVolcengineAucToken: (token: string) => void;
|
||||||
|
resetToDefault: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultSettings = {
|
||||||
|
autoRecognize: false,
|
||||||
|
listen: false,
|
||||||
|
recognitionLanguage: 'zh-CN',
|
||||||
|
volcengineAucAppId: '',
|
||||||
|
volcengineAucToken: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
// 从原有的 localStorage key 读取初始值
|
||||||
|
const getInitialVolcengineConfig = () => {
|
||||||
|
try {
|
||||||
|
return {
|
||||||
|
volcengineAucAppId: localStorage.getItem('VOLCENGINE_AUC_APPID') || '',
|
||||||
|
volcengineAucToken: localStorage.getItem('VOLCENGINE_AUC_TOKEN') || '',
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to read volcengine config from localStorage:', error);
|
||||||
|
return {
|
||||||
|
volcengineAucAppId: '',
|
||||||
|
volcengineAucToken: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSettingStore = create<SettingState>()(
|
||||||
|
persist(
|
||||||
|
(set, get) => ({
|
||||||
|
// 初始状态 - 合并默认设置和从 localStorage 读取的火山引擎配置
|
||||||
|
isOpen: false,
|
||||||
|
...defaultSettings,
|
||||||
|
...getInitialVolcengineConfig(),
|
||||||
|
mount: false,
|
||||||
|
// 弹窗控制方法
|
||||||
|
openModal: () => set({ isOpen: true }),
|
||||||
|
closeModal: () => set({ isOpen: false }),
|
||||||
|
|
||||||
|
// 设置更新方法
|
||||||
|
setAutoRecognize: (value: boolean) => set({ autoRecognize: value }),
|
||||||
|
setListen: (value: boolean) => set({ listen: value }),
|
||||||
|
setRecognitionLanguage: (language: string) => set({ recognitionLanguage: language }),
|
||||||
|
setVolcengineAucAppId: (appId: string) => {
|
||||||
|
// 同时更新 zustand 状态和原有的 localStorage key
|
||||||
|
try {
|
||||||
|
localStorage.setItem('VOLCENGINE_AUC_APPID', appId);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to save VOLCENGINE_AUC_APPID to localStorage:', error);
|
||||||
|
}
|
||||||
|
set({ volcengineAucAppId: appId });
|
||||||
|
},
|
||||||
|
setVolcengineAucToken: (token: string) => {
|
||||||
|
// 同时更新 zustand 状态和原有的 localStorage key
|
||||||
|
try {
|
||||||
|
localStorage.setItem('VOLCENGINE_AUC_TOKEN', token);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to save VOLCENGINE_AUC_TOKEN to localStorage:', error);
|
||||||
|
}
|
||||||
|
set({ volcengineAucToken: token });
|
||||||
|
},
|
||||||
|
|
||||||
|
// 重置为默认设置
|
||||||
|
resetToDefault: () => {
|
||||||
|
try {
|
||||||
|
localStorage.removeItem('VOLCENGINE_AUC_APPID');
|
||||||
|
localStorage.removeItem('VOLCENGINE_AUC_TOKEN');
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to remove volcengine config from localStorage:', error);
|
||||||
|
}
|
||||||
|
set({
|
||||||
|
...defaultSettings,
|
||||||
|
volcengineAucAppId: '',
|
||||||
|
volcengineAucToken: '',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
name: 'voice-settings',
|
||||||
|
partialize: (state) => ({
|
||||||
|
autoRecognize: state.autoRecognize,
|
||||||
|
listen: state.listen,
|
||||||
|
recognitionLanguage: state.recognitionLanguage,
|
||||||
|
mount: true,
|
||||||
|
// 火山引擎配置不通过 zustand persist 保存,而是直接使用原有的 localStorage key
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 兼容原有 config.ts 的 API
|
||||||
|
export const getConfig = () => {
|
||||||
|
const state = useSettingStore.getState();
|
||||||
|
return {
|
||||||
|
VOLCENGINE_AUC_APPID: state.volcengineAucAppId,
|
||||||
|
VOLCENGINE_AUC_TOKEN: state.volcengineAucToken,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setConfig = (config: { VOLCENGINE_AUC_APPID?: string; VOLCENGINE_AUC_TOKEN?: string }) => {
|
||||||
|
const { setVolcengineAucAppId, setVolcengineAucToken } = useSettingStore.getState();
|
||||||
|
|
||||||
|
if (config.VOLCENGINE_AUC_APPID !== undefined) {
|
||||||
|
setVolcengineAucAppId(config.VOLCENGINE_AUC_APPID);
|
||||||
|
}
|
||||||
|
if (config.VOLCENGINE_AUC_TOKEN !== undefined) {
|
||||||
|
setVolcengineAucToken(config.VOLCENGINE_AUC_TOKEN);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -3,6 +3,7 @@ import { devtools, persist } from 'zustand/middleware';
|
|||||||
import { Speak, getDayOfYear, CreateSpeakData } from '../modules/speak-db/speak';
|
import { Speak, getDayOfYear, CreateSpeakData } from '../modules/speak-db/speak';
|
||||||
import { speakService } from '../modules/speak-db/speak-service';
|
import { speakService } from '../modules/speak-db/speak-service';
|
||||||
import { getText } from '../modules/text';
|
import { getText } from '../modules/text';
|
||||||
|
import { useSettingStore } from './settingStore';
|
||||||
|
|
||||||
interface VoiceState {
|
interface VoiceState {
|
||||||
// 状态数据
|
// 状态数据
|
||||||
@@ -10,7 +11,7 @@ interface VoiceState {
|
|||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
error: string | null;
|
error: string | null;
|
||||||
currentDay: number;
|
currentDay: number;
|
||||||
|
|
||||||
// 动作方法
|
// 动作方法
|
||||||
initialize: () => Promise<void>;
|
initialize: () => Promise<void>;
|
||||||
addVoice: (url: string, duration: number, audioBlob?: Blob) => Promise<Speak>;
|
addVoice: (url: string, duration: number, audioBlob?: Blob) => Promise<Speak>;
|
||||||
@@ -45,14 +46,14 @@ const base64ToUrl = (base64: string, mimeType: string = 'audio/wav'): string =>
|
|||||||
if (base64.startsWith('blob:')) {
|
if (base64.startsWith('blob:')) {
|
||||||
return base64;
|
return base64;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将 base64 转换为 ArrayBuffer
|
// 将 base64 转换为 ArrayBuffer
|
||||||
const binaryString = window.atob(base64);
|
const binaryString = window.atob(base64);
|
||||||
const bytes = new Uint8Array(binaryString.length);
|
const bytes = new Uint8Array(binaryString.length);
|
||||||
for (let i = 0; i < binaryString.length; i++) {
|
for (let i = 0; i < binaryString.length; i++) {
|
||||||
bytes[i] = binaryString.charCodeAt(i);
|
bytes[i] = binaryString.charCodeAt(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建 Blob 和 URL
|
// 创建 Blob 和 URL
|
||||||
const blob = new Blob([bytes], { type: mimeType });
|
const blob = new Blob([bytes], { type: mimeType });
|
||||||
return URL.createObjectURL(blob);
|
return URL.createObjectURL(blob);
|
||||||
@@ -74,26 +75,26 @@ export const useVoiceStore = create<VoiceState>()(
|
|||||||
// 初始化:从 IndexedDB 获取当天的记录
|
// 初始化:从 IndexedDB 获取当天的记录
|
||||||
initialize: async () => {
|
initialize: async () => {
|
||||||
const { setLoading, setError, generateAudioUrls } = get();
|
const { setLoading, setError, generateAudioUrls } = get();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
// 初始化 speak service
|
// 初始化 speak service
|
||||||
await speakService.init();
|
await speakService.init();
|
||||||
|
|
||||||
// 获取当天的语音记录
|
// 获取当天的语音记录
|
||||||
const currentDay = getDayOfYear();
|
const currentDay = getDayOfYear();
|
||||||
const todayVoices = await speakService.getSpeaksByDay(currentDay);
|
const todayVoices = await speakService.getSpeaksByDay(currentDay);
|
||||||
|
|
||||||
set({
|
set({
|
||||||
voiceList: todayVoices,
|
voiceList: todayVoices,
|
||||||
currentDay: currentDay
|
currentDay: currentDay
|
||||||
});
|
});
|
||||||
|
|
||||||
// 为获取到的记录生成 audio URLs
|
// 为获取到的记录生成 audio URLs
|
||||||
await generateAudioUrls();
|
await generateAudioUrls();
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('初始化语音列表失败:', error);
|
console.error('初始化语音列表失败:', error);
|
||||||
setError(error instanceof Error ? error.message : '初始化失败');
|
setError(error instanceof Error ? error.message : '初始化失败');
|
||||||
@@ -105,17 +106,17 @@ export const useVoiceStore = create<VoiceState>()(
|
|||||||
// 添加新的语音记录
|
// 添加新的语音记录
|
||||||
addVoice: async (url: string, duration: number, audioBlob?: Blob) => {
|
addVoice: async (url: string, duration: number, audioBlob?: Blob) => {
|
||||||
const { setError } = get();
|
const { setError } = get();
|
||||||
|
const autoRecognize = useSettingStore.getState().autoRecognize;
|
||||||
try {
|
try {
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
let fileData: string | undefined;
|
let fileData: string | undefined;
|
||||||
|
|
||||||
// 如果提供了 audioBlob,将其转换为 base64 保存到 IndexedDB
|
// 如果提供了 audioBlob,将其转换为 base64 保存到 IndexedDB
|
||||||
if (audioBlob) {
|
if (audioBlob) {
|
||||||
fileData = await blobToBase64(audioBlob);
|
fileData = await blobToBase64(audioBlob);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建语音记录(不保存 url,只保存 base64 数据)
|
// 创建语音记录(不保存 url,只保存 base64 数据)
|
||||||
const speakData = {
|
const speakData = {
|
||||||
duration: Math.ceil(duration),
|
duration: Math.ceil(duration),
|
||||||
@@ -123,24 +124,28 @@ export const useVoiceStore = create<VoiceState>()(
|
|||||||
day: getDayOfYear(),
|
day: getDayOfYear(),
|
||||||
no: 0, // 将由 service 自动生成
|
no: 0, // 将由 service 自动生成
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
type: 'normal' as const
|
type: 'normal' as const,
|
||||||
|
text: '', // 初始为空
|
||||||
};
|
};
|
||||||
|
if (autoRecognize) {
|
||||||
|
speakData.text = await getText(fileData || '').then(res => res.text);
|
||||||
|
}
|
||||||
|
|
||||||
// 保存到 IndexedDB(不包含 url)
|
// 保存到 IndexedDB(不包含 url)
|
||||||
const newSpeak = await speakService.createSpeakAuto(speakData);
|
const newSpeak = await speakService.createSpeakAuto(speakData);
|
||||||
|
|
||||||
// 为新记录生成 URL 并添加到状态
|
// 为新记录生成 URL 并添加到状态
|
||||||
const speakWithUrl = {
|
const speakWithUrl = {
|
||||||
...newSpeak,
|
...newSpeak,
|
||||||
url: newSpeak.file ? base64ToUrl(newSpeak.file) : url
|
url: newSpeak.file ? base64ToUrl(newSpeak.file) : url
|
||||||
};
|
};
|
||||||
|
|
||||||
set(state => ({
|
set(state => ({
|
||||||
voiceList: [...state.voiceList, speakWithUrl]
|
voiceList: [...state.voiceList, speakWithUrl]
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return speakWithUrl;
|
return speakWithUrl;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('添加语音记录失败:', error);
|
console.error('添加语音记录失败:', error);
|
||||||
setError(error instanceof Error ? error.message : '添加失败');
|
setError(error instanceof Error ? error.message : '添加失败');
|
||||||
@@ -151,16 +156,16 @@ export const useVoiceStore = create<VoiceState>()(
|
|||||||
// 更新语音记录
|
// 更新语音记录
|
||||||
updateVoice: async (id: string, updates: Partial<Speak>) => {
|
updateVoice: async (id: string, updates: Partial<Speak>) => {
|
||||||
const { setError } = get();
|
const { setError } = get();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
// 从更新数据中移除 url,因为 url 不应该保存到 IndexedDB
|
// 从更新数据中移除 url,因为 url 不应该保存到 IndexedDB
|
||||||
const { url, ...updatesWithoutUrl } = updates;
|
const { url, ...updatesWithoutUrl } = updates;
|
||||||
|
|
||||||
// 更新 IndexedDB 中的记录
|
// 更新 IndexedDB 中的记录
|
||||||
const updatedSpeak = await speakService.updateSpeak(id, updatesWithoutUrl);
|
const updatedSpeak = await speakService.updateSpeak(id, updatesWithoutUrl);
|
||||||
|
|
||||||
// 更新状态中的记录
|
// 更新状态中的记录
|
||||||
set(state => ({
|
set(state => ({
|
||||||
voiceList: state.voiceList.map(voice => {
|
voiceList: state.voiceList.map(voice => {
|
||||||
@@ -175,7 +180,7 @@ export const useVoiceStore = create<VoiceState>()(
|
|||||||
return voice;
|
return voice;
|
||||||
})
|
})
|
||||||
}));
|
}));
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('更新语音记录失败:', error);
|
console.error('更新语音记录失败:', error);
|
||||||
setError(error instanceof Error ? error.message : '更新失败');
|
setError(error instanceof Error ? error.message : '更新失败');
|
||||||
@@ -186,25 +191,25 @@ export const useVoiceStore = create<VoiceState>()(
|
|||||||
// 删除语音记录
|
// 删除语音记录
|
||||||
deleteVoice: async (id: string) => {
|
deleteVoice: async (id: string) => {
|
||||||
const { setError } = get();
|
const { setError } = get();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
// 从 IndexedDB 删除
|
// 从 IndexedDB 删除
|
||||||
await speakService.deleteSpeak(id);
|
await speakService.deleteSpeak(id);
|
||||||
|
|
||||||
// 从状态中移除并释放 URL
|
// 从状态中移除并释放 URL
|
||||||
set(state => {
|
set(state => {
|
||||||
const voiceToDelete = state.voiceList.find(voice => voice.id === id);
|
const voiceToDelete = state.voiceList.find(voice => voice.id === id);
|
||||||
if (voiceToDelete && voiceToDelete.url && voiceToDelete.url.startsWith('blob:')) {
|
if (voiceToDelete && voiceToDelete.url && voiceToDelete.url.startsWith('blob:')) {
|
||||||
URL.revokeObjectURL(voiceToDelete.url);
|
URL.revokeObjectURL(voiceToDelete.url);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
voiceList: state.voiceList.filter(voice => voice.id !== id)
|
voiceList: state.voiceList.filter(voice => voice.id !== id)
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('删除语音记录失败:', error);
|
console.error('删除语音记录失败:', error);
|
||||||
setError(error instanceof Error ? error.message : '删除失败');
|
setError(error instanceof Error ? error.message : '删除失败');
|
||||||
@@ -215,36 +220,36 @@ export const useVoiceStore = create<VoiceState>()(
|
|||||||
// 识别语音记录
|
// 识别语音记录
|
||||||
recognizeVoice: async (id: string) => {
|
recognizeVoice: async (id: string) => {
|
||||||
const { setError } = get();
|
const { setError } = get();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
// 获取语音记录
|
// 获取语音记录
|
||||||
const voice = get().voiceList.find(v => v.id === id);
|
const voice = get().voiceList.find(v => v.id === id);
|
||||||
if (!voice || !voice.file) {
|
if (!voice || !voice.file) {
|
||||||
throw new Error('找不到语音记录或音频数据');
|
throw new Error('找不到语音记录或音频数据');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 调用语音识别API
|
// 调用语音识别API
|
||||||
const result = await getText(voice.file);
|
const result = await getText(voice.file);
|
||||||
const recognizedText = result.text;
|
const recognizedText = result.text;
|
||||||
|
|
||||||
if (!recognizedText) {
|
if (!recognizedText) {
|
||||||
throw new Error('语音识别失败,未能获取到文字内容');
|
throw new Error('语音识别失败,未能获取到文字内容');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新数据库中的记录
|
// 更新数据库中的记录
|
||||||
await speakService.updateSpeak(id, { text: recognizedText });
|
await speakService.updateSpeak(id, { text: recognizedText });
|
||||||
|
|
||||||
// 更新状态中的记录
|
// 更新状态中的记录
|
||||||
set(state => ({
|
set(state => ({
|
||||||
voiceList: state.voiceList.map(voice =>
|
voiceList: state.voiceList.map(voice =>
|
||||||
voice.id === id ? { ...voice, text: recognizedText } : voice
|
voice.id === id ? { ...voice, text: recognizedText } : voice
|
||||||
)
|
)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return recognizedText;
|
return recognizedText;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('语音识别失败:', error);
|
console.error('语音识别失败:', error);
|
||||||
setError(error instanceof Error ? error.message : '语音识别失败');
|
setError(error instanceof Error ? error.message : '语音识别失败');
|
||||||
@@ -255,13 +260,13 @@ export const useVoiceStore = create<VoiceState>()(
|
|||||||
// 清空今天的语音记录
|
// 清空今天的语音记录
|
||||||
clearTodayVoices: async () => {
|
clearTodayVoices: async () => {
|
||||||
const { setError, currentDay } = get();
|
const { setError, currentDay } = get();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
// 从 IndexedDB 清空今天的记录
|
// 从 IndexedDB 清空今天的记录
|
||||||
await speakService.deleteSpeaksByDay(currentDay);
|
await speakService.deleteSpeaksByDay(currentDay);
|
||||||
|
|
||||||
// 清空状态并释放所有 URL
|
// 清空状态并释放所有 URL
|
||||||
set(state => {
|
set(state => {
|
||||||
state.voiceList.forEach(voice => {
|
state.voiceList.forEach(voice => {
|
||||||
@@ -269,10 +274,10 @@ export const useVoiceStore = create<VoiceState>()(
|
|||||||
URL.revokeObjectURL(voice.url);
|
URL.revokeObjectURL(voice.url);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return { voiceList: [] };
|
return { voiceList: [] };
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('清空今天语音记录失败:', error);
|
console.error('清空今天语音记录失败:', error);
|
||||||
setError(error instanceof Error ? error.message : '清空失败');
|
setError(error instanceof Error ? error.message : '清空失败');
|
||||||
@@ -283,14 +288,14 @@ export const useVoiceStore = create<VoiceState>()(
|
|||||||
// 为所有记录生成音频 URL
|
// 为所有记录生成音频 URL
|
||||||
generateAudioUrls: async () => {
|
generateAudioUrls: async () => {
|
||||||
const { voiceList } = get();
|
const { voiceList } = get();
|
||||||
|
|
||||||
set(state => ({
|
set(state => ({
|
||||||
voiceList: state.voiceList.map(voice => {
|
voiceList: state.voiceList.map(voice => {
|
||||||
// 如果已经有 URL 且是 blob URL,跳过
|
// 如果已经有 URL 且是 blob URL,跳过
|
||||||
if (voice.url && voice.url.startsWith('blob:')) {
|
if (voice.url && voice.url.startsWith('blob:')) {
|
||||||
return voice;
|
return voice;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果有 file 数据,从 base64 生成 URL
|
// 如果有 file 数据,从 base64 生成 URL
|
||||||
if (voice.file) {
|
if (voice.file) {
|
||||||
return {
|
return {
|
||||||
@@ -298,7 +303,7 @@ export const useVoiceStore = create<VoiceState>()(
|
|||||||
url: base64ToUrl(voice.file)
|
url: base64ToUrl(voice.file)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return voice;
|
return voice;
|
||||||
})
|
})
|
||||||
}));
|
}));
|
||||||
155
web/src/components/ui/alert-dialog.tsx
Normal file
155
web/src/components/ui/alert-dialog.tsx
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { buttonVariants } from "@/components/ui/button"
|
||||||
|
|
||||||
|
function AlertDialog({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
|
||||||
|
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function AlertDialogTrigger({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
|
||||||
|
return (
|
||||||
|
<AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function AlertDialogPortal({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
|
||||||
|
return (
|
||||||
|
<AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function AlertDialogOverlay({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) {
|
||||||
|
return (
|
||||||
|
<AlertDialogPrimitive.Overlay
|
||||||
|
data-slot="alert-dialog-overlay"
|
||||||
|
className={cn(
|
||||||
|
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function AlertDialogContent({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof AlertDialogPrimitive.Content>) {
|
||||||
|
return (
|
||||||
|
<AlertDialogPortal>
|
||||||
|
<AlertDialogOverlay />
|
||||||
|
<AlertDialogPrimitive.Content
|
||||||
|
data-slot="alert-dialog-content"
|
||||||
|
className={cn(
|
||||||
|
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</AlertDialogPortal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function AlertDialogHeader({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="alert-dialog-header"
|
||||||
|
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function AlertDialogFooter({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="alert-dialog-footer"
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function AlertDialogTitle({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
|
||||||
|
return (
|
||||||
|
<AlertDialogPrimitive.Title
|
||||||
|
data-slot="alert-dialog-title"
|
||||||
|
className={cn("text-lg font-semibold", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function AlertDialogDescription({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
|
||||||
|
return (
|
||||||
|
<AlertDialogPrimitive.Description
|
||||||
|
data-slot="alert-dialog-description"
|
||||||
|
className={cn("text-muted-foreground text-sm", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function AlertDialogAction({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof AlertDialogPrimitive.Action>) {
|
||||||
|
return (
|
||||||
|
<AlertDialogPrimitive.Action
|
||||||
|
className={cn(buttonVariants(), className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function AlertDialogCancel({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel>) {
|
||||||
|
return (
|
||||||
|
<AlertDialogPrimitive.Cancel
|
||||||
|
className={cn(buttonVariants({ variant: "outline" }), className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogPortal,
|
||||||
|
AlertDialogOverlay,
|
||||||
|
AlertDialogTrigger,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogTitle,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
}
|
||||||
66
web/src/components/ui/alert.tsx
Normal file
66
web/src/components/ui/alert.tsx
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const alertVariants = cva(
|
||||||
|
"relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "bg-card text-card-foreground",
|
||||||
|
destructive:
|
||||||
|
"text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
function Alert({
|
||||||
|
className,
|
||||||
|
variant,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="alert"
|
||||||
|
role="alert"
|
||||||
|
className={cn(alertVariants({ variant }), className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="alert-title"
|
||||||
|
className={cn(
|
||||||
|
"col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function AlertDescription({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="alert-description"
|
||||||
|
className={cn(
|
||||||
|
"text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Alert, AlertTitle, AlertDescription }
|
||||||
60
web/src/components/ui/button.tsx
Normal file
60
web/src/components/ui/button.tsx
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { Slot } from "@radix-ui/react-slot"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const buttonVariants = cva(
|
||||||
|
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||||
|
destructive:
|
||||||
|
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
||||||
|
outline:
|
||||||
|
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
||||||
|
secondary:
|
||||||
|
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||||
|
ghost:
|
||||||
|
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
||||||
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
||||||
|
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
||||||
|
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
||||||
|
icon: "size-9",
|
||||||
|
"icon-sm": "size-8",
|
||||||
|
"icon-lg": "size-10",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
size: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
function Button({
|
||||||
|
className,
|
||||||
|
variant,
|
||||||
|
size,
|
||||||
|
asChild = false,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"button"> &
|
||||||
|
VariantProps<typeof buttonVariants> & {
|
||||||
|
asChild?: boolean
|
||||||
|
}) {
|
||||||
|
const Comp = asChild ? Slot : "button"
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Comp
|
||||||
|
data-slot="button"
|
||||||
|
className={cn(buttonVariants({ variant, size, className }))}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Button, buttonVariants }
|
||||||
141
web/src/components/ui/dialog.tsx
Normal file
141
web/src/components/ui/dialog.tsx
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
||||||
|
import { XIcon } from "lucide-react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
function Dialog({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
|
||||||
|
return <DialogPrimitive.Root data-slot="dialog" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogTrigger({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
|
||||||
|
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogPortal({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
|
||||||
|
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogClose({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
|
||||||
|
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogOverlay({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
|
||||||
|
return (
|
||||||
|
<DialogPrimitive.Overlay
|
||||||
|
data-slot="dialog-overlay"
|
||||||
|
className={cn(
|
||||||
|
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogContent({
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
showCloseButton = true,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
|
||||||
|
showCloseButton?: boolean
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<DialogPortal data-slot="dialog-portal">
|
||||||
|
<DialogOverlay />
|
||||||
|
<DialogPrimitive.Content
|
||||||
|
data-slot="dialog-content"
|
||||||
|
className={cn(
|
||||||
|
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
{showCloseButton && (
|
||||||
|
<DialogPrimitive.Close
|
||||||
|
data-slot="dialog-close"
|
||||||
|
className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
|
||||||
|
>
|
||||||
|
<XIcon />
|
||||||
|
<span className="sr-only">Close</span>
|
||||||
|
</DialogPrimitive.Close>
|
||||||
|
)}
|
||||||
|
</DialogPrimitive.Content>
|
||||||
|
</DialogPortal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="dialog-header"
|
||||||
|
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="dialog-footer"
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogTitle({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
|
||||||
|
return (
|
||||||
|
<DialogPrimitive.Title
|
||||||
|
data-slot="dialog-title"
|
||||||
|
className={cn("text-lg leading-none font-semibold", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogDescription({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
|
||||||
|
return (
|
||||||
|
<DialogPrimitive.Description
|
||||||
|
data-slot="dialog-description"
|
||||||
|
className={cn("text-muted-foreground text-sm", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
Dialog,
|
||||||
|
DialogClose,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogOverlay,
|
||||||
|
DialogPortal,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user