From edace856abe5be15bb1f4c74ffff024e589c6559 Mon Sep 17 00:00:00 2001 From: abearxiong Date: Wed, 22 Oct 2025 03:24:08 +0800 Subject: [PATCH] =?UTF-8?q?update=20=E4=BC=98=E5=8C=96=E5=BD=95=E9=9F=B3?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E6=A8=A1=E5=9D=97=E7=9A=84=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E5=92=8C=E7=81=AB=E5=B1=B1=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pnpm-lock.yaml | 552 +++++++++++++++++- web/package.json | 6 + web/src/apps/muse/base/table/Table.tsx | 4 +- .../apps/muse/components/PasswordInput.tsx | 46 ++ web/src/apps/muse/index.tsx | 4 +- web/src/apps/muse/videos/modules/style.css | 5 - web/src/apps/muse/{videos => voice}/index.tsx | 0 .../apps/muse/voice/modules/SettingModal.tsx | 148 +++++ .../{videos => voice}/modules/VadVoice.tsx | 256 ++++++-- .../muse/{videos => voice}/modules/auc.ts | 0 .../muse/{videos => voice}/modules/config.ts | 2 + .../modules/speak-db/README.md | 0 .../modules/speak-db/index.ts | 0 .../modules/speak-db/speak-db.ts | 0 .../modules/speak-db/speak-service.ts | 0 .../modules/speak-db/speak.ts | 0 web/src/apps/muse/voice/modules/style.css | 58 ++ .../muse/{videos => voice}/modules/text.ts | 0 .../muse/{videos => voice}/store/README.md | 0 .../muse/{videos => voice}/store/index.ts | 0 web/src/apps/muse/voice/store/settingStore.ts | 133 +++++ .../{videos => voice}/store/voiceStore.ts | 99 ++-- web/src/components/ui/alert-dialog.tsx | 155 +++++ web/src/components/ui/alert.tsx | 66 +++ web/src/components/ui/button.tsx | 60 ++ web/src/components/ui/dialog.tsx | 141 +++++ 26 files changed, 1634 insertions(+), 101 deletions(-) create mode 100644 web/src/apps/muse/components/PasswordInput.tsx delete mode 100644 web/src/apps/muse/videos/modules/style.css rename web/src/apps/muse/{videos => voice}/index.tsx (100%) create mode 100644 web/src/apps/muse/voice/modules/SettingModal.tsx rename web/src/apps/muse/{videos => voice}/modules/VadVoice.tsx (58%) rename web/src/apps/muse/{videos => voice}/modules/auc.ts (100%) rename web/src/apps/muse/{videos => voice}/modules/config.ts (94%) rename web/src/apps/muse/{videos => voice}/modules/speak-db/README.md (100%) rename web/src/apps/muse/{videos => voice}/modules/speak-db/index.ts (100%) rename web/src/apps/muse/{videos => voice}/modules/speak-db/speak-db.ts (100%) rename web/src/apps/muse/{videos => voice}/modules/speak-db/speak-service.ts (100%) rename web/src/apps/muse/{videos => voice}/modules/speak-db/speak.ts (100%) create mode 100644 web/src/apps/muse/voice/modules/style.css rename web/src/apps/muse/{videos => voice}/modules/text.ts (100%) rename web/src/apps/muse/{videos => voice}/store/README.md (100%) rename web/src/apps/muse/{videos => voice}/store/index.ts (100%) create mode 100644 web/src/apps/muse/voice/store/settingStore.ts rename web/src/apps/muse/{videos => voice}/store/voiceStore.ts (94%) create mode 100644 web/src/components/ui/alert-dialog.tsx create mode 100644 web/src/components/ui/alert.tsx create mode 100644 web/src/components/ui/button.tsx create mode 100644 web/src/components/ui/dialog.tsx diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8a3842d..e394432 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -75,6 +75,15 @@ importers: '@kevisual/registry': specifier: ^0.0.1 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': specifier: ^0.0.28 version: 0.0.28 @@ -84,6 +93,12 @@ importers: '@tailwindcss/vite': 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)) + '@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: 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) @@ -161,7 +176,7 @@ importers: version: 7.11.0 zustand: 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: '@kevisual/types': specifier: ^0.0.10 @@ -760,6 +775,190 @@ packages: '@protobufjs/utf8@1.1.0': 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': resolution: {integrity: sha512-Hvw8jN3r1SBxmjJa89HITxRcwlT6dc7CQPVtVQLrqfY8EeQcx41QeqKUol4lw8ZCeAIHKwYndHnB1K/4SAQJgQ==} @@ -1051,6 +1250,39 @@ packages: peerDependencies: 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': resolution: {integrity: sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==} @@ -1263,6 +1495,10 @@ packages: argparse@2.0.1: 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: resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} engines: {node: '>= 0.4'} @@ -1439,6 +1675,9 @@ packages: supports-color: optional: true + decode-formdata@0.9.0: + resolution: {integrity: sha512-q5uwOjR3Um5YD+ZWPOF/1sGHVW9A5rCrRwITQChRXlmPkxDFBqCm4jNTIVdGHNH9OnR+V9MoZVgRhsFb+ARbUw==} + decode-named-character-reference@1.2.0: resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} @@ -1469,6 +1708,9 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + deterministic-object-hash@2.0.2: resolution: {integrity: sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==} engines: {node: '>=18'} @@ -1677,6 +1919,10 @@ packages: resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} engines: {node: '>=18'} + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + github-slugger@2.0.0: resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} @@ -2555,12 +2801,42 @@ packages: resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} 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: resolution: {integrity: sha512-b3qKHQ3MLqOgSS+FRYKapNkJZf5EQzuf6+RLiq1/IlTHw99YrZ2NJZLk4hQIzTnnIkRg2LUqyVinu6YWWpUYew==} peerDependencies: 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-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: resolution: {integrity: sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA==} peerDependencies: @@ -3058,6 +3334,31 @@ packages: url-parse@1.5.10: 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: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -3799,6 +4100,163 @@ snapshots: '@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': dependencies: onnxruntime-web: 1.23.0 @@ -4024,6 +4482,39 @@ snapshots: tailwindcss: 4.1.14 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': {} '@types/babel__core@7.20.5': @@ -4301,6 +4792,10 @@ snapshots: argparse@2.0.1: {} + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 + aria-query@5.3.2: {} array-iterate@2.0.1: {} @@ -4539,6 +5034,8 @@ snapshots: dependencies: ms: 2.1.3 + decode-formdata@0.9.0: {} + decode-named-character-reference@1.2.0: dependencies: character-entities: 2.0.2 @@ -4560,6 +5057,8 @@ snapshots: detect-libc@2.1.2: {} + detect-node-es@1.1.0: {} + deterministic-object-hash@2.0.2: dependencies: base-64: 1.0.0 @@ -4782,6 +5281,8 @@ snapshots: get-east-asian-width@1.4.0: {} + get-nonce@1.0.1: {} + github-slugger@2.0.0: {} glob-parent@5.1.2: @@ -6002,11 +6503,38 @@ snapshots: 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): dependencies: 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): dependencies: clsx: 2.1.1 @@ -6597,6 +7125,25 @@ snapshots: querystringify: 2.2.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: {} uuid@8.3.2: {} @@ -6698,9 +7245,10 @@ snapshots: 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: '@types/react': 19.2.2 react: 19.2.0 + use-sync-external-store: 1.6.0(react@19.2.0) zwitch@2.0.4: {} diff --git a/web/package.json b/web/package.json index 471832b..031a395 100644 --- a/web/package.json +++ b/web/package.json @@ -9,6 +9,7 @@ "build": "astro build", "preview": "astro preview", "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 " }, "keywords": [], @@ -25,9 +26,14 @@ "@kevisual/query": "^0.0.29", "@kevisual/query-login": "^0.0.6", "@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", "@szhsin/react-menu": "^4.5.0", "@tailwindcss/vite": "^4.1.14", + "@tanstack/react-form": "^1.23.7", + "@tanstack/react-query": "^5.90.5", "astro": "^5.14.4", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/web/src/apps/muse/base/table/Table.tsx b/web/src/apps/muse/base/table/Table.tsx index fa36a3c..18b7033 100644 --- a/web/src/apps/muse/base/table/Table.tsx +++ b/web/src/apps/muse/base/table/Table.tsx @@ -333,8 +333,8 @@ export const Table: React.FC = ({ // Ctrl/Cmd + A 全选 if ((event.ctrlKey || event.metaKey) && event.key === 'a') { - event.preventDefault(); - handleSelectAll(true); + // event.preventDefault(); + // handleSelectAll(true); return; } diff --git a/web/src/apps/muse/components/PasswordInput.tsx b/web/src/apps/muse/components/PasswordInput.tsx new file mode 100644 index 0000000..6be7e2d --- /dev/null +++ b/web/src/apps/muse/components/PasswordInput.tsx @@ -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 = ({ + value, + onChange, + placeholder, + className = '', +}) => { + const [showPassword, setShowPassword] = useState(false); + + const togglePasswordVisibility = () => { + setShowPassword(!showPassword); + }; + + return ( +
+ onChange(e.target.value)} + placeholder={placeholder} + className={`pr-10 ${className}`} + /> + +
+ ); +}; \ No newline at end of file diff --git a/web/src/apps/muse/index.tsx b/web/src/apps/muse/index.tsx index 61ec926..8b1c0f8 100644 --- a/web/src/apps/muse/index.tsx +++ b/web/src/apps/muse/index.tsx @@ -3,7 +3,7 @@ import { AuthProvider } from '../login/AuthProvider'; import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'; 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 { BaseApp } from './base/index.tsx'; import { exampleUsage, markService } from './modules/mark-service.ts'; @@ -201,7 +201,7 @@ export const App: 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 ( + + + +
+ 语音设置 +
+ +
+
+ + 配置语音录制和识别相关设置 + +
+ +
+ {/* 语音识别设置 */} +
+ + +
+ +
+ setAutoRecognize(e.target.checked)} + className="sr-only" + /> +
setAutoRecognize(!autoRecognize)} + className={`w-11 h-6 rounded-full cursor-pointer transition-colors ${autoRecognize ? 'bg-blue-600' : 'bg-gray-200' + }`} + > +
+
+
+
+ +
+ +
+ setListen(e.target.checked)} + className="sr-only" + /> +
setListen(!listen)} + className={`w-11 h-6 rounded-full cursor-pointer transition-colors ${listen ? 'bg-blue-600' : 'bg-gray-200' + }`} + > +
+
+
+
+
+ + {/* 火山引擎配置 */} +
+

火山引擎配置

+ +
+
+ + 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" + /> +
+ +
+ + +
+
+
+
+ +
+ +
+ +
+ ); +}; \ No newline at end of file diff --git a/web/src/apps/muse/videos/modules/VadVoice.tsx b/web/src/apps/muse/voice/modules/VadVoice.tsx similarity index 58% rename from web/src/apps/muse/videos/modules/VadVoice.tsx rename to web/src/apps/muse/voice/modules/VadVoice.tsx index 938e254..98d78e0 100644 --- a/web/src/apps/muse/videos/modules/VadVoice.tsx +++ b/web/src/apps/muse/voice/modules/VadVoice.tsx @@ -2,13 +2,26 @@ import { MicVAD, utils } from "@ricky0123/vad-web" import clsx from "clsx"; import { useState, useEffect, useRef } from "react"; 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 '@szhsin/react-menu/dist/index.css'; import { toast } from 'react-toastify'; import { Speak } from "./speak-db/speak"; 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 = { data: Speak; @@ -136,19 +149,51 @@ const VoicePlayer = ({ data }: VadVoiceProps) => { 识别语音 - -
- - - - 删除 -
-
+ {data.text && data.text.trim() ? ( + + + +
+ + + + 删除 +
+
+
+ + + 确认删除 + + 此语音包含文字内容:"{data.text}"。删除后将无法恢复,确定要删除吗? + + + + 取消 + 确认删除 + + +
+ ) : ( + +
+ + + + 删除 +
+
+ )} {/* 播放/暂停按钮 */} {!isPlaying ? ( @@ -187,11 +232,92 @@ export const ShowVoicePlayer = ({ data }: { data: Speak[] }) => {
-
- {new Date(item.timestamp).toLocaleTimeString()} -
-
- #{item.no} +
+
+
+ {new Date(item.timestamp).toLocaleTimeString()} +
+
+ #{item.no} +
+
+
+ {item.text && item.text.trim() ? ( + + + + + + + 确认删除 + + 此语音包含文字内容:"{item.text}"。删除后将无法恢复,确定要删除吗? + + + + 取消 + { + 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 : '未知错误')); + }); + }} + > + 确认删除 + + + + + ) : ( + + )} +
{item.text && (
{ setError: setStoreError } = useVoiceStore(); - const [listen, setListen] = useState(false); + // 使用设置 store + const { + openModal: openSettingModal, + listen, + setListen, + autoRecognize, + setAutoRecognize + } = useSettingStore(); + const [vadStatus, setVadStatus] = useState<'idle' | 'initializing' | 'ready' | 'error'>('idle'); const [realListen, setRealListen] = useState(false); const [errorMessage, setErrorMessage] = useState(''); @@ -238,7 +372,8 @@ export const VadVoice = () => { const ref = useRef(null); const initializingRef = useRef(false); - async function initializeVAD() { + async function initializeVAD(ls: boolean = true) { + if (!ls) { return } if (ref.current || initializingRef.current) return; initializingRef.current = true; @@ -247,7 +382,6 @@ export const VadVoice = () => { try { console.log('Starting VAD initialization...'); - // 添加延迟确保资源加载完成 await new Promise((resolve) => setTimeout(resolve, 500)); @@ -310,7 +444,7 @@ export const VadVoice = () => { const handleUserInteraction = async () => { if (!userInteracted) { setUserInteracted(true); - await initializeVAD(); + // await initializeVAD(); } }; @@ -318,7 +452,6 @@ export const VadVoice = () => { useEffect(() => { initializeStore(); }, [initializeStore]); - useEffect(() => { // 页面加载时不自动初始化,等待用户交互 const handleFirstClick = () => { @@ -329,16 +462,23 @@ export const VadVoice = () => { }; document.addEventListener('click', handleFirstClick); - // handleUserInteraction() return () => { 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 = () => { if (ref.current) { ref.current.destroy(); @@ -369,20 +509,26 @@ export const VadVoice = () => { return
{/* Audio Recordings List */}
- {!userInteracted && vadStatus === 'idle' ? ( -
+ { + voiceList.length === 0 ? ( +
+
🎤
+
No recordings yet
+
Start talking to record
+
+ ) : ( + + ) + } + {!userInteracted && vadStatus === 'idle' && listen && ( +
{ + setUserInteracted(true); + initializeVAD(listen) + }}>
🎤
Click anywhere to initialize microphone
Browser requires user interaction for microphone access
- ) : voiceList.length === 0 ? ( -
-
🎤
-
No recordings yet
-
Start talking to record
-
- ) : ( - )}
@@ -401,7 +547,7 @@ export const VadVoice = () => {
)} - {realListen && ( + {listen && realListen && (
)}
@@ -433,17 +579,41 @@ export const VadVoice = () => { onClick={handleStartStop} disabled={vadStatus === 'initializing'} 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", listen ? "bg-red-100 text-red-700 hover:bg-red-200" : "bg-green-100 text-green-700 hover:bg-green-200" )} > - {vadStatus === 'initializing' ? 'Initializing...' : (listen ? 'Stop' : 'Start')} + {vadStatus === 'initializing' ? : (listen ? : + + )} + + +
+ + {/* 设置弹窗 */} +
} \ No newline at end of file diff --git a/web/src/apps/muse/videos/modules/auc.ts b/web/src/apps/muse/voice/modules/auc.ts similarity index 100% rename from web/src/apps/muse/videos/modules/auc.ts rename to web/src/apps/muse/voice/modules/auc.ts diff --git a/web/src/apps/muse/videos/modules/config.ts b/web/src/apps/muse/voice/modules/config.ts similarity index 94% rename from web/src/apps/muse/videos/modules/config.ts rename to web/src/apps/muse/voice/modules/config.ts index c2e5761..57f7b04 100644 --- a/web/src/apps/muse/videos/modules/config.ts +++ b/web/src/apps/muse/voice/modules/config.ts @@ -10,7 +10,9 @@ export const getConfig = () => { }; return { + // 火山引擎APPID VOLCENGINE_AUC_APPID: getFromLocalStorage('VOLCENGINE_AUC_APPID', ''), + // 火山引擎Access Token VOLCENGINE_AUC_TOKEN: getFromLocalStorage('VOLCENGINE_AUC_TOKEN', ''), }; }; diff --git a/web/src/apps/muse/videos/modules/speak-db/README.md b/web/src/apps/muse/voice/modules/speak-db/README.md similarity index 100% rename from web/src/apps/muse/videos/modules/speak-db/README.md rename to web/src/apps/muse/voice/modules/speak-db/README.md diff --git a/web/src/apps/muse/videos/modules/speak-db/index.ts b/web/src/apps/muse/voice/modules/speak-db/index.ts similarity index 100% rename from web/src/apps/muse/videos/modules/speak-db/index.ts rename to web/src/apps/muse/voice/modules/speak-db/index.ts diff --git a/web/src/apps/muse/videos/modules/speak-db/speak-db.ts b/web/src/apps/muse/voice/modules/speak-db/speak-db.ts similarity index 100% rename from web/src/apps/muse/videos/modules/speak-db/speak-db.ts rename to web/src/apps/muse/voice/modules/speak-db/speak-db.ts diff --git a/web/src/apps/muse/videos/modules/speak-db/speak-service.ts b/web/src/apps/muse/voice/modules/speak-db/speak-service.ts similarity index 100% rename from web/src/apps/muse/videos/modules/speak-db/speak-service.ts rename to web/src/apps/muse/voice/modules/speak-db/speak-service.ts diff --git a/web/src/apps/muse/videos/modules/speak-db/speak.ts b/web/src/apps/muse/voice/modules/speak-db/speak.ts similarity index 100% rename from web/src/apps/muse/videos/modules/speak-db/speak.ts rename to web/src/apps/muse/voice/modules/speak-db/speak.ts diff --git a/web/src/apps/muse/voice/modules/style.css b/web/src/apps/muse/voice/modules/style.css new file mode 100644 index 0000000..7ff4556 --- /dev/null +++ b/web/src/apps/muse/voice/modules/style.css @@ -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; +} \ No newline at end of file diff --git a/web/src/apps/muse/videos/modules/text.ts b/web/src/apps/muse/voice/modules/text.ts similarity index 100% rename from web/src/apps/muse/videos/modules/text.ts rename to web/src/apps/muse/voice/modules/text.ts diff --git a/web/src/apps/muse/videos/store/README.md b/web/src/apps/muse/voice/store/README.md similarity index 100% rename from web/src/apps/muse/videos/store/README.md rename to web/src/apps/muse/voice/store/README.md diff --git a/web/src/apps/muse/videos/store/index.ts b/web/src/apps/muse/voice/store/index.ts similarity index 100% rename from web/src/apps/muse/videos/store/index.ts rename to web/src/apps/muse/voice/store/index.ts diff --git a/web/src/apps/muse/voice/store/settingStore.ts b/web/src/apps/muse/voice/store/settingStore.ts new file mode 100644 index 0000000..e2b910b --- /dev/null +++ b/web/src/apps/muse/voice/store/settingStore.ts @@ -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()( + 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); + } +}; diff --git a/web/src/apps/muse/videos/store/voiceStore.ts b/web/src/apps/muse/voice/store/voiceStore.ts similarity index 94% rename from web/src/apps/muse/videos/store/voiceStore.ts rename to web/src/apps/muse/voice/store/voiceStore.ts index 174b037..e159155 100644 --- a/web/src/apps/muse/videos/store/voiceStore.ts +++ b/web/src/apps/muse/voice/store/voiceStore.ts @@ -3,6 +3,7 @@ import { devtools, persist } from 'zustand/middleware'; import { Speak, getDayOfYear, CreateSpeakData } from '../modules/speak-db/speak'; import { speakService } from '../modules/speak-db/speak-service'; import { getText } from '../modules/text'; +import { useSettingStore } from './settingStore'; interface VoiceState { // 状态数据 @@ -10,7 +11,7 @@ interface VoiceState { isLoading: boolean; error: string | null; currentDay: number; - + // 动作方法 initialize: () => Promise; addVoice: (url: string, duration: number, audioBlob?: Blob) => Promise; @@ -45,14 +46,14 @@ const base64ToUrl = (base64: string, mimeType: string = 'audio/wav'): string => if (base64.startsWith('blob:')) { return base64; } - + // 将 base64 转换为 ArrayBuffer const binaryString = window.atob(base64); const bytes = new Uint8Array(binaryString.length); for (let i = 0; i < binaryString.length; i++) { bytes[i] = binaryString.charCodeAt(i); } - + // 创建 Blob 和 URL const blob = new Blob([bytes], { type: mimeType }); return URL.createObjectURL(blob); @@ -74,26 +75,26 @@ export const useVoiceStore = create()( // 初始化:从 IndexedDB 获取当天的记录 initialize: async () => { const { setLoading, setError, generateAudioUrls } = get(); - + try { setLoading(true); setError(null); - + // 初始化 speak service await speakService.init(); - + // 获取当天的语音记录 const currentDay = getDayOfYear(); const todayVoices = await speakService.getSpeaksByDay(currentDay); - - set({ + + set({ voiceList: todayVoices, currentDay: currentDay }); - + // 为获取到的记录生成 audio URLs await generateAudioUrls(); - + } catch (error) { console.error('初始化语音列表失败:', error); setError(error instanceof Error ? error.message : '初始化失败'); @@ -105,17 +106,17 @@ export const useVoiceStore = create()( // 添加新的语音记录 addVoice: async (url: string, duration: number, audioBlob?: Blob) => { const { setError } = get(); - + const autoRecognize = useSettingStore.getState().autoRecognize; try { setError(null); - + let fileData: string | undefined; - + // 如果提供了 audioBlob,将其转换为 base64 保存到 IndexedDB if (audioBlob) { fileData = await blobToBase64(audioBlob); } - + // 创建语音记录(不保存 url,只保存 base64 数据) const speakData = { duration: Math.ceil(duration), @@ -123,24 +124,28 @@ export const useVoiceStore = create()( day: getDayOfYear(), no: 0, // 将由 service 自动生成 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) const newSpeak = await speakService.createSpeakAuto(speakData); - + // 为新记录生成 URL 并添加到状态 const speakWithUrl = { ...newSpeak, url: newSpeak.file ? base64ToUrl(newSpeak.file) : url }; - + set(state => ({ voiceList: [...state.voiceList, speakWithUrl] })); - + return speakWithUrl; - + } catch (error) { console.error('添加语音记录失败:', error); setError(error instanceof Error ? error.message : '添加失败'); @@ -151,16 +156,16 @@ export const useVoiceStore = create()( // 更新语音记录 updateVoice: async (id: string, updates: Partial) => { const { setError } = get(); - + try { setError(null); - + // 从更新数据中移除 url,因为 url 不应该保存到 IndexedDB const { url, ...updatesWithoutUrl } = updates; - + // 更新 IndexedDB 中的记录 const updatedSpeak = await speakService.updateSpeak(id, updatesWithoutUrl); - + // 更新状态中的记录 set(state => ({ voiceList: state.voiceList.map(voice => { @@ -175,7 +180,7 @@ export const useVoiceStore = create()( return voice; }) })); - + } catch (error) { console.error('更新语音记录失败:', error); setError(error instanceof Error ? error.message : '更新失败'); @@ -186,25 +191,25 @@ export const useVoiceStore = create()( // 删除语音记录 deleteVoice: async (id: string) => { const { setError } = get(); - + try { setError(null); - + // 从 IndexedDB 删除 await speakService.deleteSpeak(id); - + // 从状态中移除并释放 URL set(state => { const voiceToDelete = state.voiceList.find(voice => voice.id === id); if (voiceToDelete && voiceToDelete.url && voiceToDelete.url.startsWith('blob:')) { URL.revokeObjectURL(voiceToDelete.url); } - + return { voiceList: state.voiceList.filter(voice => voice.id !== id) }; }); - + } catch (error) { console.error('删除语音记录失败:', error); setError(error instanceof Error ? error.message : '删除失败'); @@ -215,36 +220,36 @@ export const useVoiceStore = create()( // 识别语音记录 recognizeVoice: async (id: string) => { const { setError } = get(); - + try { setError(null); - + // 获取语音记录 const voice = get().voiceList.find(v => v.id === id); if (!voice || !voice.file) { throw new Error('找不到语音记录或音频数据'); } - + // 调用语音识别API const result = await getText(voice.file); const recognizedText = result.text; - + if (!recognizedText) { throw new Error('语音识别失败,未能获取到文字内容'); } - + // 更新数据库中的记录 await speakService.updateSpeak(id, { text: recognizedText }); - + // 更新状态中的记录 set(state => ({ - voiceList: state.voiceList.map(voice => + voiceList: state.voiceList.map(voice => voice.id === id ? { ...voice, text: recognizedText } : voice ) })); - + return recognizedText; - + } catch (error) { console.error('语音识别失败:', error); setError(error instanceof Error ? error.message : '语音识别失败'); @@ -255,13 +260,13 @@ export const useVoiceStore = create()( // 清空今天的语音记录 clearTodayVoices: async () => { const { setError, currentDay } = get(); - + try { setError(null); - + // 从 IndexedDB 清空今天的记录 await speakService.deleteSpeaksByDay(currentDay); - + // 清空状态并释放所有 URL set(state => { state.voiceList.forEach(voice => { @@ -269,10 +274,10 @@ export const useVoiceStore = create()( URL.revokeObjectURL(voice.url); } }); - + return { voiceList: [] }; }); - + } catch (error) { console.error('清空今天语音记录失败:', error); setError(error instanceof Error ? error.message : '清空失败'); @@ -283,14 +288,14 @@ export const useVoiceStore = create()( // 为所有记录生成音频 URL generateAudioUrls: async () => { const { voiceList } = get(); - + set(state => ({ voiceList: state.voiceList.map(voice => { // 如果已经有 URL 且是 blob URL,跳过 if (voice.url && voice.url.startsWith('blob:')) { return voice; } - + // 如果有 file 数据,从 base64 生成 URL if (voice.file) { return { @@ -298,7 +303,7 @@ export const useVoiceStore = create()( url: base64ToUrl(voice.file) }; } - + return voice; }) })); diff --git a/web/src/components/ui/alert-dialog.tsx b/web/src/components/ui/alert-dialog.tsx new file mode 100644 index 0000000..935eecf --- /dev/null +++ b/web/src/components/ui/alert-dialog.tsx @@ -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) { + return +} + +function AlertDialogTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogPortal({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + + ) +} + +function AlertDialogHeader({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AlertDialogFooter({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AlertDialogTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogAction({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogCancel({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +} diff --git a/web/src/components/ui/alert.tsx b/web/src/components/ui/alert.tsx new file mode 100644 index 0000000..1421354 --- /dev/null +++ b/web/src/components/ui/alert.tsx @@ -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) { + return ( +
+ ) +} + +function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AlertDescription({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ) +} + +export { Alert, AlertTitle, AlertDescription } diff --git a/web/src/components/ui/button.tsx b/web/src/components/ui/button.tsx new file mode 100644 index 0000000..21409a0 --- /dev/null +++ b/web/src/components/ui/button.tsx @@ -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 & { + asChild?: boolean + }) { + const Comp = asChild ? Slot : "button" + + return ( + + ) +} + +export { Button, buttonVariants } diff --git a/web/src/components/ui/dialog.tsx b/web/src/components/ui/dialog.tsx new file mode 100644 index 0000000..6cb123b --- /dev/null +++ b/web/src/components/ui/dialog.tsx @@ -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) { + return +} + +function DialogTrigger({ + ...props +}: React.ComponentProps) { + return +} + +function DialogPortal({ + ...props +}: React.ComponentProps) { + return +} + +function DialogClose({ + ...props +}: React.ComponentProps) { + return +} + +function DialogOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DialogContent({ + className, + children, + showCloseButton = true, + ...props +}: React.ComponentProps & { + showCloseButton?: boolean +}) { + return ( + + + + {children} + {showCloseButton && ( + + + Close + + )} + + + ) +} + +function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function DialogFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function DialogTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogOverlay, + DialogPortal, + DialogTitle, + DialogTrigger, +}