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 (
-
-
+ )}
+ {audioFile.filename && (
+
+ )}
+
+ {mediaType && (
+
+ )}
+
+
)
diff --git a/src/components/ChatBox/Markdown.tsx b/src/components/ChatBox/Markdown.tsx
index f483512..5467228 100644
--- a/src/components/ChatBox/Markdown.tsx
+++ b/src/components/ChatBox/Markdown.tsx
@@ -16,7 +16,6 @@ const Markdown: FC = ({ raw }) => {
return (
= ({ mediaType, className }) => {
+ const { saveFileToAppDataDir } = useAppData()
+ const fileInputRef = useRef(null)
+ const [audioFile, setAudioFile] = useRecoilState(audioFileState)
+ const setBase64Images = useSetRecoilState(base64ImagesState)
+
+ const validate = () => true
+
+ const onFileChange = async (e: ChangeEvent) => {
+ const files = e.target.files
+ if (!files) return
+
+ if (mediaType === MediaType.Audio) {
+ const file = files[0]
+ setAudioFile({
+ ...audioFile,
+ binary: file
+ })
+
+ const filename = await saveFileToAppDataDir(file)
+ setAudioFile({
+ ...audioFile,
+ filename
+ })
+ }
+
+ if (mediaType === MediaType.Image) {
+ const promises = []
+ for (const file of files) {
+ promises.push(convertBase64(file))
+ }
+
+ try {
+ const base64Files = await Promise.all(promises)
+ setBase64Images(base64Files)
+ } catch {
+ enqueueSnackbar('Can not upload images.', { variant: 'error' })
+ }
+ }
+ }
+
+ return (
+
+ )
+}
+
+export default MediaUploader
diff --git a/src/components/ChatBox/Recorder.tsx b/src/components/ChatBox/Recorder.tsx
new file mode 100644
index 0000000..f42caea
--- /dev/null
+++ b/src/components/ChatBox/Recorder.tsx
@@ -0,0 +1,89 @@
+import { Player } from '@lottiefiles/react-lottie-player'
+import classNames from 'classnames'
+import { enqueueSnackbar } from 'notistack'
+import { FC, useRef, useState } from 'react'
+import { useRecoilState, useRecoilValue } from 'recoil'
+import RecorderJSON from 'src/assets/lotties/recorder.json'
+import { useClients } from 'src/hooks'
+import { userInputState } from 'src/stores/conversation'
+import { settingsState } from 'src/stores/settings'
+
+interface Props {
+ className?: string
+}
+
+const AudioRecorder: FC = ({ className }) => {
+ const settings = useRecoilValue(settingsState)
+ const { azureClient } = useClients()
+ const [userInput, setUserInput] = useRecoilState(userInputState)
+ const [isRecording, setIsRecording] = useState(false)
+ // const [audioUrl, setAudioUrl] = useState('')
+ const mediaRecorderRef = useRef(null)
+ const audioChunksRef = useRef([])
+ const lottieRef = useRef(null)
+
+ const startRecording = async () => {
+ if (!settings) return
+
+ try {
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
+ mediaRecorderRef.current = new MediaRecorder(stream)
+ mediaRecorderRef.current.start()
+ setIsRecording(true)
+
+ mediaRecorderRef.current.ondataavailable = (event) => {
+ audioChunksRef.current.push(event.data)
+ }
+ mediaRecorderRef.current.onstop = async () => {
+ const audioBlob = new Blob(audioChunksRef.current, {
+ type: 'audio/mp3'
+ })
+ const uint8Array = new Uint8Array(await audioBlob.arrayBuffer())
+ const transcription = await azureClient.getAudioTranscription(
+ settings.azureDeploymentNameAudioGeneration,
+ uint8Array
+ )
+ setUserInput(`${userInput}${transcription.text}`)
+
+ // const audioUrl = URL.createObjectURL(audioBlob)
+ // setAudioUrl(audioUrl)
+ audioChunksRef.current = []
+ }
+ } catch (error) {
+ enqueueSnackbar('Error accessing media devices.', { variant: 'error' })
+ }
+ }
+
+ const stopRecording = () => {
+ mediaRecorderRef.current?.stop()
+ setIsRecording(false)
+ lottieRef.current?.stop()
+ lottieRef.current?.setSeeker(0)
+ }
+
+ return (
+
+
+
+ {/* {
+ // the audio preview is just for test.
+ audioUrl && (
+
+ )} */}
+
+ )
+}
+
+export default AudioRecorder
diff --git a/src/components/Configuration/AudioTranscription.tsx b/src/components/Configuration/AudioTranscription.tsx
index 3428f80..7d072af 100644
--- a/src/components/Configuration/AudioTranscription.tsx
+++ b/src/components/Configuration/AudioTranscription.tsx
@@ -155,9 +155,11 @@ const Configuration: FC = () => {
= ({ active, conversation, onClick }) => {
const { isSameDay, display } = formatDate(conversation.updatedAt)
+ const showLastMessage = () => {
+ const { content } = conversation.messages[conversation.messages.length - 1]
+ const lastMessage = content[content.length - 1]
+
+ if (lastMessage.type === 'image_url') {
+ return '[Image]'
+ } else if (lastMessage.type === 'text') {
+ return lastMessage.text
+ }
+
+ return ''
+ }
+
return (
{conversation.avatar ? (
@@ -45,7 +58,7 @@ const ConversationItem: FC = ({ active, conversation, onClick }) => {
{conversation.messages.length > 0 && (
- {conversation.messages[conversation.messages.length - 1].content}
+ {showLastMessage()}
)}
diff --git a/src/components/Icons/SolidCloseIcon.tsx b/src/components/Icons/SolidCloseIcon.tsx
new file mode 100644
index 0000000..2b2a361
--- /dev/null
+++ b/src/components/Icons/SolidCloseIcon.tsx
@@ -0,0 +1,27 @@
+import classNames from 'classnames'
+import { FC } from 'react'
+import { SvgIconProps } from 'src/types/global'
+
+const SolidCloseIcon: FC = ({
+ className,
+ pathClassName,
+ onClick
+}) => (
+
+)
+
+export default SolidCloseIcon
diff --git a/src/components/Icons/index.tsx b/src/components/Icons/index.tsx
index 3fcef6e..2c27905 100644
--- a/src/components/Icons/index.tsx
+++ b/src/components/Icons/index.tsx
@@ -3,6 +3,7 @@ import LoadingIcon from './LoadingIcon'
import OpenAILogoIcon from './OpenAILogoIcon'
import OutlinePlusIcon from './OutlinePlusIcon'
import OutlineTranslationIcon from './OutlineTranslationIcon'
+import SolidCloseIcon from './SolidCloseIcon'
import SolidSendIcon from './SolidSendIcon'
import SolidSettingsBrightnessIcon from './SolidSettingsBrightnessIcon'
import SolidTranslationIcon from './SolidTranslationIcon'
@@ -13,6 +14,7 @@ export {
OpenAILogoIcon,
OutlinePlusIcon,
OutlineTranslationIcon,
+ SolidCloseIcon,
SolidSendIcon,
SolidSettingsBrightnessIcon,
SolidTranslationIcon
diff --git a/src/components/Sidebar/Items.tsx b/src/components/Sidebar/Items.tsx
index 9019b18..93b5e7c 100644
--- a/src/components/Sidebar/Items.tsx
+++ b/src/components/Sidebar/Items.tsx
@@ -14,7 +14,7 @@ import {
OutlineTranslationIcon,
SolidTranslationIcon
} from 'src/components/Icons'
-import { Companies, Products } from 'src/types/global'
+import { Companies, MediaType, Products } from 'src/types/global'
export const iconClassName = 'h-6 w-6 text-black dark:text-white'
export const companyClassName = 'h-8 w-8'
@@ -24,7 +24,8 @@ export default [
product: Products.ChatCompletion,
inactive: ,
active: ,
- realm: [Companies.Azure, Companies.OpenAI]
+ realm: [Companies.Azure, Companies.OpenAI],
+ multiMedia: MediaType.Image
},
{
product: Products.Completion,
@@ -32,24 +33,28 @@ export default [
),
active: ,
- realm: [Companies.Azure, Companies.OpenAI]
+ realm: [Companies.Azure, Companies.OpenAI],
+ multiMedia: undefined
},
{
product: Products.AudioTranscription,
inactive: ,
active: ,
- realm: [Companies.OpenAI]
+ realm: [Companies.Azure, Companies.OpenAI],
+ multiMedia: MediaType.Audio
},
{
product: Products.AudioTranslation,
inactive: ,
active: ,
- realm: [Companies.OpenAI]
+ realm: [Companies.Azure, Companies.OpenAI],
+ multiMedia: MediaType.Audio
},
{
product: Products.ImageGeneration,
inactive: ,
active: ,
- realm: [Companies.Azure, Companies.OpenAI]
+ realm: [Companies.Azure, Companies.OpenAI],
+ multiMedia: undefined
}
]
diff --git a/src/components/Waveform/index.tsx b/src/components/Waveform/index.tsx
index 8cea4d5..4f8ad44 100644
--- a/src/components/Waveform/index.tsx
+++ b/src/components/Waveform/index.tsx
@@ -54,7 +54,7 @@ const Waveform: FC = ({ filename }) => {
barWidth: 2,
height: 40,
waveColor: 'rgba(255, 255, 255, 0.6)',
- progressColor: '#fff'
+ progressColor: '#383351'
})
waveSurfer.load(src)
waveSurfer.on('ready', () => {
@@ -73,14 +73,14 @@ const Waveform: FC = ({ filename }) => {
{isPlaying ? (
) : (
{
const currProduct = useRecoilValue(currProductState)
const { getConversationByProduct } = useDB('conversations')
const conversations = useLiveQuery(
- getConversationByProduct,
+ getConversationByProduct as () => Promise,
[currProduct]
)
const [currConversation, setCurrConversation] = useRecoilState(
diff --git a/src/containers/Settings/index.tsx b/src/containers/Settings/index.tsx
index 1e53e08..fdda9c1 100644
--- a/src/containers/Settings/index.tsx
+++ b/src/containers/Settings/index.tsx
@@ -172,6 +172,8 @@ const Settings: FC = () => {
{...formik.getFieldProps('azureEndPoint')}
/>
+
+
{
placeholder="Eg: gpt-4o-realtime-preview"
/>
+
+
+
+
+
+