diff --git a/README.md b/README.md index c945095..994c45e 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-green.svg)](https://github.com/HyperChatBot/hyperchat/pulls) [![Node](https://img.shields.io/badge/Node.js-%3E%3D18.19.0-green.svg)](https://nodejs.org/en/) [![Rust](https://img.shields.io/badge/Rust-%3E%3D1.81.0-orange.svg)](https://nodejs.org/en/) -[![Version](https://img.shields.io/badge/Version-v1.0.5-blue.svg)](https://nodejs.org/en/) +[![Version](https://img.shields.io/badge/Version-v2.0.0-blue.svg)](https://nodejs.org/en/) [![Twitter](https://img.shields.io/badge/Twitter-Connect-brightgreen?logo=twitter)](https://twitter/YanceyOfficial) ## Introduction diff --git a/package.json b/package.json index 8d961a6..60d51ef 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "hyperchat", "private": true, - "version": "1.0.5", + "version": "2.0.0", "type": "module", "description": "ChatGPT AI Bot.", "scripts": { @@ -9,7 +9,7 @@ "build": "tsc && vite build", "preview": "vite preview", "tauri": "tauri", - "prettier": "prettier ./.prettierrc -w ./src --fix", + "prettier": "prettier ./.prettierrc -w ./src", "lint": "eslint --cache --fix", "prepare": "husky install", "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s" @@ -21,6 +21,7 @@ "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", "@heroicons/react": "^2.1.5", + "@lottiefiles/react-lottie-player": "^3.5.4", "@mui/material": "^6.1.2", "@tauri-apps/api": "^2.0.2", "@tauri-apps/plugin-fs": "~2.0.0", @@ -37,6 +38,7 @@ "immer": "^10.1.1", "js-tiktoken": "^1.0.15", "luxon": "^3.5.0", + "microsoft-cognitiveservices-speech-sdk": "^1.40.0", "notistack": "^3.0.1", "openai": "^4.67.2", "react": "^18.3.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b0b7418..50180b2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,6 +23,9 @@ dependencies: '@heroicons/react': specifier: ^2.1.5 version: 2.1.5(react@18.3.1) + '@lottiefiles/react-lottie-player': + specifier: ^3.5.4 + version: 3.5.4(react@18.3.1) '@mui/material': specifier: ^6.1.2 version: 6.1.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) @@ -71,6 +74,9 @@ dependencies: luxon: specifier: ^3.5.0 version: 3.5.0 + microsoft-cognitiveservices-speech-sdk: + specifier: ^1.40.0 + version: 1.40.0 notistack: specifier: ^3.0.1 version: 3.0.1(csstype@3.1.3)(react-dom@18.3.1)(react@18.3.1) @@ -822,6 +828,15 @@ packages: resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==} dev: false + /@es-joy/jsdoccomment@0.46.0: + resolution: {integrity: sha512-C3Axuq1xd/9VqFZpW4YAzOx5O9q/LP46uIQy/iNDpHG3fmPa6TBtvfglMCs3RBiBxAIi0Go97r8+jvTt55XMyQ==} + engines: {node: '>=16'} + dependencies: + comment-parser: 1.4.1 + esquery: 1.6.0 + jsdoc-type-pratt-parser: 4.0.0 + dev: false + /@esbuild/aix-ppc64@0.21.5: resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} @@ -1167,6 +1182,15 @@ packages: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + /@lottiefiles/react-lottie-player@3.5.4(react@18.3.1): + resolution: {integrity: sha512-2FptWtHQ+o7MzdsMKSvNZ1Mz7xtKSYI0WL9HjZ1r+CvsXR3lbLQUDp7Pwx6qhg0Akm4VluQ+8/D1S1fcr1Ao4w==} + peerDependencies: + react: 16 - 18 + dependencies: + lottie-web: 5.12.2 + react: 18.3.1 + dev: false + /@mui/core-downloads-tracker@6.1.2: resolution: {integrity: sha512-1oE4U38/TtzLWRYWEm/m70dUbpcvBx0QvDVg6NtpOmSNQC1Mbx0X/rNvYDdZnn8DIsAiVQ+SZ3am6doSswUQ4g==} dev: false @@ -1787,6 +1811,10 @@ packages: '@types/debounce': 1.2.4 dev: true + /@types/webrtc@0.0.37: + resolution: {integrity: sha512-JGAJC/ZZDhcrrmepU4sPLQLIOIAgs5oIK+Ieq90K8fdaNMhfdfqmYatJdgif1NDQtvrSlTOGJDUYHIDunuufOg==} + dev: false + /@typescript-eslint/eslint-plugin@8.8.1(@typescript-eslint/parser@8.8.1)(eslint@9.12.0)(typescript@5.6.2): resolution: {integrity: sha512-xfvdgA8AP/vxHgtgU310+WBnLB4uJQ9XdyP17RebG26rLtDrQJV3ZYrcopX91GrHmMoH8bdSwMRh2a//TiJ1jQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1966,6 +1994,20 @@ packages: resolution: {integrity: sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==} dev: true + /agent-base@5.1.1: + resolution: {integrity: sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==} + engines: {node: '>= 6.0.0'} + dev: false + + /agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + dependencies: + debug: 4.3.7 + transitivePeerDependencies: + - supports-color + dev: false + /agent-base@7.1.1: resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==} engines: {node: '>= 14'} @@ -2206,6 +2248,14 @@ packages: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} dev: false + /bent@7.3.12: + resolution: {integrity: sha512-T3yrKnVGB63zRuoco/7Ybl7BwwGZR0lceoVG5XmQyMIH9s19SV5m+a8qam4if0zQuAmOQTyPTPmsQBdAorGK3w==} + dependencies: + bytesish: 0.4.4 + caseless: 0.12.0 + is-stream: 2.0.1 + dev: false + /bidi-js@1.0.3: resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} dependencies: @@ -2248,6 +2298,10 @@ packages: update-browserslist-db: 1.1.1(browserslist@4.24.0) dev: true + /bytesish@0.4.4: + resolution: {integrity: sha512-i4uu6M4zuMUiyfZN4RU2+i9+peJh//pXhd9x1oSe1LBkZ3LEbCoygu8W0bXTukU1Jme2txKuotpCZRaC3FLxcQ==} + dev: false + /call-bind@1.0.7: resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} engines: {node: '>= 0.4'} @@ -2272,6 +2326,10 @@ packages: resolution: {integrity: sha512-7LTwJjcRkzKFmtqGsibMeuXmvFDfZq/nzIjnmgCGzKKRVzjD72selLDK1oPF/Oxzmt4fNcPvTDvGqSDG4tCALw==} dev: true + /caseless@0.12.0: + resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} + dev: false + /ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} dev: false @@ -2440,6 +2498,11 @@ packages: engines: {node: ^12.20.0 || >=14} dev: false + /comment-parser@1.4.1: + resolution: {integrity: sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==} + engines: {node: '>= 12.0.0'} + dev: false + /compare-func@2.0.0: resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} dependencies: @@ -3272,7 +3335,6 @@ packages: engines: {node: '>=0.10'} dependencies: estraverse: 5.3.0 - dev: true /esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} @@ -3284,7 +3346,6 @@ packages: /estraverse@5.3.0: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} - dev: true /estree-util-is-identifier-name@3.0.0: resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} @@ -3829,6 +3890,16 @@ packages: - supports-color dev: false + /https-proxy-agent@4.0.0: + resolution: {integrity: sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==} + engines: {node: '>= 6.0.0'} + dependencies: + agent-base: 5.1.1 + debug: 4.3.7 + transitivePeerDependencies: + - supports-color + dev: false + /https-proxy-agent@7.0.5: resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==} engines: {node: '>= 14'} @@ -4111,6 +4182,11 @@ packages: call-bind: 1.0.7 dev: true + /is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + dev: false + /is-stream@3.0.0: resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -4211,6 +4287,11 @@ packages: argparse: 2.0.1 dev: true + /jsdoc-type-pratt-parser@4.0.0: + resolution: {integrity: sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==} + engines: {node: '>=12.0.0'} + dev: false + /jsdom@23.2.0: resolution: {integrity: sha512-L88oL7D/8ufIES+Zjz7v0aes+oBMh2Xnh3ygWvL0OaICOomKEPKuPnIfBJekiXr+BHbbMjrWn/xqrDQuxFTeyA==} engines: {node: '>=18'} @@ -4443,6 +4524,10 @@ packages: dependencies: js-tokens: 4.0.0 + /lottie-web@5.12.2: + resolution: {integrity: sha512-uvhvYPC8kGPjXT3MyKMrL3JitEAmDMp30lVkuq/590Mw9ok6pWcFCwXJveo0t5uqYw1UREQHofD+jVpdjBv8wg==} + dev: false + /lowlight@1.20.0: resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==} dependencies: @@ -4968,6 +5053,22 @@ packages: picomatch: 2.3.1 dev: true + /microsoft-cognitiveservices-speech-sdk@1.40.0: + resolution: {integrity: sha512-TrvuqFkZqYuJKMw590PJ2yywh7bHxJ996CLLqO+A6TB83x7f2mWlgwMg1qoRycGF4uQndF/zPDLBiSBdwws4CA==} + dependencies: + '@es-joy/jsdoccomment': 0.46.0 + '@types/webrtc': 0.0.37 + agent-base: 6.0.2 + bent: 7.3.12 + https-proxy-agent: 4.0.0 + uuid: 9.0.1 + ws: 7.5.10 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: false + /mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} @@ -6603,6 +6704,11 @@ packages: hasBin: true dev: false + /uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + hasBin: true + dev: false + /validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} dependencies: @@ -6816,6 +6922,19 @@ packages: strip-ansi: 7.1.0 dev: true + /ws@7.5.10: + resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} + engines: {node: '>=8.3.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: false + /ws@8.18.0: resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} engines: {node: '>=10.0.0'} diff --git a/src-tauri/Info.plist b/src-tauri/Info.plist new file mode 100644 index 0000000..a3fab8c --- /dev/null +++ b/src-tauri/Info.plist @@ -0,0 +1,8 @@ + + + + + NSMicrophoneUsageDescription + Request microphone access for WebRTC + + \ No newline at end of file diff --git a/src/assets/lotties/recorder.json b/src/assets/lotties/recorder.json new file mode 100644 index 0000000..73114ba --- /dev/null +++ b/src/assets/lotties/recorder.json @@ -0,0 +1,566 @@ +{ + "v": "5.2.1", + "fr": 30, + "ip": 0, + "op": 69, + "w": 280, + "h": 280, + "nm": "Circle", + "ddd": 0, + "assets": [], + "layers": [ + { + "ddd": 0, + "ind": 1, + "ty": 4, + "nm": "Shape 2 Outlines", + "sr": 1, + "ks": { + "o": { "a": 0, "k": 100, "ix": 11 }, + "r": { "a": 0, "k": 0, "ix": 10 }, + "p": { "a": 0, "k": [140, 140, 0], "ix": 2 }, + "a": { "a": 0, "k": [7, 9.5, 0], "ix": 1 }, + "s": { "a": 0, "k": [355, 355, 100], "ix": 6 } + }, + "ao": 0, + "ef": [ + { + "ty": 5, + "nm": "Position - Overshoot", + "np": 3, + "mn": "ADBE Slider Control", + "ix": 1, + "en": 1, + "ef": [ + { + "ty": 0, + "nm": "Slider", + "mn": "ADBE Slider Control-0001", + "ix": 1, + "v": { + "a": 0, + "k": 20, + "ix": 1, + "x": "var $bm_rt;\n$bm_rt = clamp(value, 0, 100);" + } + } + ] + }, + { + "ty": 5, + "nm": "Position - Bounce", + "np": 3, + "mn": "ADBE Slider Control", + "ix": 2, + "en": 1, + "ef": [ + { + "ty": 0, + "nm": "Slider", + "mn": "ADBE Slider Control-0001", + "ix": 1, + "v": { + "a": 0, + "k": 40, + "ix": 1, + "x": "var $bm_rt;\n$bm_rt = clamp(value, 0, 100);" + } + } + ] + }, + { + "ty": 5, + "nm": "Position - Friction", + "np": 3, + "mn": "ADBE Slider Control", + "ix": 3, + "en": 1, + "ef": [ + { + "ty": 0, + "nm": "Slider", + "mn": "ADBE Slider Control-0001", + "ix": 1, + "v": { + "a": 0, + "k": 40, + "ix": 1, + "x": "var $bm_rt;\n$bm_rt = clamp(value, 0, 100);" + } + } + ] + } + ], + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [-1.66, 0], + [0, 1.66], + [0, 0], + [1.66, 0], + [0, -1.66], + [0, 0] + ], + "o": [ + [1.66, 0], + [0, 0], + [0, -1.66], + [-1.66, 0], + [0, 0], + [0, 1.66] + ], + "v": [ + [0, 2.5], + [2.99, -0.5], + [3, -6.5], + [0, -9.5], + [-3, -6.5], + [-3, -0.5] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 2", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ind": 1, + "ty": "sh", + "ix": 2, + "ks": { + "a": 0, + "k": { + "i": [ + [0, 0], + [2.76, 0], + [0, 3], + [0, 0], + [-3.28, -0.49], + [0, 0], + [0, 0], + [0, 0], + [0, 3.42] + ], + "o": [ + [0, 3], + [-2.76, 0], + [0, 0], + [0, 3.41], + [0, 0], + [0, 0], + [0, 0], + [3.28, -0.48], + [0, 0] + ], + "v": [ + [5.3, -0.5], + [0, 4.6], + [-5.3, -0.5], + [-7, -0.5], + [-1, 6.22], + [-1, 9.5], + [1, 9.5], + [1, 6.22], + [7, -0.5] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 3", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "mm", + "mm": 1, + "nm": "Merge Paths 1", + "mn": "ADBE Vector Filter - Merge", + "hd": false + }, + { + "ty": "fl", + "c": { "a": 0, "k": [1, 1, 1, 1], "ix": 4 }, + "o": { "a": 0, "k": 100, "ix": 5 }, + "r": 1, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { "a": 0, "k": [7, 9.5], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [100, 100], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Group 1", + "np": 4, + "cix": 2, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 171, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 2, + "ty": 4, + "nm": "base", + "sr": 1, + "ks": { + "o": { "a": 0, "k": 100, "ix": 11 }, + "r": { "a": 0, "k": 0, "ix": 10 }, + "p": { "a": 0, "k": [140, 140, 0], "ix": 2 }, + "a": { "a": 0, "k": [11.178, -2.697, 0], "ix": 1 }, + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.667, 0.667, 0.667], "y": [1, 1, 1] }, + "o": { "x": [0.333, 0.333, 0.333], "y": [0, 0, 0] }, + "n": ["0p667_1_0p333_0", "0p667_1_0p333_0", "0p667_1_0p333_0"], + "t": 0, + "s": [54.945, 54.945, 100], + "e": [61.945, 61.945, 100] + }, + { + "i": { "x": [0.667, 0.667, 0.667], "y": [1, 1, 1] }, + "o": { "x": [0.333, 0.333, 0.333], "y": [0, 0, 0] }, + "n": ["0p667_1_0p333_0", "0p667_1_0p333_0", "0p667_1_0p333_0"], + "t": 30, + "s": [61.945, 61.945, 100], + "e": [54.945, 54.945, 100] + }, + { "t": 60 } + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { "a": 0, "k": [331.855, 331.855], "ix": 2 }, + "p": { "a": 0, "k": [0, 0], "ix": 3 }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse", + "hd": false + }, + { + "ty": "st", + "c": { "a": 0, "k": [0, 0, 0, 1], "ix": 3 }, + "o": { "a": 0, "k": 100, "ix": 4 }, + "w": { "a": 0, "k": 0, "ix": 5 }, + "lc": 1, + "lj": 1, + "ml": 4, + "ml2": { "a": 0, "k": 4, "ix": 8 }, + "nm": "Stroke 1", + "mn": "ADBE Vector Graphic - Stroke", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.9058823529411765, 0.11764705882352941, 0.3843137254901961, 1 + ], + "ix": 4 + }, + "o": { "a": 0, "k": 100, "ix": 5 }, + "r": 1, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { "a": 0, "k": [11.178, -2.697], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [75.704, 75.704], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Ellipse 1", + "np": 3, + "cix": 2, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 900.900900900901, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 3, + "ty": 4, + "nm": "Base Layer 4", + "sr": 1, + "ks": { + "o": { + "a": 1, + "k": [ + { + "i": { "x": [0.833], "y": [0.833] }, + "o": { "x": [0.167], "y": [0.167] }, + "n": ["0p833_0p833_0p167_0p167"], + "t": 20.021, + "s": [49], + "e": [0] + }, + { "t": 45.044921875 } + ], + "ix": 11 + }, + "r": { "a": 0, "k": 0, "ix": 10 }, + "p": { "a": 0, "k": [140, 140, 0], "ix": 2 }, + "a": { "a": 0, "k": [11.178, -2.697, 0], "ix": 1 }, + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.833, 0.833, 0.833], "y": [0.833, 0.833, 0.833] }, + "o": { "x": [0.167, 0.167, 0.167], "y": [0.167, 0.167, 0.167] }, + "n": [ + "0p833_0p833_0p167_0p167", + "0p833_0p833_0p167_0p167", + "0p833_0p833_0p167_0p167" + ], + "t": 0, + "s": [31.532, 31.532, 100], + "e": [69.368, 69.368, 100] + }, + { + "i": { "x": [0.833, 0.833, 0.833], "y": [0.833, 0.833, 0.833] }, + "o": { "x": [0.167, 0.167, 0.167], "y": [0.167, 0.167, 0.167] }, + "n": [ + "0p833_0p833_0p167_0p167", + "0p833_0p833_0p167_0p167", + "0p833_0p833_0p167_0p167" + ], + "t": 20.021, + "s": [69.368, 69.368, 100], + "e": [84.882, 84.882, 100] + }, + { "t": 45.044921875 } + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { "a": 0, "k": [331.855, 331.855], "ix": 2 }, + "p": { "a": 0, "k": [0, 0], "ix": 3 }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse", + "hd": false + }, + { + "ty": "st", + "c": { "a": 0, "k": [0, 0, 0, 1], "ix": 3 }, + "o": { "a": 0, "k": 100, "ix": 4 }, + "w": { "a": 0, "k": 0, "ix": 5 }, + "lc": 1, + "lj": 1, + "ml": 4, + "ml2": { "a": 0, "k": 4, "ix": 8 }, + "nm": "Stroke 1", + "mn": "ADBE Vector Graphic - Stroke", + "hd": false + }, + { + "ty": "fl", + "c": { "a": 0, "k": [1, 0.867, 0.867, 1], "ix": 4 }, + "o": { "a": 0, "k": 100, "ix": 5 }, + "r": 1, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { "a": 0, "k": [11.178, -2.697], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [97.195, 97.195], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Ellipse 1", + "np": 3, + "cix": 2, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 900.900900900901, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 4, + "ty": 4, + "nm": "Base Layer 3", + "sr": 1, + "ks": { + "o": { + "a": 1, + "k": [ + { + "i": { "x": [0.833], "y": [0.833] }, + "o": { "x": [0.167], "y": [0.167] }, + "n": ["0p833_0p833_0p167_0p167"], + "t": 34.034, + "s": [27], + "e": [0] + }, + { "t": 59.05859375 } + ], + "ix": 11 + }, + "r": { "a": 0, "k": 0, "ix": 10 }, + "p": { "a": 0, "k": [140, 140, 0], "ix": 2 }, + "a": { "a": 0, "k": [11.178, -2.697, 0], "ix": 1 }, + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.833, 0.833, 0.833], "y": [0.833, 0.833, 0.833] }, + "o": { "x": [0.167, 0.167, 0.167], "y": [0.167, 0.167, 0.167] }, + "n": [ + "0p833_0p833_0p167_0p167", + "0p833_0p833_0p167_0p167", + "0p833_0p833_0p167_0p167" + ], + "t": 14.014, + "s": [31.532, 31.532, 100], + "e": [69.368, 69.368, 100] + }, + { + "i": { "x": [0.833, 0.833, 0.833], "y": [0.833, 0.833, 0.833] }, + "o": { "x": [0.167, 0.167, 0.167], "y": [0.167, 0.167, 0.167] }, + "n": [ + "0p833_0p833_0p167_0p167", + "0p833_0p833_0p167_0p167", + "0p833_0p833_0p167_0p167" + ], + "t": 34.034, + "s": [69.368, 69.368, 100], + "e": [84.882, 84.882, 100] + }, + { "t": 59.05859375 } + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { "a": 0, "k": [331.855, 331.855], "ix": 2 }, + "p": { "a": 0, "k": [0, 0], "ix": 3 }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse", + "hd": false + }, + { + "ty": "st", + "c": { "a": 0, "k": [0, 0, 0, 1], "ix": 3 }, + "o": { "a": 0, "k": 100, "ix": 4 }, + "w": { "a": 0, "k": 0, "ix": 5 }, + "lc": 1, + "lj": 1, + "ml": 4, + "ml2": { "a": 0, "k": 4, "ix": 8 }, + "nm": "Stroke 1", + "mn": "ADBE Vector Graphic - Stroke", + "hd": false + }, + { + "ty": "fl", + "c": { "a": 0, "k": [1, 0.6, 0.6, 1], "ix": 4 }, + "o": { "a": 0, "k": 100, "ix": 5 }, + "r": 1, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { "a": 0, "k": [11.178, -2.697], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [97.195, 97.195], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Ellipse 1", + "np": 3, + "cix": 2, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 900.900900900901, + "st": 0, + "bm": 0 + } + ], + "markers": [] +} diff --git a/src/components/ChatBox/ChatMessages.tsx b/src/components/ChatBox/ChatMessages.tsx index 4563e11..abe7ad3 100644 --- a/src/components/ChatBox/ChatMessages.tsx +++ b/src/components/ChatBox/ChatMessages.tsx @@ -1,13 +1,15 @@ +import { SpeakerWaveIcon } from '@heroicons/react/24/outline' import classNames from 'classnames' -import { FC, memo, useEffect, useMemo, useRef } from 'react' +import { ChatCompletionContentPartText } from 'openai/resources' +import { FC, memo, useEffect, useMemo, useRef, useState } from 'react' import { useRecoilValue } from 'recoil' import ChatGPTLogoImg from 'src/assets/chatbot.png' import NoDataIllustration from 'src/assets/illustrations/no-data.svg' -import { useSettings } from 'src/hooks' -import { isAudioProduct } from 'src/shared/utils' +import { useSettings, useSpeech } from 'src/hooks' +import { isSupportAudio } from 'src/shared/utils' import { currConversationState, loadingState } from 'src/stores/conversation' import { currProductState } from 'src/stores/global' -import { Roles } from 'src/types/conversation' +import { Message, Roles } from 'src/types/conversation' import { Products } from 'src/types/global' import Waveform from '../Waveform' import ChatBubble from './ChatBubble' @@ -20,6 +22,8 @@ const ChatMessages: FC = () => { const { settings } = useSettings() const currProduct = useRecoilValue(currProductState) const currConversation = useRecoilValue(currConversationState) + const [audioUrl, setAudioUrl] = useState('') + const createSpeech = useSpeech() const hasMessages = useMemo( () => currConversation && currConversation.messages.length > 0, [currConversation] @@ -44,6 +48,23 @@ const ChatMessages: FC = () => { } } + const createTTSUrl = async (text: string) => { + if (typeof createSpeech === 'function') { + const url = await createSpeech(text) + if (url) { + setAudioUrl(url) + } + } + } + + const getAudioFilename = (message: Message) => { + if (isSupportAudio(currProduct) && message.content[0].type === 'audio') { + return message.content[0].audioUrl.url + } + + return '' + } + useEffect(() => { scrollToBottom() }, [currConversation]) @@ -65,9 +86,9 @@ const ChatMessages: FC = () => { avatar={getBotLogo(message.role)} date={message.createdAt} > - {isAudioProduct(currProduct) && message.fileName ? ( + {getAudioFilename(message) ? ( <> - + {message.content} ) : ( @@ -75,9 +96,49 @@ const ChatMessages: FC = () => { {loading && !message.content ? ( ) : message.role === Roles.Assistant ? ( - +
+ + + {audioUrl &&
) : ( - message.content +
+ {message.content.map((item, key) => { + if (item.type === 'image_url') { + return ( + + ) + } + + if (item.type === 'text') { + return

{item.text}

+ } + })} +
)} )} diff --git a/src/components/ChatBox/InputBox.tsx b/src/components/ChatBox/InputBox.tsx index a1ad92b..72ade82 100644 --- a/src/components/ChatBox/InputBox.tsx +++ b/src/components/ChatBox/InputBox.tsx @@ -1,71 +1,156 @@ -import { MicrophoneIcon } from '@heroicons/react/24/solid' -import Tooltip from '@mui/material/Tooltip' import classNames from 'classnames' -import { ChangeEvent, FC, memo, useEffect, useRef, useState } from 'react' -import { useRecoilValue } from 'recoil' -import { useAppData, useRequest } from 'src/hooks' -import { isAudioProduct } from 'src/shared/utils' -import { currConversationState, loadingState } from 'src/stores/conversation' +import { produce } from 'immer' +import { + ChatCompletionContentPart, + ChatCompletionContentPartImage, + ChatCompletionContentPartText +} from 'openai/resources' +import { FC, memo, useEffect, useRef, useState } from 'react' +import { useRecoilState, useRecoilValue } from 'recoil' +import items from 'src/components/Sidebar/Items' +import { + useAudio, + useChatCompletion, + useCompletion, + useImageGeneration +} from 'src/hooks' +import { + audioFileState, + base64ImagesState, + currConversationState, + loadingState, + userInputState +} from 'src/stores/conversation' import { currProductState } from 'src/stores/global' -import { HashFile } from 'src/types/global' -import Divider from '../Divider' -import { LoadingIcon, SolidSendIcon } from '../Icons' +import { settingsState } from 'src/stores/settings' +import { AudioContentPart } from 'src/types/conversation' +import { Products } from 'src/types/global' +import { LoadingIcon, SolidCloseIcon, SolidSendIcon } from '../Icons' +import WaveForm from '../Waveform' +import MediaUploader from './MediaUploader' +import AudioRecorder from './Recorder' const InputBox: FC = () => { - const fileInputRef = useRef(null) - const textareaRef = useRef(null) const currConversation = useRecoilValue(currConversationState) const currProduct = useRecoilValue(currProductState) + const settings = useRecoilValue(settingsState) const loading = useRecoilValue(loadingState) - const { saveFileToAppDataDir } = useAppData() - const [prompt, setPrompt] = useState('') + const [userInput, setUserInput] = useRecoilState(userInputState) + const [audioFile, setAudioFile] = useRecoilState(audioFileState) + const [base64Images, setBase64Images] = useRecoilState(base64ImagesState) + const createChatCompletion = useChatCompletion() + const createAudio = useAudio() + const createImage = useImageGeneration() + const createCompletion = useCompletion() + const textareaRef = useRef(null) const [isTyping, setIsTyping] = useState(false) - const [hashFile, setHashFile] = useState(null) - const requests = useRequest(prompt, hashFile as HashFile) + const mediaType = items.find( + (item) => item.product === currProduct + )?.multiMedia - const onFileChange = async (e: ChangeEvent) => { - const file = e.target.files && e.target.files[0] - - if (file) { - const hashName = await saveFileToAppDataDir(file) - setHashFile({ - file, - hashName + const deleteBase64Image = (idx: number) => { + setBase64Images( + produce(base64Images, (draft) => { + draft?.splice(idx, 1) }) - } + ) } const resetInput = () => { - setPrompt('') - setHashFile(null) + setUserInput('') + setAudioFile({ + filename: '', + binary: undefined + }) + setBase64Images(null) + } + + const validate = () => { + if (loading) return false + return userInput.trim().length !== 0 + } + + const handleRequest = () => { + if (!settings || !validate()) return + + if (currProduct === Products.ChatCompletion) { + const chatMessageImageContent: + | ChatCompletionContentPartImage[] + | undefined = base64Images?.map((imageUrl) => ({ + type: 'image_url', + image_url: { + url: imageUrl + } + })) + + const chatMessageTextContent: ChatCompletionContentPartText = { + type: 'text', + text: userInput + } + + const chatCompletionUserMessage: ChatCompletionContentPart[] = [ + ...(chatMessageImageContent || []), + chatMessageTextContent + ] - if (textareaRef.current) { - textareaRef.current.blur() - textareaRef.current.value = '' + if (createChatCompletion) { + createChatCompletion(chatCompletionUserMessage) + } } - if (fileInputRef.current) { - fileInputRef.current.value = '' + if ( + currProduct === Products.AudioTranscription || + currProduct === Products.AudioTranslation + ) { + const audioContentPart: AudioContentPart[] = [ + { + type: 'audio', + audioUrl: { url: audioFile.filename }, + text: userInput, + binary: audioFile.binary + } + ] + + if (createAudio) { + if (currProduct === Products.AudioTranscription) { + const createAudioTranscription = + createAudio[Products.AudioTranscription] + createAudioTranscription(audioContentPart) + } + + if (currProduct === Products.AudioTranslation) { + const createAudioTranslation = createAudio[Products.AudioTranslation] + createAudioTranslation(audioContentPart) + } + } } - } - // Prompt is optional in audio products. - const validate = () => { - if (loading) return false + if (currProduct === Products.ImageGeneration) { + const imageGenerationTextContent: ChatCompletionContentPartText[] = [ + { + type: 'text', + text: userInput + } + ] - if (isAudioProduct(currProduct)) { - return Boolean(hashFile) - } else { - return prompt.trim().length !== 0 + if (createImage) { + createImage(imageGenerationTextContent) + } } - } - const handleRequest = () => { - if (!validate()) return + if (currProduct === Products.Completion) { + const completionTextContent: ChatCompletionContentPartText[] = [ + { + type: 'text', + text: userInput + } + ] - if (requests[currProduct]) { - requests[currProduct]() + if (createCompletion) { + createCompletion(completionTextContent) + } } + resetInput() } @@ -80,7 +165,7 @@ const InputBox: FC = () => { const end = event.target.selectionEnd const value = event.target.value - setPrompt(value.substring(0, start) + '\n' + value.substring(end)) + setUserInput(value.substring(0, start) + '\n' + value.substring(end)) event.target.selectionStart = event.target.selectionEnd = start + 1 } @@ -98,96 +183,84 @@ const InputBox: FC = () => { textareaRef?.current?.scrollHeight > 400 ? 'auto' : 'hidden' }` } - }, [prompt]) + }, [userInput]) if (!currConversation) return null return ( -
-
-