From 51b73ed8d9c8b6f55126375ffa9ad766856ac0a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=A3=E8=87=BB?= Date: Tue, 13 Aug 2024 20:24:37 +0800 Subject: [PATCH] feat: web ui update (#198) --- playground/package.json | 7 +- playground/postcss.config.js | 5 + .../src/{css/globals.css => app/global.css} | 18 +- playground/src/app/home/index.module.scss | 70 - playground/src/app/home/page.tsx | 79 +- playground/src/app/index.module.scss | 4 +- playground/src/app/layout.tsx | 3 +- playground/src/assets/color_picker.svg | 17 + playground/src/assets/info.svg | 3 + playground/src/assets/logo.svg | 68 +- playground/src/assets/logo_small.svg | 33 + playground/src/assets/pdf.svg | 3 + playground/src/assets/voice.svg | 3 + playground/src/common/constant.ts | 18 +- playground/src/common/hooks.ts | 7 +- playground/src/common/request.ts | 36 + playground/src/common/utils.ts | 5 + .../chat/chatItem/index.module.scss | 62 - .../src/components/chat/chatItem/index.tsx | 41 - playground/src/components/chat/index.tsx | 32 - .../components/customSelect/index.module.scss | 22 + .../src/components/customSelect/index.tsx | 19 + playground/src/components/header/index.tsx | 38 - .../components/icons/colorPicker/index.tsx | 6 + playground/src/components/icons/index.tsx | 4 + .../src/components/icons/info/index.tsx | 6 + .../src/components/icons/logo/index.tsx | 4 +- playground/src/components/icons/pdf/index.tsx | 6 + playground/src/components/icons/types.ts | 2 + .../src/components/icons/voice/index.tsx | 6 + .../components/loginCard/index.module.scss | 2 +- playground/src/components/loginCard/index.tsx | 16 +- .../src/components/menu/index.module.scss | 24 - playground/src/components/menu/index.tsx | 53 - .../components/pdfSelect/index.module.scss | 8 + playground/src/components/pdfSelect/index.tsx | 72 + .../pdfSelect/upload/index.module.scss | 7 + .../src/components/pdfSelect/upload/index.tsx | 74 ++ .../src/components/setting/Info/index.tsx | 22 - .../src/components/setting/index.module.scss | 100 -- .../mobile/chat/chatItem/index.module.scss | 86 ++ .../platform/mobile/chat/chatItem/index.tsx | 50 + .../mobile}/chat/index.module.scss | 36 +- playground/src/platform/mobile/chat/index.tsx | 64 + .../mobile/description/index.module.scss | 71 + .../mobile/description}/index.tsx | 69 +- .../platform/mobile/entry/index.module.scss | 18 + .../src/platform/mobile/entry/index.tsx | 30 + .../platform/mobile/header/index.module.scss | 51 + .../src/platform/mobile/header/index.tsx | 46 + .../header/infoPopover}/index.module.scss | 11 +- .../mobile/header/infoPopover}/index.tsx | 36 +- .../mobile}/header/network/index.module.scss | 0 .../mobile}/header/network/index.tsx | 11 +- .../colorPicker/index.module.scss | 24 + .../header/stylePopover/colorPicker/index.tsx | 22 + .../header/stylePopover}/index.module.scss | 10 +- .../mobile/header/stylePopover}/index.tsx | 30 +- .../src/platform/mobile/menu/context.ts | 9 + .../platform/mobile/menu/index.module.scss | 69 + playground/src/platform/mobile/menu/index.tsx | 76 ++ .../mobile}/rtc/agent/index.module.scss | 0 .../mobile}/rtc/agent/index.tsx | 0 .../rtc/audioVisualizer/index.module.scss | 0 .../mobile}/rtc/audioVisualizer/index.tsx | 0 .../camSection/camSelect/index.module.scss | 0 .../rtc/camSection/camSelect/index.tsx | 0 .../mobile/rtc/camSection/index.module.scss | 54 + .../platform/mobile/rtc/camSection/index.tsx | 42 + .../mobile}/rtc/index.module.scss | 20 +- .../mobile}/rtc/index.tsx | 24 +- .../mobile/rtc/micSection/index.module.scss | 58 + .../platform/mobile/rtc/micSection/index.tsx | 70 + .../micSection/micSelect/index.module.scss | 0 .../rtc/micSection/micSelect/index.tsx | 0 .../rtc/streamPlayer/index.module.scss | 0 .../mobile}/rtc/streamPlayer/index.tsx | 0 .../rtc/streamPlayer/localStreamPlayer.tsx | 0 .../pc/chat/chatItem/index.module.scss | 90 ++ .../src/platform/pc/chat/chatItem/index.tsx | 51 + .../src/platform/pc/chat/index.module.scss | 73 ++ playground/src/platform/pc/chat/index.tsx | 59 + .../platform/pc/description/index.module.scss | 72 + .../src/platform/pc/description/index.tsx | 99 ++ .../src/platform/pc/entry/index.module.scss | 17 + playground/src/platform/pc/entry/index.tsx | 22 + .../pc}/header/index.module.scss | 12 +- playground/src/platform/pc/header/index.tsx | 46 + .../pc/header/infoPopover}/index.module.scss | 13 +- .../platform/pc/header/infoPopover/index.tsx | 57 + .../pc/header/network/index.module.scss | 0 .../src/platform/pc/header/network/index.tsx | 37 + .../colorPicker/index.module.scss | 24 + .../header/stylePopover/colorPicker/index.tsx | 22 + .../pc/header/stylePopover/index.module.scss | 51 + .../platform/pc/header/stylePopover/index.tsx | 54 + .../platform/pc/rtc/agent/index.module.scss | 31 + .../src/platform/pc/rtc/agent/index.tsx | 34 + .../pc/rtc/audioVisualizer/index.module.scss | 17 + .../platform/pc/rtc/audioVisualizer/index.tsx | 48 + .../camSection/camSelect/index.module.scss | 4 + .../pc/rtc/camSection/camSelect/index.tsx | 57 + .../pc}/rtc/camSection/index.module.scss | 0 .../pc}/rtc/camSection/index.tsx | 6 +- .../src/platform/pc/rtc/index.module.scss | 55 + playground/src/platform/pc/rtc/index.tsx | 128 ++ .../pc}/rtc/micSection/index.module.scss | 0 .../pc}/rtc/micSection/index.tsx | 2 +- .../micSection/micSelect/index.module.scss | 4 + .../pc/rtc/micSection/micSelect/index.tsx | 58 + .../pc/rtc/streamPlayer/index.module.scss | 6 + .../platform/pc/rtc/streamPlayer/index.tsx | 1 + .../pc/rtc/streamPlayer/localStreamPlayer.tsx | 46 + playground/src/protobuf/SttMessage001.js | 143 -- playground/src/protobuf/SttMessage002.js | 130 -- playground/src/protobuf/SttMessage003.js | 1161 ----------------- playground/src/store/reducers/global.ts | 18 +- playground/src/types/index.ts | 27 + 118 files changed, 2756 insertions(+), 2111 deletions(-) rename playground/src/{css/globals.css => app/global.css} (74%) delete mode 100644 playground/src/app/home/index.module.scss create mode 100644 playground/src/assets/color_picker.svg create mode 100644 playground/src/assets/info.svg create mode 100644 playground/src/assets/logo_small.svg create mode 100644 playground/src/assets/pdf.svg create mode 100644 playground/src/assets/voice.svg delete mode 100644 playground/src/components/chat/chatItem/index.module.scss delete mode 100644 playground/src/components/chat/chatItem/index.tsx delete mode 100644 playground/src/components/chat/index.tsx create mode 100644 playground/src/components/customSelect/index.module.scss create mode 100644 playground/src/components/customSelect/index.tsx delete mode 100644 playground/src/components/header/index.tsx create mode 100644 playground/src/components/icons/colorPicker/index.tsx create mode 100644 playground/src/components/icons/info/index.tsx create mode 100644 playground/src/components/icons/pdf/index.tsx create mode 100644 playground/src/components/icons/voice/index.tsx delete mode 100644 playground/src/components/menu/index.module.scss delete mode 100644 playground/src/components/menu/index.tsx create mode 100644 playground/src/components/pdfSelect/index.module.scss create mode 100644 playground/src/components/pdfSelect/index.tsx create mode 100644 playground/src/components/pdfSelect/upload/index.module.scss create mode 100644 playground/src/components/pdfSelect/upload/index.tsx delete mode 100644 playground/src/components/setting/Info/index.tsx delete mode 100644 playground/src/components/setting/index.module.scss create mode 100644 playground/src/platform/mobile/chat/chatItem/index.module.scss create mode 100644 playground/src/platform/mobile/chat/chatItem/index.tsx rename playground/src/{components => platform/mobile}/chat/index.module.scss (76%) create mode 100644 playground/src/platform/mobile/chat/index.tsx create mode 100644 playground/src/platform/mobile/description/index.module.scss rename playground/src/{components/setting => platform/mobile/description}/index.tsx (50%) create mode 100644 playground/src/platform/mobile/entry/index.module.scss create mode 100644 playground/src/platform/mobile/entry/index.tsx create mode 100644 playground/src/platform/mobile/header/index.module.scss create mode 100644 playground/src/platform/mobile/header/index.tsx rename playground/src/{components/setting/Info => platform/mobile/header/infoPopover}/index.module.scss (84%) rename playground/src/{components/setting/status => platform/mobile/header/infoPopover}/index.tsx (51%) rename playground/src/{components => platform/mobile}/header/network/index.module.scss (100%) rename playground/src/{components => platform/mobile}/header/network/index.tsx (78%) create mode 100644 playground/src/platform/mobile/header/stylePopover/colorPicker/index.module.scss create mode 100644 playground/src/platform/mobile/header/stylePopover/colorPicker/index.tsx rename playground/src/{components/setting/themeSelect => platform/mobile/header/stylePopover}/index.module.scss (92%) rename playground/src/{components/setting/themeSelect => platform/mobile/header/stylePopover}/index.tsx (61%) create mode 100644 playground/src/platform/mobile/menu/context.ts create mode 100644 playground/src/platform/mobile/menu/index.module.scss create mode 100644 playground/src/platform/mobile/menu/index.tsx rename playground/src/{components => platform/mobile}/rtc/agent/index.module.scss (100%) rename playground/src/{components => platform/mobile}/rtc/agent/index.tsx (100%) rename playground/src/{components => platform/mobile}/rtc/audioVisualizer/index.module.scss (100%) rename playground/src/{components => platform/mobile}/rtc/audioVisualizer/index.tsx (100%) rename playground/src/{components => platform/mobile}/rtc/camSection/camSelect/index.module.scss (100%) rename playground/src/{components => platform/mobile}/rtc/camSection/camSelect/index.tsx (100%) create mode 100644 playground/src/platform/mobile/rtc/camSection/index.module.scss create mode 100644 playground/src/platform/mobile/rtc/camSection/index.tsx rename playground/src/{components => platform/mobile}/rtc/index.module.scss (79%) rename playground/src/{components => platform/mobile}/rtc/index.tsx (76%) create mode 100644 playground/src/platform/mobile/rtc/micSection/index.module.scss create mode 100644 playground/src/platform/mobile/rtc/micSection/index.tsx rename playground/src/{components => platform/mobile}/rtc/micSection/micSelect/index.module.scss (100%) rename playground/src/{components => platform/mobile}/rtc/micSection/micSelect/index.tsx (100%) rename playground/src/{components => platform/mobile}/rtc/streamPlayer/index.module.scss (100%) rename playground/src/{components => platform/mobile}/rtc/streamPlayer/index.tsx (100%) rename playground/src/{components => platform/mobile}/rtc/streamPlayer/localStreamPlayer.tsx (100%) create mode 100644 playground/src/platform/pc/chat/chatItem/index.module.scss create mode 100644 playground/src/platform/pc/chat/chatItem/index.tsx create mode 100644 playground/src/platform/pc/chat/index.module.scss create mode 100644 playground/src/platform/pc/chat/index.tsx create mode 100644 playground/src/platform/pc/description/index.module.scss create mode 100644 playground/src/platform/pc/description/index.tsx create mode 100644 playground/src/platform/pc/entry/index.module.scss create mode 100644 playground/src/platform/pc/entry/index.tsx rename playground/src/{components => platform/pc}/header/index.module.scss (87%) create mode 100644 playground/src/platform/pc/header/index.tsx rename playground/src/{components/setting/status => platform/pc/header/infoPopover}/index.module.scss (83%) create mode 100644 playground/src/platform/pc/header/infoPopover/index.tsx create mode 100644 playground/src/platform/pc/header/network/index.module.scss create mode 100644 playground/src/platform/pc/header/network/index.tsx create mode 100644 playground/src/platform/pc/header/stylePopover/colorPicker/index.module.scss create mode 100644 playground/src/platform/pc/header/stylePopover/colorPicker/index.tsx create mode 100644 playground/src/platform/pc/header/stylePopover/index.module.scss create mode 100644 playground/src/platform/pc/header/stylePopover/index.tsx create mode 100644 playground/src/platform/pc/rtc/agent/index.module.scss create mode 100644 playground/src/platform/pc/rtc/agent/index.tsx create mode 100644 playground/src/platform/pc/rtc/audioVisualizer/index.module.scss create mode 100644 playground/src/platform/pc/rtc/audioVisualizer/index.tsx create mode 100644 playground/src/platform/pc/rtc/camSection/camSelect/index.module.scss create mode 100644 playground/src/platform/pc/rtc/camSection/camSelect/index.tsx rename playground/src/{components => platform/pc}/rtc/camSection/index.module.scss (100%) rename playground/src/{components => platform/pc}/rtc/camSection/index.tsx (94%) create mode 100644 playground/src/platform/pc/rtc/index.module.scss create mode 100644 playground/src/platform/pc/rtc/index.tsx rename playground/src/{components => platform/pc}/rtc/micSection/index.module.scss (100%) rename playground/src/{components => platform/pc}/rtc/micSection/index.tsx (98%) create mode 100644 playground/src/platform/pc/rtc/micSection/micSelect/index.module.scss create mode 100644 playground/src/platform/pc/rtc/micSection/micSelect/index.tsx create mode 100644 playground/src/platform/pc/rtc/streamPlayer/index.module.scss create mode 100644 playground/src/platform/pc/rtc/streamPlayer/index.tsx create mode 100644 playground/src/platform/pc/rtc/streamPlayer/localStreamPlayer.tsx delete mode 100644 playground/src/protobuf/SttMessage001.js delete mode 100644 playground/src/protobuf/SttMessage002.js delete mode 100644 playground/src/protobuf/SttMessage003.js diff --git a/playground/package.json b/playground/package.json index d67fddb7..85ecc3db 100644 --- a/playground/package.json +++ b/playground/package.json @@ -19,9 +19,11 @@ "@reduxjs/toolkit": "^2.2.3", "antd": "^5.15.3", "@ant-design/icons": "^5.3.7", - "agora-rtc-sdk-ng": "^4.21.0" + "agora-rtc-sdk-ng": "^4.21.0", + "react-colorful": "^5.6.1" }, "devDependencies": { + "@minko-fe/postcss-pxtoviewport": "^1.3.2", "typescript": "^5", "@types/node": "^20", "@types/react": "^18", @@ -34,5 +36,6 @@ "sass": "^1.77.5", "@svgr/webpack": "^8.1.0", "protobufjs-cli": "^1.1.2" - } + }, + "packageManager": "yarn@1.22.22+sha1.ac34549e6aa8e7ead463a7407e1c7390f61a6610" } diff --git a/playground/postcss.config.js b/playground/postcss.config.js index 90d9fffc..ea748d4d 100644 --- a/playground/postcss.config.js +++ b/playground/postcss.config.js @@ -1,5 +1,10 @@ module.exports = { plugins: { autoprefixer: {}, + "@minko-fe/postcss-pxtoviewport": { + viewportWidth: 375, + exclude: /node_modules/, + include: /\/src\/platform\/mobile\//, + } }, } diff --git a/playground/src/css/globals.css b/playground/src/app/global.css similarity index 74% rename from playground/src/css/globals.css rename to playground/src/app/global.css index 7d9fb8d2..daded34b 100644 --- a/playground/src/css/globals.css +++ b/playground/src/app/global.css @@ -8,9 +8,6 @@ html, body { background-color: #0F0F11; font-family: "PingFang SC"; - width: 100vw; - height: 100vh; - overflow: auto; } a { @@ -51,3 +48,18 @@ a { color: var(--Grey-300, #EAECF0) !important; } + +.ant-popover-inner { + /* width: 260px !important; */ + background: #1E2025 !important; +} + + +.ant-select-selection-placeholder { + color: var(--Grey-600, #667085) !important; +} + + +.ant-empty-description { + color: var(--Grey-600, #667085) !important; +} diff --git a/playground/src/app/home/index.module.scss b/playground/src/app/home/index.module.scss deleted file mode 100644 index 8b6f8011..00000000 --- a/playground/src/app/home/index.module.scss +++ /dev/null @@ -1,70 +0,0 @@ -.home { - display: flex; - flex-direction: column; - position: relative; - width: 100%; - height: 100%; - background-color: #0F0F11; - box-sizing: border-box; - - .content { - margin-top: 48px; - flex: 1 1 auto; - display: flex; - width: 100%; - padding: 18px 22px; - align-items: flex-start; - gap: 24px; - flex-shrink: 0; - box-sizing: border-box; - } - - .smallScreen { - left: 12px; - right: 12px; - top: 64px; - bottom: 16px; - position: fixed; - - .menuWrapper { - position: absolute; - left: 0; - right: 0; - top: 0; - height: 32px; - } - - .bodyWrapper { - position: absolute; - left: 0; - right: 0; - top: 48px; - bottom: 0; - overflow-y: hidden; - overflow-x: hidden; - - .item { - position: absolute; - left: 0; - right: 0; - top: 0; - bottom: 0; - overflow-y: auto; - - &::-webkit-scrollbar { - width: 4px - } - - &::-webkit-scrollbar-track { - background-color: transparent; - } - - &::-webkit-scrollbar-thumb { - background-color: #6B6B6B; - border-radius: 5px; - } - } - } - - } -} diff --git a/playground/src/app/home/page.tsx b/playground/src/app/home/page.tsx index e0437f4e..aa5adfec 100644 --- a/playground/src/app/home/page.tsx +++ b/playground/src/app/home/page.tsx @@ -1,84 +1,23 @@ "use client" -import { useMemo, useState, useRef, useEffect } from "react" -import dynamic from "next/dynamic"; -import Chat from "@/components/chat" -import Setting from "@/components/setting" import AuthInitializer from "@/components/authInitializer" -import Menu from "@/components/menu" -const Rtc = dynamic(() => import("@/components/rtc"), { +import { isMobile } from "@/common" +import dynamic from 'next/dynamic' + +const PCEntry = dynamic(() => import('@/platform/pc/entry'), { ssr: false, -}); -const Header = dynamic(() => import("@/components/header"), { +}) + +const MobileEntry = dynamic(() => import('@/platform/mobile/entry'), { ssr: false, -}); -import { useSmallScreen, useAppSelector } from "@/common" -import styles from "./index.module.scss" +}) export default function Home() { - const chatItems = useAppSelector(state => state.global.chatItems) - const wrapperRef = useRef(null) - const [activeMenu, setActiveMenu] = useState("Settings") - const { isSmallScreen } = useSmallScreen() - - useEffect(() => { - if (!wrapperRef.current) { - return - } - if (!isSmallScreen) { - return - } - wrapperRef.current.scrollTop = wrapperRef.current.scrollHeight - }, [isSmallScreen, chatItems]) - - const onMenuChange = (item: string) => { - setActiveMenu(item) - } return ( -
-
- {isSmallScreen ? -
-
- -
-
-
- -
-
- -
-
- -
-
-
- : -
- - - -
- } -
+ {isMobile() ? : }
- ); } diff --git a/playground/src/app/index.module.scss b/playground/src/app/index.module.scss index 4c59dc50..78585596 100644 --- a/playground/src/app/index.module.scss +++ b/playground/src/app/index.module.scss @@ -14,7 +14,9 @@ $shadows-big: multiple-box-shadow(100); .login { - position: relative; + position: absolute; + left: 0; + top: 0; width: 100%; height: 100%; overflow: hidden; diff --git a/playground/src/app/layout.tsx b/playground/src/app/layout.tsx index 66b5991f..5ca9b722 100644 --- a/playground/src/app/layout.tsx +++ b/playground/src/app/layout.tsx @@ -1,8 +1,9 @@ import { ConfigProvider } from "antd" import { StoreProvider } from "@/store"; -import "@/css/globals.css"; import type { Metadata, Viewport } from "next"; +import './global.css' + export const metadata: Metadata = { title: "Ai Agent", diff --git a/playground/src/assets/color_picker.svg b/playground/src/assets/color_picker.svg new file mode 100644 index 00000000..fb9bb33e --- /dev/null +++ b/playground/src/assets/color_picker.svg @@ -0,0 +1,17 @@ + + + + + + + + + + diff --git a/playground/src/assets/info.svg b/playground/src/assets/info.svg new file mode 100644 index 00000000..8ca99511 --- /dev/null +++ b/playground/src/assets/info.svg @@ -0,0 +1,3 @@ + + + diff --git a/playground/src/assets/logo.svg b/playground/src/assets/logo.svg index b9921348..af99893a 100644 --- a/playground/src/assets/logo.svg +++ b/playground/src/assets/logo.svg @@ -1,26 +1,46 @@ - - - - - - + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - \ No newline at end of file + diff --git a/playground/src/assets/logo_small.svg b/playground/src/assets/logo_small.svg new file mode 100644 index 00000000..34e755bd --- /dev/null +++ b/playground/src/assets/logo_small.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/playground/src/assets/pdf.svg b/playground/src/assets/pdf.svg new file mode 100644 index 00000000..dc67f4d5 --- /dev/null +++ b/playground/src/assets/pdf.svg @@ -0,0 +1,3 @@ + + + diff --git a/playground/src/assets/voice.svg b/playground/src/assets/voice.svg new file mode 100644 index 00000000..86a880b0 --- /dev/null +++ b/playground/src/assets/voice.svg @@ -0,0 +1,3 @@ + + + diff --git a/playground/src/common/constant.ts b/playground/src/common/constant.ts index 27cf634e..e520a195 100644 --- a/playground/src/common/constant.ts +++ b/playground/src/common/constant.ts @@ -1,4 +1,4 @@ -import { IOptions, ColorItem } from "@/types" +import { IOptions, ColorItem, LanguageOptionItem, VoiceOptionItem } from "@/types" export const REQUEST_URL = process.env.NEXT_PUBLIC_REQUEST_URL ?? "" export const GITHUB_URL = "https://github.com/rte-design/ASTRA.ai" @@ -9,7 +9,7 @@ export const DEFAULT_OPTIONS: IOptions = { userId: 0 } export const DESCRIPTION = "This is an AI voice assistant powered by ASTRA.ai framework, Agora, Azure and ChatGPT." -export const LANG_OPTIONS = [ +export const LANGUAGE_OPTIONS: LanguageOptionItem[] = [ { label: "English", value: "en-US" @@ -19,7 +19,7 @@ export const LANG_OPTIONS = [ value: "zh-CN" } ] -export const VOICE_OPTIONS = [ +export const VOICE_OPTIONS: VoiceOptionItem[] = [ { label: "Male", value: "male" @@ -31,22 +31,22 @@ export const VOICE_OPTIONS = [ ] export const COLOR_LIST: ColorItem[] = [{ active: "#0888FF", - default: "#112941" + default: "#143354" }, { active: "#563FD8", - default: "#221C40" + default: "#2C2553" }, { active: "#18A957", - default: "#112A1E" + default: "#173526" }, { active: "#FFAB08", - default: "#392B13" + default: "#423115" }, { active: "#FD5C63", - default: "#3C2023" + default: "#462629" }, { active: "#E225B2", - default: "#371530" + default: "#481C3F" }] diff --git a/playground/src/common/hooks.ts b/playground/src/common/hooks.ts index b58dd303..9759fa29 100644 --- a/playground/src/common/hooks.ts +++ b/playground/src/common/hooks.ts @@ -69,10 +69,9 @@ export const useMultibandTrackVolume = ( return frequencyBands; }; -export const useAutoScroll = (ref: React.RefObject) => { +export const useAutoScroll = (ref: React.RefObject) => { const callback: MutationCallback = (mutationList, observer) => { - // console.log("[test] callback", mutationList, observer) mutationList.forEach((mutation) => { switch (mutation.type) { case "childList": @@ -99,10 +98,6 @@ export const useAutoScroll = (ref: React.RefObject) => { observer.disconnect(); }; }, [ref]); - - - - } export const useSmallScreen = () => { diff --git a/playground/src/common/request.ts b/playground/src/common/request.ts index 0100cd99..8329911d 100644 --- a/playground/src/common/request.ts +++ b/playground/src/common/request.ts @@ -71,6 +71,42 @@ export const apiStopService = async (channel: string) => { return resp } +export const apiGetDocumentList = async () => { + const url = `${REQUEST_URL}/vector/document/preset/list` + let resp: any = await fetch(url, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }) + resp = (await resp.json()) || {} + if (resp.code !== "0") { + throw new Error(resp.msg) + } + return resp +} + +export const apiUpdateDocument = async (options: { channel: string, collection: string, fileName: string }) => { + const url = `${REQUEST_URL}/vector/document/update` + const { channel, collection, fileName } = options + const data = { + request_id: genUUID(), + channel_name: channel, + collection: collection, + file_name: fileName + } + let resp: any = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }) + resp = (await resp.json()) || {} + return resp +} + + // ping/pong export const apiPing = async (channel: string) => { const url = `${REQUEST_URL}/ping` diff --git a/playground/src/common/utils.ts b/playground/src/common/utils.ts index 4a9d9496..d2a144f9 100644 --- a/playground/src/common/utils.ts +++ b/playground/src/common/utils.ts @@ -52,3 +52,8 @@ export const genUUID = () => { return v.toString(16) }) } + + +export const isMobile = () => { + return /Mobile|iPhone|iPad|Android|Windows Phone/i.test(navigator.userAgent) +} diff --git a/playground/src/components/chat/chatItem/index.module.scss b/playground/src/components/chat/chatItem/index.module.scss deleted file mode 100644 index f1a17165..00000000 --- a/playground/src/components/chat/chatItem/index.module.scss +++ /dev/null @@ -1,62 +0,0 @@ -.chatItem { - display: flex; - justify-content: flex-start; - - .left { - flex: 0 0 auto; - display: flex; - width: 32px; - height: 32px; - padding: 10px; - flex-direction: column; - justify-content: center; - align-items: center; - gap: 10px; - border-radius: 200px; - background: var(--Grey-700, #475467); - - .text { - color: var(---white, #FFF); - text-align: center; - font-size: 14px; - font-weight: 500; - line-height: 150%; - } - } - - .right { - margin-left: 12px; - flex: 1 1 auto; - - .userName { - color: var(--Grey-600, #667085); - font-size: 12px; - font-weight: 400; - line-height: 18px; - } - - .userName.isAgent { - color: var(--theme-color, #667085) !important; - } - - .text { - margin-top: 6px; - color: var(--Grey-300, #EAECF0); - font-size: 14px; - font-weight: 400; - line-height: 21px; - padding-right: 4px; - white-space: pre-wrap; - display: inline; - } - - .text.isAgent { - color: var(--theme-color, #EAECF0) !important; - } - - } -} - -.chatItem+.chatItem { - margin-top: 10px; -} diff --git a/playground/src/components/chat/chatItem/index.tsx b/playground/src/components/chat/chatItem/index.tsx deleted file mode 100644 index e814039a..00000000 --- a/playground/src/components/chat/chatItem/index.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { IChatItem } from "@/types" -import styles from "./index.module.scss" -import { usePrevious } from "@/common" -import { use, useEffect, useMemo, useState } from "react" - -interface ChatItemProps { - data: IChatItem -} - - -let flag = false - -const ChatItem = (props: ChatItemProps) => { - const { data } = props - const { text, type } = data - - - const abUserName = useMemo(() => { - return type == "agent" ? "Ag" : "Yo" - }, [type]) - - const coUserName = useMemo(() => { - return type == "agent" ? "Agent" : "You" - }, [type]) - - - return
- - {abUserName} - - -
{coUserName}
-
- {text} -
-
-
-} - - -export default ChatItem diff --git a/playground/src/components/chat/index.tsx b/playground/src/components/chat/index.tsx deleted file mode 100644 index 352fc108..00000000 --- a/playground/src/components/chat/index.tsx +++ /dev/null @@ -1,32 +0,0 @@ -"use client" - -import { useEffect, useRef } from "react" -import ChatItem from "./chatItem" -import { TranscriptionIcon } from "@/components/icons" -import { genRandomChatList, useSmallScreen, useAutoScroll, useAppSelector } from "@/common" -import styles from "./index.module.scss" - -const MOCK_CHAT_LIST = genRandomChatList(10) - -const Chat = () => { - const chatItems = useAppSelector(state => state.global.chatItems) - // const chatItems = MOCK_CHAT_LIST - const chatRef = useRef(null) - const { isSmallScreen } = useSmallScreen() - useAutoScroll(chatRef) - - return
-
- - Chat -
-
- {chatItems.map((item, index) => { - return - })} -
-
-} - - -export default Chat diff --git a/playground/src/components/customSelect/index.module.scss b/playground/src/components/customSelect/index.module.scss new file mode 100644 index 00000000..0649e994 --- /dev/null +++ b/playground/src/components/customSelect/index.module.scss @@ -0,0 +1,22 @@ +.selectWrapper { + position: relative; + + .prefixIconWrapper { + position: absolute; + z-index: 1; + width: 3rem; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + } + + :global(.customSelect) { + width: 100%; + + :global(.ant-select-selector) { + padding-left: calc(3rem - 8px) !important; + } + } + +} diff --git a/playground/src/components/customSelect/index.tsx b/playground/src/components/customSelect/index.tsx new file mode 100644 index 00000000..8dd1b188 --- /dev/null +++ b/playground/src/components/customSelect/index.tsx @@ -0,0 +1,19 @@ +import { Select, SelectProps } from "antd" +import styles from "./index.module.scss" + +type CustomSelectProps = SelectProps & { + prefixIcon?: React.ReactNode; +} + +const CustomSelect = (props: CustomSelectProps) => { + + const { prefixIcon, className, ...rest } = props; + + return
+ {prefixIcon &&
{prefixIcon}
} + +
+} + + +export default CustomSelect diff --git a/playground/src/components/header/index.tsx b/playground/src/components/header/index.tsx deleted file mode 100644 index c8ed31f8..00000000 --- a/playground/src/components/header/index.tsx +++ /dev/null @@ -1,38 +0,0 @@ -"use client" - -import { useAppSelector, GITHUB_URL, useSmallScreen } from "@/common" -import Network from "./network" -import { GithubIcon, LogoIcon } from "@/components/icons" - -import styles from "./index.module.scss" -import { useMemo } from "react" - -const Header = () => { - const options = useAppSelector(state => state.global.options) - const { channel } = options - const { isSmallScreen } = useSmallScreen() - - const channelNameText = useMemo(() => { - return !isSmallScreen ? `Channel Nameļ¼š${channel}` : channel - }, [isSmallScreen, channel]) - - const onClickGithub = () => { - if (typeof window !== "undefined") { - window.open(GITHUB_URL, "_blank") - } - } - - return
- - - - {channelNameText} - - - - -
-} - - -export default Header diff --git a/playground/src/components/icons/colorPicker/index.tsx b/playground/src/components/icons/colorPicker/index.tsx new file mode 100644 index 00000000..81efcb12 --- /dev/null +++ b/playground/src/components/icons/colorPicker/index.tsx @@ -0,0 +1,6 @@ +import { IconProps } from "../types" +import ColorPickerSvg from "@/assets/color_picker.svg" + +export const ColorPickerIcon = (props: IconProps) => { + return +} diff --git a/playground/src/components/icons/index.tsx b/playground/src/components/icons/index.tsx index deae1f21..e303674a 100644 --- a/playground/src/components/icons/index.tsx +++ b/playground/src/components/icons/index.tsx @@ -4,3 +4,7 @@ export * from "./network" export * from "./github" export * from "./transcription" export * from "./logo" +export * from "./info" +export * from "./colorPicker" +export * from "./voice" +export * from "./pdf" diff --git a/playground/src/components/icons/info/index.tsx b/playground/src/components/icons/info/index.tsx new file mode 100644 index 00000000..cf783be9 --- /dev/null +++ b/playground/src/components/icons/info/index.tsx @@ -0,0 +1,6 @@ +import { IconProps } from "../types" +import InfoSvg from "@/assets/info.svg" + +export const InfoIcon = (props: IconProps) => { + return +} diff --git a/playground/src/components/icons/logo/index.tsx b/playground/src/components/icons/logo/index.tsx index 53cb20fc..f86d5246 100644 --- a/playground/src/components/icons/logo/index.tsx +++ b/playground/src/components/icons/logo/index.tsx @@ -1,6 +1,8 @@ import { IconProps } from "../types" import LogoSvg from "@/assets/logo.svg" +import SmallLogoSvg from "@/assets/logo_small.svg" export const LogoIcon = (props: IconProps) => { - return + const { size = "default" } = props + return size == "small" ? : } diff --git a/playground/src/components/icons/pdf/index.tsx b/playground/src/components/icons/pdf/index.tsx new file mode 100644 index 00000000..83de8b2d --- /dev/null +++ b/playground/src/components/icons/pdf/index.tsx @@ -0,0 +1,6 @@ +import { IconProps } from "../types" +import PdfSvg from "@/assets/pdf.svg" + +export const PdfIcon = (props: IconProps) => { + return +} diff --git a/playground/src/components/icons/types.ts b/playground/src/components/icons/types.ts index 61bd05b2..c37e8133 100644 --- a/playground/src/components/icons/types.ts +++ b/playground/src/components/icons/types.ts @@ -3,6 +3,8 @@ export interface IconProps { height?: number color?: string viewBox?: string + size?: "small" | "default" // style?: React.CSSProperties transform?: string + onClick?: () => void } diff --git a/playground/src/components/icons/voice/index.tsx b/playground/src/components/icons/voice/index.tsx new file mode 100644 index 00000000..87164cea --- /dev/null +++ b/playground/src/components/icons/voice/index.tsx @@ -0,0 +1,6 @@ +import { IconProps } from "../types" +import VoiceSvg from "@/assets/voice.svg" + +export const VoiceIcon = (props: IconProps) => { + return +} diff --git a/playground/src/components/loginCard/index.module.scss b/playground/src/components/loginCard/index.module.scss index 52ea07e6..966ebc20 100644 --- a/playground/src/components/loginCard/index.module.scss +++ b/playground/src/components/loginCard/index.module.scss @@ -47,7 +47,7 @@ gap: 12px; .text { - margin-top: 12px; + margin-top: 8px; color: var(--Grey-300, #EAECF0); text-align: center; font-size: 18px; diff --git a/playground/src/components/loginCard/index.tsx b/playground/src/components/loginCard/index.tsx index f672e79c..a161e912 100644 --- a/playground/src/components/loginCard/index.tsx +++ b/playground/src/components/loginCard/index.tsx @@ -1,14 +1,17 @@ "use client" -import { version } from "../../../package.json" +import packageData from "../../../package.json" import { useRouter } from 'next/navigation' import { message } from "antd" -import { ChangeEvent, InputHTMLAttributes, useState } from "react" -import { GithubIcon,LogoIcon } from "../icons" +import { useState } from "react" +import { GithubIcon, LogoIcon } from "../icons" import { GITHUB_URL, getRandomUserId, useAppDispatch, getRandomChannel } from "@/common" import { setOptions } from "@/store/reducers/global" import styles from "./index.module.scss" + +const { version } = packageData + const LoginCard = () => { const dispatch = useAppDispatch() const router = useRouter() @@ -52,8 +55,8 @@ const LoginCard = () => {
- - ASTRA.ai Agents Playground + + Agents Playground
@@ -66,6 +69,9 @@ const LoginCard = () => {
Version {version}
+ + + return } export default LoginCard diff --git a/playground/src/components/menu/index.module.scss b/playground/src/components/menu/index.module.scss deleted file mode 100644 index bd6f2c94..00000000 --- a/playground/src/components/menu/index.module.scss +++ /dev/null @@ -1,24 +0,0 @@ -.menu { - width: 100%; - background: #181A1E; - border: 1px solid #272A2F; - box-sizing: border-box; - border-radius: 4px; - - .menuItem { - display: inline-block; - height: 32px; - line-height: 32px; - padding: 0 15px; - color: #667085; - background: #181A1E; - cursor: pointer; - border-right: 1px solid #272A2F; - box-sizing: border-box; - } - - .active { - color: #EAECF0; - background: #0F0F11; - } -} diff --git a/playground/src/components/menu/index.tsx b/playground/src/components/menu/index.tsx deleted file mode 100644 index 524fdde8..00000000 --- a/playground/src/components/menu/index.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { useState } from "react" -import styles from "./index.module.scss" - -interface MenuProps { - onChange: (text: string) => void -} - -interface MenuItem { - text: string - active: boolean -} - -const DEFAULT_MENU_LIST: MenuItem[] = [ - { - text: "Settings", - active: true - }, { - text: "Chat", - active: false - }, { - text: "Agent", - active: false - }] - -const Menu = (props: MenuProps) => { - const { onChange } = props - const [menuList, setMenuList] = useState(DEFAULT_MENU_LIST) - - const onClickItem = (index: number) => { - if (menuList[index].active) { - return - } - const newMenuList = menuList.map((item, i) => { - return { - ...item, - active: i == index - } - }) - setMenuList(newMenuList) - onChange(menuList[index].text) - } - - return
- {menuList.map((item, index) => { - return onClickItem(index)}>{item.text} - })} -
-} - -export default Menu diff --git a/playground/src/components/pdfSelect/index.module.scss b/playground/src/components/pdfSelect/index.module.scss new file mode 100644 index 00000000..adb93280 --- /dev/null +++ b/playground/src/components/pdfSelect/index.module.scss @@ -0,0 +1,8 @@ +// .pdfSelect { + // min-width: 200px; + // max-width: 300px; + // } +.dropdownRender { + display: flex; + justify-content: flex-end; +} diff --git a/playground/src/components/pdfSelect/index.tsx b/playground/src/components/pdfSelect/index.tsx new file mode 100644 index 00000000..31544e60 --- /dev/null +++ b/playground/src/components/pdfSelect/index.tsx @@ -0,0 +1,72 @@ +import { ReactElement, useState } from "react" +import { PdfIcon } from "@/components/icons" +import CustomSelect from "@/components/customSelect" +import { Divider, message } from 'antd'; +import { useEffect } from 'react'; +import { apiGetDocumentList, apiUpdateDocument, useAppSelector } from "@/common" +import PdfUpload from "./upload" +import { OptionType, IPdfData } from "@/types" + +import styles from "./index.module.scss" + +const PdfSelect = () => { + const options = useAppSelector(state => state.global.options) + const { channel } = options + const [pdfOptions, setPdfOptions] = useState([]) + + + useEffect(() => { + getPDFOptions() + }, []) + + + const getPDFOptions = async () => { + const res = await apiGetDocumentList() + setPdfOptions(res.data.map((item: any) => { + return { + value: item.collection, + label: item.file_name + } + })) + } + + const onUploadSuccess = (data: IPdfData) => { + setPdfOptions([...pdfOptions, { + value: data.collection, + label: data.fileName + }]) + } + + const pdfDropdownRender = (menu: ReactElement) => { + return <> + {menu} + +
+ +
+ + } + + + const onSelectPdf = async (val: string) => { + const item = pdfOptions.find(item => item.value === val) + if (!item) { + return message.error("Please select a PDF file") + } + await apiUpdateDocument({ + collection: val, + fileName: item.label, + channel + }) + } + + + return } + onChange={onSelectPdf} + options={pdfOptions} + dropdownRender={pdfDropdownRender} + className={styles.pdfSelect} placeholder="Select a PDF file"> +} + +export default PdfSelect diff --git a/playground/src/components/pdfSelect/upload/index.module.scss b/playground/src/components/pdfSelect/upload/index.module.scss new file mode 100644 index 00000000..fd559b5e --- /dev/null +++ b/playground/src/components/pdfSelect/upload/index.module.scss @@ -0,0 +1,7 @@ +.btn { + color: var(--theme-color, #EAECF0); + + &:hover { + color: var(--theme-color, #EAECF0) !important; + } +} diff --git a/playground/src/components/pdfSelect/upload/index.tsx b/playground/src/components/pdfSelect/upload/index.tsx new file mode 100644 index 00000000..043d054b --- /dev/null +++ b/playground/src/components/pdfSelect/upload/index.tsx @@ -0,0 +1,74 @@ +import { Select, Button, message, Upload, UploadProps } from "antd" +import { useState } from "react" +import { PlusOutlined, LoadingOutlined } from '@ant-design/icons'; +import { REQUEST_URL, useAppSelector, genUUID } from "@/common" +import { IPdfData } from "@/types" + +import styles from "./index.module.scss" + +interface PdfSelectProps { + onSuccess?: (data: IPdfData) => void +} + +const PdfUpload = (props: PdfSelectProps) => { + const { onSuccess } = props + const agentConnected = useAppSelector(state => state.global.agentConnected) + const options = useAppSelector(state => state.global.options) + const { channel, userId } = options + + const [uploading, setUploading] = useState(false) + + const uploadProps: UploadProps = { + accept: "application/pdf", + maxCount: 1, + showUploadList: false, + action: `${REQUEST_URL}/vector/document/upload`, + data: { + channel_name: channel, + uid: String(userId), + request_id: genUUID() + }, + onChange: (info) => { + const { file } = info + const { status, name } = file + if (status == "uploading") { + setUploading(true) + } else if (status == 'done') { + setUploading(false) + const { response } = file + if (response.code == "0") { + message.success(`Upload ${name} success`) + const { collection, file_name } = response.data + onSuccess && onSuccess({ + fileName: file_name, + collection + }) + } else { + message.error(response.msg) + } + } else if (status == 'error') { + setUploading(false) + message.error(`Upload ${name} failed`) + } + } + } + + const onClickUploadPDF = (e: any) => { + if (!agentConnected) { + message.error("Please connect to agent first") + e.stopPropagation() + } + } + + + return + + +} + + +export default PdfUpload diff --git a/playground/src/components/setting/Info/index.tsx b/playground/src/components/setting/Info/index.tsx deleted file mode 100644 index cdc20dfa..00000000 --- a/playground/src/components/setting/Info/index.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { useAppSelector } from "@/common" - -import styles from "./index.module.scss" - -const Info = () => { - const options = useAppSelector(state => state.global.options) - const { channel, userId } = options - - return
-
INFO
-
- Room - {channel} -
-
- Participant - {userId} -
-
-} - -export default Info diff --git a/playground/src/components/setting/index.module.scss b/playground/src/components/setting/index.module.scss deleted file mode 100644 index 314994c3..00000000 --- a/playground/src/components/setting/index.module.scss +++ /dev/null @@ -1,100 +0,0 @@ -.setting { - flex: 0 0 260px; - display: flex; - flex-direction: column; - align-items: flex-start; - flex-shrink: 0; - align-self: stretch; - border-radius: 8px; - border: 1px solid #272A2F; - background: #181A1D; - - - .description { - display: flex; - padding: 24px 16px; - flex-direction: column; - align-items: flex-start; - gap: 12px; - align-self: stretch; - border-bottom: 1px solid #272A2F; - - .title { - color: var(--Grey-300, #EAECF0); - font-size: 14px; - font-weight: 600; - line-height: 150%; - letter-spacing: 0.449px; - } - - .text { - color: var(--Grey-600, #667085); - font-size: 14px; - font-weight: 400; - line-height: 150%; - } - - .btnConnect { - display: flex; - padding: 8px 14px; - justify-content: center; - align-items: center; - gap: 8px; - align-self: stretch; - border-radius: 6px; - background: var(--theme-color, #0888FF); - box-shadow: 0px 1px 2px 0px rgba(16, 24, 40, 0.05); - cursor: pointer; - user-select: none; - caret-color: transparent; - - .btnText { - color: var(---White, #FFF); - font-size: 14px; - font-weight: 500; - line-height: 20px; - } - - .btnText.disconnect { - color: var(--Error-400-T, #E95C7B); - } - - .loading { - margin-left: 4px; - } - } - - - .btnConnect.disconnect { - background: #181A1D; - border: 1px solid var(--Error-400-T, #E95C7B); - } - - - } - - - .selectWrapper { - display: flex; - padding: 24px 16px; - flex-direction: column; - align-items: flex-start; - gap: 12px; - align-self: stretch; - border: 1px solid #272A2F; - - .title { - color: var(--Grey-300, #EAECF0); - font-size: 14px; - font-weight: 600; - line-height: 150%; - letter-spacing: 0.449px; - } - - .select { - width: 100%; - } - } - - -} diff --git a/playground/src/platform/mobile/chat/chatItem/index.module.scss b/playground/src/platform/mobile/chat/chatItem/index.module.scss new file mode 100644 index 00000000..27057120 --- /dev/null +++ b/playground/src/platform/mobile/chat/chatItem/index.module.scss @@ -0,0 +1,86 @@ +.agentChatItem { + width: 100%; + display: flex; + justify-content: flex-start; + + .left { + flex: 0 0 auto; + display: flex; + width: 32px; + height: 32px; + padding: 10px; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 10px; + border-radius: 200px; + background: var(--Grey-700, #475467); + + .userName { + color: var(---white, #FFF); + text-align: center; + font-size: 14px; + font-weight: 500; + line-height: 150%; + } + } + + .right { + margin-left: 12px; + + .userName { + font-size: 14px; + font-weight: 500; + line-height: 20px; + color: var(--theme-color, #667085) !important; + } + + + .agent { + color: var(--theme-color, #EAECF0) !important; + } + + } +} + +.userChatItem { + width: 100%; + display: flex; + flex-direction: column; + justify-content: flex-end; + align-items: flex-end; + + .userName { + text-align: right; + color: var(--Grey-600, #667085); + font-weight: 500; + line-height: 20px; + } + + + +} + + +.chatItem { + .text { + margin-top: 6px; + color: #FFF; + display: flex; + padding: 8px 14px; + flex-direction: column; + justify-content: left; + font-size: 14px; + font-weight: 400; + line-height: 21px; + white-space: pre-wrap; + border-radius: 0px 8px 8px 8px; + border: 1px solid #272A2F; + background: #1E2024; + box-shadow: 0px 2px 2px 0px rgba(0, 0, 0, 0.25); + } +} + +.chatItem+.chatItem { + margin-top: 14px; +} diff --git a/playground/src/platform/mobile/chat/chatItem/index.tsx b/playground/src/platform/mobile/chat/chatItem/index.tsx new file mode 100644 index 00000000..bde3350c --- /dev/null +++ b/playground/src/platform/mobile/chat/chatItem/index.tsx @@ -0,0 +1,50 @@ +import { IChatItem } from "@/types" +import styles from "./index.module.scss" + +interface ChatItemProps { + data: IChatItem +} + + +const AgentChatItem = (props: ChatItemProps) => { + const { data } = props + const { text } = data + + + return
+ + Ag + + +
Agent
+
+ {text} +
+
+
+} + +const UserChatItem = (props: ChatItemProps) => { + const { data } = props + const { text } = data + + return
+
You
+
{text}
+
+} + + +const ChatItem = (props: ChatItemProps) => { + const { data } = props + + + return ( + data.type === "agent" ? : + ); + + +} + + +export default ChatItem diff --git a/playground/src/components/chat/index.module.scss b/playground/src/platform/mobile/chat/index.module.scss similarity index 76% rename from playground/src/components/chat/index.module.scss rename to playground/src/platform/mobile/chat/index.module.scss index 50fdb1e5..8fa6d901 100644 --- a/playground/src/components/chat/index.module.scss +++ b/playground/src/platform/mobile/chat/index.module.scss @@ -1,23 +1,18 @@ .chat { flex: 1 1 auto; - min-width: 200px; display: flex; flex-direction: column; align-items: flex-start; align-self: stretch; - border-radius: 8px; - border: 1px solid #272A2F; background: #181A1D; overflow: hidden; .header { - display: flex; - height: 40px; - padding: 0px 16px; align-items: center; align-self: stretch; border-bottom: 1px solid #272A2F; + .text { margin-left: 4px; color: var(--Grey-300, #EAECF0); @@ -27,15 +22,22 @@ line-height: 40px; letter-spacing: 0.449px; } + + .languageSelect { + width: 100%; + } + + + + } .content { + margin-top: 16px; display: flex; - padding: 12px 24px; flex-direction: column; align-items: flex-start; gap: 10px; - flex: 1 0 500px; align-self: stretch; overflow-y: auto; @@ -55,9 +57,19 @@ } - .content.small { - flex: 1 1 auto; - min-height: 400px; - } +} + + +.dropdownRender { + display: flex; + justify-content: flex-end; + + .btn { + color: var(--theme-color, #EAECF0); + + &:hover { + color: var(--theme-color, #EAECF0) !important; + } + } } diff --git a/playground/src/platform/mobile/chat/index.tsx b/playground/src/platform/mobile/chat/index.tsx new file mode 100644 index 00000000..e18c0558 --- /dev/null +++ b/playground/src/platform/mobile/chat/index.tsx @@ -0,0 +1,64 @@ +import { ReactElement, useEffect, useContext, useState } from "react" +import ChatItem from "./chatItem" +import { IChatItem } from "@/types" +import { useAppDispatch, useAutoScroll, LANGUAGE_OPTIONS, useAppSelector } from "@/common" +import { setLanguage } from "@/store/reducers/global" +import { Select, } from 'antd'; +import { MenuContext } from "../menu/context" +import PdfSelect from "@/components/pdfSelect" + +import styles from "./index.module.scss" + + +const Chat = () => { + const chatItems = useAppSelector(state => state.global.chatItems) + const language = useAppSelector(state => state.global.language) + const agentConnected = useAppSelector(state => state.global.agentConnected) + const dispatch = useAppDispatch() + // genRandomChatList + // const [chatItems, setChatItems] = useState([]) + const context = useContext(MenuContext); + + if (!context) { + throw new Error("MenuContext is not found") + } + + const { scrollToBottom } = context; + + + useEffect(() => { + scrollToBottom() + }, [chatItems, scrollToBottom]) + + + + const onLanguageChange = (val: any) => { + dispatch(setLanguage(val)) + } + + + + return
+
+
+ +
+
+ +
+
+
+ {chatItems.map((item, index) => { + return + })} +
+
+} + + +export default Chat diff --git a/playground/src/platform/mobile/description/index.module.scss b/playground/src/platform/mobile/description/index.module.scss new file mode 100644 index 00000000..7305f5a7 --- /dev/null +++ b/playground/src/platform/mobile/description/index.module.scss @@ -0,0 +1,71 @@ +.description { + position: relative; + display: flex; + padding: 12px 16px; + height: 60px; + align-items: center; + gap: 12px; + align-self: stretch; + border-bottom: 1px solid #272A2F; + background: #181A1D; + box-sizing: border-box; + + .title { + color: var(--Grey-300, #EAECF0); + font-size: 14px; + font-style: normal; + font-weight: 600; + flex: 1 1 auto; + /* 21px */ + letter-spacing: 0.449px; + } + + .text { + margin-left: 12px; + flex: 1 1 auto; + color: var(--Grey-600, #667085); + font-size: 14px; + font-style: normal; + font-weight: 400; + } + + + .btnConnect { + width: 150px; + display: flex; + padding: 8px 14px; + justify-content: center; + align-items: center; + gap: 8px; + align-self: stretch; + border-radius: 6px; + background: var(--theme-color, #0888FF); + box-shadow: 0px 1px 2px 0px rgba(16, 24, 40, 0.05); + cursor: pointer; + user-select: none; + caret-color: transparent; + box-sizing: border-box; + + .btnText { + color: var(---White, #FFF); + font-size: 14px; + font-weight: 500; + line-height: 20px; + } + + .btnText.disconnect { + color: var(--Error-400-T, #E95C7B); + } + + .loading { + margin-left: 4px; + } + } + + + .btnConnect.disconnect { + background: #181A1D; + border: 1px solid var(--Error-400-T, #E95C7B); + } + +} diff --git a/playground/src/components/setting/index.tsx b/playground/src/platform/mobile/description/index.tsx similarity index 50% rename from playground/src/components/setting/index.tsx rename to playground/src/platform/mobile/description/index.tsx index d48feb30..505bb2d7 100644 --- a/playground/src/components/setting/index.tsx +++ b/playground/src/platform/mobile/description/index.tsx @@ -1,29 +1,22 @@ -"use client" - import { setAgentConnected } from "@/store/reducers/global" import { - DESCRIPTION, useAppDispatch, useAppSelector, apiPing, - LANG_OPTIONS, VOICE_OPTIONS, apiStartService, apiStopService + DESCRIPTION, useAppDispatch, useAppSelector, apiPing, genUUID, + apiStartService, apiStopService, REQUEST_URL } from "@/common" -import Info from "./Info" -import Status from "./status" -import { Select, Button, message } from "antd" -import StyleSelect from "./themeSelect" +import { message } from "antd" import { useEffect, useState } from "react" -import { LoadingOutlined } from "@ant-design/icons" +import { LoadingOutlined, } from "@ant-design/icons" import styles from "./index.module.scss" - - let intervalId: any -const Setting = () => { +const Description = () => { const dispatch = useAppDispatch() const agentConnected = useAppSelector(state => state.global.agentConnected) const channel = useAppSelector(state => state.global.options.channel) const userId = useAppSelector(state => state.global.options.userId) - const [lang, setLang] = useState("en-US") - const [voice, setVoice] = useState("male") + const language = useAppSelector(state => state.global.language) + const voiceType = useAppSelector(state => state.global.voiceType) const [loading, setLoading] = useState(false) useEffect(() => { @@ -54,8 +47,8 @@ const Setting = () => { const res = await apiStartService({ channel, userId, - language: lang, - voiceType: voice + language, + voiceType }) const { code, msg } = res || {} if (code != 0) { @@ -90,40 +83,16 @@ const Setting = () => { } } - - return
- {/* description */} -
-
DESCRIPTION
-
{DESCRIPTION}
-
- - {!agentConnected ? "Connect" : "Disconnect"} - {loading ? : null} - -
-
- {/* info */} - - {/* status */} - - {/* select */} -
-
LANGUAGE
- -
-
-
Voice
- -
- {/* style */} - -
+ return
+ Description + + + {!agentConnected ? "Connect" : "Disconnect"} + {loading ? : null} + + +
} -export default Setting +export default Description diff --git a/playground/src/platform/mobile/entry/index.module.scss b/playground/src/platform/mobile/entry/index.module.scss new file mode 100644 index 00000000..41322c12 --- /dev/null +++ b/playground/src/platform/mobile/entry/index.module.scss @@ -0,0 +1,18 @@ +.entry { + position: relative; + height: 100%; + box-sizing: border-box; + + .content { + position: relative; + padding: 16px; + box-sizing: border-box; + + + .body { + margin-top: 16px; + display: flex; + gap: 24px; + } + } +} diff --git a/playground/src/platform/mobile/entry/index.tsx b/playground/src/platform/mobile/entry/index.tsx new file mode 100644 index 00000000..c5f51d5c --- /dev/null +++ b/playground/src/platform/mobile/entry/index.tsx @@ -0,0 +1,30 @@ +import Chat from "../chat" +import Description from "../description" +import Rtc from "../rtc" +import Header from "../header" +import Menu, { IMenuData } from "../menu" +import styles from "./index.module.scss" + + +const MenuData: IMenuData[] = [{ + name: "Agent", + component: , +}, { + name: "Chat", + component: , +}] + + +const MobileEntry = () => { + + return
+
+ +
+ +
+
+} + + +export default MobileEntry diff --git a/playground/src/platform/mobile/header/index.module.scss b/playground/src/platform/mobile/header/index.module.scss new file mode 100644 index 00000000..96afeb9c --- /dev/null +++ b/playground/src/platform/mobile/header/index.module.scss @@ -0,0 +1,51 @@ +.header { + display: flex; + width: 100%; + height: 48px; + padding: 16px; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid #24262A; + background: #1E2024; + box-shadow: 0px 12px 16px -4px rgba(8, 15, 52, 0.06), 0px 4px 6px -2px rgba(8, 15, 52, 0.03); + box-sizing: border-box; + z-index: 999; + + .logoWrapper { + display: flex; + align-items: center; + + .text { + margin-left: 8px; + color: var(---white, #FFF); + text-align: right; + font-family: Inter; + font-size: 16px; + font-weight: 500; + } + } + + .content { + padding-left: 12px; + display: flex; + align-items: center; + justify-content: flex-start; + height: 48px; + flex: 1 1 auto; + color: var(--Grey-300, #EAECF0); + font-size: 16px; + font-weight: 500; + line-height: 48px; + letter-spacing: 0.449px; + text-align: center; + + .text { + margin-left: 4px; + font-size: 12px; + } + } + + .githubWrapper { + margin-right: 12px; + } +} diff --git a/playground/src/platform/mobile/header/index.tsx b/playground/src/platform/mobile/header/index.tsx new file mode 100644 index 00000000..50d21ddf --- /dev/null +++ b/playground/src/platform/mobile/header/index.tsx @@ -0,0 +1,46 @@ +"use client" + +import { useAppSelector, GITHUB_URL, useSmallScreen } from "@/common" +import Network from "./network" +import InfoPopover from "./infoPopover" +import StylePopover from "./stylePopover" +import { GithubIcon, LogoIcon, InfoIcon, ColorPickerIcon } from "@/components/icons" + +import styles from "./index.module.scss" + +const Header = () => { + const themeColor = useAppSelector(state => state.global.themeColor) + const options = useAppSelector(state => state.global.options) + const { channel } = options + + + const onClickGithub = () => { + if (typeof window !== "undefined") { + window.open(GITHUB_URL, "_blank") + } + } + + + + return
+ + + + + + + Channel Name: {channel} + + + + + + + + + +
+} + + +export default Header diff --git a/playground/src/components/setting/Info/index.module.scss b/playground/src/platform/mobile/header/infoPopover/index.module.scss similarity index 84% rename from playground/src/components/setting/Info/index.module.scss rename to playground/src/platform/mobile/header/infoPopover/index.module.scss index b5bad7a2..cd3f72f8 100644 --- a/playground/src/components/setting/Info/index.module.scss +++ b/playground/src/platform/mobile/header/infoPopover/index.module.scss @@ -1,11 +1,10 @@ .info { display: flex; - padding: 24px 16px; + padding: 12px 16px; flex-direction: column; align-items: flex-start; - gap: 12px; + gap: 8px; align-self: stretch; - border-bottom: 1px solid #272A2F; .title { color: var(--Grey-300, #EAECF0); @@ -35,4 +34,10 @@ line-height: 150%; } } + + .slider { + height: 1px; + width: 100%; + background-color: #0D0F12; + } } diff --git a/playground/src/components/setting/status/index.tsx b/playground/src/platform/mobile/header/infoPopover/index.tsx similarity index 51% rename from playground/src/components/setting/status/index.tsx rename to playground/src/platform/mobile/header/infoPopover/index.tsx index ed1418ea..cd451418 100644 --- a/playground/src/components/setting/status/index.tsx +++ b/playground/src/platform/mobile/header/infoPopover/index.tsx @@ -1,8 +1,19 @@ import { useMemo } from "react" import { useAppSelector } from "@/common" +import { Popover } from 'antd'; + + import styles from "./index.module.scss" -const Status = () => { +interface InfoPopoverProps { + children?: React.ReactNode +} + +const InfoPopover = (props: InfoPopoverProps) => { + const { children } = props + const options = useAppSelector(state => state.global.options) + const { channel, userId } = options + const roomConnected = useAppSelector(state => state.global.roomConnected) const agentConnected = useAppSelector(state => state.global.agentConnected) @@ -14,7 +25,19 @@ const Status = () => { return agentConnected ? "TRUE" : "FALSE" }, [agentConnected]) - return
+ + + const content =
+
INFO
+
+ Room + {channel} +
+
+ Participant + {userId} +
+
STATUS
Room connected
@@ -24,8 +47,11 @@ const Status = () => {
Agent connected
{agentConnectedText}
-
-} + -export default Status + return {children} + +} + +export default InfoPopover diff --git a/playground/src/components/header/network/index.module.scss b/playground/src/platform/mobile/header/network/index.module.scss similarity index 100% rename from playground/src/components/header/network/index.module.scss rename to playground/src/platform/mobile/header/network/index.module.scss diff --git a/playground/src/components/header/network/index.tsx b/playground/src/platform/mobile/header/network/index.tsx similarity index 78% rename from playground/src/components/header/network/index.tsx rename to playground/src/platform/mobile/header/network/index.tsx index 7e7c6501..92b4e33b 100644 --- a/playground/src/components/header/network/index.tsx +++ b/playground/src/platform/mobile/header/network/index.tsx @@ -1,11 +1,18 @@ "use client"; +import React from "react"; import { rtcManager } from "@/manager" import { NetworkQuality } from "agora-rtc-sdk-ng" import { useEffect, useState } from "react" import { NetworkIcon } from "@/components/icons" -const NetWork = () => { +interface NetworkProps { + style?: React.CSSProperties +} + +const NetWork = (props: NetworkProps) => { + const { style } = props + const [networkQuality, setNetworkQuality] = useState() useEffect(() => { @@ -21,7 +28,7 @@ const NetWork = () => { } return ( - + ) diff --git a/playground/src/platform/mobile/header/stylePopover/colorPicker/index.module.scss b/playground/src/platform/mobile/header/stylePopover/colorPicker/index.module.scss new file mode 100644 index 00000000..405e7781 --- /dev/null +++ b/playground/src/platform/mobile/header/stylePopover/colorPicker/index.module.scss @@ -0,0 +1,24 @@ +.colorPicker { + height: 24px; + display: flex; + align-items: center; + + :global(.react-colorful) { + width: 220px; + height: 8px; + } + + :global(.react-colorful__saturation) { + display: none; + } + + :global(.react-colorful__hue) { + border-radius: 8px !important; + height: 8px; + } + + :global(.react-colorful__pointer) { + width: 24px; + height: 24px; + } +} diff --git a/playground/src/platform/mobile/header/stylePopover/colorPicker/index.tsx b/playground/src/platform/mobile/header/stylePopover/colorPicker/index.tsx new file mode 100644 index 00000000..28163d77 --- /dev/null +++ b/playground/src/platform/mobile/header/stylePopover/colorPicker/index.tsx @@ -0,0 +1,22 @@ +"use client" + +import { HexColorPicker } from "react-colorful"; +import { useAppSelector, useAppDispatch } from "@/common" +import { setThemeColor } from "@/store/reducers/global" +import styles from "./index.module.scss"; + +const ColorPicker = () => { + const dispatch = useAppDispatch() + const themeColor = useAppSelector(state => state.global.themeColor) + + const onColorChange = (color: string) => { + console.log(color); + dispatch(setThemeColor(color)) + }; + + return
+ +
+}; + +export default ColorPicker; diff --git a/playground/src/components/setting/themeSelect/index.module.scss b/playground/src/platform/mobile/header/stylePopover/index.module.scss similarity index 92% rename from playground/src/components/setting/themeSelect/index.module.scss rename to playground/src/platform/mobile/header/stylePopover/index.module.scss index 8ecf6d43..defdcc12 100644 --- a/playground/src/components/setting/themeSelect/index.module.scss +++ b/playground/src/platform/mobile/header/stylePopover/index.module.scss @@ -1,11 +1,12 @@ -.style { +.info { + padding: 12px 16px; display: flex; - padding: 24px 16px; flex-direction: column; align-items: flex-start; - gap: 12px; + gap: 16px; align-self: stretch; + .title { color: var(--Grey-300, #EAECF0); font-size: 14px; @@ -16,7 +17,7 @@ .color { font-size: 0; - white-space:nowrap; + white-space: nowrap; .item { position: relative; @@ -47,5 +48,4 @@ } - } diff --git a/playground/src/components/setting/themeSelect/index.tsx b/playground/src/platform/mobile/header/stylePopover/index.tsx similarity index 61% rename from playground/src/components/setting/themeSelect/index.tsx rename to playground/src/platform/mobile/header/stylePopover/index.tsx index 58580cd9..f8508323 100644 --- a/playground/src/components/setting/themeSelect/index.tsx +++ b/playground/src/platform/mobile/header/stylePopover/index.tsx @@ -1,11 +1,21 @@ -import { useState } from "react" -import { COLOR_LIST, useAppSelector,useAppDispatch } from "@/common" -import styles from "./index.module.scss" +import { useMemo } from "react" +import { COLOR_LIST, useAppSelector, useAppDispatch } from "@/common" import { setThemeColor } from "@/store/reducers/global" +import ColorPicker from "./colorPicker" +import { Popover } from 'antd'; + + +import styles from "./index.module.scss" + +interface StylePopoverProps { + children?: React.ReactNode +} -const ThemeSelect = () => { +const StylePopover = (props: StylePopoverProps) => { + const { children } = props const dispatch = useAppDispatch() - const themeColor = useAppSelector((state) => state.global.themeColor) + const themeColor = useAppSelector(state => state.global.themeColor) + const onClickColor = (index: number) => { const target = COLOR_LIST[index] @@ -14,7 +24,7 @@ const ThemeSelect = () => { } } - return
+ const content =
STYLE
{ @@ -33,8 +43,12 @@ const ThemeSelect = () => { }) }
+
-} -export default ThemeSelect + return {children} + +} + +export default StylePopover diff --git a/playground/src/platform/mobile/menu/context.ts b/playground/src/platform/mobile/menu/context.ts new file mode 100644 index 00000000..41c52911 --- /dev/null +++ b/playground/src/platform/mobile/menu/context.ts @@ -0,0 +1,9 @@ +import { createContext } from "react" + +export interface MenuContextType { + scrollToBottom: () => void; +} + +export const MenuContext = createContext({ + scrollToBottom: () => { } +}); diff --git a/playground/src/platform/mobile/menu/index.module.scss b/playground/src/platform/mobile/menu/index.module.scss new file mode 100644 index 00000000..58b1b3fe --- /dev/null +++ b/playground/src/platform/mobile/menu/index.module.scss @@ -0,0 +1,69 @@ +.menu { + width: 100%; + border: 1px solid #272A2F; + border-radius: 4px; + background: #0F0F11; + overflow: hidden; + box-sizing: border-box; + + .header { + height: 40px; + overflow: hidden; + border-bottom: 1px solid #272A2F; + box-sizing: border-box; + + .menuItem { + height: 40px; + padding: 0 16px; + color: var(--Grey-300, #EAECF0); + font-size: 14px; + font-weight: 600; + line-height: 40px; + letter-spacing: 0.449px; + display: inline-block; + color: #667085; + background: #181A1E; + cursor: pointer; + border-right: 1px solid #272A2F; + box-sizing: border-box; + overflow: hidden; + background: #0F0F11; + } + + .active { + color: #EAECF0; + background: #181A1D; + } + } + + + .content { + position: relative; + background: #181A1D; + // header 48px + // description 60px + // paddingTop 16px 16px + // menu header 40px + height: calc(100vh - 48px - 60px - 32px - 40px - 2px); + overflow: hidden; + box-sizing: border-box; + + .item { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + padding: 16px; + z-index: -1; + overflow: auto; + visibility: hidden; + box-sizing: border-box; + } + + .active { + z-index: 1; + visibility: visible; + } + } +} diff --git a/playground/src/platform/mobile/menu/index.tsx b/playground/src/platform/mobile/menu/index.tsx new file mode 100644 index 00000000..2e20de78 --- /dev/null +++ b/playground/src/platform/mobile/menu/index.tsx @@ -0,0 +1,76 @@ +"use client" + +import { ReactElement, useEffect, useState, useRef, useMemo, useCallback } from "react" +import { useAutoScroll } from "@/common" +import { MenuContext } from "./context" +import styles from "./index.module.scss" + +export interface IMenuData { + name: string, + component: ReactElement +} + +export interface IMenuContentComponentPros { + scrollToBottom: () => void +} + +interface MenuProps { + data: IMenuData[] +} + + +const Menu = (props: MenuProps) => { + const { data } = props + const [activeIndex, setActiveIndex] = useState(0) + const contentRefList = useRef<(HTMLDivElement | null)[]>([]) + + const onClickItem = (index: number) => { + setActiveIndex(index) + } + + useEffect(() => { + scrollToTop() + }, [activeIndex]) + + const scrollToBottom = useCallback(() => { + const current = contentRefList.current?.[activeIndex] + if (current) { + current.scrollTop = current.scrollHeight + } + }, [contentRefList, activeIndex]) + + const scrollToTop = useCallback(() => { + const current = contentRefList.current?.[activeIndex] + if (current) { + current.scrollTop = 0 + } + }, [contentRefList, activeIndex]) + + + return
+
+ {data.map((item, index) => { + return onClickItem(index)}>{item.name} + })} +
+
+ + {data.map((item, index) => { + return
{ + contentRefList.current[index] = el; + }} + className={`${styles.item} ${index == activeIndex ? styles.active : ''}`}> + {item.component} +
+ })} +
+
+
+} + +export default Menu diff --git a/playground/src/components/rtc/agent/index.module.scss b/playground/src/platform/mobile/rtc/agent/index.module.scss similarity index 100% rename from playground/src/components/rtc/agent/index.module.scss rename to playground/src/platform/mobile/rtc/agent/index.module.scss diff --git a/playground/src/components/rtc/agent/index.tsx b/playground/src/platform/mobile/rtc/agent/index.tsx similarity index 100% rename from playground/src/components/rtc/agent/index.tsx rename to playground/src/platform/mobile/rtc/agent/index.tsx diff --git a/playground/src/components/rtc/audioVisualizer/index.module.scss b/playground/src/platform/mobile/rtc/audioVisualizer/index.module.scss similarity index 100% rename from playground/src/components/rtc/audioVisualizer/index.module.scss rename to playground/src/platform/mobile/rtc/audioVisualizer/index.module.scss diff --git a/playground/src/components/rtc/audioVisualizer/index.tsx b/playground/src/platform/mobile/rtc/audioVisualizer/index.tsx similarity index 100% rename from playground/src/components/rtc/audioVisualizer/index.tsx rename to playground/src/platform/mobile/rtc/audioVisualizer/index.tsx diff --git a/playground/src/components/rtc/camSection/camSelect/index.module.scss b/playground/src/platform/mobile/rtc/camSection/camSelect/index.module.scss similarity index 100% rename from playground/src/components/rtc/camSection/camSelect/index.module.scss rename to playground/src/platform/mobile/rtc/camSection/camSelect/index.module.scss diff --git a/playground/src/components/rtc/camSection/camSelect/index.tsx b/playground/src/platform/mobile/rtc/camSection/camSelect/index.tsx similarity index 100% rename from playground/src/components/rtc/camSection/camSelect/index.tsx rename to playground/src/platform/mobile/rtc/camSection/camSelect/index.tsx diff --git a/playground/src/platform/mobile/rtc/camSection/index.module.scss b/playground/src/platform/mobile/rtc/camSection/index.module.scss new file mode 100644 index 00000000..76f4ad1e --- /dev/null +++ b/playground/src/platform/mobile/rtc/camSection/index.module.scss @@ -0,0 +1,54 @@ +.camera { + position: relative; + width: 100%; + height: 100%; + box-sizing: border-box; + + .title { + margin-bottom: 10px; + color: var(--Grey-300, #EAECF0); + font-size: 14px; + font-weight: 500; + line-height: 150%; + letter-spacing: 0.449px; + } + + .select { + height: 32px; + display: flex; + width: 100%; + justify-content: flex-start; + align-items: center; + + .iconWrapper { + flex: 0 0 auto; + margin-right: 12px; + display: flex; + width: 32px; + height: 32px; + flex-direction: column; + justify-content: center; + align-items: center; + flex-shrink: 0; + border-radius: 6px; + border: 1px solid #2B2F36; + cursor: pointer; + } + + .select { + flex: 0 0 auto; + width: 200px; + } + } + + .view { + position: relative; + margin-top: 12px; + min-height: 210px; + height: 210px; + border-radius: 6px; + border: 1px solid #272A2F; + background: #1E2024; + box-shadow: 0px 2px 2px 0px rgba(0, 0, 0, 0.25); + } +} diff --git a/playground/src/platform/mobile/rtc/camSection/index.tsx b/playground/src/platform/mobile/rtc/camSection/index.tsx new file mode 100644 index 00000000..2bd0e8db --- /dev/null +++ b/playground/src/platform/mobile/rtc/camSection/index.tsx @@ -0,0 +1,42 @@ +"use client" + +import CamSelect from "./camSelect" +import { CamIcon } from "@/components/icons" +import styles from "./index.module.scss" +import { ICameraVideoTrack } from 'agora-rtc-sdk-ng'; +import { LocalStreamPlayer } from "../streamPlayer" +import { useState, useEffect, useMemo } from 'react'; +import { useSmallScreen } from "@/common" + +interface CamSectionProps { + videoTrack?: ICameraVideoTrack +} + +const CamSection = (props: CamSectionProps) => { + const { videoTrack } = props + const [videoMute, setVideoMute] = useState(false) + + useEffect(() => { + videoTrack?.setMuted(videoMute) + }, [videoTrack, videoMute]) + + const onClickMute = () => { + setVideoMute(!videoMute) + } + + return
+
CAMERA
+
+ + + + +
+
+ +
+
+} + + +export default CamSection; diff --git a/playground/src/components/rtc/index.module.scss b/playground/src/platform/mobile/rtc/index.module.scss similarity index 79% rename from playground/src/components/rtc/index.module.scss rename to playground/src/platform/mobile/rtc/index.module.scss index 4aa3f5c7..ff7b7958 100644 --- a/playground/src/components/rtc/index.module.scss +++ b/playground/src/platform/mobile/rtc/index.module.scss @@ -15,14 +15,20 @@ height: 40px; padding: 0px 16px; align-items: center; - gap: 238px; align-self: stretch; - color: var(--Grey-300, #EAECF0); - font-size: 14px; - font-weight: 600; - line-height: 150%; - letter-spacing: 0.449px; border-bottom: 1px solid #272A2F; + + .text { + flex: 1 1 auto; + font-weight: 600; + line-height: 150%; + letter-spacing: 0.449px; + color: var(--Grey-300, #EAECF0); + } + + .voiceSelect { + flex: 0 0 120px; + } } .you { @@ -44,6 +50,6 @@ text-align: center; } - + } } diff --git a/playground/src/components/rtc/index.tsx b/playground/src/platform/mobile/rtc/index.tsx similarity index 76% rename from playground/src/components/rtc/index.tsx rename to playground/src/platform/mobile/rtc/index.tsx index b5f42cce..bc15c070 100644 --- a/playground/src/components/rtc/index.tsx +++ b/playground/src/platform/mobile/rtc/index.tsx @@ -1,21 +1,25 @@ "use client" import { ICameraVideoTrack, IMicrophoneAudioTrack } from "agora-rtc-sdk-ng" -import { useAppSelector, useAppDispatch } from "@/common" +import { useAppSelector, useAppDispatch, VOICE_OPTIONS } from "@/common" import { ITextItem } from "@/types" import { rtcManager, IUserTracks, IRtcUser } from "@/manager" -import { setRoomConnected, addChatItem } from "@/store/reducers/global" +import { setRoomConnected, addChatItem, setVoiceType } from "@/store/reducers/global" import MicSection from "./micSection" import CamSection from "./camSection" import Agent from "./agent" import styles from "./index.module.scss" -import { useRef, useEffect, useState } from "react" +import { useRef, useEffect, useState, Fragment } from "react" +import { VoiceIcon } from "@/components/icons" +import CustomSelect from "@/components/customSelect" let hasInit = false const Rtc = () => { const dispatch = useAppDispatch() const options = useAppSelector(state => state.global.options) + const voiceType = useAppSelector(state => state.global.voiceType) + const agentConnected = useAppSelector(state => state.global.agentConnected) const { userId, channel } = options const [videoTrack, setVideoTrack] = useState() const [audioTrack, setAudioTrack] = useState() @@ -93,8 +97,20 @@ const Rtc = () => { } } + const onVoiceChange = (value: any) => { + dispatch(setVoiceType(value)) + } + + return
-
Audio & Video
+
+ Audio & Video + } + options={VOICE_OPTIONS} onChange={onVoiceChange}> +
{/* agent */} {/* you */} diff --git a/playground/src/platform/mobile/rtc/micSection/index.module.scss b/playground/src/platform/mobile/rtc/micSection/index.module.scss new file mode 100644 index 00000000..60cc6fe1 --- /dev/null +++ b/playground/src/platform/mobile/rtc/micSection/index.module.scss @@ -0,0 +1,58 @@ +.microphone { + position: relative; + width: 100%; + height: 100%; + box-sizing: border-box; + + .title { + margin-bottom: 10px; + color: var(--Grey-300, #EAECF0); + font-size: 14px; + font-weight: 500; + line-height: 150%; + letter-spacing: 0.449px; + } + + + .select { + height: 32px; + display: flex; + width: 100%; + justify-content: flex-start; + align-items: center; + + + .iconWrapper { + flex: 0 0 auto; + margin-right: 12px; + display: flex; + width: 32px; + height: 32px; + flex-direction: column; + justify-content: center; + align-items: center; + flex-shrink: 0; + border-radius: 6px; + border: 1px solid #2B2F36; + cursor: pointer; + } + + + } + + .view { + margin-top: 12px; + display: flex; + height: 120px; + padding: 24px; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 10px; + align-self: stretch; + border-radius: 6px; + border: 1px solid #272A2F; + background: #1E2024; + box-shadow: 0px 2px 2px 0px rgba(0, 0, 0, 0.25); + } +} diff --git a/playground/src/platform/mobile/rtc/micSection/index.tsx b/playground/src/platform/mobile/rtc/micSection/index.tsx new file mode 100644 index 00000000..3c739159 --- /dev/null +++ b/playground/src/platform/mobile/rtc/micSection/index.tsx @@ -0,0 +1,70 @@ +"use client" + +import { useEffect, useMemo, useState } from "react" +import { useMultibandTrackVolume, useSmallScreen } from "@/common" +import AudioVisualizer from "../audioVisualizer" +import { MicIcon } from "@/components/icons" +import styles from "./index.module.scss" +import { IMicrophoneAudioTrack } from 'agora-rtc-sdk-ng'; +import MicSelect from "./micSelect"; + +interface MicSectionProps { + audioTrack?: IMicrophoneAudioTrack +} + +const MicSection = (props: MicSectionProps) => { + const { audioTrack } = props + const [audioMute, setAudioMute] = useState(false) + const [mediaStreamTrack, setMediaStreamTrack] = useState() + + + + useEffect(() => { + audioTrack?.on("track-updated", onAudioTrackupdated) + if (audioTrack) { + setMediaStreamTrack(audioTrack.getMediaStreamTrack()) + } + + return () => { + audioTrack?.off("track-updated", onAudioTrackupdated) + } + }, [audioTrack]) + + useEffect(() => { + audioTrack?.setMuted(audioMute) + }, [audioTrack, audioMute]) + + const subscribedVolumes = useMultibandTrackVolume(mediaStreamTrack, 20); + + const onAudioTrackupdated = (track: MediaStreamTrack) => { + console.log("[test] audio track updated", track) + setMediaStreamTrack(track) + } + + const onClickMute = () => { + setAudioMute(!audioMute) + } + + return
+
MICROPHONE
+
+ + + + +
+
+ +
+
+} + + +export default MicSection; diff --git a/playground/src/components/rtc/micSection/micSelect/index.module.scss b/playground/src/platform/mobile/rtc/micSection/micSelect/index.module.scss similarity index 100% rename from playground/src/components/rtc/micSection/micSelect/index.module.scss rename to playground/src/platform/mobile/rtc/micSection/micSelect/index.module.scss diff --git a/playground/src/components/rtc/micSection/micSelect/index.tsx b/playground/src/platform/mobile/rtc/micSection/micSelect/index.tsx similarity index 100% rename from playground/src/components/rtc/micSection/micSelect/index.tsx rename to playground/src/platform/mobile/rtc/micSection/micSelect/index.tsx diff --git a/playground/src/components/rtc/streamPlayer/index.module.scss b/playground/src/platform/mobile/rtc/streamPlayer/index.module.scss similarity index 100% rename from playground/src/components/rtc/streamPlayer/index.module.scss rename to playground/src/platform/mobile/rtc/streamPlayer/index.module.scss diff --git a/playground/src/components/rtc/streamPlayer/index.tsx b/playground/src/platform/mobile/rtc/streamPlayer/index.tsx similarity index 100% rename from playground/src/components/rtc/streamPlayer/index.tsx rename to playground/src/platform/mobile/rtc/streamPlayer/index.tsx diff --git a/playground/src/components/rtc/streamPlayer/localStreamPlayer.tsx b/playground/src/platform/mobile/rtc/streamPlayer/localStreamPlayer.tsx similarity index 100% rename from playground/src/components/rtc/streamPlayer/localStreamPlayer.tsx rename to playground/src/platform/mobile/rtc/streamPlayer/localStreamPlayer.tsx diff --git a/playground/src/platform/pc/chat/chatItem/index.module.scss b/playground/src/platform/pc/chat/chatItem/index.module.scss new file mode 100644 index 00000000..f28ef7ee --- /dev/null +++ b/playground/src/platform/pc/chat/chatItem/index.module.scss @@ -0,0 +1,90 @@ +.agentChatItem { + width: 100%; + display: flex; + justify-content: flex-start; + + .left { + flex: 0 0 auto; + display: flex; + width: 32px; + height: 32px; + padding: 10px; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 10px; + border-radius: 200px; + background: var(--Grey-700, #475467); + + .userName { + color: var(---white, #FFF); + text-align: center; + font-size: 14px; + font-weight: 500; + line-height: 150%; + } + } + + .right { + margin-left: 12px; + flex: 1 1 auto; + + .userName { + font-size: 14px; + font-weight: 500; + line-height: 20px; + color: var(--theme-color, #667085) !important; + } + + + .agent { + color: var(--theme-color, #EAECF0) !important; + } + + } +} + +.userChatItem { + width: 100%; + display: flex; + flex-direction: column; + justify-content: flex-end; + align-items: flex-end; + + .userName { + text-align: right; + color: var(--Grey-600, #667085); + font-weight: 500; + line-height: 20px; + } + + + +} + + +.chatItem { + .text { + max-width: 80%; + width: fit-content; + margin-top: 6px; + color: #FFF; + display: flex; + padding: 8px 14px; + flex-direction: column; + justify-content: center; + align-items: flex-start; + font-size: 14px; + font-weight: 400; + line-height: 21px; + white-space: pre-wrap; + border-radius: 0px 8px 8px 8px; + border: 1px solid #272A2F; + background: #1E2024; + box-shadow: 0px 2px 2px 0px rgba(0, 0, 0, 0.25); + } +} + +.chatItem+.chatItem { + margin-top: 14px; +} diff --git a/playground/src/platform/pc/chat/chatItem/index.tsx b/playground/src/platform/pc/chat/chatItem/index.tsx new file mode 100644 index 00000000..6364aaea --- /dev/null +++ b/playground/src/platform/pc/chat/chatItem/index.tsx @@ -0,0 +1,51 @@ +import { IChatItem } from "@/types" +import styles from "./index.module.scss" +import { usePrevious } from "@/common" +import { use, useEffect, useMemo, useState } from "react" + +interface ChatItemProps { + data: IChatItem +} + + +const AgentChatItem = (props: ChatItemProps) => { + const { data } = props + const { text } = data + + return
+ + Ag + + +
Agent
+
+ {text} +
+
+
+} + +const UserChatItem = (props: ChatItemProps) => { + const { data } = props + const { text } = data + + return
+
You
+
{text}
+
+} + + +const ChatItem = (props: ChatItemProps) => { + const { data } = props + + + return ( + data.type === "agent" ? : + ); + + +} + + +export default ChatItem diff --git a/playground/src/platform/pc/chat/index.module.scss b/playground/src/platform/pc/chat/index.module.scss new file mode 100644 index 00000000..b5c2707c --- /dev/null +++ b/playground/src/platform/pc/chat/index.module.scss @@ -0,0 +1,73 @@ +.chat { + flex: 1 1 auto; + min-width: 500px; + display: flex; + flex-direction: column; + align-items: flex-start; + align-self: stretch; + border-radius: 8px; + border: 1px solid #272A2F; + background: #181A1D; + overflow: hidden; + + .header { + display: flex; + height: 42px; + padding: 0px 16px; + align-items: center; + align-self: stretch; + border-bottom: 1px solid #272A2F; + + .left { + flex: 1 1 auto; + + .text { + margin-left: 4px; + color: var(--Grey-300, #EAECF0); + font-size: 14px; + font-weight: 600; + height: 40px; + line-height: 40px; + letter-spacing: 0.449px; + } + + .languageSelect { + margin-left: 12px; + width: 100px; + } + } + + + .right { + flex: 0 0 230px; + } + + } + + .content { + display: flex; + padding: 12px 24px; + flex-direction: column; + align-items: flex-start; + gap: 10px; + flex: 1 0 500px; + align-self: stretch; + overflow-y: auto; + + + &::-webkit-scrollbar { + width: 6px + } + + &::-webkit-scrollbar-track { + background-color: transparent; + } + + &::-webkit-scrollbar-thumb { + background-color: #6B6B6B; + border-radius: 4px; + } + } + + +} diff --git a/playground/src/platform/pc/chat/index.tsx b/playground/src/platform/pc/chat/index.tsx new file mode 100644 index 00000000..98cdd083 --- /dev/null +++ b/playground/src/platform/pc/chat/index.tsx @@ -0,0 +1,59 @@ +"use client" + +import { ReactElement, useEffect, useRef, useState } from "react" +import ChatItem from "./chatItem" +import { + genRandomChatList, useAppDispatch, useAutoScroll, + LANGUAGE_OPTIONS, useAppSelector, +} from "@/common" +import { setLanguage } from "@/store/reducers/global" +import { Select, } from 'antd'; +import PdfSelect from "@/components/pdfSelect" + +import styles from "./index.module.scss" + + + + +const Chat = () => { + const dispatch = useAppDispatch() + const chatItems = useAppSelector(state => state.global.chatItems) + const language = useAppSelector(state => state.global.language) + const agentConnected = useAppSelector(state => state.global.agentConnected) + + // const chatItems = genRandomChatList(10) + const chatRef = useRef(null) + + + useAutoScroll(chatRef) + + + const onLanguageChange = (val: any) => { + dispatch(setLanguage(val)) + } + + + + + return
+
+ + Chat + + + + + +
+
+ {chatItems.map((item, index) => { + return + })} +
+
+} + + +export default Chat diff --git a/playground/src/platform/pc/description/index.module.scss b/playground/src/platform/pc/description/index.module.scss new file mode 100644 index 00000000..7efe9d6d --- /dev/null +++ b/playground/src/platform/pc/description/index.module.scss @@ -0,0 +1,72 @@ +.description { + position: relative; + display: flex; + padding: 12px 16px; + align-items: center; + gap: 12px; + align-self: stretch; + border-radius: 8px; + border: 1px solid #272A2F; + background: #181A1D; + + .title { + color: var(--Grey-300, #EAECF0); + font-size: 14px; + font-style: normal; + font-weight: 600; + /* 21px */ + letter-spacing: 0.449px; + } + + .text { + margin-left: 12px; + flex: 1 1 auto; + color: var(--Grey-600, #667085); + font-size: 14px; + font-style: normal; + font-weight: 400; + } + + + .btnConnect { + width: 150px; + display: flex; + padding: 8px 14px; + justify-content: center; + align-items: center; + gap: 8px; + align-self: stretch; + border-radius: 6px; + background: var(--theme-color, #0888FF); + box-shadow: 0px 1px 2px 0px rgba(16, 24, 40, 0.05); + cursor: pointer; + user-select: none; + caret-color: transparent; + box-sizing: border-box; + + .btnText { + width: 100px; + text-align: center; + color: var(---White, #FFF); + font-size: 14px; + font-weight: 500; + line-height: 20px; + } + + .btnText.disconnect { + color: var(--Error-400-T, #E95C7B); + } + + + .loading { + margin-left: 4px; + } + } + + + .btnConnect.disconnect { + background: #181A1D; + border: 1px solid var(--Error-400-T, #E95C7B); + } + +} diff --git a/playground/src/platform/pc/description/index.tsx b/playground/src/platform/pc/description/index.tsx new file mode 100644 index 00000000..f7965392 --- /dev/null +++ b/playground/src/platform/pc/description/index.tsx @@ -0,0 +1,99 @@ +import { setAgentConnected } from "@/store/reducers/global" +import { + DESCRIPTION, useAppDispatch, useAppSelector, apiPing, genUUID, + apiStartService, apiStopService, REQUEST_URL +} from "@/common" +import { Select, Button, message, Upload } from "antd" +import { useEffect, useState, MouseEventHandler } from "react" +import { LoadingOutlined, UploadOutlined } from "@ant-design/icons" +import styles from "./index.module.scss" + +let intervalId: any + +const Description = () => { + const dispatch = useAppDispatch() + const agentConnected = useAppSelector(state => state.global.agentConnected) + const channel = useAppSelector(state => state.global.options.channel) + const userId = useAppSelector(state => state.global.options.userId) + const language = useAppSelector(state => state.global.language) + const voiceType = useAppSelector(state => state.global.voiceType) + const [loading, setLoading] = useState(false) + + useEffect(() => { + if (channel) { + checkAgentConnected() + } + }, [channel]) + + + const checkAgentConnected = async () => { + const res: any = await apiPing(channel) + if (res?.code == 0) { + dispatch(setAgentConnected(true)) + } + } + + const onClickConnect = async () => { + if (loading) { + return + } + setLoading(true) + if (agentConnected) { + await apiStopService(channel) + dispatch(setAgentConnected(false)) + message.success("Agent disconnected") + stopPing() + } else { + const res = await apiStartService({ + channel, + userId, + language, + voiceType + }) + const { code, msg } = res || {} + if (code != 0) { + if (code == "10001") { + message.error("The number of users experiencing the program simultaneously has exceeded the limit. Please try again later.") + } else { + message.error(`code:${code},msg:${msg}`) + } + setLoading(false) + throw new Error(msg) + } + dispatch(setAgentConnected(true)) + message.success("Agent connected") + startPing() + } + setLoading(false) + } + + const startPing = () => { + if (intervalId) { + stopPing() + } + intervalId = setInterval(() => { + apiPing(channel) + }, 3000) + } + + const stopPing = () => { + if (intervalId) { + clearInterval(intervalId) + intervalId = null + } + } + + return
+ Description + RTT is an AI voice assistant powered by Shengwang Agents, Deepgram, Eleven Labs, and ChatGPT. It is running on Shengwang Playground. + + + {!agentConnected ? "Connect" : "Disconnect"} + {loading ? : null} + + +
+} + + +export default Description diff --git a/playground/src/platform/pc/entry/index.module.scss b/playground/src/platform/pc/entry/index.module.scss new file mode 100644 index 00000000..f138183f --- /dev/null +++ b/playground/src/platform/pc/entry/index.module.scss @@ -0,0 +1,17 @@ +.entry { + position: relative; + height: 100%; + box-sizing: border-box; + + .content { + position: relative; + padding: 16px; + box-sizing: border-box; + + .body { + margin-top: 16px; + display: flex; + gap: 24px; + } + } +} diff --git a/playground/src/platform/pc/entry/index.tsx b/playground/src/platform/pc/entry/index.tsx new file mode 100644 index 00000000..e7acd7f1 --- /dev/null +++ b/playground/src/platform/pc/entry/index.tsx @@ -0,0 +1,22 @@ +import Chat from "../chat" +import Description from "../description" +import Rtc from "../rtc" +import Header from "../header" + +import styles from "./index.module.scss" + +const PCEntry = () => { + return
+
+
+ +
+ + +
+
+
+} + + +export default PCEntry diff --git a/playground/src/components/header/index.module.scss b/playground/src/platform/pc/header/index.module.scss similarity index 87% rename from playground/src/components/header/index.module.scss rename to playground/src/platform/pc/header/index.module.scss index d70e83f3..049d0181 100644 --- a/playground/src/components/header/index.module.scss +++ b/playground/src/platform/pc/header/index.module.scss @@ -1,8 +1,4 @@ .header { - position: fixed; - left: 0; - right: 0; - top: 0; display: flex; width: 100%; height: 48px; @@ -30,6 +26,9 @@ } .content { + display: flex; + align-items: center; + justify-content: center; height: 48px; flex: 1 1 auto; color: var(--Grey-300, #EAECF0); @@ -38,10 +37,13 @@ line-height: 48px; letter-spacing: 0.449px; text-align: center; + + .text { + margin-left: 4px; + } } .githubWrapper { - cursor: pointer; margin-right: 12px; } } diff --git a/playground/src/platform/pc/header/index.tsx b/playground/src/platform/pc/header/index.tsx new file mode 100644 index 00000000..15d3fcef --- /dev/null +++ b/playground/src/platform/pc/header/index.tsx @@ -0,0 +1,46 @@ +"use client" + +import { useAppSelector, GITHUB_URL, useSmallScreen } from "@/common" +import Network from "./network" +import InfoPopover from "./infoPopover" +import StylePopover from "./stylePopover" +import { GithubIcon, LogoIcon, InfoIcon, ColorPickerIcon } from "@/components/icons" + +import styles from "./index.module.scss" + +const Header = () => { + const themeColor = useAppSelector(state => state.global.themeColor) + const options = useAppSelector(state => state.global.options) + const { channel } = options + + + const onClickGithub = () => { + if (typeof window !== "undefined") { + window.open(GITHUB_URL, "_blank") + } + } + + + + return
+ + + + + + + Channel Name: {channel} + + + + + + + + + +
+} + + +export default Header diff --git a/playground/src/components/setting/status/index.module.scss b/playground/src/platform/pc/header/infoPopover/index.module.scss similarity index 83% rename from playground/src/components/setting/status/index.module.scss rename to playground/src/platform/pc/header/infoPopover/index.module.scss index aa84abeb..cd3f72f8 100644 --- a/playground/src/components/setting/status/index.module.scss +++ b/playground/src/platform/pc/header/infoPopover/index.module.scss @@ -1,11 +1,10 @@ -.status { +.info { display: flex; - padding: 24px 16px; + padding: 12px 16px; flex-direction: column; align-items: flex-start; - gap: 12px; + gap: 8px; align-self: stretch; - border-bottom: 1px solid #272A2F; .title { color: var(--Grey-300, #EAECF0); @@ -35,4 +34,10 @@ line-height: 150%; } } + + .slider { + height: 1px; + width: 100%; + background-color: #0D0F12; + } } diff --git a/playground/src/platform/pc/header/infoPopover/index.tsx b/playground/src/platform/pc/header/infoPopover/index.tsx new file mode 100644 index 00000000..cd451418 --- /dev/null +++ b/playground/src/platform/pc/header/infoPopover/index.tsx @@ -0,0 +1,57 @@ +import { useMemo } from "react" +import { useAppSelector } from "@/common" +import { Popover } from 'antd'; + + +import styles from "./index.module.scss" + +interface InfoPopoverProps { + children?: React.ReactNode +} + +const InfoPopover = (props: InfoPopoverProps) => { + const { children } = props + const options = useAppSelector(state => state.global.options) + const { channel, userId } = options + + const roomConnected = useAppSelector(state => state.global.roomConnected) + const agentConnected = useAppSelector(state => state.global.agentConnected) + + const roomConnectedText = useMemo(() => { + return roomConnected ? "TRUE" : "FALSE" + }, [roomConnected]) + + const agentConnectedText = useMemo(() => { + return agentConnected ? "TRUE" : "FALSE" + }, [agentConnected]) + + + + const content =
+
INFO
+
+ Room + {channel} +
+
+ Participant + {userId} +
+
+
STATUS
+
+
Room connected
+
{roomConnectedText}
+
+
+
Agent connected
+
{agentConnectedText}
+
+
+ + + return {children} + +} + +export default InfoPopover diff --git a/playground/src/platform/pc/header/network/index.module.scss b/playground/src/platform/pc/header/network/index.module.scss new file mode 100644 index 00000000..e69de29b diff --git a/playground/src/platform/pc/header/network/index.tsx b/playground/src/platform/pc/header/network/index.tsx new file mode 100644 index 00000000..92b4e33b --- /dev/null +++ b/playground/src/platform/pc/header/network/index.tsx @@ -0,0 +1,37 @@ +"use client"; + +import React from "react"; +import { rtcManager } from "@/manager" +import { NetworkQuality } from "agora-rtc-sdk-ng" +import { useEffect, useState } from "react" +import { NetworkIcon } from "@/components/icons" + +interface NetworkProps { + style?: React.CSSProperties +} + +const NetWork = (props: NetworkProps) => { + const { style } = props + + const [networkQuality, setNetworkQuality] = useState() + + useEffect(() => { + rtcManager.on("networkQuality", onNetworkQuality) + + return () => { + rtcManager.off("networkQuality", onNetworkQuality) + } + }, []) + + const onNetworkQuality = (quality: NetworkQuality) => { + setNetworkQuality(quality) + } + + return ( + + + + ) +} + +export default NetWork diff --git a/playground/src/platform/pc/header/stylePopover/colorPicker/index.module.scss b/playground/src/platform/pc/header/stylePopover/colorPicker/index.module.scss new file mode 100644 index 00000000..405e7781 --- /dev/null +++ b/playground/src/platform/pc/header/stylePopover/colorPicker/index.module.scss @@ -0,0 +1,24 @@ +.colorPicker { + height: 24px; + display: flex; + align-items: center; + + :global(.react-colorful) { + width: 220px; + height: 8px; + } + + :global(.react-colorful__saturation) { + display: none; + } + + :global(.react-colorful__hue) { + border-radius: 8px !important; + height: 8px; + } + + :global(.react-colorful__pointer) { + width: 24px; + height: 24px; + } +} diff --git a/playground/src/platform/pc/header/stylePopover/colorPicker/index.tsx b/playground/src/platform/pc/header/stylePopover/colorPicker/index.tsx new file mode 100644 index 00000000..28163d77 --- /dev/null +++ b/playground/src/platform/pc/header/stylePopover/colorPicker/index.tsx @@ -0,0 +1,22 @@ +"use client" + +import { HexColorPicker } from "react-colorful"; +import { useAppSelector, useAppDispatch } from "@/common" +import { setThemeColor } from "@/store/reducers/global" +import styles from "./index.module.scss"; + +const ColorPicker = () => { + const dispatch = useAppDispatch() + const themeColor = useAppSelector(state => state.global.themeColor) + + const onColorChange = (color: string) => { + console.log(color); + dispatch(setThemeColor(color)) + }; + + return
+ +
+}; + +export default ColorPicker; diff --git a/playground/src/platform/pc/header/stylePopover/index.module.scss b/playground/src/platform/pc/header/stylePopover/index.module.scss new file mode 100644 index 00000000..98c7f182 --- /dev/null +++ b/playground/src/platform/pc/header/stylePopover/index.module.scss @@ -0,0 +1,51 @@ +.info { + display: flex; + padding: 12px 16px; + flex-direction: column; + align-items: flex-start; + gap: 16px; + align-self: stretch; + + + .title { + color: var(--Grey-300, #EAECF0); + font-size: 14px; + font-weight: 600; + line-height: 150%; + letter-spacing: 0.449px; + } + + .color { + font-size: 0; + white-space: nowrap; + + .item { + position: relative; + display: inline-block; + width: 28px; + height: 28px; + border-radius: 4px; + border: 2px solid transparent; + font-size: 0; + cursor: pointer; + + .inner { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + width: 18px; + height: 18px; + border-radius: 2px; + box-sizing: border-box; + } + } + + .item+.item { + margin-left: 12px; + } + + } + + +} diff --git a/playground/src/platform/pc/header/stylePopover/index.tsx b/playground/src/platform/pc/header/stylePopover/index.tsx new file mode 100644 index 00000000..f8508323 --- /dev/null +++ b/playground/src/platform/pc/header/stylePopover/index.tsx @@ -0,0 +1,54 @@ +import { useMemo } from "react" +import { COLOR_LIST, useAppSelector, useAppDispatch } from "@/common" +import { setThemeColor } from "@/store/reducers/global" +import ColorPicker from "./colorPicker" +import { Popover } from 'antd'; + + +import styles from "./index.module.scss" + +interface StylePopoverProps { + children?: React.ReactNode +} + +const StylePopover = (props: StylePopoverProps) => { + const { children } = props + const dispatch = useAppDispatch() + const themeColor = useAppSelector(state => state.global.themeColor) + + + const onClickColor = (index: number) => { + const target = COLOR_LIST[index] + if (target.active !== themeColor) { + dispatch(setThemeColor(target.active)) + } + } + + const content =
+
STYLE
+
+ { + COLOR_LIST.map((item, index) => { + return onClickColor(index)} + className={styles.item} + key={index}> + + + }) + } +
+ +
+ + + return {children} + +} + +export default StylePopover diff --git a/playground/src/platform/pc/rtc/agent/index.module.scss b/playground/src/platform/pc/rtc/agent/index.module.scss new file mode 100644 index 00000000..fa3ae2ec --- /dev/null +++ b/playground/src/platform/pc/rtc/agent/index.module.scss @@ -0,0 +1,31 @@ +.agent { + position: relative; + display: flex; + height: 292px; + padding: 20px 16px; + flex-direction: column; + justify-content: flex-start; + align-items: center; + align-self: stretch; + background: linear-gradient(154deg, rgba(27, 66, 166, 0.16) 0%, rgba(27, 45, 140, 0.00) 18%), linear-gradient(153deg, rgba(23, 24, 28, 0.00) 53.75%, #11174E 100%), #0F0F11; + box-shadow: 0px 3.999px 48.988px 0px rgba(0, 7, 72, 0.12); + backdrop-filter: blur(7); + box-sizing: border-box; + + .text { + margin-top: 50px; + color: var(--theme-color, #EAECF0); + font-size: 24px; + font-weight: 600; + line-height: 150%; + letter-spacing: 0.449px; + } + + .view { + margin-top: 32px; + display: flex; + align-items: center; + justify-content: center; + height: 56px; + } +} diff --git a/playground/src/platform/pc/rtc/agent/index.tsx b/playground/src/platform/pc/rtc/agent/index.tsx new file mode 100644 index 00000000..a7fd7944 --- /dev/null +++ b/playground/src/platform/pc/rtc/agent/index.tsx @@ -0,0 +1,34 @@ +"use client" + +import { useAppSelector, useMultibandTrackVolume } from "@/common" +import AudioVisualizer from "../audioVisualizer" +import { IMicrophoneAudioTrack } from 'agora-rtc-sdk-ng'; +import styles from "./index.module.scss" + +interface AgentProps { + audioTrack?: IMicrophoneAudioTrack +} + +const Agent = (props: AgentProps) => { + const { audioTrack } = props + + const subscribedVolumes = useMultibandTrackVolume(audioTrack, 12); + + return
+
Agent
+
+ +
+
+ +} + + +export default Agent; diff --git a/playground/src/platform/pc/rtc/audioVisualizer/index.module.scss b/playground/src/platform/pc/rtc/audioVisualizer/index.module.scss new file mode 100644 index 00000000..1beae944 --- /dev/null +++ b/playground/src/platform/pc/rtc/audioVisualizer/index.module.scss @@ -0,0 +1,17 @@ +.audioVisualizer { + display: flex; + justify-content: center; + align-items: center; + + + .item {} + + .agent { + background-color: var(--theme-color, #EAECF0); + box-shadow: 0 0 10px var(--theme-color, #EAECF0); + } + + .user { + background-color: var(--Grey-300, #EAECF0); + } +} diff --git a/playground/src/platform/pc/rtc/audioVisualizer/index.tsx b/playground/src/platform/pc/rtc/audioVisualizer/index.tsx new file mode 100644 index 00000000..bc21f554 --- /dev/null +++ b/playground/src/platform/pc/rtc/audioVisualizer/index.tsx @@ -0,0 +1,48 @@ +"use client" + +import { useState, useEffect } from "react" +import styles from "./index.module.scss" + +interface AudioVisualizerProps { + type: "agent" | "user"; + frequencies: Float32Array[]; + gap: number; + barWidth: number; + minBarHeight: number; + maxBarHeight: number + borderRadius: number; +} + + +const AudioVisualizer = (props: AudioVisualizerProps) => { + const { frequencies, gap, barWidth, minBarHeight, maxBarHeight, borderRadius, type } = props; + + const summedFrequencies = frequencies.map((bandFrequencies) => { + const sum = bandFrequencies.reduce((a, b) => a + b, 0) + if (sum <= 0) { + return 0 + } + return Math.sqrt(sum / bandFrequencies.length); + }); + + return
{ + summedFrequencies.map((frequency, index) => { + + const style = { + height: minBarHeight + frequency * (maxBarHeight - minBarHeight) + "px", + borderRadius: borderRadius + "px", + width: barWidth + "px", + transition: + "background-color 0.35s ease-out, transform 0.25s ease-out", + // transform: transform, + } + + return + }) + }
+} + + +export default AudioVisualizer; diff --git a/playground/src/platform/pc/rtc/camSection/camSelect/index.module.scss b/playground/src/platform/pc/rtc/camSection/camSelect/index.module.scss new file mode 100644 index 00000000..8ca5088b --- /dev/null +++ b/playground/src/platform/pc/rtc/camSection/camSelect/index.module.scss @@ -0,0 +1,4 @@ +.select { + flex: 0 0 200px; + width: 200px; +} diff --git a/playground/src/platform/pc/rtc/camSection/camSelect/index.tsx b/playground/src/platform/pc/rtc/camSection/camSelect/index.tsx new file mode 100644 index 00000000..33a5e003 --- /dev/null +++ b/playground/src/platform/pc/rtc/camSection/camSelect/index.tsx @@ -0,0 +1,57 @@ +"use client" + +import AgoraRTC, { ICameraVideoTrack } from "agora-rtc-sdk-ng" +import { useState, useEffect } from "react" +import { Select } from "antd" + +import styles from "./index.module.scss" + +interface CamSelectProps { + videoTrack?: ICameraVideoTrack +} + +interface SelectItem { + label: string + value: string + deviceId: string +} + +const DEFAULT_ITEM: SelectItem = { + label: "Default", + value: "default", + deviceId: "" +} + +const CamSelect = (props: CamSelectProps) => { + const { videoTrack } = props + const [items, setItems] = useState([DEFAULT_ITEM]); + const [value, setValue] = useState("default"); + + useEffect(() => { + if (videoTrack) { + const label = videoTrack?.getTrackLabel(); + setValue(label); + AgoraRTC.getCameras().then(arr => { + setItems(arr.map(item => ({ + label: item.label, + value: item.label, + deviceId: item.deviceId + }))); + }); + } + }, [videoTrack]); + + const onChange = async (value: string) => { + const target = items.find(item => item.value === value); + if (target) { + setValue(target.value); + if (videoTrack) { + await videoTrack.setDevice(target.deviceId); + } + } + } + + return +} + +export default CamSelect diff --git a/playground/src/components/rtc/camSection/index.module.scss b/playground/src/platform/pc/rtc/camSection/index.module.scss similarity index 100% rename from playground/src/components/rtc/camSection/index.module.scss rename to playground/src/platform/pc/rtc/camSection/index.module.scss diff --git a/playground/src/components/rtc/camSection/index.tsx b/playground/src/platform/pc/rtc/camSection/index.tsx similarity index 94% rename from playground/src/components/rtc/camSection/index.tsx rename to playground/src/platform/pc/rtc/camSection/index.tsx index f5b62e06..99e5392c 100644 --- a/playground/src/components/rtc/camSection/index.tsx +++ b/playground/src/platform/pc/rtc/camSection/index.tsx @@ -15,11 +15,11 @@ interface CamSectionProps { const CamSection = (props: CamSectionProps) => { const { videoTrack } = props const [videoMute, setVideoMute] = useState(false) - const {xs} = useSmallScreen() + const { xs } = useSmallScreen() - const CamText = useMemo(()=>{ + const CamText = useMemo(() => { return xs ? "CAM" : "CAMERA" - },[xs]) + }, [xs]) useEffect(() => { videoTrack?.setMuted(videoMute) diff --git a/playground/src/platform/pc/rtc/index.module.scss b/playground/src/platform/pc/rtc/index.module.scss new file mode 100644 index 00000000..b62025c5 --- /dev/null +++ b/playground/src/platform/pc/rtc/index.module.scss @@ -0,0 +1,55 @@ +.rtc { + flex: 0 0 420px; + display: flex; + flex-direction: column; + align-items: flex-start; + flex-shrink: 0; + align-self: stretch; + border-radius: 8px; + border: 1px solid #272A2F; + background: #181A1D; + box-sizing: border-box; + + .header { + display: flex; + height: 42px; + padding: 0px 16px; + align-items: center; + align-self: stretch; + border-bottom: 1px solid #272A2F; + + .text { + flex: 1 1 auto; + font-weight: 600; + line-height: 150%; + letter-spacing: 0.449px; + color: var(--Grey-300, #EAECF0); + } + + .voiceSelect { + flex: 0 0 120px; + } + } + + .you { + display: flex; + padding: 24px 16px; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 24px; + align-self: stretch; + border-top: 1px solid #272A2F; + + .title { + color: var(--Grey-300, #EAECF0); + font-size: 24px; + font-weight: 600; + line-height: 150%; + letter-spacing: 0.449px; + text-align: center; + } + + + } +} diff --git a/playground/src/platform/pc/rtc/index.tsx b/playground/src/platform/pc/rtc/index.tsx new file mode 100644 index 00000000..1195ca0f --- /dev/null +++ b/playground/src/platform/pc/rtc/index.tsx @@ -0,0 +1,128 @@ +"use client" + +import { ICameraVideoTrack, IMicrophoneAudioTrack } from "agora-rtc-sdk-ng" +import { useAppSelector, useAppDispatch, VOICE_OPTIONS } from "@/common" +import { ITextItem } from "@/types" +import { rtcManager, IUserTracks, IRtcUser } from "@/manager" +import { setRoomConnected, addChatItem, setVoiceType } from "@/store/reducers/global" +import MicSection from "./micSection" +import CamSection from "./camSection" +import Agent from "./agent" +import styles from "./index.module.scss" +import { useRef, useEffect, useState, Fragment } from "react" +import { VoiceIcon } from "@/components/icons" +import CustomSelect from "@/components/customSelect" + +let hasInit = false + +const Rtc = () => { + const dispatch = useAppDispatch() + const options = useAppSelector(state => state.global.options) + const voiceType = useAppSelector(state => state.global.voiceType) + const agentConnected = useAppSelector(state => state.global.agentConnected) + const { userId, channel } = options + const [videoTrack, setVideoTrack] = useState() + const [audioTrack, setAudioTrack] = useState() + const [remoteuser, setRemoteUser] = useState() + + useEffect(() => { + if (!options.channel) { + return + } + if (hasInit) { + return + } + + init() + + return () => { + if (hasInit) { + destory() + } + } + }, [options.channel]) + + + const init = async () => { + console.log("[test] init") + rtcManager.on("localTracksChanged", onLocalTracksChanged) + rtcManager.on("textChanged", onTextChanged) + rtcManager.on("remoteUserChanged", onRemoteUserChanged) + await rtcManager.createTracks() + await rtcManager.join({ + channel, + userId + }) + await rtcManager.publish() + dispatch(setRoomConnected(true)) + hasInit = true + } + + const destory = async () => { + console.log("[test] destory") + rtcManager.off("textChanged", onTextChanged) + rtcManager.off("localTracksChanged", onLocalTracksChanged) + rtcManager.off("remoteUserChanged", onRemoteUserChanged) + await rtcManager.destroy() + dispatch(setRoomConnected(false)) + hasInit = false + } + + const onRemoteUserChanged = (user: IRtcUser) => { + console.log("[test] onRemoteUserChanged", user) + setRemoteUser(user) + } + + const onLocalTracksChanged = (tracks: IUserTracks) => { + console.log("[test] onLocalTracksChanged", tracks) + const { videoTrack, audioTrack } = tracks + if (videoTrack) { + setVideoTrack(videoTrack) + } + if (audioTrack) { + setAudioTrack(audioTrack) + } + } + + const onTextChanged = (text: ITextItem) => { + if (text.dataType == "transcribe") { + const isAgent = Number(text.uid) != Number(userId) + dispatch(addChatItem({ + userId: text.uid, + text: text.text, + type: isAgent ? "agent" : "user", + isFinal: text.isFinal, + time: text.time + })) + } + } + + const onVoiceChange = (value: any) => { + dispatch(setVoiceType(value)) + } + + + return
+
+ Audio & Video + } + options={VOICE_OPTIONS} onChange={onVoiceChange}> +
+ {/* agent */} + + {/* you */} +
+
You
+ {/* microphone */} + + {/* camera */} + +
+
+} + + +export default Rtc; diff --git a/playground/src/components/rtc/micSection/index.module.scss b/playground/src/platform/pc/rtc/micSection/index.module.scss similarity index 100% rename from playground/src/components/rtc/micSection/index.module.scss rename to playground/src/platform/pc/rtc/micSection/index.module.scss diff --git a/playground/src/components/rtc/micSection/index.tsx b/playground/src/platform/pc/rtc/micSection/index.tsx similarity index 98% rename from playground/src/components/rtc/micSection/index.tsx rename to playground/src/platform/pc/rtc/micSection/index.tsx index e1e8a10b..6d97f3e2 100644 --- a/playground/src/components/rtc/micSection/index.tsx +++ b/playground/src/platform/pc/rtc/micSection/index.tsx @@ -18,7 +18,7 @@ const MicSection = (props: MicSectionProps) => { const [mediaStreamTrack, setMediaStreamTrack] = useState() const { xs } = useSmallScreen() - const MicText = useMemo(() => { + const MicText = useMemo(() => { return xs ? "MIC" : "MICROPHONE" }, [xs]) diff --git a/playground/src/platform/pc/rtc/micSection/micSelect/index.module.scss b/playground/src/platform/pc/rtc/micSection/micSelect/index.module.scss new file mode 100644 index 00000000..8ca5088b --- /dev/null +++ b/playground/src/platform/pc/rtc/micSection/micSelect/index.module.scss @@ -0,0 +1,4 @@ +.select { + flex: 0 0 200px; + width: 200px; +} diff --git a/playground/src/platform/pc/rtc/micSection/micSelect/index.tsx b/playground/src/platform/pc/rtc/micSection/micSelect/index.tsx new file mode 100644 index 00000000..efc842b5 --- /dev/null +++ b/playground/src/platform/pc/rtc/micSection/micSelect/index.tsx @@ -0,0 +1,58 @@ +"use client" + +import AgoraRTC from "agora-rtc-sdk-ng" +import { useState, useEffect } from "react" +import { Select } from "antd" +import { IMicrophoneAudioTrack } from "agora-rtc-sdk-ng" + +import styles from "./index.module.scss" + +interface MicSelectProps { + audioTrack?: IMicrophoneAudioTrack +} + +interface SelectItem { + label: string + value: string + deviceId: string +} + +const DEFAULT_ITEM: SelectItem = { + label: "Default", + value: "default", + deviceId: "" +} + +const MicSelect = (props: MicSelectProps) => { + const { audioTrack } = props + const [items, setItems] = useState([DEFAULT_ITEM]); + const [value, setValue] = useState("default"); + + useEffect(() => { + if (audioTrack) { + const label = audioTrack?.getTrackLabel(); + setValue(label); + AgoraRTC.getMicrophones().then(arr => { + setItems(arr.map(item => ({ + label: item.label, + value: item.label, + deviceId: item.deviceId + }))); + }); + } + }, [audioTrack]); + + const onChange = async (value: string) => { + const target = items.find(item => item.value === value); + if (target) { + setValue(target.value); + if (audioTrack) { + await audioTrack.setDevice(target.deviceId); + } + } + } + + return +} + +export default MicSelect diff --git a/playground/src/platform/pc/rtc/streamPlayer/index.module.scss b/playground/src/platform/pc/rtc/streamPlayer/index.module.scss new file mode 100644 index 00000000..b1c57c10 --- /dev/null +++ b/playground/src/platform/pc/rtc/streamPlayer/index.module.scss @@ -0,0 +1,6 @@ +.streamPlayer { + position: relative; + width: 100%; + height: 100%; + overflow: hidden; +} diff --git a/playground/src/platform/pc/rtc/streamPlayer/index.tsx b/playground/src/platform/pc/rtc/streamPlayer/index.tsx new file mode 100644 index 00000000..ba78e377 --- /dev/null +++ b/playground/src/platform/pc/rtc/streamPlayer/index.tsx @@ -0,0 +1 @@ +export * from "./localStreamPlayer" diff --git a/playground/src/platform/pc/rtc/streamPlayer/localStreamPlayer.tsx b/playground/src/platform/pc/rtc/streamPlayer/localStreamPlayer.tsx new file mode 100644 index 00000000..e3e7f06a --- /dev/null +++ b/playground/src/platform/pc/rtc/streamPlayer/localStreamPlayer.tsx @@ -0,0 +1,46 @@ +"use client" + +import { + ICameraVideoTrack, + IMicrophoneAudioTrack, + IRemoteAudioTrack, + IRemoteVideoTrack, + VideoPlayerConfig, +} from "agora-rtc-sdk-ng" +import { useRef, useState, useLayoutEffect, forwardRef, useEffect, useMemo } from "react" + +import styles from "./index.module.scss" + +interface StreamPlayerProps { + videoTrack?: ICameraVideoTrack + audioTrack?: IMicrophoneAudioTrack + style?: React.CSSProperties + fit?: "cover" | "contain" | "fill" + onClick?: () => void + mute?: boolean +} + +export const LocalStreamPlayer = forwardRef((props: StreamPlayerProps, ref) => { + const { videoTrack, audioTrack, mute = false, style = {}, fit = "cover", onClick = () => { } } = props + const vidDiv = useRef(null) + + useLayoutEffect(() => { + const config = { fit } as VideoPlayerConfig + if (mute) { + videoTrack?.stop() + } else { + if (!videoTrack?.isPlaying) { + videoTrack?.play(vidDiv.current!, config) + } + } + + return () => { + videoTrack?.stop() + } + }, [videoTrack, fit, mute]) + + // local audio track need not to be played + // useLayoutEffect(() => {}, [audioTrack, localAudioMute]) + + return
+}) diff --git a/playground/src/protobuf/SttMessage001.js b/playground/src/protobuf/SttMessage001.js deleted file mode 100644 index 16c2ebe7..00000000 --- a/playground/src/protobuf/SttMessage001.js +++ /dev/null @@ -1,143 +0,0 @@ -/* eslint-disable block-scoped-var, id-length, no-control-regex, no-magic-numbers, no-prototype-builtins, no-redeclare, no-shadow, no-var, sort-vars */ -;(function (global, factory) { - /* global define, require, module */ - - /* AMD */ if (typeof define === "function" && define.amd) define(["protobufjs/light"], factory) - /* CommonJS */ else if ( - typeof require === "function" && - typeof module === "object" && - module && - module.exports - ) - module.exports = factory(require("protobufjs/light")) -})(this, function ($protobuf) { - "use strict" - - var $root = ($protobuf.roots.default || ($protobuf.roots.default = new $protobuf.Root())).addJSON( - { - Agora: { - nested: { - SpeechToText: { - options: { - objc_class_prefix: "Stt", - csharp_namespace: "AgoraSTTSample.Protobuf", - java_package: "io.agora.rtc.speech2text", - java_outer_classname: "AgoraSpeech2TextProtobuffer", - }, - nested: { - Text: { - fields: { - vendor: { - type: "int32", - id: 1, - }, - version: { - type: "int32", - id: 2, - }, - seqnum: { - type: "int32", - id: 3, - }, - uid: { - type: "int64", - id: 4, - }, - flag: { - type: "int32", - id: 5, - }, - time: { - type: "int64", - id: 6, - }, - lang: { - type: "int32", - id: 7, - }, - starttime: { - type: "int32", - id: 8, - }, - offtime: { - type: "int32", - id: 9, - }, - words: { - rule: "repeated", - type: "Word", - id: 10, - }, - endOfSegment: { - type: "bool", - id: 11, - }, - durationMs: { - type: "int32", - id: 12, - }, - dataType: { - type: "string", - id: 13, - }, - trans: { - rule: "repeated", - type: "Translation", - id: 14, - }, - culture: { - type: "string", - id: 15, - }, - }, - }, - Word: { - fields: { - text: { - type: "string", - id: 1, - }, - startMs: { - type: "int32", - id: 2, - }, - durationMs: { - type: "int32", - id: 3, - }, - isFinal: { - type: "bool", - id: 4, - }, - confidence: { - type: "double", - id: 5, - }, - }, - }, - Translation: { - fields: { - isFinal: { - type: "bool", - id: 1, - }, - lang: { - type: "string", - id: 2, - }, - texts: { - rule: "repeated", - type: "string", - id: 3, - }, - }, - }, - }, - }, - }, - }, - }, - ) - - return $root -}) diff --git a/playground/src/protobuf/SttMessage002.js b/playground/src/protobuf/SttMessage002.js deleted file mode 100644 index 4634fccc..00000000 --- a/playground/src/protobuf/SttMessage002.js +++ /dev/null @@ -1,130 +0,0 @@ -/* eslint-disable block-scoped-var, id-length, no-control-regex, no-magic-numbers, no-prototype-builtins, no-redeclare, no-shadow, no-var, sort-vars */ -import * as $protobuf from "protobufjs/light" - -const $root = ($protobuf.roots.default || ($protobuf.roots.default = new $protobuf.Root())).addJSON( - { - Agora: { - nested: { - SpeechToText: { - options: { - objc_class_prefix: "Stt", - csharp_namespace: "AgoraSTTSample.Protobuf", - java_package: "io.agora.rtc.speech2text", - java_outer_classname: "AgoraSpeech2TextProtobuffer", - }, - nested: { - Text: { - fields: { - vendor: { - type: "int32", - id: 1, - }, - version: { - type: "int32", - id: 2, - }, - seqnum: { - type: "int32", - id: 3, - }, - uid: { - type: "int64", - id: 4, - }, - flag: { - type: "int32", - id: 5, - }, - time: { - type: "int64", - id: 6, - }, - lang: { - type: "int32", - id: 7, - }, - starttime: { - type: "int32", - id: 8, - }, - offtime: { - type: "int32", - id: 9, - }, - words: { - rule: "repeated", - type: "Word", - id: 10, - }, - endOfSegment: { - type: "bool", - id: 11, - }, - durationMs: { - type: "int32", - id: 12, - }, - dataType: { - type: "string", - id: 13, - }, - trans: { - rule: "repeated", - type: "Translation", - id: 14, - }, - culture: { - type: "string", - id: 15, - }, - }, - }, - Word: { - fields: { - text: { - type: "string", - id: 1, - }, - startMs: { - type: "int32", - id: 2, - }, - durationMs: { - type: "int32", - id: 3, - }, - isFinal: { - type: "bool", - id: 4, - }, - confidence: { - type: "double", - id: 5, - }, - }, - }, - Translation: { - fields: { - isFinal: { - type: "bool", - id: 1, - }, - lang: { - type: "string", - id: 2, - }, - texts: { - rule: "repeated", - type: "string", - id: 3, - }, - }, - }, - }, - }, - }, - }, - }, -) - -export { $root as default } diff --git a/playground/src/protobuf/SttMessage003.js b/playground/src/protobuf/SttMessage003.js deleted file mode 100644 index 2afd970c..00000000 --- a/playground/src/protobuf/SttMessage003.js +++ /dev/null @@ -1,1161 +0,0 @@ -/* eslint-disable block-scoped-var, id-length, no-control-regex, no-magic-numbers, no-prototype-builtins, no-redeclare, no-shadow, no-var, sort-vars */ -import * as $protobuf from "protobufjs/minimal" - -// Common aliases -const $Reader = $protobuf.Reader -const $Writer = $protobuf.Writer -const $util = $protobuf.util - -// Exported root namespace -const $root = $protobuf.roots.default || ($protobuf.roots.default = {}) - -export const Agora = ($root.Agora = (() => { - /** - * Namespace Agora. - * @exports Agora - * @namespace - */ - const Agora = {} - - Agora.SpeechToText = (function () { - /** - * Namespace SpeechToText. - * @memberof Agora - * @namespace - */ - const SpeechToText = {} - - SpeechToText.Text = (function () { - /** - * Properties of a Text. - * @memberof Agora.SpeechToText - * @interface IText - * @property {number|null} [vendor] Text vendor - * @property {number|null} [version] Text version - * @property {number|null} [seqnum] Text seqnum - * @property {number|Long|null} [uid] Text uid - * @property {number|null} [flag] Text flag - * @property {number|Long|null} [time] Text time - * @property {number|null} [lang] Text lang - * @property {number|null} [starttime] Text starttime - * @property {number|null} [offtime] Text offtime - * @property {Array.|null} [words] Text words - * @property {boolean|null} [endOfSegment] Text endOfSegment - * @property {number|null} [durationMs] Text durationMs - * @property {string|null} [dataType] Text dataType - * @property {Array.|null} [trans] Text trans - * @property {string|null} [culture] Text culture - */ - - /** - * Constructs a new Text. - * @memberof Agora.SpeechToText - * @classdesc Represents a Text. - * @implements IText - * @constructor - * @param {Agora.SpeechToText.IText=} [properties] Properties to set - */ - function Text(properties) { - this.words = [] - this.trans = [] - if (properties) - for (let keys = Object.keys(properties), i = 0; i < keys.length; ++i) - if (properties[keys[i]] != null) this[keys[i]] = properties[keys[i]] - } - - /** - * Text vendor. - * @member {number} vendor - * @memberof Agora.SpeechToText.Text - * @instance - */ - Text.prototype.vendor = 0 - - /** - * Text version. - * @member {number} version - * @memberof Agora.SpeechToText.Text - * @instance - */ - Text.prototype.version = 0 - - /** - * Text seqnum. - * @member {number} seqnum - * @memberof Agora.SpeechToText.Text - * @instance - */ - Text.prototype.seqnum = 0 - - /** - * Text uid. - * @member {number|Long} uid - * @memberof Agora.SpeechToText.Text - * @instance - */ - Text.prototype.uid = $util.Long ? $util.Long.fromBits(0, 0, false) : 0 - - /** - * Text flag. - * @member {number} flag - * @memberof Agora.SpeechToText.Text - * @instance - */ - Text.prototype.flag = 0 - - /** - * Text time. - * @member {number|Long} time - * @memberof Agora.SpeechToText.Text - * @instance - */ - Text.prototype.time = $util.Long ? $util.Long.fromBits(0, 0, false) : 0 - - /** - * Text lang. - * @member {number} lang - * @memberof Agora.SpeechToText.Text - * @instance - */ - Text.prototype.lang = 0 - - /** - * Text starttime. - * @member {number} starttime - * @memberof Agora.SpeechToText.Text - * @instance - */ - Text.prototype.starttime = 0 - - /** - * Text offtime. - * @member {number} offtime - * @memberof Agora.SpeechToText.Text - * @instance - */ - Text.prototype.offtime = 0 - - /** - * Text words. - * @member {Array.} words - * @memberof Agora.SpeechToText.Text - * @instance - */ - Text.prototype.words = $util.emptyArray - - /** - * Text endOfSegment. - * @member {boolean} endOfSegment - * @memberof Agora.SpeechToText.Text - * @instance - */ - Text.prototype.endOfSegment = false - - /** - * Text durationMs. - * @member {number} durationMs - * @memberof Agora.SpeechToText.Text - * @instance - */ - Text.prototype.durationMs = 0 - - /** - * Text dataType. - * @member {string} dataType - * @memberof Agora.SpeechToText.Text - * @instance - */ - Text.prototype.dataType = "" - - /** - * Text trans. - * @member {Array.} trans - * @memberof Agora.SpeechToText.Text - * @instance - */ - Text.prototype.trans = $util.emptyArray - - /** - * Text culture. - * @member {string} culture - * @memberof Agora.SpeechToText.Text - * @instance - */ - Text.prototype.culture = "" - - /** - * Creates a new Text instance using the specified properties. - * @function create - * @memberof Agora.SpeechToText.Text - * @static - * @param {Agora.SpeechToText.IText=} [properties] Properties to set - * @returns {Agora.SpeechToText.Text} Text instance - */ - Text.create = function create(properties) { - return new Text(properties) - } - - /** - * Encodes the specified Text message. Does not implicitly {@link Agora.SpeechToText.Text.verify|verify} messages. - * @function encode - * @memberof Agora.SpeechToText.Text - * @static - * @param {Agora.SpeechToText.IText} message Text message or plain object to encode - * @param {$protobuf.Writer} [writer] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - Text.encode = function encode(message, writer) { - if (!writer) writer = $Writer.create() - if (message.vendor != null && Object.hasOwnProperty.call(message, "vendor")) - writer.uint32(/* id 1, wireType 0 = */ 8).int32(message.vendor) - if (message.version != null && Object.hasOwnProperty.call(message, "version")) - writer.uint32(/* id 2, wireType 0 = */ 16).int32(message.version) - if (message.seqnum != null && Object.hasOwnProperty.call(message, "seqnum")) - writer.uint32(/* id 3, wireType 0 = */ 24).int32(message.seqnum) - if (message.uid != null && Object.hasOwnProperty.call(message, "uid")) - writer.uint32(/* id 4, wireType 0 = */ 32).int64(message.uid) - if (message.flag != null && Object.hasOwnProperty.call(message, "flag")) - writer.uint32(/* id 5, wireType 0 = */ 40).int32(message.flag) - if (message.time != null && Object.hasOwnProperty.call(message, "time")) - writer.uint32(/* id 6, wireType 0 = */ 48).int64(message.time) - if (message.lang != null && Object.hasOwnProperty.call(message, "lang")) - writer.uint32(/* id 7, wireType 0 = */ 56).int32(message.lang) - if (message.starttime != null && Object.hasOwnProperty.call(message, "starttime")) - writer.uint32(/* id 8, wireType 0 = */ 64).int32(message.starttime) - if (message.offtime != null && Object.hasOwnProperty.call(message, "offtime")) - writer.uint32(/* id 9, wireType 0 = */ 72).int32(message.offtime) - if (message.words != null && message.words.length) - for (let i = 0; i < message.words.length; ++i) - $root.Agora.SpeechToText.Word.encode( - message.words[i], - writer.uint32(/* id 10, wireType 2 = */ 82).fork(), - ).ldelim() - if (message.endOfSegment != null && Object.hasOwnProperty.call(message, "endOfSegment")) - writer.uint32(/* id 11, wireType 0 = */ 88).bool(message.endOfSegment) - if (message.durationMs != null && Object.hasOwnProperty.call(message, "durationMs")) - writer.uint32(/* id 12, wireType 0 = */ 96).int32(message.durationMs) - if (message.dataType != null && Object.hasOwnProperty.call(message, "dataType")) - writer.uint32(/* id 13, wireType 2 = */ 106).string(message.dataType) - if (message.trans != null && message.trans.length) - for (let i = 0; i < message.trans.length; ++i) - $root.Agora.SpeechToText.Translation.encode( - message.trans[i], - writer.uint32(/* id 14, wireType 2 = */ 114).fork(), - ).ldelim() - if (message.culture != null && Object.hasOwnProperty.call(message, "culture")) - writer.uint32(/* id 15, wireType 2 = */ 122).string(message.culture) - return writer - } - - /** - * Encodes the specified Text message, length delimited. Does not implicitly {@link Agora.SpeechToText.Text.verify|verify} messages. - * @function encodeDelimited - * @memberof Agora.SpeechToText.Text - * @static - * @param {Agora.SpeechToText.IText} message Text message or plain object to encode - * @param {$protobuf.Writer} [writer] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - Text.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim() - } - - /** - * Decodes a Text message from the specified reader or buffer. - * @function decode - * @memberof Agora.SpeechToText.Text - * @static - * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from - * @param {number} [length] Message length if known beforehand - * @returns {Agora.SpeechToText.Text} Text - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - Text.decode = function decode(reader, length) { - if (!(reader instanceof $Reader)) reader = $Reader.create(reader) - const end = length === undefined ? reader.len : reader.pos + length - const message = new $root.Agora.SpeechToText.Text() - while (reader.pos < end) { - const tag = reader.uint32() - switch (tag >>> 3) { - case 1: { - message.vendor = reader.int32() - break - } - case 2: { - message.version = reader.int32() - break - } - case 3: { - message.seqnum = reader.int32() - break - } - case 4: { - message.uid = reader.int64() - break - } - case 5: { - message.flag = reader.int32() - break - } - case 6: { - message.time = reader.int64() - break - } - case 7: { - message.lang = reader.int32() - break - } - case 8: { - message.starttime = reader.int32() - break - } - case 9: { - message.offtime = reader.int32() - break - } - case 10: { - if (!(message.words && message.words.length)) message.words = [] - message.words.push($root.Agora.SpeechToText.Word.decode(reader, reader.uint32())) - break - } - case 11: { - message.endOfSegment = reader.bool() - break - } - case 12: { - message.durationMs = reader.int32() - break - } - case 13: { - message.dataType = reader.string() - break - } - case 14: { - if (!(message.trans && message.trans.length)) message.trans = [] - message.trans.push( - $root.Agora.SpeechToText.Translation.decode(reader, reader.uint32()), - ) - break - } - case 15: { - message.culture = reader.string() - break - } - default: - reader.skipType(tag & 7) - break - } - } - return message - } - - /** - * Decodes a Text message from the specified reader or buffer, length delimited. - * @function decodeDelimited - * @memberof Agora.SpeechToText.Text - * @static - * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from - * @returns {Agora.SpeechToText.Text} Text - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - Text.decodeDelimited = function decodeDelimited(reader) { - if (!(reader instanceof $Reader)) reader = new $Reader(reader) - return this.decode(reader, reader.uint32()) - } - - /** - * Verifies a Text message. - * @function verify - * @memberof Agora.SpeechToText.Text - * @static - * @param {Object.} message Plain object to verify - * @returns {string|null} `null` if valid, otherwise the reason why it is not - */ - Text.verify = function verify(message) { - if (typeof message !== "object" || message === null) return "object expected" - if (message.vendor != null && message.hasOwnProperty("vendor")) - if (!$util.isInteger(message.vendor)) return "vendor: integer expected" - if (message.version != null && message.hasOwnProperty("version")) - if (!$util.isInteger(message.version)) return "version: integer expected" - if (message.seqnum != null && message.hasOwnProperty("seqnum")) - if (!$util.isInteger(message.seqnum)) return "seqnum: integer expected" - if (message.uid != null && message.hasOwnProperty("uid")) - if ( - !$util.isInteger(message.uid) && - !(message.uid && $util.isInteger(message.uid.low) && $util.isInteger(message.uid.high)) - ) - return "uid: integer|Long expected" - if (message.flag != null && message.hasOwnProperty("flag")) - if (!$util.isInteger(message.flag)) return "flag: integer expected" - if (message.time != null && message.hasOwnProperty("time")) - if ( - !$util.isInteger(message.time) && - !( - message.time && - $util.isInteger(message.time.low) && - $util.isInteger(message.time.high) - ) - ) - return "time: integer|Long expected" - if (message.lang != null && message.hasOwnProperty("lang")) - if (!$util.isInteger(message.lang)) return "lang: integer expected" - if (message.starttime != null && message.hasOwnProperty("starttime")) - if (!$util.isInteger(message.starttime)) return "starttime: integer expected" - if (message.offtime != null && message.hasOwnProperty("offtime")) - if (!$util.isInteger(message.offtime)) return "offtime: integer expected" - if (message.words != null && message.hasOwnProperty("words")) { - if (!Array.isArray(message.words)) return "words: array expected" - for (let i = 0; i < message.words.length; ++i) { - const error = $root.Agora.SpeechToText.Word.verify(message.words[i]) - if (error) return "words." + error - } - } - if (message.endOfSegment != null && message.hasOwnProperty("endOfSegment")) - if (typeof message.endOfSegment !== "boolean") return "endOfSegment: boolean expected" - if (message.durationMs != null && message.hasOwnProperty("durationMs")) - if (!$util.isInteger(message.durationMs)) return "durationMs: integer expected" - if (message.dataType != null && message.hasOwnProperty("dataType")) - if (!$util.isString(message.dataType)) return "dataType: string expected" - if (message.trans != null && message.hasOwnProperty("trans")) { - if (!Array.isArray(message.trans)) return "trans: array expected" - for (let i = 0; i < message.trans.length; ++i) { - const error = $root.Agora.SpeechToText.Translation.verify(message.trans[i]) - if (error) return "trans." + error - } - } - if (message.culture != null && message.hasOwnProperty("culture")) - if (!$util.isString(message.culture)) return "culture: string expected" - return null - } - - /** - * Creates a Text message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof Agora.SpeechToText.Text - * @static - * @param {Object.} object Plain object - * @returns {Agora.SpeechToText.Text} Text - */ - Text.fromObject = function fromObject(object) { - if (object instanceof $root.Agora.SpeechToText.Text) return object - const message = new $root.Agora.SpeechToText.Text() - if (object.vendor != null) message.vendor = object.vendor | 0 - if (object.version != null) message.version = object.version | 0 - if (object.seqnum != null) message.seqnum = object.seqnum | 0 - if (object.uid != null) - if ($util.Long) (message.uid = $util.Long.fromValue(object.uid)).unsigned = false - else if (typeof object.uid === "string") message.uid = parseInt(object.uid, 10) - else if (typeof object.uid === "number") message.uid = object.uid - else if (typeof object.uid === "object") - message.uid = new $util.LongBits(object.uid.low >>> 0, object.uid.high >>> 0).toNumber() - if (object.flag != null) message.flag = object.flag | 0 - if (object.time != null) - if ($util.Long) (message.time = $util.Long.fromValue(object.time)).unsigned = false - else if (typeof object.time === "string") message.time = parseInt(object.time, 10) - else if (typeof object.time === "number") message.time = object.time - else if (typeof object.time === "object") - message.time = new $util.LongBits( - object.time.low >>> 0, - object.time.high >>> 0, - ).toNumber() - if (object.lang != null) message.lang = object.lang | 0 - if (object.starttime != null) message.starttime = object.starttime | 0 - if (object.offtime != null) message.offtime = object.offtime | 0 - if (object.words) { - if (!Array.isArray(object.words)) - throw TypeError(".Agora.SpeechToText.Text.words: array expected") - message.words = [] - for (let i = 0; i < object.words.length; ++i) { - if (typeof object.words[i] !== "object") - throw TypeError(".Agora.SpeechToText.Text.words: object expected") - message.words[i] = $root.Agora.SpeechToText.Word.fromObject(object.words[i]) - } - } - if (object.endOfSegment != null) message.endOfSegment = Boolean(object.endOfSegment) - if (object.durationMs != null) message.durationMs = object.durationMs | 0 - if (object.dataType != null) message.dataType = String(object.dataType) - if (object.trans) { - if (!Array.isArray(object.trans)) - throw TypeError(".Agora.SpeechToText.Text.trans: array expected") - message.trans = [] - for (let i = 0; i < object.trans.length; ++i) { - if (typeof object.trans[i] !== "object") - throw TypeError(".Agora.SpeechToText.Text.trans: object expected") - message.trans[i] = $root.Agora.SpeechToText.Translation.fromObject(object.trans[i]) - } - } - if (object.culture != null) message.culture = String(object.culture) - return message - } - - /** - * Creates a plain object from a Text message. Also converts values to other types if specified. - * @function toObject - * @memberof Agora.SpeechToText.Text - * @static - * @param {Agora.SpeechToText.Text} message Text - * @param {$protobuf.IConversionOptions} [options] Conversion options - * @returns {Object.} Plain object - */ - Text.toObject = function toObject(message, options) { - if (!options) options = {} - const object = {} - if (options.arrays || options.defaults) { - object.words = [] - object.trans = [] - } - if (options.defaults) { - object.vendor = 0 - object.version = 0 - object.seqnum = 0 - if ($util.Long) { - const long = new $util.Long(0, 0, false) - object.uid = - options.longs === String - ? long.toString() - : options.longs === Number - ? long.toNumber() - : long - } else object.uid = options.longs === String ? "0" : 0 - object.flag = 0 - if ($util.Long) { - const long = new $util.Long(0, 0, false) - object.time = - options.longs === String - ? long.toString() - : options.longs === Number - ? long.toNumber() - : long - } else object.time = options.longs === String ? "0" : 0 - object.lang = 0 - object.starttime = 0 - object.offtime = 0 - object.endOfSegment = false - object.durationMs = 0 - object.dataType = "" - object.culture = "" - } - if (message.vendor != null && message.hasOwnProperty("vendor")) - object.vendor = message.vendor - if (message.version != null && message.hasOwnProperty("version")) - object.version = message.version - if (message.seqnum != null && message.hasOwnProperty("seqnum")) - object.seqnum = message.seqnum - if (message.uid != null && message.hasOwnProperty("uid")) - if (typeof message.uid === "number") - object.uid = options.longs === String ? String(message.uid) : message.uid - else - object.uid = - options.longs === String - ? $util.Long.prototype.toString.call(message.uid) - : options.longs === Number - ? new $util.LongBits(message.uid.low >>> 0, message.uid.high >>> 0).toNumber() - : message.uid - if (message.flag != null && message.hasOwnProperty("flag")) object.flag = message.flag - if (message.time != null && message.hasOwnProperty("time")) - if (typeof message.time === "number") - object.time = options.longs === String ? String(message.time) : message.time - else - object.time = - options.longs === String - ? $util.Long.prototype.toString.call(message.time) - : options.longs === Number - ? new $util.LongBits(message.time.low >>> 0, message.time.high >>> 0).toNumber() - : message.time - if (message.lang != null && message.hasOwnProperty("lang")) object.lang = message.lang - if (message.starttime != null && message.hasOwnProperty("starttime")) - object.starttime = message.starttime - if (message.offtime != null && message.hasOwnProperty("offtime")) - object.offtime = message.offtime - if (message.words && message.words.length) { - object.words = [] - for (let j = 0; j < message.words.length; ++j) - object.words[j] = $root.Agora.SpeechToText.Word.toObject(message.words[j], options) - } - if (message.endOfSegment != null && message.hasOwnProperty("endOfSegment")) - object.endOfSegment = message.endOfSegment - if (message.durationMs != null && message.hasOwnProperty("durationMs")) - object.durationMs = message.durationMs - if (message.dataType != null && message.hasOwnProperty("dataType")) - object.dataType = message.dataType - if (message.trans && message.trans.length) { - object.trans = [] - for (let j = 0; j < message.trans.length; ++j) - object.trans[j] = $root.Agora.SpeechToText.Translation.toObject( - message.trans[j], - options, - ) - } - if (message.culture != null && message.hasOwnProperty("culture")) - object.culture = message.culture - return object - } - - /** - * Converts this Text to JSON. - * @function toJSON - * @memberof Agora.SpeechToText.Text - * @instance - * @returns {Object.} JSON object - */ - Text.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions) - } - - /** - * Gets the default type url for Text - * @function getTypeUrl - * @memberof Agora.SpeechToText.Text - * @static - * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") - * @returns {string} The default type url - */ - Text.getTypeUrl = function getTypeUrl(typeUrlPrefix) { - if (typeUrlPrefix === undefined) { - typeUrlPrefix = "type.googleapis.com" - } - return typeUrlPrefix + "/Agora.SpeechToText.Text" - } - - return Text - })() - - SpeechToText.Word = (function () { - /** - * Properties of a Word. - * @memberof Agora.SpeechToText - * @interface IWord - * @property {string|null} [text] Word text - * @property {number|null} [startMs] Word startMs - * @property {number|null} [durationMs] Word durationMs - * @property {boolean|null} [isFinal] Word isFinal - * @property {number|null} [confidence] Word confidence - */ - - /** - * Constructs a new Word. - * @memberof Agora.SpeechToText - * @classdesc Represents a Word. - * @implements IWord - * @constructor - * @param {Agora.SpeechToText.IWord=} [properties] Properties to set - */ - function Word(properties) { - if (properties) - for (let keys = Object.keys(properties), i = 0; i < keys.length; ++i) - if (properties[keys[i]] != null) this[keys[i]] = properties[keys[i]] - } - - /** - * Word text. - * @member {string} text - * @memberof Agora.SpeechToText.Word - * @instance - */ - Word.prototype.text = "" - - /** - * Word startMs. - * @member {number} startMs - * @memberof Agora.SpeechToText.Word - * @instance - */ - Word.prototype.startMs = 0 - - /** - * Word durationMs. - * @member {number} durationMs - * @memberof Agora.SpeechToText.Word - * @instance - */ - Word.prototype.durationMs = 0 - - /** - * Word isFinal. - * @member {boolean} isFinal - * @memberof Agora.SpeechToText.Word - * @instance - */ - Word.prototype.isFinal = false - - /** - * Word confidence. - * @member {number} confidence - * @memberof Agora.SpeechToText.Word - * @instance - */ - Word.prototype.confidence = 0 - - /** - * Creates a new Word instance using the specified properties. - * @function create - * @memberof Agora.SpeechToText.Word - * @static - * @param {Agora.SpeechToText.IWord=} [properties] Properties to set - * @returns {Agora.SpeechToText.Word} Word instance - */ - Word.create = function create(properties) { - return new Word(properties) - } - - /** - * Encodes the specified Word message. Does not implicitly {@link Agora.SpeechToText.Word.verify|verify} messages. - * @function encode - * @memberof Agora.SpeechToText.Word - * @static - * @param {Agora.SpeechToText.IWord} message Word message or plain object to encode - * @param {$protobuf.Writer} [writer] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - Word.encode = function encode(message, writer) { - if (!writer) writer = $Writer.create() - if (message.text != null && Object.hasOwnProperty.call(message, "text")) - writer.uint32(/* id 1, wireType 2 = */ 10).string(message.text) - if (message.startMs != null && Object.hasOwnProperty.call(message, "startMs")) - writer.uint32(/* id 2, wireType 0 = */ 16).int32(message.startMs) - if (message.durationMs != null && Object.hasOwnProperty.call(message, "durationMs")) - writer.uint32(/* id 3, wireType 0 = */ 24).int32(message.durationMs) - if (message.isFinal != null && Object.hasOwnProperty.call(message, "isFinal")) - writer.uint32(/* id 4, wireType 0 = */ 32).bool(message.isFinal) - if (message.confidence != null && Object.hasOwnProperty.call(message, "confidence")) - writer.uint32(/* id 5, wireType 1 = */ 41).double(message.confidence) - return writer - } - - /** - * Encodes the specified Word message, length delimited. Does not implicitly {@link Agora.SpeechToText.Word.verify|verify} messages. - * @function encodeDelimited - * @memberof Agora.SpeechToText.Word - * @static - * @param {Agora.SpeechToText.IWord} message Word message or plain object to encode - * @param {$protobuf.Writer} [writer] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - Word.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim() - } - - /** - * Decodes a Word message from the specified reader or buffer. - * @function decode - * @memberof Agora.SpeechToText.Word - * @static - * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from - * @param {number} [length] Message length if known beforehand - * @returns {Agora.SpeechToText.Word} Word - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - Word.decode = function decode(reader, length) { - if (!(reader instanceof $Reader)) reader = $Reader.create(reader) - const end = length === undefined ? reader.len : reader.pos + length - const message = new $root.Agora.SpeechToText.Word() - while (reader.pos < end) { - const tag = reader.uint32() - switch (tag >>> 3) { - case 1: { - message.text = reader.string() - break - } - case 2: { - message.startMs = reader.int32() - break - } - case 3: { - message.durationMs = reader.int32() - break - } - case 4: { - message.isFinal = reader.bool() - break - } - case 5: { - message.confidence = reader.double() - break - } - default: - reader.skipType(tag & 7) - break - } - } - return message - } - - /** - * Decodes a Word message from the specified reader or buffer, length delimited. - * @function decodeDelimited - * @memberof Agora.SpeechToText.Word - * @static - * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from - * @returns {Agora.SpeechToText.Word} Word - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - Word.decodeDelimited = function decodeDelimited(reader) { - if (!(reader instanceof $Reader)) reader = new $Reader(reader) - return this.decode(reader, reader.uint32()) - } - - /** - * Verifies a Word message. - * @function verify - * @memberof Agora.SpeechToText.Word - * @static - * @param {Object.} message Plain object to verify - * @returns {string|null} `null` if valid, otherwise the reason why it is not - */ - Word.verify = function verify(message) { - if (typeof message !== "object" || message === null) return "object expected" - if (message.text != null && message.hasOwnProperty("text")) - if (!$util.isString(message.text)) return "text: string expected" - if (message.startMs != null && message.hasOwnProperty("startMs")) - if (!$util.isInteger(message.startMs)) return "startMs: integer expected" - if (message.durationMs != null && message.hasOwnProperty("durationMs")) - if (!$util.isInteger(message.durationMs)) return "durationMs: integer expected" - if (message.isFinal != null && message.hasOwnProperty("isFinal")) - if (typeof message.isFinal !== "boolean") return "isFinal: boolean expected" - if (message.confidence != null && message.hasOwnProperty("confidence")) - if (typeof message.confidence !== "number") return "confidence: number expected" - return null - } - - /** - * Creates a Word message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof Agora.SpeechToText.Word - * @static - * @param {Object.} object Plain object - * @returns {Agora.SpeechToText.Word} Word - */ - Word.fromObject = function fromObject(object) { - if (object instanceof $root.Agora.SpeechToText.Word) return object - const message = new $root.Agora.SpeechToText.Word() - if (object.text != null) message.text = String(object.text) - if (object.startMs != null) message.startMs = object.startMs | 0 - if (object.durationMs != null) message.durationMs = object.durationMs | 0 - if (object.isFinal != null) message.isFinal = Boolean(object.isFinal) - if (object.confidence != null) message.confidence = Number(object.confidence) - return message - } - - /** - * Creates a plain object from a Word message. Also converts values to other types if specified. - * @function toObject - * @memberof Agora.SpeechToText.Word - * @static - * @param {Agora.SpeechToText.Word} message Word - * @param {$protobuf.IConversionOptions} [options] Conversion options - * @returns {Object.} Plain object - */ - Word.toObject = function toObject(message, options) { - if (!options) options = {} - const object = {} - if (options.defaults) { - object.text = "" - object.startMs = 0 - object.durationMs = 0 - object.isFinal = false - object.confidence = 0 - } - if (message.text != null && message.hasOwnProperty("text")) object.text = message.text - if (message.startMs != null && message.hasOwnProperty("startMs")) - object.startMs = message.startMs - if (message.durationMs != null && message.hasOwnProperty("durationMs")) - object.durationMs = message.durationMs - if (message.isFinal != null && message.hasOwnProperty("isFinal")) - object.isFinal = message.isFinal - if (message.confidence != null && message.hasOwnProperty("confidence")) - object.confidence = - options.json && !isFinite(message.confidence) - ? String(message.confidence) - : message.confidence - return object - } - - /** - * Converts this Word to JSON. - * @function toJSON - * @memberof Agora.SpeechToText.Word - * @instance - * @returns {Object.} JSON object - */ - Word.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions) - } - - /** - * Gets the default type url for Word - * @function getTypeUrl - * @memberof Agora.SpeechToText.Word - * @static - * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") - * @returns {string} The default type url - */ - Word.getTypeUrl = function getTypeUrl(typeUrlPrefix) { - if (typeUrlPrefix === undefined) { - typeUrlPrefix = "type.googleapis.com" - } - return typeUrlPrefix + "/Agora.SpeechToText.Word" - } - - return Word - })() - - SpeechToText.Translation = (function () { - /** - * Properties of a Translation. - * @memberof Agora.SpeechToText - * @interface ITranslation - * @property {boolean|null} [isFinal] Translation isFinal - * @property {string|null} [lang] Translation lang - * @property {Array.|null} [texts] Translation texts - */ - - /** - * Constructs a new Translation. - * @memberof Agora.SpeechToText - * @classdesc Represents a Translation. - * @implements ITranslation - * @constructor - * @param {Agora.SpeechToText.ITranslation=} [properties] Properties to set - */ - function Translation(properties) { - this.texts = [] - if (properties) - for (let keys = Object.keys(properties), i = 0; i < keys.length; ++i) - if (properties[keys[i]] != null) this[keys[i]] = properties[keys[i]] - } - - /** - * Translation isFinal. - * @member {boolean} isFinal - * @memberof Agora.SpeechToText.Translation - * @instance - */ - Translation.prototype.isFinal = false - - /** - * Translation lang. - * @member {string} lang - * @memberof Agora.SpeechToText.Translation - * @instance - */ - Translation.prototype.lang = "" - - /** - * Translation texts. - * @member {Array.} texts - * @memberof Agora.SpeechToText.Translation - * @instance - */ - Translation.prototype.texts = $util.emptyArray - - /** - * Creates a new Translation instance using the specified properties. - * @function create - * @memberof Agora.SpeechToText.Translation - * @static - * @param {Agora.SpeechToText.ITranslation=} [properties] Properties to set - * @returns {Agora.SpeechToText.Translation} Translation instance - */ - Translation.create = function create(properties) { - return new Translation(properties) - } - - /** - * Encodes the specified Translation message. Does not implicitly {@link Agora.SpeechToText.Translation.verify|verify} messages. - * @function encode - * @memberof Agora.SpeechToText.Translation - * @static - * @param {Agora.SpeechToText.ITranslation} message Translation message or plain object to encode - * @param {$protobuf.Writer} [writer] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - Translation.encode = function encode(message, writer) { - if (!writer) writer = $Writer.create() - if (message.isFinal != null && Object.hasOwnProperty.call(message, "isFinal")) - writer.uint32(/* id 1, wireType 0 = */ 8).bool(message.isFinal) - if (message.lang != null && Object.hasOwnProperty.call(message, "lang")) - writer.uint32(/* id 2, wireType 2 = */ 18).string(message.lang) - if (message.texts != null && message.texts.length) - for (let i = 0; i < message.texts.length; ++i) - writer.uint32(/* id 3, wireType 2 = */ 26).string(message.texts[i]) - return writer - } - - /** - * Encodes the specified Translation message, length delimited. Does not implicitly {@link Agora.SpeechToText.Translation.verify|verify} messages. - * @function encodeDelimited - * @memberof Agora.SpeechToText.Translation - * @static - * @param {Agora.SpeechToText.ITranslation} message Translation message or plain object to encode - * @param {$protobuf.Writer} [writer] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - Translation.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim() - } - - /** - * Decodes a Translation message from the specified reader or buffer. - * @function decode - * @memberof Agora.SpeechToText.Translation - * @static - * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from - * @param {number} [length] Message length if known beforehand - * @returns {Agora.SpeechToText.Translation} Translation - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - Translation.decode = function decode(reader, length) { - if (!(reader instanceof $Reader)) reader = $Reader.create(reader) - const end = length === undefined ? reader.len : reader.pos + length - const message = new $root.Agora.SpeechToText.Translation() - while (reader.pos < end) { - const tag = reader.uint32() - switch (tag >>> 3) { - case 1: { - message.isFinal = reader.bool() - break - } - case 2: { - message.lang = reader.string() - break - } - case 3: { - if (!(message.texts && message.texts.length)) message.texts = [] - message.texts.push(reader.string()) - break - } - default: - reader.skipType(tag & 7) - break - } - } - return message - } - - /** - * Decodes a Translation message from the specified reader or buffer, length delimited. - * @function decodeDelimited - * @memberof Agora.SpeechToText.Translation - * @static - * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from - * @returns {Agora.SpeechToText.Translation} Translation - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - Translation.decodeDelimited = function decodeDelimited(reader) { - if (!(reader instanceof $Reader)) reader = new $Reader(reader) - return this.decode(reader, reader.uint32()) - } - - /** - * Verifies a Translation message. - * @function verify - * @memberof Agora.SpeechToText.Translation - * @static - * @param {Object.} message Plain object to verify - * @returns {string|null} `null` if valid, otherwise the reason why it is not - */ - Translation.verify = function verify(message) { - if (typeof message !== "object" || message === null) return "object expected" - if (message.isFinal != null && message.hasOwnProperty("isFinal")) - if (typeof message.isFinal !== "boolean") return "isFinal: boolean expected" - if (message.lang != null && message.hasOwnProperty("lang")) - if (!$util.isString(message.lang)) return "lang: string expected" - if (message.texts != null && message.hasOwnProperty("texts")) { - if (!Array.isArray(message.texts)) return "texts: array expected" - for (let i = 0; i < message.texts.length; ++i) - if (!$util.isString(message.texts[i])) return "texts: string[] expected" - } - return null - } - - /** - * Creates a Translation message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof Agora.SpeechToText.Translation - * @static - * @param {Object.} object Plain object - * @returns {Agora.SpeechToText.Translation} Translation - */ - Translation.fromObject = function fromObject(object) { - if (object instanceof $root.Agora.SpeechToText.Translation) return object - const message = new $root.Agora.SpeechToText.Translation() - if (object.isFinal != null) message.isFinal = Boolean(object.isFinal) - if (object.lang != null) message.lang = String(object.lang) - if (object.texts) { - if (!Array.isArray(object.texts)) - throw TypeError(".Agora.SpeechToText.Translation.texts: array expected") - message.texts = [] - for (let i = 0; i < object.texts.length; ++i) message.texts[i] = String(object.texts[i]) - } - return message - } - - /** - * Creates a plain object from a Translation message. Also converts values to other types if specified. - * @function toObject - * @memberof Agora.SpeechToText.Translation - * @static - * @param {Agora.SpeechToText.Translation} message Translation - * @param {$protobuf.IConversionOptions} [options] Conversion options - * @returns {Object.} Plain object - */ - Translation.toObject = function toObject(message, options) { - if (!options) options = {} - const object = {} - if (options.arrays || options.defaults) object.texts = [] - if (options.defaults) { - object.isFinal = false - object.lang = "" - } - if (message.isFinal != null && message.hasOwnProperty("isFinal")) - object.isFinal = message.isFinal - if (message.lang != null && message.hasOwnProperty("lang")) object.lang = message.lang - if (message.texts && message.texts.length) { - object.texts = [] - for (let j = 0; j < message.texts.length; ++j) object.texts[j] = message.texts[j] - } - return object - } - - /** - * Converts this Translation to JSON. - * @function toJSON - * @memberof Agora.SpeechToText.Translation - * @instance - * @returns {Object.} JSON object - */ - Translation.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions) - } - - /** - * Gets the default type url for Translation - * @function getTypeUrl - * @memberof Agora.SpeechToText.Translation - * @static - * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") - * @returns {string} The default type url - */ - Translation.getTypeUrl = function getTypeUrl(typeUrlPrefix) { - if (typeUrlPrefix === undefined) { - typeUrlPrefix = "type.googleapis.com" - } - return typeUrlPrefix + "/Agora.SpeechToText.Translation" - } - - return Translation - })() - - return SpeechToText - })() - - return Agora -})()) - -export { $root as default } diff --git a/playground/src/store/reducers/global.ts b/playground/src/store/reducers/global.ts index 377ece81..995b22d1 100644 --- a/playground/src/store/reducers/global.ts +++ b/playground/src/store/reducers/global.ts @@ -1,4 +1,4 @@ -import { IOptions, IChatItem } from "@/types" +import { IOptions, IChatItem, Language, VoiceType } from "@/types" import { createSlice, PayloadAction } from "@reduxjs/toolkit" import { DEFAULT_OPTIONS, COLOR_LIST, setOptionsToLocal, genRandomChatList } from "@/common" @@ -6,7 +6,9 @@ export interface InitialState { options: IOptions roomConnected: boolean, agentConnected: boolean, - themeColor: string + themeColor: string, + language: Language + voiceType: VoiceType chatItems: IChatItem[] } @@ -16,6 +18,8 @@ const getInitialState = (): InitialState => { themeColor: COLOR_LIST[0].active, roomConnected: false, agentConnected: false, + language: "en-US", + voiceType: "female", chatItems: [] } } @@ -75,6 +79,12 @@ export const globalSlice = createSlice({ setAgentConnected: (state, action: PayloadAction) => { state.agentConnected = action.payload }, + setLanguage: (state, action: PayloadAction) => { + state.language = action.payload + }, + setVoiceType: (state, action: PayloadAction) => { + state.voiceType = action.payload + }, reset: (state) => { Object.assign(state, getInitialState()) document.documentElement.style.setProperty('--theme-color', COLOR_LIST[0].active); @@ -82,7 +92,9 @@ export const globalSlice = createSlice({ }, }) -export const { reset, setOptions, setRoomConnected, setAgentConnected, addChatItem, setThemeColor } = +export const { reset, setOptions, + setRoomConnected, setAgentConnected, setVoiceType, + addChatItem, setThemeColor, setLanguage } = globalSlice.actions export default globalSlice.reducer diff --git a/playground/src/types/index.ts b/playground/src/types/index.ts index a42188d7..1f695c39 100644 --- a/playground/src/types/index.ts +++ b/playground/src/types/index.ts @@ -1,3 +1,6 @@ +export type Language = "en-US" | "zh-CN" +export type VoiceType = "male" | "female" + export interface ColorItem { active: string, default: string @@ -30,3 +33,27 @@ export interface ITextItem { isFinal: boolean } + +export interface LanguageOptionItem { + label: string + value: Language +} + + +export interface VoiceOptionItem { + label: string + value: VoiceType +} + + +export interface OptionType { + value: string; + label: string; +} + + +export interface IPdfData { + fileName: string, + collection: string +} +