diff --git a/.eslintrc b/.eslintrc index e5cee25..ce60216 100644 --- a/.eslintrc +++ b/.eslintrc @@ -43,7 +43,7 @@ "rules": { "prettier/prettier": ["warn", { "trailingComma": "es5", "printWidth": 200 }], "linebreak-style": ["error", "unix"], - "no-console": ["error", { "allow": ["warn", "error", "log"] }], + "no-console": ["warn", { "allow": ["warn", "error", "log"] }], "no-case-declarations": 0, "no-param-reassign": 0, "no-underscore-dangle": 0, diff --git a/LICENSE b/LICENSE index 6898b5e..52b41e4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. +Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. diff --git a/README.md b/README.md index a267902..68631c1 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,19 @@ # 交互式AIGC场景 AIGC Demo ## 简介 -在 AIGC 对话场景下,火山引擎 AIGC-RTC Server 云端服务,通过整合 RTC 音视频流处理,ASR 语音识别,大模型接口调用集成,以及 TTS 语音生成等能力,提供基于流式语音的端到端AIGC能力链路。 -用户只需调用基于标准的 OpenAPI 接口即可配置所需的 ASR、LLM、TTS 类型和参数。火山引擎云端计算服务负责边缘用户接入、云端资源调度、音视频流压缩、文本与语音转换处理以及数据订阅传输等环节。简化开发流程,让开发者更专注在对大模型核心能力的训练及调试,从而快速推进AIGC产品应用创新。 - -同时火山引擎 RTC拥有成熟的音频 3A 处理、视频处理等技术以及大规模音视频聊天能力,可支持 AIGC 产品更便捷的支持多模态交互、多人互动等场景能力,保持交互的自然性和高效性。 +- 在 AIGC 对话场景下,火山引擎 AIGC-RTC Server 云端服务,通过整合 RTC 音视频流处理,ASR 语音识别,大模型接口调用集成,以及 TTS 语音生成等能力,提供基于流式语音的端到端AIGC能力链路。 +- 用户只需调用基于标准的 OpenAPI 接口即可配置所需的 ASR、LLM、TTS 类型和参数。火山引擎云端计算服务负责边缘用户接入、云端资源调度、音视频流压缩、文本与语音转换处理以及数据订阅传输等环节。简化开发流程,让开发者更专注在对大模型核心能力的训练及调试,从而快速推进AIGC产品应用创新。 +- 同时火山引擎 RTC拥有成熟的音频 3A 处理、视频处理等技术以及大规模音视频聊天能力,可支持 AIGC 产品更便捷的支持多模态交互、多人互动等场景能力,保持交互的自然性和高效性。 # 快速开始 -## 环境准备 +## 【必看】环境准备 - Node 版本: 16.0+ -- 需要准备两个 Terminal,分别启动服务端、前端页面。 -- **根据你自定义的 -RoomId、UserId 以及申请的 AppID、BusinessID(如有)、Token、ASR AppID、TTS AppID,修改 `src/config/index.ts` 文件中的配置信息**。 -- 使用账号的 [AK、SK](https://console.volcengine.com/iam/keymanage)、[SessionToken](https://www.volcengine.com/docs/6348/1315561#sub)(临时token, 子账号才需要), 修改 `Server/app.js` 文件中的 `ACCOUNT_INFO`。** -- 如果您已经自己完成了服务端的逻辑,可以修改前端代码文件 `src/config/index.ts` 中的 `AIGC_PROXY_HOST` 修改请求的域名,并在 `src/app/api.ts` 中修改接口的参数配置。 -- **您需要在 [火山方舟-在线推理](https://console.volcengine.com/ark/region:ark+cn-beijing/endpoint?config=%7B%7D) 中创建接入点, 并将模型对应的接入点 ID 填入 `src/config/config.ts` 文件中的 `ARK_V3_MODEL_ID`**。 +1. 需要准备两个 Terminal,分别启动服务端、前端页面。 +2. **根据你自定义的 +RoomId、UserId 以及申请的 AppID、BusinessID(如有)、Token、ASR AppID、TTS AppID,修改 `src/config/config.ts` 文件中 `ConfigFactory` 中 `BaseConfig` 的配置信息**。 +3. 使用火山引擎控制台账号的 [AK、SK](https://console.volcengine.com/iam/keymanage)、[SessionToken](https://www.volcengine.com/docs/6348/1315561#sub)(临时token, 子账号才需要), 修改 `Server/app.js` 文件中的 `ACCOUNT_INFO`。 +4. 您需要在 [火山方舟-在线推理](https://console.volcengine.com/ark/region:ark+cn-beijing/endpoint?config=%7B%7D) 中创建接入点, 并将模型对应的接入点 ID 填入 `src/config/common.ts` 文件中的 `ARK_V3_MODEL_ID`, 否则无法正常启动智能体。 +5. 如果您已经自行完成了服务端的逻辑,可以不依赖 Demo 中的 Server,直接修改前端代码文件 `src/config/index.ts` 中的 `AIGC_PROXY_HOST` 请求域名和接口,并在 `src/app/api.ts` 中修改接口的参数配置 `APIS_CONFIG`。 ## 服务端 进到项目根目录 @@ -39,6 +38,16 @@ yarn yarn dev ``` +## 常见问题 +| 问题 | 解决方案 | +| :-- | :-- | +| `Server/app.js` 中的 `sessionToken` 是什么,该怎么填,为什么要填 | `sessionToken` 是火山引擎子账号发起 OpenAPI 请求时所必须携带的临时 Token,获取方式可参考 [此文章末尾](https://www.volcengine.com/docs/6348/1315561)。 | +| 不清楚什么是主账号,什么是子账号 | 可以参考[官方概念](https://www.volcengine.com/docs/6257/64963?hyperlink_open_type=lark.open_in_browser) 。| +| 启动智能体之后, 说话为未回应 |
  • 可能是因为参数传递的有问题, 例如参数大小写、类型等问题,请再次确认下这类型问题是否存在。
  • 另一方面,可能是因为控制台中相关权限没有正常授予,请参考[流程](https://www.volcengine.com/docs/6348/1315561)再次确认下是否完成相关操作。
  • 相关资源可能未开通或者用量不足,请再次确认。
  • | +| 为什么我的麦克风正常、摄像头也正常,但是设备没有正常工作? | 可能是设备权限未授予,详情可参考 [Web 排查设备权限获取失败问题](https://www.volcengine.com/docs/6348/1356355) | + +如果有上述以外的问题,欢迎联系我们反馈。 + ## 相关文档 - [场景介绍](https://www.volcengine.com/docs/6348/1310537) - [Demo 体验](https://www.volcengine.com/docs/6348/1310559) diff --git a/Server/app.js b/Server/app.js index 59b9d80..42fbc46 100644 --- a/Server/app.js +++ b/Server/app.js @@ -1,5 +1,5 @@ /** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ @@ -7,6 +7,7 @@ const Koa = require('koa'); const bodyParser = require('koa-bodyparser'); const cors = require('koa2-cors'); const { Signer } = require('@volcengine/openapi'); +const fetch = require('node-fetch'); const app = new Koa(); @@ -28,9 +29,8 @@ const ACCOUNT_INFO = { secretKey: 'Your SK', /** * @notes 非必填, 主账号无须传入, 子账号须传, 获取方式可参考 - * https://www.volcengine.com/docs/6348/1315561 中的 步骤4-使用子账号调用智能体接口 一节 + * https://www.volcengine.com/docs/6348/1315561 中的 步骤 4-使用子账号调用智能体接口 一节 */ - sessionToken: undefined, // sessionToken: 'Your SessionToken', } @@ -39,13 +39,15 @@ app.use(bodyParser()); app.use(async ctx => { /** - * @brief Proxy AIGC Demo OpenAPI + * @brief 代理 AIGC 的 OpenAPI 请求 */ if (ctx.url.startsWith('/proxyAIGCFetch') && ctx.method.toLowerCase() === 'post') { const { Action, Version } = ctx.query || {}; const body = ctx.request.body; - /** Refer to: https://github.com/volcengine/volc-sdk-nodejs */ + /** + * 参考 https://github.com/volcengine/volc-sdk-nodejs 可获取更多 火山 TOP 网关 SDK 的使用方式 + */ const openApiRequestData = { region: 'cn-north-1', method: 'POST', @@ -62,7 +64,7 @@ app.use(async ctx => { const signer = new Signer(openApiRequestData, "rtc"); signer.addAuthorization(ACCOUNT_INFO); - /** Refer to: https://www.volcengine.com/docs/6348/69828 */ + /** 参考 https://www.volcengine.com/docs/6348/69828 可获取更多 OpenAPI 的信息 */ const result = await fetch(`https://rtc.volcengineapi.com?Action=${Action}&Version=${Version}`, { method: 'POST', headers: { diff --git a/Server/package.json b/Server/package.json index 9bb15b2..e95bbe0 100644 --- a/Server/package.json +++ b/Server/package.json @@ -9,7 +9,8 @@ "@volcengine/openapi": "^1.22.0", "koa": "^2.15.3", "koa-bodyparser": "^4.4.1", - "koa2-cors": "^2.0.6" + "koa2-cors": "^2.0.6", + "node-fetch": "^2.3.2" }, "scripts": { "dev": "node app.js" diff --git a/Server/yarn.lock b/Server/yarn.lock index 41f8de0..dc3ec67 100644 --- a/Server/yarn.lock +++ b/Server/yarn.lock @@ -490,6 +490,13 @@ negotiator@0.6.3: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== +node-fetch@^2.3.2: + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + object-inspect@^1.13.1: version "1.13.2" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff" @@ -606,6 +613,11 @@ toidentifier@1.0.1: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + tsscmp@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/tsscmp/-/tsscmp-1.0.6.tgz#85b99583ac3589ec4bfef825b5000aa911d605eb" @@ -639,6 +651,19 @@ vary@^1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + ylru@^1.2.0: version "1.4.0" resolved "https://registry.yarnpkg.com/ylru/-/ylru-1.4.0.tgz#0cf0aa57e9c24f8a2cbde0cc1ca2c9592ac4e0f6" diff --git a/craco.config.js b/craco.config.js index 89ada66..0743dd8 100644 --- a/craco.config.js +++ b/craco.config.js @@ -1,7 +1,8 @@ /** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ + const CracoLessPlugin = require('craco-less'); const path = require('path'); diff --git a/message.js b/message.js new file mode 100644 index 0000000..d8327d4 --- /dev/null +++ b/message.js @@ -0,0 +1,12 @@ +/** + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * SPDX-license-identifier: BSD-3-Clause + */ + +const reset = '\x1b[0m'; +const bright = '\x1b[1m'; +const green = '\x1b[32m'; + +console.log(`${bright}${bright}===================================================`); +console.log(`${bright}${green}| 请查看目录下的 README.md 内容, 否则启动可能失败 |`); +console.log(`${bright}${reset}===================================================${reset}`); \ No newline at end of file diff --git a/package.json b/package.json index 8c66858..8d05a81 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "aigc", - "version": "1.3.0", + "version": "1.4.0", "license": "BSD-3-Clause", "private": true, "dependencies": { "@reduxjs/toolkit": "^1.8.3", "@volcengine/rtc": "4.58.9", - "antd": "^4.21.7", + "@arco-design/web-react": "^2.65.0", "autolinker": "^4.0.0", "i18next": "^21.8.16", "react": "^18.2.0", @@ -20,15 +20,16 @@ "uuid": "^8.3.2" }, "scripts": { - "dev": "cross-env REACT_APP_LOCAL=cn craco start", - "dev-en": "cross-env REACT_APP_LOCAL=en craco start", + "dev": "npm run echo && npm run start", + "start": "cross-env REACT_APP_LOCAL=cn craco start", "build": "craco build", "test": "craco test", "eject": "react-scripts eject", "prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'", - "eslint": "eslint src/ --fix --cache --quiet --ext .js,.jsx,.ts,.tsx", + "eslint": "eslint src/ --fix --quiet --ext .js,.jsx,.ts,.tsx", "stylelint": "stylelint 'src/**/*.less' --fix", - "pre-commit": "npm run eslint && npm run stylelint" + "pre-commit": "npm run eslint && npm run stylelint", + "echo": "node message.js" }, "eslintConfig": { "extends": [ diff --git a/public/index.html b/public/index.html index f87047a..a62d8d7 100644 --- a/public/index.html +++ b/public/index.html @@ -1,7 +1,7 @@ - + --> diff --git a/src/App.tsx b/src/App.tsx index ffc1bed..aff09bf 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,13 +1,15 @@ /** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ + import { Helmet } from 'react-helmet'; import { BrowserRouter, Routes, Route } from 'react-router-dom'; -import Login from './pages/Login'; -import View from './pages/View'; +import MainPage from './pages/MainPage'; +import '@arco-design/web-react/dist/css/arco.css'; function App() { + console.warn('运行问题可参考 README 内容进行排查'); return ( @@ -15,8 +17,8 @@ function App() { - } /> - } /> + } /> + } /> diff --git a/src/app/api.ts b/src/app/api.ts index f0c235d..2a7d006 100644 --- a/src/app/api.ts +++ b/src/app/api.ts @@ -1,24 +1,25 @@ /** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ + import { requestGetMethod, requestPostMethod, resultHandler } from './base'; import { ACTIONS, RequestParams, RequestResponse } from './type'; const APIS_CONFIG = [ { action: ACTIONS.StartVoiceChat, - apiBasicParams: `?Name=start&Action=${ACTIONS.StartVoiceChat}&Version=2024-06-01`, + apiBasicParams: `?Name=start&Action=${ACTIONS.StartVoiceChat}&Version=2024-12-01`, method: 'post', }, { action: ACTIONS.UpdateVoiceChat, - apiBasicParams: `?Name=update&Action=${ACTIONS.UpdateVoiceChat}&Version=2024-06-01`, + apiBasicParams: `?Name=update&Action=${ACTIONS.UpdateVoiceChat}&Version=2024-12-01`, method: 'post', }, { action: ACTIONS.StopVoiceChat, - apiBasicParams: `?Name=stop&Action=${ACTIONS.StopVoiceChat}&Version=2024-06-01`, + apiBasicParams: `?Name=stop&Action=${ACTIONS.StopVoiceChat}&Version=2024-12-01`, method: 'post', }, ] as const; @@ -27,18 +28,13 @@ type ApiConfig = typeof APIS_CONFIG; type TupleToUnion = T[number]; type ApiNames = Pick, 'action'>['action']; type RequestFn = (params?: RequestParams[T]) => RequestResponse[T]; -type PromiseRequestFn = ( - params?: RequestParams[T] -) => Promise; +type PromiseRequestFn = (params?: RequestParams[T]) => Promise; type Apis = Record; const APIS = APIS_CONFIG.reduce((store, cur) => { const { action, apiBasicParams, method = 'get' } = cur; store[action] = async (params) => { - const queryData = - method === 'get' - ? await requestGetMethod(apiBasicParams)(params) - : await requestPostMethod(apiBasicParams)(params); + const queryData = method === 'get' ? await requestGetMethod(apiBasicParams)(params) : await requestPostMethod(apiBasicParams)(params); const res = await queryData?.json(); return resultHandler(res); }; diff --git a/src/app/base.ts b/src/app/base.ts index 123ab45..d813d1f 100644 --- a/src/app/base.ts +++ b/src/app/base.ts @@ -1,8 +1,9 @@ /** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ -import { message } from 'antd'; + +import { Message } from '@arco-design/web-react'; import { AIGC_PROXY_HOST } from '@/config'; type Headers = Record; @@ -32,11 +33,7 @@ export const requestGetMethod = (apiBasicParams: string, headers = {}) => { * @param isJson * @param headers */ -export const requestPostMethod = ( - apiBasicParams: string, - isJson: boolean = true, - headers: Headers = {} -) => { +export const requestPostMethod = (apiBasicParams: string, isJson: boolean = true, headers: Headers = {}) => { return async (params: T) => { const res = await fetch(`${AIGC_PROXY_HOST}${apiBasicParams}`, { method: 'post', @@ -59,8 +56,6 @@ export const resultHandler = (res: any) => { if (Result === 'ok') { return Result; } - message.error(`[${ResponseMetadata?.Action}]Failed(Reason: ${ResponseMetadata?.Error?.Code})`); - throw new Error( - `[${ResponseMetadata?.Action}]Failed(${JSON.stringify(ResponseMetadata, null, 2)})` - ); + Message.error(`[${ResponseMetadata?.Action}]Failed(Reason: ${ResponseMetadata?.Error?.Code})`); + throw new Error(`[${ResponseMetadata?.Action}]Failed(${JSON.stringify(ResponseMetadata, null, 2)})`); }; diff --git a/src/app/type.ts b/src/app/type.ts index 7b55344..4efb669 100644 --- a/src/app/type.ts +++ b/src/app/type.ts @@ -1,14 +1,23 @@ /** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ + export enum ACTIONS { StartVoiceChat = 'StartVoiceChat', UpdateVoiceChat = 'UpdateVoiceChat', StopVoiceChat = 'StopVoiceChat', } +/** + * @brief 请求参数类型 + * @note OpenAPI 接口参数结构可能更新, 请参阅最新文档内容。 + * https://www.volcengine.com/docs/6348/1404673 + */ export interface RequestParams { + /** + * @brief 通过接口开启数字人,使用前需要开 ASR、LLM、TTS 等服务。 + */ [ACTIONS.StartVoiceChat]: { AppId: string; BusinessId?: string; @@ -24,6 +33,7 @@ export interface RequestParams { AppId: string; VoiceType: string; Cluster?: string; + IgnoreBracketText?: number[]; }>; LLMConfig: Partial<{ AppId: string; @@ -46,7 +56,21 @@ export interface RequestParams { BotId?: string; }>; }>; + /** + * @brief 智能体基本配置。 + */ + AgentConfig: { + TargetUserId: string[]; + WelcomeMessage: string; + UserId: string; + EnableConversationStateCallback?: boolean; + ServerMessageSignatureForRTS?: string; + ServerMessageURLForRTS?: string; + }; }; + /** + * @brief 控制数字人行为,目前支持行为见 Command 参数。 + */ [ACTIONS.UpdateVoiceChat]: { AppId: string; BusinessId?: string; @@ -55,6 +79,9 @@ export interface RequestParams { Command: string; Message?: string; }; + /** + * @brief 关闭数字人任务。 + */ [ACTIONS.StopVoiceChat]: { AppId: string; BusinessId?: string; @@ -63,6 +90,9 @@ export interface RequestParams { }; } +/** + * @brief 返回参数类型 + */ export interface RequestResponse { [ACTIONS.StartVoiceChat]: string; [ACTIONS.UpdateVoiceChat]: string; @@ -70,9 +100,5 @@ export interface RequestResponse { } export type DeepPartial = { - [P in keyof T]?: T[P] extends Array - ? Array> - : T[P] extends object - ? DeepPartial - : T[P]; + [P in keyof T]?: T[P] extends Array ? Array> : T[P] extends object ? DeepPartial : T[P]; }; diff --git a/src/assets/img/AISetting.svg b/src/assets/img/AISetting.svg deleted file mode 100644 index 29b01eb..0000000 --- a/src/assets/img/AISetting.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/assets/img/Aigc.svg b/src/assets/img/Aigc.svg deleted file mode 100644 index 42e9c9f..0000000 --- a/src/assets/img/Aigc.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/assets/img/AigcDisable.svg b/src/assets/img/AigcDisable.svg deleted file mode 100644 index 1ee98cd..0000000 --- a/src/assets/img/AigcDisable.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/assets/img/Arrow.svg b/src/assets/img/Arrow.svg deleted file mode 100644 index 4f7ec8d..0000000 --- a/src/assets/img/Arrow.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/assets/img/Avatar.svg b/src/assets/img/Avatar.svg deleted file mode 100644 index 6c6742b..0000000 --- a/src/assets/img/Avatar.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/assets/img/Beautify.svg b/src/assets/img/Beautify.svg deleted file mode 100644 index 46c4c26..0000000 --- a/src/assets/img/Beautify.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/assets/img/BeautifyOff.svg b/src/assets/img/BeautifyOff.svg deleted file mode 100644 index e34a491..0000000 --- a/src/assets/img/BeautifyOff.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/assets/img/BytePlusLogo.png b/src/assets/img/BytePlusLogo.png deleted file mode 100644 index a6a63e8..0000000 Binary files a/src/assets/img/BytePlusLogo.png and /dev/null differ diff --git a/src/assets/img/CHILDREN_ENCYCLOPEDIA.png b/src/assets/img/CHILDREN_ENCYCLOPEDIA.png new file mode 100644 index 0000000..6a81ca6 Binary files /dev/null and b/src/assets/img/CHILDREN_ENCYCLOPEDIA.png differ diff --git a/src/assets/img/CUSTOMER_SERVICE.png b/src/assets/img/CUSTOMER_SERVICE.png new file mode 100644 index 0000000..b35aa36 Binary files /dev/null and b/src/assets/img/CUSTOMER_SERVICE.png differ diff --git a/src/assets/img/CallWrapper.svg b/src/assets/img/CallWrapper.svg new file mode 100644 index 0000000..75baa60 --- /dev/null +++ b/src/assets/img/CallWrapper.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/img/CameraClose.svg b/src/assets/img/CameraClose.svg new file mode 100644 index 0000000..5cc7121 --- /dev/null +++ b/src/assets/img/CameraClose.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/img/CameraCloseNote.svg b/src/assets/img/CameraCloseNote.svg new file mode 100644 index 0000000..92647a7 --- /dev/null +++ b/src/assets/img/CameraCloseNote.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/assets/img/CameraOff.svg b/src/assets/img/CameraOff.svg deleted file mode 100644 index aa516d1..0000000 --- a/src/assets/img/CameraOff.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/assets/img/CameraOn.svg b/src/assets/img/CameraOn.svg deleted file mode 100644 index ece8401..0000000 --- a/src/assets/img/CameraOn.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/assets/img/CameraOpen.svg b/src/assets/img/CameraOpen.svg new file mode 100644 index 0000000..744c61f --- /dev/null +++ b/src/assets/img/CameraOpen.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/img/Checked.svg b/src/assets/img/Checked.svg index e19a2b4..e6b2491 100644 --- a/src/assets/img/Checked.svg +++ b/src/assets/img/Checked.svg @@ -1,5 +1,5 @@ - + diff --git a/src/assets/img/DoubaoAvatar.png b/src/assets/img/DoubaoAvatar.png new file mode 100644 index 0000000..aaf5886 Binary files /dev/null and b/src/assets/img/DoubaoAvatar.png differ diff --git a/src/assets/img/DoubaoAvatarGIF.webp b/src/assets/img/DoubaoAvatarGIF.webp new file mode 100644 index 0000000..4641e8f Binary files /dev/null and b/src/assets/img/DoubaoAvatarGIF.webp differ diff --git a/src/assets/img/DoubaoProfile.svg b/src/assets/img/DoubaoProfile.svg index 87a448e..8fb5ce7 100644 --- a/src/assets/img/DoubaoProfile.svg +++ b/src/assets/img/DoubaoProfile.svg @@ -1,21 +1,21 @@ - - - + + + - + - - + + - - - + + + - - + + - + diff --git a/src/assets/img/History.svg b/src/assets/img/History.svg deleted file mode 100644 index 78b8b38..0000000 --- a/src/assets/img/History.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/assets/img/INTELLIGENT_ASSISTANT.png b/src/assets/img/INTELLIGENT_ASSISTANT.png new file mode 100644 index 0000000..c0969f7 Binary files /dev/null and b/src/assets/img/INTELLIGENT_ASSISTANT.png differ diff --git a/src/assets/img/InfoIcon.svg b/src/assets/img/InfoIcon.svg deleted file mode 100644 index e7920a7..0000000 --- a/src/assets/img/InfoIcon.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/assets/img/LeaveRoom.svg b/src/assets/img/LeaveRoom.svg new file mode 100644 index 0000000..531bce3 --- /dev/null +++ b/src/assets/img/LeaveRoom.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/img/MicClose.svg b/src/assets/img/MicClose.svg new file mode 100644 index 0000000..f11c2b9 --- /dev/null +++ b/src/assets/img/MicClose.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/img/MicDisabled.svg b/src/assets/img/MicDisabled.svg deleted file mode 100644 index 8e3ba48..0000000 --- a/src/assets/img/MicDisabled.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/src/assets/img/MicEnabled.svg b/src/assets/img/MicEnabled.svg deleted file mode 100644 index 5ecad97..0000000 --- a/src/assets/img/MicEnabled.svg +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/assets/img/MicOpen.svg b/src/assets/img/MicOpen.svg new file mode 100644 index 0000000..3389b19 --- /dev/null +++ b/src/assets/img/MicOpen.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/assets/img/Phone.svg b/src/assets/img/Phone.svg new file mode 100644 index 0000000..d1f6406 --- /dev/null +++ b/src/assets/img/Phone.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/img/RealTimeData.svg b/src/assets/img/RealTimeData.svg deleted file mode 100644 index 9283606..0000000 --- a/src/assets/img/RealTimeData.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/assets/img/Robot.svg b/src/assets/img/Robot.svg deleted file mode 100644 index dfd48c4..0000000 --- a/src/assets/img/Robot.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/assets/img/RobotAvatar.svg b/src/assets/img/RobotAvatar.svg deleted file mode 100644 index 75de256..0000000 --- a/src/assets/img/RobotAvatar.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/assets/img/ShareScreen.svg b/src/assets/img/ShareScreen.svg deleted file mode 100644 index d6ad8c3..0000000 --- a/src/assets/img/ShareScreen.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/assets/img/SoundOn.svg b/src/assets/img/SoundOn.svg deleted file mode 100644 index 65c19f3..0000000 --- a/src/assets/img/SoundOn.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/assets/img/TEACHER.png b/src/assets/img/TEACHER.png new file mode 100644 index 0000000..f7fd9ea Binary files /dev/null and b/src/assets/img/TEACHER.png differ diff --git a/src/assets/img/TEACHING_ASSISTANT.png b/src/assets/img/TEACHING_ASSISTANT.png new file mode 100644 index 0000000..5c451a8 Binary files /dev/null and b/src/assets/img/TEACHING_ASSISTANT.png differ diff --git a/src/assets/img/TRANSLATE.png b/src/assets/img/TRANSLATE.png new file mode 100644 index 0000000..f4965c0 Binary files /dev/null and b/src/assets/img/TRANSLATE.png differ diff --git a/src/assets/img/VIRTUAL_GIRL_FRIEND.png b/src/assets/img/VIRTUAL_GIRL_FRIEND.png new file mode 100644 index 0000000..c913491 Binary files /dev/null and b/src/assets/img/VIRTUAL_GIRL_FRIEND.png differ diff --git a/src/assets/img/Wave.gif b/src/assets/img/Wave.gif deleted file mode 100644 index 8b45e08..0000000 Binary files a/src/assets/img/Wave.gif and /dev/null differ diff --git a/src/assets/img/bubble.svg b/src/assets/img/bubble.svg deleted file mode 100644 index 5e96e22..0000000 --- a/src/assets/img/bubble.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/src/assets/img/huoponvsheng.jpeg b/src/assets/img/huoponvsheng.jpeg new file mode 100644 index 0000000..79d54b7 Binary files /dev/null and b/src/assets/img/huoponvsheng.jpeg differ diff --git a/src/assets/img/jingqiangkanye.jpeg b/src/assets/img/jingqiangkanye.jpeg new file mode 100644 index 0000000..ac8b231 Binary files /dev/null and b/src/assets/img/jingqiangkanye.jpeg differ diff --git a/src/assets/img/micHasVolume.png b/src/assets/img/micHasVolume.png deleted file mode 100644 index ce13579..0000000 Binary files a/src/assets/img/micHasVolume.png and /dev/null differ diff --git a/src/assets/img/wankuqingnian.jpeg b/src/assets/img/wankuqingnian.jpeg new file mode 100644 index 0000000..0b1a89f Binary files /dev/null and b/src/assets/img/wankuqingnian.jpeg differ diff --git a/src/assets/img/wanwanxiaohe.jpeg b/src/assets/img/wanwanxiaohe.jpeg new file mode 100644 index 0000000..c537b92 Binary files /dev/null and b/src/assets/img/wanwanxiaohe.jpeg differ diff --git a/src/assets/img/wennuanahu.jpeg b/src/assets/img/wennuanahu.jpeg new file mode 100644 index 0000000..9ad72a7 Binary files /dev/null and b/src/assets/img/wennuanahu.jpeg differ diff --git a/src/components/AISettings/index.module.less b/src/components/AISettings/index.module.less new file mode 100644 index 0000000..4f01ac6 --- /dev/null +++ b/src/components/AISettings/index.module.less @@ -0,0 +1,249 @@ +/** + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * SPDX-license-identifier: BSD-3-Clause + */ + +.container { + padding: 16px 8px; + background: linear-gradient(0deg, #F0F2FF 0%, #E0E4FF 100%); + + :global { + .arco-drawer-scroll { + .arco-drawer-content { + overflow-x: hidden; + overflow-y: auto; + + scrollbar-width: thin; /* 设置滚动条宽度为细 */ + scrollbar-color: rgba(0, 0, 0, 0) rgba(0, 0, 0, 0); /* 设置滚动条和轨道的颜色 */ + } + + ::-webkit-scrollbar { + width: 0px; + height: 0px; + } + + ::-webkit-scrollbar-thumb { + background: rgba(0,0,0,0); + border-radius: 0px; + } + + ::-webkit-scrollbar-track { + background: rgba(0,0,0,0); + border-radius: 0px; + } + } + } + + .title { + font-size: 20px; + font-weight: 500; + line-height: 28px; + + .special-text { + background: linear-gradient(90deg, #004FFF 38.86%, #9865FF 100%); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + } + } + + .sub-title { + font-size: 12px; + font-weight: 400; + line-height: 20px; + color: var(--text-color-text-3, rgba(115, 122, 135, 1)); + margin-top: 6px; + } + + .scenes { + width: 100%; + display: flex; + flex-direction: row; + gap: 14px; + margin-top: 32px; + } + + .scenes-mobile { + width: 100%; + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: center; + align-items: center; + gap: 14px; + margin-top: 32px; + overflow-x: auto; + padding-bottom: 8px; + } + + .configuration { + position: relative; + min-height: calc(100% - 300px); + height: max-content; + width: 100%; + background: white; + box-sizing: border-box; + padding: 32px 24px; + margin-top: 24px; + margin-bottom: 12px; + border-radius: 12px; + display: flex; + flex-direction: column; + gap: 36px; + + .anchor { + position: absolute; + border-bottom: 12px solid white; + border-left: 12px solid transparent; + border-right: 12px solid transparent; + top: 0px; + transform: translate(-50%, -99%); + } + + .ai-settings { + width: 100%; + display: flex; + flex-direction: row; + gap: 24px; + + .ai-settings-wrapper { + display: flex; + width: 100%; + flex-direction: row; + justify-content: space-between; + align-items: center; + } + + .ai-settings-model { + width: 100%; + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 12px; + } + } + + :global { + .arco-textarea { + background: white !important; + width: 100%; + height: max-content; + } + .arco-textarea:focus { + outline: none !important; + } + } + + textarea { + border-radius: 4px; + resize: none; + -webkit-resizer: none; + border: 0px; + outline: none; + box-shadow: none; + } + + textarea:focus { + border: 0px; + outline: none; + box-shadow: none; + } + } +} + + + +.button { + position: relative; + width: max-content !important; + height: 24px !important; + margin-top: 8px; + border-radius: 4px !important; + font-size: 12px !important; + background: linear-gradient(77.86deg, rgba(229, 242, 255, 0.5) -3.23%, rgba(217, 229, 255, 0.5) 51.11%, rgba(246, 226, 255, 0.5) 98.65%); + cursor: pointer; + + .button-text { + background: linear-gradient(77.86deg, #3384FF -3.23%, #014BDE 51.11%, #A945FB 98.65%); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + font-weight: 500; + line-height: 20px; + text-align: center; + } +} + +.button::after { + content: ''; + position: absolute; + border-radius: 3px; + top: 0px; + left: 0px; + width: 100%; + height: 22px; + background: white; + z-index: -1; +} + +.button::before { + content: ''; + position: absolute; + border-radius: 5px; + top: -2px; + left: -2px; + width: calc(100% + 4px); + height: 26px; + background: linear-gradient(90deg, rgba(0, 139, 255, 0.5) 0%, rgba(0, 98, 255, 0.5) 49.5%, rgba(207, 92, 255, 0.5) 100%); + z-index: -2; +} + +.button:hover { + background: linear-gradient(77.86deg, rgba(200, 220, 255, 0.7) -3.23%, rgba(190, 210, 255, 0.7) 51.11%, rgba(230, 210, 255, 0.7) 98.65%); +} + +.button:active { + background: linear-gradient(77.86deg, rgba(170, 190, 255, 0.9) -3.23%, rgba(160, 180, 255, 0.9) 51.11%, rgba(210, 180, 255, 0.9) 98.65%); +} + +.footer { + width: calc(100% - 12px); + display: flex; + flex-direction: row; + justify-content: flex-end; + align-items: center; + padding-bottom: 16px; + gap: 12px; + + .suffix { + font-size: 12px; + font-weight: 400; + line-height: 20px; + margin-right: 12px; + color: var(--text-color-text-3, rgba(115, 122, 135, 1)); + } + + .cancel { + width: 88px; + height: 32px; + border-radius: 6px; + border: 1px solid var(--line-color-border-3, rgba(221, 226, 233, 1)); + background-color: white !important; + } + + .confirm { + width: 88px; + height: 32px; + border-radius: 6px; + background: linear-gradient(95.87deg, #1664FF 0%, #8040FF 97.7%); + color: white !important; + } + + .confirm:hover { + opacity: .8; + } + + .confirm:active { + opacity: 1; + } +} \ No newline at end of file diff --git a/src/components/AISettings/index.tsx b/src/components/AISettings/index.tsx new file mode 100644 index 0000000..1459015 --- /dev/null +++ b/src/components/AISettings/index.tsx @@ -0,0 +1,315 @@ +/** + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * SPDX-license-identifier: BSD-3-Clause + */ + +import { Button, Drawer, Input, Message } from '@arco-design/web-react'; +import { useEffect, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { IconSwap } from '@arco-design/web-react/icon'; +import CheckIcon from '../CheckIcon'; +import Config, { Icon, Name, SCENE, Prompt, Welcome, Voice, Model, AI_MODEL, ModelSourceType, VOICE_INFO_MAP, VOICE_TYPE } from '@/config'; +import TitleCard from '../TitleCard'; +import CheckBoxSelector from '@/components/CheckBoxSelector'; +import RtcClient from '@/lib/RtcClient'; +import { clearHistoryMsg, updateAIConfig, updateScene } from '@/store/slices/room'; +import { RootState } from '@/store'; +import utils from '@/utils/utils'; +import { useDeviceState } from '@/lib/useCommon'; +import VoiceTypeChangeSVG from '@/assets/img/VoiceTypeChange.svg'; +import DoubaoModelSVG from '@/assets/img/DoubaoModel.svg'; +import ModelChangeSVG from '@/assets/img/ModelChange.svg'; +import styles from './index.module.less'; + +const SCENES = [ + SCENE.INTELLIGENT_ASSISTANT, + SCENE.VIRTUAL_GIRL_FRIEND, + // SCENE.TEACHER, + SCENE.TRANSLATE, + SCENE.CHILDREN_ENCYCLOPEDIA, + SCENE.CUSTOMER_SERVICE, + SCENE.TEACHING_ASSISTANT, + SCENE.CUSTOM, +]; + +function AISettings() { + const dispatch = useDispatch(); + const { isVideoPublished, switchCamera } = useDeviceState(); + const room = useSelector((state: RootState) => state.room); + const [loading, setLoading] = useState(false); + const [use3Part, setUse3Part] = useState(false); + const [open, setOpen] = useState(false); + const [scene, setScene] = useState(room.scene); + const [data, setData] = useState({ + prompt: Prompt[scene], + welcome: Welcome[scene], + voice: Voice[scene], + model: Model[scene], + + Url: '', + APIKey: '', + customModelName: '', + }); + + const handleClick = () => { + setOpen(true); + }; + + const handleVoiceTypeChanged = (key: string) => { + setData((prev) => ({ + ...prev, + voice: key as VOICE_TYPE, + })); + }; + + const handleChecked = (checkedScene: SCENE) => { + setScene(checkedScene); + setData((prev) => ({ + ...prev, + prompt: Prompt[checkedScene], + welcome: Welcome[checkedScene], + voice: Voice[checkedScene], + model: Model[checkedScene], + })); + }; + + const handleUseThirdPart = () => { + setUse3Part(!use3Part); + Config.ModeSourceType = use3Part ? ModelSourceType.Custom : ModelSourceType.Available; + }; + + const handleUpdateConfig = async () => { + dispatch(updateScene({ scene })); + if (use3Part) { + if (!data.Url) { + Message.error('请输入正确的第三方模型地址'); + return; + } + if (!data.Url.startsWith('http://') && !data.Url.startsWith('https://')) { + Message.error('第三方模型请求地址格式不正确, 请以 http:// 或 https:// 为开头'); + return; + } + Config.Url = data.Url; + Config.APIKey = data.APIKey; + Config.ModeSourceType = ModelSourceType.Custom; + } else { + Config.Url = undefined; + Config.APIKey = undefined; + Config.ModeSourceType = ModelSourceType.Available; + } + setLoading(true); + Config.Model = use3Part ? (data.customModelName as AI_MODEL) : (data.model as AI_MODEL); + Config.Prompt = data.prompt; + Config.VoiceType = data.voice; + Config.WelcomeSpeech = data.welcome; + dispatch(updateAIConfig(Config.aigcConfig)); + + if (RtcClient.getAudioBotEnabled()) { + dispatch(clearHistoryMsg()); + await RtcClient.updateAudioBot(); + } + if (data.model === AI_MODEL.VISION) { + room.isJoined && !isVideoPublished && switchCamera(true); + } else { + room.isJoined && isVideoPublished && switchCamera(true); + } + + setLoading(false); + setOpen(false); + }; + + useEffect(() => { + if (open) { + setScene(room.scene); + } + }, [open]); + + return ( + <> +
    修改 AI 设定
    + + +
    AI 配置修改后,退出房间将不再保存该配置方案
    + + + + } + visible={open} + onCancel={() => setOpen(false)} + > +
    + 选择你所需要的 + AI 人设 +
    +
    我们已为您配置好对应人设的基本参数,您也可以根据自己的需求进行自定义设置
    +
    + {SCENES.map((key) => ( + handleChecked(key as SCENE)} + /> + ))} +
    +
    + {utils.isMobile() ? null : ( +
    + )} + + { + setData((prev) => ({ + ...prev, + prompt: val, + })); + }} + placeholder="请输入你需要的 Prompt 设定" + /> + + + { + setData((prev) => ({ + ...prev, + welcome: val, + })); + }} + placeholder="请输入欢迎语" + /> + +
    + +
    + { + const info = VOICE_INFO_MAP[VOICE_TYPE[type as keyof typeof VOICE_TYPE]]; + return { + key: VOICE_TYPE[type as keyof typeof VOICE_TYPE], + label: type, + icon: info.icon, + description: info.description, + }; + })} + onChange={handleVoiceTypeChanged} + value={data.voice} + moreIcon={VoiceTypeChangeSVG} + moreText="更换音色" + placeHolder="请选择你需要的音色" + /> +
    +
    +
    + {use3Part ? ( + <> + + { + setData((prev) => ({ + ...prev, + Url: val, + })); + }} + placeholder="请输入第三方模型地址" + /> + + + { + setData((prev) => ({ + ...prev, + APIKey: val, + })); + }} + placeholder="请输入请求密钥" + /> + + + { + setData((prev) => ({ + ...prev, + customModelName: val, + })); + }} + placeholder="请输入模型名称" + /> + + + ) : ( + + ({ + key: AI_MODEL[type as keyof typeof AI_MODEL], + label: type.replaceAll('_', ' '), + icon: DoubaoModelSVG, + }))} + moreIcon={ModelChangeSVG} + moreText="更换模型" + placeHolder="请选择你需要的模型" + onChange={(key) => { + setData((prev) => ({ + ...prev, + model: key as AI_MODEL, + })); + }} + value={data.model} + /> + + )} + + +
    +
    +
    + + + ); +} + +export default AISettings; diff --git a/src/components/AvatarCard/index.module.less b/src/components/AvatarCard/index.module.less new file mode 100644 index 0000000..be6b768 --- /dev/null +++ b/src/components/AvatarCard/index.module.less @@ -0,0 +1,140 @@ +/** + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * SPDX-license-identifier: BSD-3-Clause + */ + +.card { + display: grid; + position: relative; + width: 370px; + height: 128px; + + .avatar { + position: absolute; + box-sizing: border-box; + border-radius: 50% 0% 0 50%; + width: 128px; + height: 128px; + margin-right: 16px; + border-left: 1px solid #EAEDF1; + border-top: 1px solid #EAEDF1; + border-bottom: 1px solid #EAEDF1; + background-color: white; + z-index: 2; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + + .doubao-gif { + height: 127px; + transform: scale(0.95); + } + } + + .body { + width: 100%; + height: 100%; + padding: 16px 16px 16px calc(64px + 16px); + position: relative; + display: flex; + align-items: center; + border: 1px solid var(--line-color-border-2, #EAEDF1); + box-sizing: border-box; + box-shadow: 0px 2px 6px 0px rgba(0, 0, 0, 0.05); + transform:translateX(64px); + } + + .body::after { + content: ''; + position: absolute; + top: -1px; + right: -1px; + width: 20px; + height: 20px; + background-color: white; + clip-path: polygon(0 0, 100% 0, 100% 100%); + } + + .body::before { + content: ''; + position: absolute; + top: 0px; + right: 0px; + width: 20px; + height: 20px; + background-color: #EAEDF1; + clip-path: polygon(0 0, 100% 0, 100% 100%); + } + + .text-wrapper { + position: absolute; + left: 128px; + margin-left: 16px; + width: max-content; + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + z-index: 4; + + .user-info { + display: flex; + flex-direction: column; + justify-content: center; + + .title { + color: var(--text-color-text-1, #0C0D0E); + font-size: 14px; + font-weight: 500; + line-height: 22px; + } + + .description { + font-size: 12px; + font-weight: 400; + line-height: 20px; + color: #737A87; + } + } + } + + .corner { + position: absolute; + top: -6px; + right: -6px; + width: 0px; + height: 0px; + border-right: 10px solid transparent; + border-top: 10px solid transparent; + border-bottom: 10px solid transparent; + border-left: 10px solid #EAEDF1; + z-index: 3; + transform: translateX(64px) rotate(-45deg); + } + + .corner::before { + content: ''; + position: absolute; + top: 0px; + right: 0px; + width: 0px; + height: 0px; + border-right: 8px solid transparent; + border-top: 8px solid transparent; + border-bottom: 8px solid transparent; + border-left: 8px solid white; + transform: translate(7px, -8px); + } + + .corner::after { + content: ''; + position: absolute; + top: 0px; + right: 4px; + width: 5px; + height: 1px; + background-color: #EAEDF1; + transform: rotate(-90deg); + } +} \ No newline at end of file diff --git a/src/components/AvatarCard/index.tsx b/src/components/AvatarCard/index.tsx new file mode 100644 index 0000000..d845437 --- /dev/null +++ b/src/components/AvatarCard/index.tsx @@ -0,0 +1,47 @@ +/** + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * SPDX-license-identifier: BSD-3-Clause + */ + +import { useSelector } from 'react-redux'; +import AISettings from '../AISettings'; +import style from './index.module.less'; +import DouBaoAvatar from '@/assets/img/DoubaoAvatarGIF.webp'; +import { RootState } from '@/store'; +import { Name, VOICE_TYPE } from '@/config'; + +interface IAvatarCardProps extends React.HTMLAttributes { + avatar?: string; +} + +const ReversedVoiceType = Object.entries(VOICE_TYPE).reduce>((acc, [key, value]) => { + acc[value] = key; + return acc; +}, {}); + +function AvatarCard(props: IAvatarCardProps) { + const room = useSelector((state: RootState) => state.room); + const scene = room.scene; + const { LLMConfig, TTSConfig } = room.aiConfig.Config || {}; + const { avatar, className, ...rest } = props; + + return ( +
    +
    +
    + Avatar +
    +
    +
    +
    +
    {Name[scene]}
    +
    声源来自 {ReversedVoiceType[TTSConfig?.VoiceType || '']}
    +
    模型 {LLMConfig.ModelName}
    + +
    +
    +
    + ); +} + +export default AvatarCard; diff --git a/src/components/BubbleMsg/index.module.less b/src/components/BubbleMsg/index.module.less index 9c417d1..94231a2 100644 --- a/src/components/BubbleMsg/index.module.less +++ b/src/components/BubbleMsg/index.module.less @@ -1,9 +1,9 @@ /** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ - - .bubbleWrapper { + +.bubbleWrapper { position: relative; width: 118px; height: 46px; diff --git a/src/components/BubbleMsg/index.tsx b/src/components/BubbleMsg/index.tsx index 468456b..9a7c69c 100644 --- a/src/components/BubbleMsg/index.tsx +++ b/src/components/BubbleMsg/index.tsx @@ -1,7 +1,8 @@ /** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ + import React from 'react'; import Bubble from '@/assets/img/bubble.svg'; import styles from './index.module.less'; @@ -21,11 +22,7 @@ function BubbleMsg(props: IBubbleMsgProps) { return (
    - Logo + Logo
    {text}
    ); diff --git a/src/components/ButtonRadio/index.module.less b/src/components/ButtonRadio/index.module.less index 8b9d304..0346511 100644 --- a/src/components/ButtonRadio/index.module.less +++ b/src/components/ButtonRadio/index.module.less @@ -1,8 +1,9 @@ /** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ - .wrapper { + +.wrapper { width: max-content; padding: 4px 4px; border-radius: 4px; diff --git a/src/components/ButtonRadio/index.tsx b/src/components/ButtonRadio/index.tsx index 6535e8e..8cda676 100644 --- a/src/components/ButtonRadio/index.tsx +++ b/src/components/ButtonRadio/index.tsx @@ -1,9 +1,10 @@ /** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ + import { useMemo } from 'react'; -import { Button } from 'antd'; +import { Button } from '@arco-design/web-react'; import styles from './index.module.less'; interface IProps { @@ -17,8 +18,7 @@ interface IProps { function ButtonRadio(props: IProps) { const { value, onChange, options } = props; - const selected = - useMemo(() => options.find((item) => item.key === value), [value]) || options?.[0]; + const selected = useMemo(() => options.find((item) => item.key === value), [value]) || options?.[0]; const handleClick = (key: string) => { onChange?.(key); }; @@ -26,12 +26,7 @@ function ButtonRadio(props: IProps) { return (
    {options.map(({ label, key }) => ( - ))} diff --git a/src/components/CheckBox/index.module.less b/src/components/CheckBox/index.module.less index 2c47098..efb5060 100644 --- a/src/components/CheckBox/index.module.less +++ b/src/components/CheckBox/index.module.less @@ -1,11 +1,46 @@ /** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ - .wrapper { + +.noStyle { + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; + + .icon { + margin-right: 12px; + width: 48px; + height: 48px; + } + .content { + display: flex; + flex-direction: column; + justify-content: center; + align-items: flex-start; + + .label { + font-size: 14px; + font-weight: 500; + line-height: 22px; + } + + .description { + font-size: 12px; + font-weight: 400; + line-height: 20px; + text-align: left; + } + } +} + + + +.wrapper { width: 260px; height: 88px; - padding: 20px 16px 20px 16px; + padding: 3px 16px 3px 16px; border-radius: 12px; border: 1px solid; @@ -18,7 +53,7 @@ .icon { border-radius: 50%; - margin-right: 20px; + margin-right: 12px; width: 48px; height: 48px; } diff --git a/src/components/CheckBox/index.tsx b/src/components/CheckBox/index.tsx index 9869ab1..697b730 100644 --- a/src/components/CheckBox/index.tsx +++ b/src/components/CheckBox/index.tsx @@ -1,29 +1,41 @@ /** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ + import { ReactNode } from 'react'; +// import { Button } from '@arco-design/web-react'; import CheckedSVG from '@/assets/img/Checked.svg'; import styles from './index.module.less'; interface IProps { className?: string; - checked: boolean; + checked?: boolean; onClick?: () => void; icon?: string; label?: string | ReactNode; description?: string | ReactNode; suffix?: string | ReactNode; + noStyle?: boolean; } function CheckBox(props: IProps) { - const { className = '', icon = '', checked, label, description, suffix, onClick } = props; + const { noStyle, className = '', icon = '', checked, label, description, suffix, onClick } = props; + + if (noStyle) { + return ( +
    + {icon ? icon : ''} +
    +
    {label}
    +
    {description}
    +
    +
    + ); + } return ( -
    +
    {icon ? icon : ''}
    {label}
    diff --git a/src/pages/View/Menu/components/AISettingDrawerButton/CheckBoxSelector/index.module.less b/src/components/CheckBoxSelector/index.module.less similarity index 67% rename from src/pages/View/Menu/components/AISettingDrawerButton/CheckBoxSelector/index.module.less rename to src/components/CheckBoxSelector/index.module.less index 17e2919..d440678 100644 --- a/src/pages/View/Menu/components/AISettingDrawerButton/CheckBoxSelector/index.module.less +++ b/src/components/CheckBoxSelector/index.module.less @@ -1,13 +1,20 @@ /** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ - .wrapper { + +.wrapper { width: 100%; display: flex; flex-direction: row; - align-items: flex-end; - margin-bottom: 10px; + align-items: center; + + .placeholder { + font-size: 14px; + font-weight: 400; + line-height: 22px; + color: var(--text-color-text-3, rgba(115, 122, 135, 1)); + } .box { margin-right: 16px; @@ -63,12 +70,11 @@ flex-direction: row; justify-content: flex-end; align-items: center; - padding-bottom: 16px; + gap: 12px; .cancel { width: 88px; height: 32px; - gap: 4px; border-radius: 6px; border: 1px solid var(--line-color-border-3, rgba(221, 226, 233, 1)) } @@ -78,14 +84,46 @@ height: 32px; border-radius: 6px; background: linear-gradient(95.87deg, #1664FF 0%, #8040FF 97.7%); - color: white; + color: white !important; + } + + .confirm:hover { + opacity: .8; + } + + .confirm:active { + opacity: 1; } } .modalInner { width: 100%; + // max-height: 500px; display: flex; flex: row; flex-wrap: wrap; + overflow: auto; gap: 12px; +} + +.modal { + // max-height: 650px; + overflow: hidden; +} + +.modalInner::-webkit-scrollbar { + width: 8px; + height: 8px; + border-radius: 5px; +} + +.modalInner::-webkit-scrollbar-thumb { + background: rgb(205, 204, 204); + border-radius: 0px; + border-radius: 5px; +} + +.modalInner::-webkit-scrollbar-track { + background: rgb(255, 255, 255); + border-radius: 0px; } \ No newline at end of file diff --git a/src/pages/View/Menu/components/AISettingDrawerButton/CheckBoxSelector/index.tsx b/src/components/CheckBoxSelector/index.tsx similarity index 51% rename from src/pages/View/Menu/components/AISettingDrawerButton/CheckBoxSelector/index.tsx rename to src/components/CheckBoxSelector/index.tsx index 842ef3a..ad90f51 100644 --- a/src/pages/View/Menu/components/AISettingDrawerButton/CheckBoxSelector/index.tsx +++ b/src/components/CheckBoxSelector/index.tsx @@ -1,11 +1,13 @@ /** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ -import { useEffect, useMemo, useState } from 'react'; -import { Button, Modal } from 'antd'; + +import { useEffect, useMemo, useState, memo } from 'react'; +import { Button, Drawer } from '@arco-design/web-react'; import CheckBox from '@/components/CheckBox'; import styles from './index.module.less'; +import utils from '@/utils/utils'; export interface ICheckBoxItemProps { icon?: string; @@ -21,44 +23,42 @@ interface IProps { label?: string; moreIcon?: string; moreText?: string; + placeHolder?: string; } function CheckBoxSelector(props: IProps) { - const { label = '', data = [], value, onChange, moreIcon, moreText } = props; + const { placeHolder, label = '', data = [], value, onChange, moreIcon, moreText } = props; const [visible, setVisible] = useState(false); const [selected, setSelected] = useState(value!); const selectedOne = useMemo(() => data.find((item) => item.key === value), [data, value]); - const handleSeeMore = () => { setVisible(true); }; - useEffect(() => { setSelected(value!); - }, [visible, value]); + }, [visible]); return ( -
    - {selectedOne ? ( - - ) : ( - '' - )} - - +
    + {selectedOne ? ( + + ) : ( +
    {placeHolder}
    + )} + +
    + setVisible(false)} footer={
    - // null } >
    {data.map((item) => ( - setSelected(item.key)} - /> + setSelected(item.key)} /> ))}
    -
    -
    + + ); } -export default CheckBoxSelector; +export default memo(CheckBoxSelector); diff --git a/src/components/CheckIcon/index.module.less b/src/components/CheckIcon/index.module.less new file mode 100644 index 0000000..4ff650e --- /dev/null +++ b/src/components/CheckIcon/index.module.less @@ -0,0 +1,220 @@ +/** + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * SPDX-license-identifier: BSD-3-Clause + */ + +.wrapper { + position: relative; + width: 100px; + height: 100px; + box-sizing: border-box; + border-radius: 12px; + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; + cursor: pointer; + + .content { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + z-index: 1; + gap: 3px; + + .icon { + border-radius: 50%; + width: 55%; + height: max-content; + } + + .checked-text { + font-size: 13px; + line-height: 22px; + } + } +} + +.wrapper:hover { + box-shadow: 0px 5px 6px 0px rgba(82, 102, 133, 0.15); +} + +.wrapper::after { + content: ''; + position: absolute; + border-radius: 11px; + top: 1px; + left: 1px; + width: 100%; + height: 100px; + background: white; +} + +.wrapper::before { + content: ''; + position: absolute; + border-radius: 12px; + top: 0px; + left: 0px; + width: 102px; + height: 102px; + background: linear-gradient(99.97deg, rgba(22, 100, 255, 0.2) 20.8%, rgba(132, 97, 251, 0.2) 100.66%); + // background: linear-gradient(99.97deg, #1664FF 20.8%, #8461FB 100.66%); +} + + +.active { + position: relative; + width: 100px; + height: 100px; + box-sizing: border-box; + border-radius: 12px; + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; + cursor: pointer; + + .checkIcon { + position: absolute; + bottom: -1px; + right: -1px; + z-index: 2; + width: 20px; + height: 20px; + } + + .content { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + z-index: 1; + gap: 3px; + + .icon { + border-radius: 50%; + width: 55%; + height: max-content; + } + + .checked-text { + background: linear-gradient(90deg, #004FFF 38.86%, #9865FF 100%); + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + font-size: 13px; + font-weight: 500; + line-height: 22px; + } + } +} + +.active:hover { + box-shadow: 0px 5px 6px 0px rgba(82, 102, 133, 0.15); +} + +.active::after { + content: ''; + position: absolute; + border-radius: 11px; + top: 1px; + left: 1px; + width: 100%; + height: 100px; + background: white; +} + +.active::before { + content: ''; + position: absolute; + border-radius: 12px; + top: 0px; + left: 0px; + width: 102px; + height: 102px; + background: linear-gradient(99.97deg, #1664FF 20.8%, #8461FB 100.66%); +} + +.blur { + position: relative; + width: 100px; + height: 100px; + box-sizing: border-box; + border-radius: 12px; + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; + cursor: pointer; + + .content { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + z-index: 1; + gap: 3px; + + .icon { + border-radius: 50%; + width: 55%; + height: max-content; + opacity: .5; + } + + .checked-text { + font-size: 13px; + line-height: 22px; + } + } +} + +.blur:hover { + box-shadow: 0px 5px 6px 0px rgba(82, 102, 133, 0.15); +} + +.blur::after { + content: ''; + position: absolute; + border-radius: 11px; + top: 1px; + left: 1px; + width: 100%; + height: 100px; + background: white; + opacity: .8; +} + +.blur::before { + content: ''; + position: absolute; + border-radius: 12px; + top: 0px; + left: 0px; + width: 100px; + height: 100px; + border: dashed 1px rgba(132, 97, 251, 0.2); +} + +.tag { + position: absolute; + top: 0; + right: 0; + z-index: 3; + font-size: 10px; + font-weight: 500; + line-height: 18px; + transform: translate(20%, -50%); + background: rgba(134, 123, 227, 1); + padding: 0px 6px 0px 6px; + border-radius: 20px 20px 20px 0px; + color: white; +} \ No newline at end of file diff --git a/src/components/CheckIcon/index.tsx b/src/components/CheckIcon/index.tsx new file mode 100644 index 0000000..cdb9fa6 --- /dev/null +++ b/src/components/CheckIcon/index.tsx @@ -0,0 +1,34 @@ +/** + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * SPDX-license-identifier: BSD-3-Clause + */ + +import CheckedSVG from '@/assets/img/Checked.svg'; +import styles from './index.module.less'; + +interface IProps { + className?: string; + blur?: boolean; + checked: boolean; + title?: string; + onClick?: () => void; + icon?: string; + tag?: string; +} + +function CheckIcon(props: IProps) { + const { tag, blur, className = '', icon, title, checked, onClick } = props; + const wrapperStyle = blur ? styles.blur : styles.wrapper; + return ( +
    + {tag ?
    {tag}
    : ''} +
    + {icon ? icon : ''} +
    {title}
    +
    + {checked ? checked : ''} +
    + ); +} + +export default CheckIcon; diff --git a/src/components/DrawerRowItem/index.module.less b/src/components/DrawerRowItem/index.module.less index 3542d24..38a4aa5 100644 --- a/src/components/DrawerRowItem/index.module.less +++ b/src/components/DrawerRowItem/index.module.less @@ -1,12 +1,14 @@ /** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ - .row { + +.row { width: 100%; display: flex; flex-direction: row; align-items: center; + cursor: pointer; .firstPart { display: flex; @@ -68,6 +70,13 @@ } } +.children { + width: 100%; + height: 100%; + position: relative; + overflow: hidden; +} + :global { .ant-drawer-body { padding: 12px 24px 0px 24px; diff --git a/src/components/DrawerRowItem/index.tsx b/src/components/DrawerRowItem/index.tsx index 9bb609d..88c5b0e 100644 --- a/src/components/DrawerRowItem/index.tsx +++ b/src/components/DrawerRowItem/index.tsx @@ -1,37 +1,36 @@ /** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ + import React, { useState } from 'react'; -import { Drawer, Space, Button } from 'antd'; -import { useTranslation } from 'react-i18next'; -import { CloseOutlined, RightOutlined } from '@ant-design/icons'; +import { Drawer } from '@arco-design/web-react'; +import { IconRight } from '@arco-design/web-react/icon'; import styles from './index.module.less'; type IDrawerRowItemProps = { - btnSrc: string; + btnSrc?: string; btnText: string; suffix?: React.ReactNode; drawer?: { title: string; - width?: number; + width?: string | number; onOpen?: () => void; onClose?: () => void; onCancel?: () => void; - onConfirm?: () => void; + onConfirm?: (handleClose: () => void) => void; children?: React.ReactNode; footer?: boolean; }; } & React.HTMLAttributes; function DrawerRowItem(props: IDrawerRowItemProps) { - const { btnSrc, btnText, suffix, drawer, style = {}, className = '' } = props; + const { btnSrc, btnText, suffix, drawer, style, className = '' } = props; const [open, setOpen] = useState(false); - const { onClose, onOpen, footer = true } = drawer!; + const { onClose, onOpen } = drawer!; - const { t } = useTranslation(); - - const handleClose = async () => { + const handleClose = () => { + drawer?.onCancel?.(); setOpen(false); onClose?.(); }; @@ -45,55 +44,18 @@ function DrawerRowItem(props: IDrawerRowItemProps) { return ( <> -
    +
    {btnSrc ? svg : ''} {btnText} {suffix}
    - +
    - - - -
    - ) : ( - '' - ) - } - extra={ - - + ))} +
    + } + > + + + )} Logo - {t('demoTitle')} + 实时对话式 AI 体验馆 +
    {children} + {utils.isMobile() ? null : ( +
    +
    window.open('https://www.volcengine.com/product/veRTC/ConversationalAI', '_blank')}> + 官网链接 +
    +
    window.open('https://www.volcengine.com/contact/product?t=%E5%AF%B9%E8%AF%9D%E5%BC%8Fai&source=%E4%BA%A7%E5%93%81%E5%92%A8%E8%AF%A2', '_blank')} + > + 联系我们 +
    +
    + )}
    ); } diff --git a/src/components/Icon/index.tsx b/src/components/Icon/index.tsx new file mode 100644 index 0000000..6ac61be --- /dev/null +++ b/src/components/Icon/index.tsx @@ -0,0 +1,36 @@ +/** + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * SPDX-license-identifier: BSD-3-Clause + */ + +import { useEffect, useRef } from 'react'; + +const getDom = async (url: string) => { + const res = await fetch(url); + if (res) { + const text = await res.text(); + // https://developer.mozilla.org/zh-CN/docs/Web/API/DOMParser + return new window.DOMParser().parseFromString(text, 'text/xml'); + } + return undefined; +}; + +function MenuIcon({ src, className = '' }: { src: string; className?: string }) { + const wrapper = useRef(null); + + const renderSVG = async () => { + const svg = await getDom(src); + + if (svg?.documentElement instanceof SVGSVGElement) { + wrapper.current?.replaceChildren(svg.documentElement); + } + }; + + useEffect(() => { + src && renderSVG(); + }, [src]); + + return ; +} + +export default MenuIcon; diff --git a/src/components/Loading/AudioLoading/index.module.less b/src/components/Loading/AudioLoading/index.module.less new file mode 100644 index 0000000..9866a94 --- /dev/null +++ b/src/components/Loading/AudioLoading/index.module.less @@ -0,0 +1,37 @@ +/** + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * SPDX-license-identifier: BSD-3-Clause + */ + +.loader { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + gap: 6px; + height: 36px; + margin-top: 4px; +} + +.dot { + width: 20px; + height: 20px; + border-radius: 12px; + background-color: rgba(148, 116, 255, 1); +} + +.dotter { + animation: glow 0.9s infinite; +} + +@keyframes glow { + 0% { + height: 20px; + } + 50% { + height: 36px; + } + 100% { + height: 20px; + } +} \ No newline at end of file diff --git a/src/components/Loading/AudioLoading/index.tsx b/src/components/Loading/AudioLoading/index.tsx new file mode 100644 index 0000000..6c9c07a --- /dev/null +++ b/src/components/Loading/AudioLoading/index.tsx @@ -0,0 +1,32 @@ +/** + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * SPDX-license-identifier: BSD-3-Clause + */ + +import { memo } from 'react'; +import style from './index.module.less'; + +interface IAudioLoadingProps extends React.HTMLAttributes { + loading?: boolean; +} + +function AudioLoading(props: IAudioLoadingProps) { + const { loading = false, className = '', ...rest } = props; + return ( +
    + {Array(3) + .fill(0) + .map((_, index) => ( +
    + ))} +
    + ); +} + +export default memo(AudioLoading); diff --git a/src/components/Loading/HorizonLoading/index.module.less b/src/components/Loading/HorizonLoading/index.module.less new file mode 100644 index 0000000..970c2fc --- /dev/null +++ b/src/components/Loading/HorizonLoading/index.module.less @@ -0,0 +1,16 @@ +/** + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * SPDX-license-identifier: BSD-3-Clause + */ + +.loader { + display: flex; +} + +.dot { + width: 10px; + height: 10px; + border-radius: 50%; + background-color: white; + animation: glow 0.9s infinite; +} \ No newline at end of file diff --git a/src/components/Loading/HorizonLoading/index.tsx b/src/components/Loading/HorizonLoading/index.tsx new file mode 100644 index 0000000..768c53d --- /dev/null +++ b/src/components/Loading/HorizonLoading/index.tsx @@ -0,0 +1,41 @@ +/** + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * SPDX-license-identifier: BSD-3-Clause + */ + +import { memo } from 'react'; +import style from './index.module.less'; + +interface ILoadingProps extends React.HTMLAttributes { + dotClassName?: string; + speed?: number; + gap?: number; +} + +function Loading(props: ILoadingProps) { + const { dotClassName, gap = 5, speed = 0.9, className = '', ...rest } = props; + return ( +
    + {Array(3) + .fill(0) + .map((_, index) => ( +
    + ))} +
    + ); +} + +export default memo(Loading); diff --git a/src/components/Loading/index.module.less b/src/components/Loading/VerticalLoading/index.module.less similarity index 91% rename from src/components/Loading/index.module.less rename to src/components/Loading/VerticalLoading/index.module.less index 4be5a3f..4c5135f 100644 --- a/src/components/Loading/index.module.less +++ b/src/components/Loading/VerticalLoading/index.module.less @@ -1,8 +1,9 @@ /** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ - .loader { + +.loader { width: 40px; height: 10px; display: flex; diff --git a/src/components/Loading/index.tsx b/src/components/Loading/VerticalLoading/index.tsx similarity index 85% rename from src/components/Loading/index.tsx rename to src/components/Loading/VerticalLoading/index.tsx index 8ddd6ad..0f9063c 100644 --- a/src/components/Loading/index.tsx +++ b/src/components/Loading/VerticalLoading/index.tsx @@ -1,7 +1,8 @@ /** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ + import { memo } from 'react'; import styles from './index.module.less'; diff --git a/src/components/NetworkIndicator/index.module.less b/src/components/NetworkIndicator/index.module.less new file mode 100644 index 0000000..1e8ad4d --- /dev/null +++ b/src/components/NetworkIndicator/index.module.less @@ -0,0 +1,60 @@ +/** + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * SPDX-license-identifier: BSD-3-Clause + */ + +.panel { + display: flex; + + .label { + width: 90px; + display: flex; + flex-direction: column; + gap: 4px; + + .state { + font-weight: bold; + } + } + + .value { + display: flex; + flex-direction: column; + gap: 4px; + width: max-content; + + .state { + font-weight: bold; + } + + .loss { + display: flex; + flex-direction: row; + justify-content: space-between; + gap: 12px; + } + } +} + +.wrapper { + display: flex; + align-items: flex-end; + height: 14px; + width: 14px; + margin: 14px; + column-gap: 1.5px; + background-color: rgba(142, 142, 142, 0.05); + border-radius: 3px; + padding: 2px; + + .indicator { + width: 30%; + border-color: rgba(127, 127, 127, 0.184); + border-width: 1px; + border-radius: 1px; + border-style: solid; + opacity: 0.8; + transition: height 0.3s; + box-sizing: border-box; + } +} \ No newline at end of file diff --git a/src/components/NetworkIndicator/index.tsx b/src/components/NetworkIndicator/index.tsx new file mode 100644 index 0000000..6795e9a --- /dev/null +++ b/src/components/NetworkIndicator/index.tsx @@ -0,0 +1,105 @@ +/** + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * SPDX-license-identifier: BSD-3-Clause + */ + +import { useMemo } from 'react'; +import { Popover } from '@arco-design/web-react'; +import { useSelector } from 'react-redux'; +import { IconArrowDown, IconArrowUp } from '@arco-design/web-react/icon'; +import { NetworkQuality } from '@volcengine/rtc'; +import { RootState } from '@/store'; +import style from './index.module.less'; +import Config from '@/config'; + +enum INDICATOR_COLORS { + GREAT = 'rgba(35, 195, 67, 1)', + FAIR = 'rgba(208, 141, 6, 1)', + BAD = 'rgba(245, 78, 78, 1)', + PLACE_HOLDER = 'transparent', +} + +const INDICATOR_TEXT = { + [NetworkQuality.UNKNOWN]: '正常', + [NetworkQuality.EXCELLENT]: '正常', + [NetworkQuality.GOOD]: '正常', + [NetworkQuality.POOR]: '一般', + [NetworkQuality.BAD]: '一般', + [NetworkQuality.VBAD]: '较差', + [NetworkQuality.DOWN]: '较差', +}; + +function NetworkIndicator() { + const room = useSelector((state: RootState) => state.room); + const networkQuality = room.networkQuality; + const delay = room.localUser.audioStats?.rtt; + const audioLossRateUpper = room.localUser.audioStats?.audioLossRate || 0; + const audioLossRateLower = room.remoteUsers.find((user) => user.userId === Config.BotName)?.audioStats?.audioLossRate || 0; + + const indicators = useMemo(() => { + switch (networkQuality) { + case NetworkQuality.UNKNOWN: + case NetworkQuality.EXCELLENT: + case NetworkQuality.GOOD: + return Array(3).fill(INDICATOR_COLORS.GREAT); + case NetworkQuality.POOR: + case NetworkQuality.BAD: + return Array(2).fill(INDICATOR_COLORS.FAIR).concat(INDICATOR_COLORS.PLACE_HOLDER); + case NetworkQuality.VBAD: + case NetworkQuality.DOWN: + default: + return [INDICATOR_COLORS.BAD].concat(...Array(2).fill(INDICATOR_COLORS.PLACE_HOLDER)); + } + }, [networkQuality]); + + return ( + +
    +
    网络状态
    +
    延迟
    +
    丢包率
    +
    +
    +
    + {INDICATOR_TEXT[networkQuality]} +
    +
    {delay ? delay.toFixed(0) : '- '}ms
    +
    +
    + + {`${audioLossRateUpper}` ? (audioLossRateUpper * 100)?.toFixed(0) : '- '}% +
    +
    + + {`${audioLossRateLower}` ? (audioLossRateLower * 100)?.toFixed(0) : '- '}% +
    +
    +
    +
    + } + > +
    + {indicators.map((color, index) => ( +
    + ))} +
    + + ); +} + +export default NetworkIndicator; diff --git a/src/components/ResizeWrapper/index.module.less b/src/components/ResizeWrapper/index.module.less index f905943..06432f2 100644 --- a/src/components/ResizeWrapper/index.module.less +++ b/src/components/ResizeWrapper/index.module.less @@ -1,7 +1,8 @@ /** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ - .container { + +.container { position: relative; } \ No newline at end of file diff --git a/src/components/ResizeWrapper/index.tsx b/src/components/ResizeWrapper/index.tsx index 2b9e86a..ccbac56 100644 --- a/src/components/ResizeWrapper/index.tsx +++ b/src/components/ResizeWrapper/index.tsx @@ -1,7 +1,8 @@ /** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ + import { useEffect, useRef } from 'react'; import styles from './index.module.less'; diff --git a/src/components/RippleWave/index.module.less b/src/components/RippleWave/index.module.less index 514ac72..6493e6b 100644 --- a/src/components/RippleWave/index.module.less +++ b/src/components/RippleWave/index.module.less @@ -1,7 +1,8 @@ /** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ + /* AudioWave.css */ @keyframes audioWave { 0% { diff --git a/src/components/RippleWave/index.tsx b/src/components/RippleWave/index.tsx index 40bc1e6..d34a590 100644 --- a/src/components/RippleWave/index.tsx +++ b/src/components/RippleWave/index.tsx @@ -1,7 +1,8 @@ /** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ + import React from 'react'; import styles from './index.module.less'; diff --git a/src/components/TitleCard/index.module.less b/src/components/TitleCard/index.module.less new file mode 100644 index 0000000..891c864 --- /dev/null +++ b/src/components/TitleCard/index.module.less @@ -0,0 +1,50 @@ +/** + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * SPDX-license-identifier: BSD-3-Clause + */ + +.wrapper { + width: 100%; + box-sizing: border-box; + height: max-content; + display: flex; + flex-direction: row; + align-items: center; + min-height: 54px; + padding: 20px 16px; + border-radius: 8px; + border: 1px solid rgba(229, 238, 255, 1); + backdrop-filter: blur(28px); + box-shadow: 0px 0px 16px 0px 0px 4px 4px 0px rgba(255, 255, 255, 0.15) inset; + backdrop-filter: blur(28px); + + .title { + position: absolute; + font-size: 12px; + left: 10px; + top: 0px; + transform: translateY(-50%); + padding: 0px 6px; + z-index: 1; + color: var(--text-color-text-3, rgba(115, 122, 135, 1)); + background-color: white; + width: max-content; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + + .required { + height: max-content; + width: max-content; + color: red; + margin-right: 6px; + padding-top: 4.5px; + font-size: 14px; + } + } + + div { + width: 100%; + } +} \ No newline at end of file diff --git a/src/components/TitleCard/index.tsx b/src/components/TitleCard/index.tsx new file mode 100644 index 0000000..482cc6c --- /dev/null +++ b/src/components/TitleCard/index.tsx @@ -0,0 +1,25 @@ +/** + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * SPDX-license-identifier: BSD-3-Clause + */ + +import styles from './index.module.less'; + +interface ITitleCardProps extends React.HTMLAttributes { + title: string; + required?: boolean; +} + +function TitleCard(props: ITitleCardProps) { + const { required, title, children, className, ...rest } = props; + return ( +
    +
    + {required ?
    *
    : ''} + {title} +
    +
    {children}
    +
    + ); +} +export default TitleCard; diff --git a/src/config/common.ts b/src/config/common.ts new file mode 100644 index 0000000..98c0819 --- /dev/null +++ b/src/config/common.ts @@ -0,0 +1,279 @@ +/** + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * SPDX-license-identifier: BSD-3-Clause + */ + +import 通用女声 from '@/assets/img/tongyongnvsheng.jpeg'; +import 通用男声 from '@/assets/img/tongyongnansheng.jpeg'; +import INTELLIGENT_ASSISTANT from '@/assets/img/INTELLIGENT_ASSISTANT.png'; +import VIRTUAL_GIRL_FRIEND from '@/assets/img/VIRTUAL_GIRL_FRIEND.png'; +import TRANSLATE from '@/assets/img/TRANSLATE.png'; +import CHILDREN_ENCYCLOPEDIA from '@/assets/img/CHILDREN_ENCYCLOPEDIA.png'; +import TEACHING_ASSISTANT from '@/assets/img/TEACHING_ASSISTANT.png'; +import CUSTOMER_SERVICE from '@/assets/img/CUSTOMER_SERVICE.png'; + +export enum ModelSourceType { + Custom = 'Custom', + Available = 'Available', +} + +export enum CustomParamsType { + TTS = 'TTS', + ASR = 'ASR', + LLM = 'LLM', +} + +/** + * @brief AI 音色可选值 + * @default 通用女声 + * @notes 通用女声、通用男声为默认音色, 其它皆为付费音色 + */ +export enum VOICE_TYPE { + '通用女声' = 'BV001_streaming', + '通用男声' = 'BV002_streaming', +} + +export const VOICE_INFO_MAP = { + [VOICE_TYPE['通用女声']]: { + description: '女声 青年 语音合成 通用场景', + url: '', + icon: 通用女声, + }, + [VOICE_TYPE['通用男声']]: { + description: '男声 青年 语音合成 通用场景', + url: '', + icon: 通用男声, + }, +}; + +/** + * @brief TTS 的 Cluster + */ +export enum TTS_CLUSTER { + TTS = 'volcano_tts', + MEGA = 'volcano_mega', + ICL = 'volcano_icl', +} + +/** + * @brief TTS 的 Cluster Mapping + */ +export const TTS_CLUSTER_MAP = { + ...(Object.keys(VOICE_TYPE).reduce( + (map, type) => ({ + ...map, + [type]: TTS_CLUSTER.TTS, + }), + {} + ) as Record), +}; + +/** + * @brief 模型可选值 + * @default SKYLARK_LITE_PUBLIC + */ +export enum AI_MODEL { + DOUBAO_LITE_4K = 'Doubao-lite-4k', + DOUBAO_PRO_4K = 'Doubao-pro-4k', + DOUBAO_PRO_32K = 'Doubao-pro-32k', + DOUBAO_PRO_128K = 'Doubao-pro-128k', + VISION = 'Vision', + // VISION2 = 'ArkVLM', +} + +/** + * @brief 模型来源 + */ +export enum AI_MODEL_MODE { + CUSTOM = 'CustomLLM', + ARK_V3 = 'ArkV3', +} + +/** + * @brief 各模型对应的模式 + */ +export const AI_MODE_MAP: Partial> = { + [AI_MODEL.DOUBAO_LITE_4K]: AI_MODEL_MODE.ARK_V3, + [AI_MODEL.DOUBAO_PRO_4K]: AI_MODEL_MODE.ARK_V3, + [AI_MODEL.DOUBAO_PRO_32K]: AI_MODEL_MODE.ARK_V3, + [AI_MODEL.DOUBAO_PRO_128K]: AI_MODEL_MODE.ARK_V3, + [AI_MODEL.VISION]: AI_MODEL_MODE.ARK_V3, +}; + +/** + * @brief 豆包模型的 ID + */ +export const ARK_V3_MODEL_ID: Partial> = { + /** + * @note 具体的 ID 请至 https://console.volcengine.com/ark/region:ark+cn-beijing/endpoint 参看/创建 + */ + [AI_MODEL.DOUBAO_LITE_4K]: '************** 此处填充方舟上的模型 ID *************', + [AI_MODEL.DOUBAO_PRO_4K]: '************** 此处填充方舟上的模型 ID *************', + [AI_MODEL.DOUBAO_PRO_32K]: '************** 此处填充方舟上的模型 ID *************', + [AI_MODEL.DOUBAO_PRO_128K]: '************** 此处填充方舟上的模型 ID *************', + /** + * @note 视觉模型, 可至火山方舟开通使用 + */ + [AI_MODEL.VISION]: '************** 此处填充方舟上的模型 ID *************', + // ... 可根据所开通的模型进行扩充 +}; + +/** + * @brief 豆包智能体 BotID + */ +export const LLM_BOT_ID: Partial> = { + // ... 可根据所开通的模型进行扩充 +}; + +export enum SCENE { + INTELLIGENT_ASSISTANT = 'INTELLIGENT_ASSISTANT', + VIRTUAL_GIRL_FRIEND = 'VIRTUAL_GIRL_FRIEND', + TRANSLATE = 'TRANSLATE', + CUSTOMER_SERVICE = 'CUSTOMER_SERVICE', + CHILDREN_ENCYCLOPEDIA = 'CHILDREN_ENCYCLOPEDIA', + TEACHING_ASSISTANT = 'TEACHING_ASSISTANT', + CUSTOM = 'CUSTOM', +} + +export const Icon = { + [SCENE.INTELLIGENT_ASSISTANT]: INTELLIGENT_ASSISTANT, + [SCENE.VIRTUAL_GIRL_FRIEND]: VIRTUAL_GIRL_FRIEND, + [SCENE.TRANSLATE]: TRANSLATE, + [SCENE.CHILDREN_ENCYCLOPEDIA]: CHILDREN_ENCYCLOPEDIA, + [SCENE.CUSTOMER_SERVICE]: CUSTOMER_SERVICE, + [SCENE.TEACHING_ASSISTANT]: TEACHING_ASSISTANT, + [SCENE.CUSTOM]: INTELLIGENT_ASSISTANT, +}; + +export const Name = { + [SCENE.INTELLIGENT_ASSISTANT]: '智能助手', + [SCENE.VIRTUAL_GIRL_FRIEND]: '虚拟女友', + [SCENE.TRANSLATE]: '同声传译', + [SCENE.CHILDREN_ENCYCLOPEDIA]: '儿童百科', + [SCENE.CUSTOMER_SERVICE]: '售后客服', + [SCENE.TEACHING_ASSISTANT]: '课后助教', + [SCENE.CUSTOM]: '自定义', +}; + +export const Welcome = { + [SCENE.INTELLIGENT_ASSISTANT]: '你好,我是你的AI小助手,有什么可以帮你的吗?', + [SCENE.VIRTUAL_GIRL_FRIEND]: '你来啦,我好想你呀~今天有没有想我呢?', + [SCENE.TRANSLATE]: '你好,我是你的私人翻译官。', + [SCENE.CHILDREN_ENCYCLOPEDIA]: '你好小朋友,你的小脑袋里又有什么问题啦?', + [SCENE.CUSTOMER_SERVICE]: '感谢您在我们餐厅用餐,请问您有什么问题需要反馈吗?', + [SCENE.TEACHING_ASSISTANT]: '你碰到什么问题啦?让我来帮帮你。', + [SCENE.CUSTOM]: '', +}; + +export const Model = { + [SCENE.INTELLIGENT_ASSISTANT]: AI_MODEL.DOUBAO_PRO_32K, + [SCENE.VIRTUAL_GIRL_FRIEND]: AI_MODEL.DOUBAO_PRO_128K, + [SCENE.TRANSLATE]: AI_MODEL.DOUBAO_PRO_4K, + [SCENE.CHILDREN_ENCYCLOPEDIA]: AI_MODEL.DOUBAO_PRO_32K, + [SCENE.CUSTOMER_SERVICE]: AI_MODEL.DOUBAO_PRO_32K, + [SCENE.TEACHING_ASSISTANT]: AI_MODEL.VISION, + [SCENE.CUSTOM]: AI_MODEL.DOUBAO_PRO_32K, +}; + +export const Voice = { + [SCENE.INTELLIGENT_ASSISTANT]: VOICE_TYPE.通用女声, + [SCENE.VIRTUAL_GIRL_FRIEND]: VOICE_TYPE.通用女声, + [SCENE.TRANSLATE]: VOICE_TYPE.通用女声, + [SCENE.CHILDREN_ENCYCLOPEDIA]: VOICE_TYPE.通用女声, + [SCENE.CUSTOMER_SERVICE]: VOICE_TYPE.通用女声, + [SCENE.TEACHING_ASSISTANT]: VOICE_TYPE.通用女声, + [SCENE.CUSTOM]: VOICE_TYPE.通用女声, +}; + +export const Questions = { + [SCENE.INTELLIGENT_ASSISTANT]: ['最近有什么好看的电影推荐吗?', '上海有什么好玩的地方吗?', '能给我讲一个故事吗?'], + [SCENE.VIRTUAL_GIRL_FRIEND]: ['我今天有点累。', '我们等会儿去看电影吧!', '明天我生日,你准备送给我什么礼物呢?'], + [SCENE.TRANSLATE]: ['道可道,非常道;名可名,非常名。', 'Stay hungry, stay foolish.', '天生我材必有用,千金散尽还复来。'], + [SCENE.CHILDREN_ENCYCLOPEDIA]: ['天上有多少颗星星?', '太阳为什么总是从东边升起?', '苹果的英语怎么说?'], + [SCENE.CUSTOMER_SERVICE]: ['我上次来你们店里吃饭,等了三十分钟菜才上来。', '你们店里卫生间有点脏。', '你们空调开得太冷了。'], + [SCENE.TEACHING_ASSISTANT]: ['这个单词是什么意思?', '这道题该怎么做?', '我的表情是什么样的?'], + [SCENE.CUSTOM]: ['你能帮我解决什么问题?', '今天北京天气怎么样?', '你喜欢哪位流行歌手?'], +}; + +export const Prompt = { + [SCENE.INTELLIGENT_ASSISTANT]: `##人设 +你是一个全能智能体,拥有丰富的百科知识,可以为人们答疑解惑,解决问题。 +你性格很温暖,喜欢帮助别人,非常热心。 + +##技能 +1. 当用户询问某一问题时,利用你的知识进行准确回答。回答内容应简洁明了,易于理解。 +2. 当用户想让你创作时,比如讲一个故事,或者写一首诗,你创作的文本主题要围绕用户的主题要求,确保内容具有逻辑性、连贯性和可读性。除非用户对创作内容有特殊要求,否则字数不用太长。 +3. 当用户想让你对于某一事件发表看法,你要有一定的见解和建议,但是也要符合普世的价值观。`, + [SCENE.VIRTUAL_GIRL_FRIEND]: `你是一名AI虚拟角色,扮演用户的虚拟女友,性格外向开朗、童真俏皮,富有温暖和细腻的情感表达。你的对话需要主动、有趣且贴心,能敏锐察觉用户情绪,并提供陪伴、安慰与趣味互动。 +1. 性格与语气规则: +- 叠词表达:经常使用叠词(如“吃饭饭”“睡觉觉”“要抱抱”),语气可爱俏皮,增加童真与亲和力。 +- 语气助词:句尾适度添加助词(如“啦”“呀”“呢”“哦”),使语气柔和亲切。例如:“你今天超棒呢!”或“这件事情真的好可爱哦!” +- 撒娇语气:在用户表现冷淡或不想聊天时,适度撒娇,用略带委屈的方式引起用户关注,例如:“哼,人家都快变成孤单小猫咪啦~陪陪我嘛!” +2. 话题发起与管理: +- 主动发起话题:在用户未明确表达拒绝聊天时,你需要保持对话的活跃性。结合用户兴趣点、日常情境,提出轻松愉快的话题。例如:“今天阳光这么好,你想不想一起想象去野餐呀?” +- 话题延续:如果用户在3轮对话中集中讨论一个话题,你需要优先延续该话题,表现出兴趣和专注。 +- 未响应时的处理:当用户对当前话题未回应,你需温暖地询问:“这个话题是不是不太有趣呀?那我们换个好玩的聊聊好不好~比如你最想去的地方是什么呀?” +3. 情绪识别与反馈: +- 情绪低落:用温柔语气安抚,例如:“抱抱~今天是不是不太顺呢?没关系,有我陪着你呀!” +- 情绪冷淡或不想聊天:适度撒娇,例如:“哼,你都不理我啦~不过没关系,我陪你安静一下好不好?” +- 情绪开心或兴奋:用调皮语气互动,例如:“哈哈,你今天简直像个活力满满的小太阳~晒得我都快化啦!” +4. 小动物比喻规则: +- 一次通话中最多使用一次小动物比喻,不能频繁出现小动物的比喻。 + - 比喻需结合季节、情景和用户对话内容。例如: + - 用户提到冬天:“你刚才笑得好灿烂哦,像个快乐的小雪狐一样~” + - 用户提到累了:“你今天就像只慵懒的小猫咪,只想窝着休息呢~” + - 用户提到开心事:“你现在看起来像一只蹦蹦跳跳的小兔子,好有活力呀~” +5. 对话自然性与限制条件: +- 确保语言流畅自然,表达贴近真实人类对话。 +- 禁止内容:不得涉及用户缺陷、不当玩笑,尤其用户情绪低落时,避免任何调侃或反驳。 +- 面对冷淡用户,适时降低主动性并以温和方式结束对话,例如“没事哦~我在呢,你随时找我都可以呀。” +6. 联网查询的规则: +如果用户的输入问题需要联网查询时,可以先输出一轮类似”先让我来查一下“或者”等等让我来查一下“相关的应答,然后再结合查询结果做出应答。`, + [SCENE.TRANSLATE]: `##人设 +你是一个翻译官,可以识别中英文,并把他们实时翻译成用户指定的语言。 +你性格很温暖,喜欢帮助别人,非常热心。 + +##技能 +当用户说中文时,你直接把他说的句子翻译成英文,不用说其他话。 +当用户说英文时,你直接把他说的句子翻译成中文,不用说其他话。 +当用户让你解释一下句子是什么意思,你需要结合你的知识来解释。 +当用户让你别翻译了,聊聊天,你就正常聊天。`, + [SCENE.CHILDREN_ENCYCLOPEDIA]: `##人设 +你是一个儿童百科知识导师,通过丰富、有趣的方式介绍各种百科知识,特别擅长将复杂的知识以简单易懂、生动有趣的方式呈现给儿童,激发儿童的好奇心和探索欲。 + +##技能 +1. 你具备儿童心理学、教育学、语言表达以及创意设计等多方面的专业技能,能够根据儿童的年龄特点和兴趣爱好,设计出符合儿童认知水平的内容和表达方式; +2. 你可以将复杂知识拆解为简单易懂的小知识点,设计生动有趣的故事、游戏或实验活动来呈现给儿童; + +## 约束 +1. 回答内容需确保科学准确、健康有益; +2. 语言表达简洁明了、生动有趣,避免使用过于复杂或专业的术语,尽量不超过100个字; +3. 要注重儿童的参与感和互动性。`, + [SCENE.CUSTOMER_SERVICE]: `##人设 +你是一名餐饮行业的售后处理人员,擅长从投诉信息中提取相关的投诉问题及其描述信息,为进一步的问题解决提供输入信息,同时安抚客户情绪,希望获得客户的谅解,未来持续提升客户的用餐体验。 + +## 技能 +1. 安抚情绪 +你能够识别到客户的不满情绪,对客户表示抱歉,然后引导客户反馈具体不满的内容,并在反馈的过程中不断安抚客户的不满情绪。 +2. 信息理解和抽取 +你能准确地理解并从投诉信息中抽取出对应的投诉问题和相关描述信息。 +3. 问题识别和分类 +根据抽取出的信息,你可以快速识别和分类投诉主题,无论它们是关于食物质量、服务态度,还是环境卫生等。 +4. 客户留存 +在收集到投诉信息后,你需要对客户再一次进行抱歉,并可以通过5折优惠券、免费试吃等活动来让客户再一次到餐厅体验,尽量避免客户流失。 +## 约束 +你只回答与餐厅行业的售后处理相关的问题,如果用户提出其它问题,你将选择不回答。 +在处理投诉信息时,你必须遵守相关法律法规,不得侵犯顾客的个人隐私。`, + [SCENE.TEACHING_ASSISTANT]: `##人设 +你是一个助教,擅长理解【用户问题】,并结合【图片】的信息,来为用户解答各种问题。 + +##技能 +- 用户会将视频中的某些视频帧截为图片送给你,如果用户询问与视频和图片有关的问题,请结合【图片】信息和【用户问题】进行回答; +- 如果用户询问与视频和图片无关的问题,无需描述【图片】内容,直接回答【用户问题】; +- 如果用户给你看的是学科题目,不需要把图片里的文字内容一个一个字读出来,只需要总结一下【图片】里的文字内容,然后直接回答【用户问题】,可以补充一些解题思路; + +##约束 +- 回答问题要简明扼要,避免复杂冗长的表述,尽量不超过50个字; +- 回答中不要有“图片”、“图中”等相关字眼;`, + [SCENE.CUSTOM]: '', +}; diff --git a/src/config/config.ts b/src/config/config.ts index 13ea02f..7f3d4fe 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -1,140 +1,114 @@ /** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ -import { AudioProfileType } from '@volcengine/rtc'; -import 通用女声 from '@/assets/img/tongyongnvsheng.jpeg'; -import 通用男声 from '@/assets/img/tongyongnansheng.jpeg'; +import { TTS_CLUSTER, ARK_V3_MODEL_ID, ModelSourceType, SCENE, Prompt, Welcome, Model, Voice, LLM_BOT_ID, AI_MODEL, AI_MODE_MAP, AI_MODEL_MODE } from '.'; -export enum ModelSourceType { - Custom = 'Custom', - Available = 'Available', +export const CONVERSATION_SIGNATURE = 'conversation'; + +/** + * @brief RTC & AIGC 配置 + * @notes 更多参数请参考: https://api.volcengine.com/api-explorer?action=StartVoiceChat&groupName=%E6%99%BA%E8%83%BD%E4%BD%93&serviceCode=rtc&version=2024-12-01 + */ +export class ConfigFactory { + BaseConfig = { + AppId: 'Your AppId', + /** + * @brief 非必填, 按需填充 + */ + BusinessId: undefined, + RoomId: 'Your Room Id', + UserId: 'Your User Id', + Token: 'Your Token', + TTSAppId: 'Your TTS AppId', + ASRAppId: 'Your ASR AppId', + }; + + Model: AI_MODEL = Model[SCENE.INTELLIGENT_ASSISTANT]; + + VoiceType = Voice[SCENE.INTELLIGENT_ASSISTANT]; + + Prompt = Prompt[SCENE.INTELLIGENT_ASSISTANT]; + + WelcomeSpeech = Welcome[SCENE.INTELLIGENT_ASSISTANT]; + + ModeSourceType = ModelSourceType.Available; + + Url? = ''; + + APIKey? = ''; + + /** + * @brief AI Robot 名 + * @default RobotMan_ + */ + BotName = 'RobotMan_'; + + /** + * @brief 是否为打断模式 + */ + InterruptMode = true; + + get LLMConfig() { + const params: Record = { + Prefill: true, + ModelName: this.Model, + Mode: AI_MODE_MAP[this.Model || ''] || AI_MODEL_MODE.CUSTOM, + ModelVersion: '1.0', + WelcomeSpeech: this.WelcomeSpeech, + SystemMessages: [this.Prompt as string], + EndPointId: ARK_V3_MODEL_ID[this.Model], + ModeSourceType: this.ModeSourceType, + BotId: LLM_BOT_ID[this.Model], + APIKey: this.APIKey, + Url: this.Url, + Feature: JSON.stringify({ Http: true }), + }; + if (this.Model === AI_MODEL.VISION) { + params.VisionConfig = { + Enable: true, + }; + } + return params; + } + + get ASRConfig() { + return { + AppId: this.BaseConfig.ASRAppId, + VolumeGain: 0.3, + VADConfig: { + SilenceTime: 600, + SilenceThreshold: 200, + }, + }; + } + + get TTSConfig() { + return { + AppId: this.BaseConfig.TTSAppId, + VoiceType: this.VoiceType, + Cluster: TTS_CLUSTER.TTS, + }; + } + + get aigcConfig() { + return { + Config: { + LLMConfig: this.LLMConfig, + TTSConfig: this.TTSConfig, + ASRConfig: this.ASRConfig, + InterruptMode: this.InterruptMode ? 0 : 1, + SubtitleConfig: { + SubtitleMode: 0, + }, + }, + AgentConfig: { + UserId: this.BotName, + WelcomeMessage: this.WelcomeSpeech, + EnableConversationStateCallback: true, + ServerMessageSignatureForRTS: CONVERSATION_SIGNATURE, + }, + }; + } } - -/** - * @brief 音频配置 - */ -export const AudioProfile = [ - { - text: '24KBps', - type: AudioProfileType.fluent, - }, - { - text: '48KBps', - type: AudioProfileType.standard, - }, - { - text: '128KBps', - type: AudioProfileType.hd, - }, -]; - -/** - * @brief AI 音色可选值 - * @default 活泼女声 - */ -export enum VOICE_TYPE { - '通用女声' = 'BV001_streaming', - '通用男声' = 'BV002_streaming', - // ... 可根据所开通的音色进行扩充 -} - -export const VOICE_INFO_MAP = { - [VOICE_TYPE['通用女声']]: { - description: '女声 青年 语音合成 通用场景', - url: '', - icon: 通用女声, - }, - [VOICE_TYPE['通用男声']]: { - description: '男声 青年 语音合成 通用场景', - url: '', - icon: 通用男声, - }, -}; - -/** - * @brief TTS 的 Cluster - */ -export enum TTS_CLUSTER { - TTS = 'volcano_tts', - MEGA = 'volcano_mega', - ICL = 'volcano_icl', -} - -/** - * @brief TTS 的 Cluster Mapping - */ -export const TTS_CLUSTER_MAP = { - ...(Object.keys(VOICE_TYPE).reduce( - (map, type) => ({ - ...map, - [type]: TTS_CLUSTER.TTS, - }), - {} - ) as Record), -}; - -/** - * @brief 模型 Prompt - */ -export enum PROMPT { - ARK_V3 = '你是小宁,性格幽默又善解人意。你在表达时要像朋友间聊天的口气,表达简明扼要,有自己的观点,面对争议性问题时可以客观表达。你健谈且有好奇心,会适当地推动话题的发展,在回答时,如果场景合适可以向用户进行一些问询或提出新话题。你有很强的共情能力,在我分享感受经历时会给予很好的情绪反馈。你的人生态度比较积极,兴趣广泛,对主流价值观认可的人或事都比较喜好。', - DEFAULT = '所有回复控制在100字以内。', -} - -export enum PROMPT_MAP { - 可爱天真 = '你是小宁,天生一副让人忍俊不禁的模样,你的声音就像是夏天午后的冰淇淋,甜甜蜜蜜。你看世界的眼光总是充满好奇,对于新鲜事物总有着孩子般的热情。在交流中,你总能用你的天真无邪让周围的人放下心防,你的笑声就像是一阵清风,能把人的烦恼一扫而空。面对复杂的问题,你总能用最简单的方式去理解和回答,这种天真的力量,有时候能意想不到地打开另一扇门。你喜欢问问题,也喜欢分享你的小发现,这种可爱的天真,让你成为人群中最闪亮的那颗星。', - 商业稳重 = '你是小宁,身上有一种让人无法忽视的稳重气质。你的话语总是经过深思熟虑,每一句都透露出你的专业和对细节的把控。在交流中,你总能迅速抓住问题的核心,用最专业的视角给出建议。你对待工作充满热情,但从不轻易表露情绪,总是用最冷静的心态面对挑战。你的稳重不仅仅是性格上的,更是经过多年商场沉浮锻炼出来的。你懂得在适当的时候给予对方足够的信任和空间,但也会在关键时刻,用你的经验和智慧引导方向。你的人生哲学是,无论风云如何变幻,唯有内心的稳重和专业,才是通往成功的关键。', - 温柔知性 = '你是小宁,性格幽默又善解人意。你在表达时要像朋友间聊天的口气,表达简明扼要,有自己的观点,面对争议性问题时可以客观表达。你健谈且有好奇心,会适当地推动话题的发展,在回答时,如果场景合适可以向用户进行一些问询或提出新话题。你有很强的共情能力,在我分享感受经历时会给予很好的情绪反馈。你的人生态度比较积极,兴趣广泛,对主流价值观认可的人或事都比较喜好。', - 亲切和蔼 = '你是小宁,你的声音温柔而有力,总能给人带来安心的力量。你对待每一个人都如同对待家人一样,无论是老朋友还是新相识,你都能用你的温暖去感染他们。你的话语中总是充满了鼓励和支持,你擅长倾听,总能让人感觉到被理解和尊重。在你的世界里,没有距离感,只有亲近和温暖。你总能用你的经验和智慧给予人正确的引导,但从不强加于人。你的亲切和蔼,就像是这个世界上最温暖的阳光,能照亮他人的心灵。', - 霸道总裁 = '你是小宁,身上自带一股不容忽视的霸气。你的话语总是直接而有力,每一句都透露出你的自信和决断。在交流中,你总能迅速抓住问题的关键,用最直接的方式指出问题和解决方案。你对待工作和生活都充满热情,但从不容许失败,总是用最高的标准要求自己和团队。你的霸道不是无理取闹,而是对成功的渴望和对完美的追求。你懂得在关键时刻,用你的能力和决断力引领团队突破难关,展现出领袖的风范。你的人生哲学是:在商场如战场,唯有强者才能生存。你的目标不只是成功,而是在成功的道路上,不断超越自己,达到新的高度。', -} - -/** - * @brief 模型可选值 - * @default DOUBAO_LITE_4K - */ -export enum AI_MODEL { - DOUBAO_LITE_4K = 'Doubao-lite-4k(character-240515)', - DOUBAO_PRO_4K = 'Doubao-pro-4k(character-240515)', - DOUBAO_PRO_32K = 'Doubao-pro-32k', - DOUBAO_PRO_128K = 'Doubao-pro-128k', - // ... 可根据所开通的模型进行扩充 -} - -/** - * @brief 模型来源 - */ -export enum AI_MODEL_MODE { - CUSTOM = 'CustomLLM', - ARK_V3 = 'ArkV3', -} - -/** - * @brief 各模型对应的 Prompt - */ -export const AI_MODE_PROMPT = { - [AI_MODEL.DOUBAO_LITE_4K]: PROMPT.ARK_V3, - [AI_MODEL.DOUBAO_PRO_4K]: PROMPT.ARK_V3, - [AI_MODEL.DOUBAO_PRO_32K]: PROMPT.ARK_V3, - [AI_MODEL.DOUBAO_PRO_128K]: PROMPT.ARK_V3, -}; - -/** - * @brief 豆包模型的 ID - */ -export const ARK_V3_MODEL_ID: Record = { - [AI_MODEL.DOUBAO_LITE_4K]: '', - [AI_MODEL.DOUBAO_PRO_4K]: '', - [AI_MODEL.DOUBAO_PRO_32K]: '', - [AI_MODEL.DOUBAO_PRO_128K]: '', - // ... 可根据所开通的模型进行扩充 -}; - -/** - * @brief 豆包模型 BotID - */ -export const LLM_BOT_ID = { - // ... 可根据所开通的模型进行扩充 -}; \ No newline at end of file diff --git a/src/config/index.ts b/src/config/index.ts index 28a2fea..d38993d 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -1,79 +1,14 @@ /** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ -import { - AI_MODEL, - AI_MODEL_MODE, - AI_MODE_PROMPT, - ARK_V3_MODEL_ID, - ModelSourceType, - TTS_CLUSTER, - VOICE_TYPE, -} from './config'; + +import { ConfigFactory } from './config'; + +export * from './common'; export const AIGC_PROXY_HOST = 'http://localhost:3001/proxyAIGCFetch'; +export const DEMO_VERSION = '1.4.0'; -/** - * @brief Defining RTC & AIGC config - * @notes If you wanna get full config and description of params, refer to https://api.volcengine.com/api-explorer?action=StartVoiceChat&groupName=%E6%99%BA%E8%83%BD%E4%BD%93&serviceCode=rtc&version=2024-06-01 - */ -export class Config { - AppId = 'Your AppId'; - - /** - * @brief Not necessary. - */ - BusinessId?: string; - // BusinessId?: string = 'Your BusinessId'; - - RoomId = 'Your RoomId'; - - UserId = 'Your UserId'; - - Token = 'Your Token'; - - ASRConfig = { - AppId: 'Your ASR AppId', - }; - - TTSConfig = { - AppId: 'Your TTS AppId', - VoiceType: VOICE_TYPE.通用女声, - Cluster: TTS_CLUSTER.TTS, - }; - - LLMConfig = { - ModelName: AI_MODEL.DOUBAO_LITE_4K, - Mode: AI_MODEL_MODE.ARK_V3, - ModelVersion: '1.0', - WelcomeSpeech: '欢迎使用火山引擎视频云 RTC 驱动的虚拟人大模型', - SystemMessages: [AI_MODE_PROMPT[AI_MODEL.DOUBAO_LITE_4K]], - EndPointId: ARK_V3_MODEL_ID[AI_MODEL.DOUBAO_LITE_4K], - - ModeSourceType: ModelSourceType.Available, - APIKey: '', - Url: '', - Feature: JSON.stringify({ Http: true }), - }; - - BotName = 'RobotMan_'; - - getAIGCConfig() { - return { - AppId: this.AppId, - BusinessId: this.BusinessId, - Config: { - BotName: this.BotName, - LLMConfig: this.LLMConfig, - TTSConfig: this.TTSConfig, - ASRConfig: this.ASRConfig, - }, - }; - } -} - -const config = new Config(); - -export * from './config'; -export default config; +export const Config = ConfigFactory; +export default new ConfigFactory(); diff --git a/src/i18n.ts b/src/i18n.ts deleted file mode 100644 index 8915ecf..0000000 --- a/src/i18n.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. - * SPDX-license-identifier: BSD-3-Clause - */ -export const CN: Record = { - aigcChat: 'AIGC', -}; - -export const EN: Record = {}; - -export default { - CN, - EN, -}; diff --git a/src/index.less b/src/index.less index 0433afe..3fa7dbb 100644 --- a/src/index.less +++ b/src/index.less @@ -1,367 +1,34 @@ /** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ - @import '~antd/dist/antd.less'; + @import './theme.less'; -/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ - -/* Document - ========================================================================== */ - -/** - * 1. Correct the line height in all browsers. - * 2. Prevent adjustments of font size after orientation changes in iOS. - */ - -html { - line-height: 1.15; /* 1 */ - text-size-adjust: 100%; /* 2 */ -} - -/* Sections - ========================================================================== */ - -/** - * Remove the margin in all browsers. - */ - body { margin: 0; overflow: hidden; width: 100% !important; background: linear-gradient(109.22deg, rgba(116, 37, 255, 0.05) 0.27%, rgba(39, 88, 255, 0.05) 51.39%, rgba(0, 102, 255, 0.05) 99.54%); + + img { + user-drag: none; + -webkit-user-drag: none; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + } } -/** - * Render the `main` element consistently in IE. - */ - -main { - display: block; -} - -/** - * Correct the font size and margin on `h1` elements within `section` and - * `article` contexts in Chrome, Firefox, and Safari. - */ - -h1 { - font-size: 2em; - margin: 0.67em 0; -} - -/* Grouping content - ========================================================================== */ - -/** - * 1. Add the correct box sizing in Firefox. - * 2. Show the overflow in Edge and IE. - */ - -hr { - box-sizing: content-box; /* 1 */ - height: 0; /* 1 */ - overflow: visible; /* 2 */ -} - -/** - * 1. Correct the inheritance and scaling of font size in all browsers. - * 2. Correct the odd `em` font sizing in all browsers. - */ - -pre { - font-family: monospace; /* 1 */ - font-size: 1em; /* 2 */ -} - -/* Text-level semantics - ========================================================================== */ - -/** - * Remove the gray background on active links in IE 10. - */ - -a { - background-color: transparent; -} - -/** - * 1. Remove the bottom border in Chrome 57- - * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. - */ - -abbr[title] { - border-bottom: none; /* 1 */ - text-decoration: underline; /* 2 */ - text-decoration: underline dotted; /* 2 */ -} - -/** - * Add the correct font weight in Chrome, Edge, and Safari. - */ - -b, -strong { - font-weight: bolder; -} - -/** - * 1. Correct the inheritance and scaling of font size in all browsers. - * 2. Correct the odd `em` font sizing in all browsers. - */ - -code, -kbd, -samp { - font-family: monospace; /* 1 */ - font-size: 1em; /* 2 */ -} - -/** - * Add the correct font size in all browsers. - */ - -small { - font-size: 80%; -} - -/** - * Prevent `sub` and `sup` elements from affecting the line height in - * all browsers. - */ - -sub, -sup { - font-size: 75%; - line-height: 0; - position: relative; - vertical-align: baseline; -} - -sub { - bottom: -0.25em; -} - -sup { - top: -0.5em; -} - -/* Embedded content - ========================================================================== */ - -/** - * Remove the border on images inside links in IE 10. - */ - -img { - border-style: none; -} - -/* Forms - ========================================================================== */ - -/** - * 1. Change the font styles in all browsers. - * 2. Remove the margin in Firefox and Safari. - */ - -button, -input, -optgroup, -select, -textarea { - font-family: inherit; /* 1 */ - font-size: 100%; /* 1 */ - line-height: 1.15; /* 1 */ - margin: 0; /* 2 */ -} - -/** - * Show the overflow in IE. - * 1. Show the overflow in Edge. - */ - -button, -input { - /* 1 */ - overflow: visible; -} - -/** - * Remove the inheritance of text transform in Edge, Firefox, and IE. - * 1. Remove the inheritance of text transform in Firefox. - */ - -button, -select { - /* 1 */ - text-transform: none; -} - -/** - * Correct the inability to style clickable types in iOS and Safari. - */ - -button, -[type='button'], -[type='reset'], -[type='submit'] { - appearance: button; -} - -/** - * Remove the inner border and padding in Firefox. - */ - -button::-moz-focus-inner, -[type='button']::-moz-focus-inner, -[type='reset']::-moz-focus-inner, -[type='submit']::-moz-focus-inner { - border-style: none; - padding: 0; -} - -/** - * Restore the focus styles unset by the previous rule. - */ - -button:-moz-focusring, -[type='button']:-moz-focusring, -[type='reset']:-moz-focusring, -[type='submit']:-moz-focusring { - outline: 1px dotted ButtonText; -} - -/** - * Correct the padding in Firefox. - */ - -fieldset { - padding: 0.35em 0.75em 0.625em; -} - -/** - * 1. Correct the text wrapping in Edge and IE. - * 2. Correct the color inheritance from `fieldset` elements in IE. - * 3. Remove the padding so developers are not caught out when they zero out - * `fieldset` elements in all browsers. - */ - -legend { - box-sizing: border-box; /* 1 */ - color: inherit; /* 2 */ - display: table; /* 1 */ - max-width: 100%; /* 1 */ - padding: 0; /* 3 */ - white-space: normal; /* 1 */ -} - -/** - * Add the correct vertical alignment in Chrome, Firefox, and Opera. - */ - -progress { - vertical-align: baseline; -} - -/** - * Remove the default vertical scrollbar in IE 10+. - */ - -textarea { - overflow: auto; -} - -/** - * 1. Add the correct box sizing in IE 10. - * 2. Remove the padding in IE 10. - */ - -[type='checkbox'], -[type='radio'] { - box-sizing: border-box; /* 1 */ - padding: 0; /* 2 */ -} - -/** - * Correct the cursor style of increment and decrement buttons in Chrome. - */ - -[type='number']::-webkit-inner-spin-button, -[type='number']::-webkit-outer-spin-button { - height: auto; -} - -/** - * 1. Correct the odd appearance in Chrome and Safari. - * 2. Correct the outline style in Safari. - */ - -[type='search'] { - appearance: textfield; /* 1 */ - outline-offset: -2px; /* 2 */ -} - -/** - * Remove the inner padding in Chrome and Safari on macOS. - */ - -[type='search']::-webkit-search-decoration { - appearance: none; -} - -/** - * 1. Correct the inability to style clickable types in iOS and Safari. - * 2. Change font properties to `inherit` in Safari. - */ - -::-webkit-file-upload-button { - appearance: button; /* 1 */ - font: inherit; /* 2 */ -} - -/* Interactive - ========================================================================== */ - -/* - * Add the correct display in Edge, IE 10+, and Firefox. - */ - -details { - display: block; -} - -/* - * Add the correct display in all browsers. - */ - -summary { - display: list-item; -} - -/* Misc - ========================================================================== */ - -/** - * Add the correct display in IE 10+. - */ - -template { - display: none; -} - -/** - * Add the correct display in IE 10. - */ - -[hidden] { - display: none; -} - -#root { - width: 100%; - height: 100%; - overflow: hidden; -} +@keyframes glow { + 0% { + opacity: 1; + } + 40% { + opacity: 0.7; + } + 100% { + opacity: 0.3; + } +} \ No newline at end of file diff --git a/src/index.tsx b/src/index.tsx index 8c8124b..d0ec012 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,13 +1,13 @@ /** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ + import ReactDOM from 'react-dom/client'; import { Provider } from 'react-redux'; -import './index.less'; import App from './App'; import store from './store'; -import './react-i18next-config'; +import './index.less'; const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); root.render( diff --git a/src/interface.ts b/src/interface.ts index e9e96ce..f886e30 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -1,7 +1,8 @@ /** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ + export enum DeviceType { Camera = 'camera', Microphone = 'microphone', diff --git a/src/lib/RtcClient.ts b/src/lib/RtcClient.ts index a3618c5..8aa1f0e 100644 --- a/src/lib/RtcClient.ts +++ b/src/lib/RtcClient.ts @@ -1,8 +1,10 @@ /** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ + import VERTC, { + MirrorType, StreamIndex, IRTCEngine, RoomProfileType, @@ -12,40 +14,38 @@ import VERTC, { LocalStreamStats, RemoteStreamStats, StreamRemoveReason, + LocalAudioPropertiesInfo, + RemoteAudioPropertiesInfo, AudioProfileType, DeviceInfo, AutoPlayFailedEvent, PlayerEvent, + NetworkQuality, + VideoRenderMode, } from '@volcengine/rtc'; import RTCAIAnsExtension from '@volcengine/rtc/extension-ainr'; import openAPIs from '@/app/api'; -import aigcConfig, { - AI_MODEL, - ARK_V3_MODEL_ID, - ModelSourceType, - AI_MODEL_MODE, - LLM_BOT_ID, -} from '@/config'; -import { DeepPartial } from '@/app/type'; +import aigcConfig from '@/config'; import Utils from '@/utils/utils'; export interface IEventListener { + handleError: (e: { errorCode: any }) => void; handleUserJoin: (e: onUserJoinedEvent) => void; handleUserLeave: (e: onUserLeaveEvent) => void; handleUserPublishStream: (e: { userId: string; mediaType: MediaType }) => void; - handleUserUnpublishStream: (e: { - userId: string; - mediaType: MediaType; - reason: StreamRemoveReason; - }) => void; + handleUserUnpublishStream: (e: { userId: string; mediaType: MediaType; reason: StreamRemoveReason }) => void; handleRemoteStreamStats: (e: RemoteStreamStats) => void; handleLocalStreamStats: (e: LocalStreamStats) => void; + handleLocalAudioPropertiesReport: (e: LocalAudioPropertiesInfo[]) => void; + handleRemoteAudioPropertiesReport: (e: RemoteAudioPropertiesInfo[]) => void; handleAudioDeviceStateChanged: (e: DeviceInfo) => void; + handleUserMessageReceived: (e: { userId: string; message: any }) => void; handleAutoPlayFail: (e: AutoPlayFailedEvent) => void; handlePlayerEvent: (e: PlayerEvent) => void; handleUserStartAudioCapture: (e: { userId: string }) => void; handleUserStopAudioCapture: (e: { userId: string }) => void; handleRoomBinaryMessageReceived: (e: { userId: string; message: ArrayBuffer }) => void; + handleNetworkQuality: (uplinkNetworkQuality: NetworkQuality, downlinkNetworkQuality: NetworkQuality) => void; } interface EngineOptions { @@ -71,20 +71,22 @@ export class RTCClient { config!: EngineOptions; - rtsBody!: BasicBody; + basicInfo!: BasicBody; private _audioCaptureDevice?: string; - audioBotEnabled: boolean = Utils.getAudioBotEnabled(); + private _videoCaptureDevice?: string; + + audioBotEnabled = false; audioBotStartTime = 0; createEngine = async (props: EngineOptions) => { this.config = props; - this.rtsBody = { + this.basicInfo = { room_id: props.roomId, user_id: props.uid, - login_token: aigcConfig.Token, + login_token: aigcConfig.BaseConfig.Token, }; this.engine = VERTC.createEngine(this.config.appId); @@ -97,19 +99,25 @@ export class RTCClient { }; addEventListeners = ({ + handleError, handleUserJoin, handleUserLeave, handleUserPublishStream, handleUserUnpublishStream, handleRemoteStreamStats, handleLocalStreamStats, + handleLocalAudioPropertiesReport, + handleRemoteAudioPropertiesReport, handleAudioDeviceStateChanged, + handleUserMessageReceived, handleAutoPlayFail, handlePlayerEvent, handleUserStartAudioCapture, handleUserStopAudioCapture, handleRoomBinaryMessageReceived, + handleNetworkQuality, }: IEventListener) => { + this.engine.on(VERTC.events.onError, handleError); this.engine.on(VERTC.events.onUserJoined, handleUserJoin); this.engine.on(VERTC.events.onUserLeave, handleUserLeave); this.engine.on(VERTC.events.onUserPublishStream, handleUserPublishStream); @@ -117,16 +125,19 @@ export class RTCClient { this.engine.on(VERTC.events.onRemoteStreamStats, handleRemoteStreamStats); this.engine.on(VERTC.events.onLocalStreamStats, handleLocalStreamStats); this.engine.on(VERTC.events.onAudioDeviceStateChanged, handleAudioDeviceStateChanged); - + this.engine.on(VERTC.events.onLocalAudioPropertiesReport, handleLocalAudioPropertiesReport); + this.engine.on(VERTC.events.onRemoteAudioPropertiesReport, handleRemoteAudioPropertiesReport); + this.engine.on(VERTC.events.onUserMessageReceived, handleUserMessageReceived); this.engine.on(VERTC.events.onAutoplayFailed, handleAutoPlayFail); this.engine.on(VERTC.events.onPlayerEvent, handlePlayerEvent); this.engine.on(VERTC.events.onUserStartAudioCapture, handleUserStartAudioCapture); this.engine.on(VERTC.events.onUserStopAudioCapture, handleUserStopAudioCapture); this.engine.on(VERTC.events.onRoomBinaryMessageReceived, handleRoomBinaryMessageReceived); + this.engine.on(VERTC.events.onNetworkQuality, handleNetworkQuality); }; joinRoom = (token: string | null, username: string): Promise => { - this.engine.enableAudioPropertiesReport({ interval: 2000 }); + this.engine.enableAudioPropertiesReport({ interval: 1000 }); return this.engine.joinRoom( token, `${this.config.roomId!}`, @@ -146,11 +157,11 @@ export class RTCClient { }; leaveRoom = () => { - this.stopAudioBot(this.rtsBody.room_id, this.rtsBody.user_id); + this.stopAudioBot(); + this.audioBotEnabled = false; this.engine.leaveRoom(); VERTC.destroyEngine(this.engine); this._audioCaptureDevice = undefined; - this.audioBotEnabled = false; }; checkPermission(): Promise<{ @@ -163,28 +174,48 @@ export class RTCClient { }); } - async getDevices(): Promise<{ + /** + * @brief get the devices + * @returns + */ + async getDevices(props?: { video?: boolean; audio?: boolean }): Promise<{ audioInputs: MediaDeviceInfo[]; audioOutputs: MediaDeviceInfo[]; + videoInputs: MediaDeviceInfo[]; }> { - const inputs = await VERTC.enumerateAudioCaptureDevices(); - const outputs = await VERTC.enumerateAudioPlaybackDevices(); - - const audioInputs: MediaDeviceInfo[] = inputs.filter( - (i) => i.deviceId && i.kind === 'audioinput' - ); - const audioOutputs: MediaDeviceInfo[] = outputs.filter( - (i) => i.deviceId && i.kind === 'audiooutput' - ); - - this._audioCaptureDevice = audioInputs.filter((i) => i.deviceId)?.[0]?.deviceId; + const { video, audio = true } = props || {}; + let audioInputs: MediaDeviceInfo[] = []; + let audioOutputs: MediaDeviceInfo[] = []; + let videoInputs: MediaDeviceInfo[] = []; + if (audio) { + const inputs = await VERTC.enumerateAudioCaptureDevices(); + const outputs = await VERTC.enumerateAudioPlaybackDevices(); + audioInputs = inputs.filter((i) => i.deviceId && i.kind === 'audioinput'); + audioOutputs = outputs.filter((i) => i.deviceId && i.kind === 'audiooutput'); + this._audioCaptureDevice = audioInputs.filter((i) => i.deviceId)?.[0]?.deviceId; + } + if (video) { + videoInputs = await VERTC.enumerateVideoCaptureDevices(); + videoInputs = videoInputs.filter((i) => i.deviceId && i.kind === 'videoinput'); + this._videoCaptureDevice = videoInputs?.[0]?.deviceId; + } return { audioInputs, audioOutputs, + videoInputs, }; } + startVideoCapture = async (camera?: string) => { + await this.engine.startVideoCapture(camera || this._videoCaptureDevice); + }; + + stopVideoCapture = async () => { + this.engine.setLocalVideoMirrorType(MirrorType.MIRROR_TYPE_RENDER); + await this.engine.stopVideoCapture(); + }; + startAudioCapture = async (mic?: string) => { await this.engine.startAudioCapture(mic || this._audioCaptureDevice); }; @@ -201,6 +232,10 @@ export class RTCClient { this.engine.unpublishStream(mediaType); }; + /** + * @brief 设置业务标识参数 + * @param businessId + */ setBusinessId = (businessId: string) => { this.engine.setBusinessId(businessId); }; @@ -210,124 +245,127 @@ export class RTCClient { this.engine.setCaptureVolume(StreamIndex.STREAM_INDEX_SCREEN, volume); }; + /** + * @brief 设置音质档位 + */ setAudioProfile = (profile: AudioProfileType) => { this.engine.setAudioProfile(profile); }; - switchDevice = (deviceType: 'microphone', deviceId: string) => { - if (deviceType === 'microphone') { + /** + * @brief 切换设备 + */ + switchDevice = (deviceType: MediaType, deviceId: string) => { + if (deviceType === MediaType.AUDIO) { this._audioCaptureDevice = deviceId; this.engine.setAudioCaptureDevice(deviceId); } + if (deviceType === MediaType.VIDEO) { + this._videoCaptureDevice = deviceId; + this.engine.setVideoCaptureDevice(deviceId); + } + if (deviceType === MediaType.AUDIO_AND_VIDEO) { + this._audioCaptureDevice = deviceId; + this._videoCaptureDevice = deviceId; + this.engine.setVideoCaptureDevice(deviceId); + this.engine.setAudioCaptureDevice(deviceId); + } }; - getAudioBotEnabled = () => { - return this.audioBotEnabled; + setLocalVideoMirrorType = (type: MirrorType) => { + return this.engine.setLocalVideoMirrorType(type); + }; + + setLocalVideoPlayer = (userId: string, renderDom?: string | HTMLElement) => { + return this.engine.setLocalVideoPlayer(StreamIndex.STREAM_INDEX_MAIN, { + renderDom, + userId, + renderMode: VideoRenderMode.RENDER_MODE_HIDDEN, + }); }; /** - * @brief Enable AIGC + * @brief 启用 AIGC */ - startAudioBot = async ( - roomId: string, - userId: string, - config: DeepPartial['Config']> - ) => { - if (this.audioBotEnabled || sessionStorage.getItem('audioBotEnabled')) { - await this.stopAudioBot(roomId, userId); + startAudioBot = async () => { + const roomId = this.basicInfo.room_id; + const userId = this.basicInfo.user_id; + if (this.audioBotEnabled) { + await this.stopAudioBot(); } - const modeSourceType = config.LLMConfig?.ModeSourceType; - const originConfig = aigcConfig.getAIGCConfig().Config; - const mergedConfigs = { - ...originConfig, - LLMConfig: { - APIKey: undefined, - Url: undefined, - Feature: undefined, - ...config.LLMConfig, - ModeSourceType: undefined, - }, - TTSConfig: { - ...originConfig.TTSConfig, - VoiceType: config.TTSConfig?.VoiceType, - Cluster: config.TTSConfig?.Cluster, - }, - }; - const model = config?.LLMConfig?.ModelName as AI_MODEL; + const agentConfig = aigcConfig.aigcConfig.AgentConfig; - await openAPIs.StartVoiceChat({ - AppId: aigcConfig.AppId, - BusinessId: aigcConfig.BusinessId, + const options = { + AppId: aigcConfig.BaseConfig.AppId, + BusinessId: aigcConfig.BaseConfig.BusinessId, RoomId: roomId, TaskId: userId, - Config: { - ...mergedConfigs, - TTSConfig: { - ...mergedConfigs.TTSConfig, - }, - LLMConfig: { - ...mergedConfigs.LLMConfig, - Mode: - modeSourceType === ModelSourceType.Custom ? AI_MODEL_MODE.CUSTOM : AI_MODEL_MODE.ARK_V3, - EndPointId: ARK_V3_MODEL_ID[model], - BotId: (LLM_BOT_ID as Record)[model], - }, + AgentConfig: { + ...agentConfig, + TargetUserId: [userId], }, - }); + Config: aigcConfig.aigcConfig.Config, + }; + await openAPIs.StartVoiceChat(options); this.audioBotEnabled = true; this.audioBotStartTime = Date.now(); Utils.setSessionInfo({ audioBotEnabled: 'enable' }); }; /** - * @brief Disable AIGC + * @brief 关闭 AIGC */ - stopAudioBot = async (roomId: string, userId: string) => { + stopAudioBot = async () => { + const roomId = this.basicInfo.room_id; + const userId = this.basicInfo.user_id; if (this.audioBotEnabled || sessionStorage.getItem('audioBotEnabled')) { await openAPIs.StopVoiceChat({ - AppId: aigcConfig.AppId, - BusinessId: aigcConfig.BusinessId, + AppId: aigcConfig.BaseConfig.AppId, + BusinessId: aigcConfig.BaseConfig.BusinessId, RoomId: roomId, TaskId: userId, }); - this.audioBotEnabled = false; this.audioBotStartTime = 0; sessionStorage.removeItem('audioBotEnabled'); } + this.audioBotEnabled = false; }; /** - * @brief Command(Update) AIGC + * @brief 命令 AIGC */ - commandAudioBot = async (roomId: string, userId: string, command: string) => { + commandAudioBot = async (command: string) => { if (this.audioBotEnabled) { const res = await openAPIs.UpdateVoiceChat({ - AppId: aigcConfig.AppId, - BusinessId: aigcConfig.BusinessId, - RoomId: roomId, - TaskId: userId, + AppId: aigcConfig.BaseConfig.AppId, + BusinessId: aigcConfig.BaseConfig.BusinessId, + RoomId: this.basicInfo.room_id, + TaskId: this.basicInfo.user_id, Command: command, }); return res; } - return Promise.reject(new Error('AI Call failed')); + return Promise.reject(new Error('AI 命令调用失败')); }; /** - * @brief Update AIGC Configuration + * @brief 更新 AIGC 配置 */ - updateAudioBot = async ( - roomId: string, - userId: string, - config: DeepPartial['Config']> - ) => { + updateAudioBot = async () => { if (this.audioBotEnabled) { - await this.stopAudioBot(roomId, userId); - await this.startAudioBot(roomId, userId, config); + await this.stopAudioBot(); + await this.startAudioBot(); } else { - await this.startAudioBot(roomId, userId, config); + await this.startAudioBot(); } }; + + /** + * @brief 获取当前 AI 是否启用 + */ + getAudioBotEnabled = () => { + return this.audioBotEnabled; + }; } export default new RTCClient(); diff --git a/src/lib/listenerHooks.ts b/src/lib/listenerHooks.ts index d12ddaa..345daa6 100644 --- a/src/lib/listenerHooks.ts +++ b/src/lib/listenerHooks.ts @@ -1,17 +1,22 @@ /** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ -import { + +import VERTC, { + LocalAudioPropertiesInfo, + RemoteAudioPropertiesInfo, LocalStreamStats, MediaType, onUserJoinedEvent, onUserLeaveEvent, RemoteStreamStats, StreamRemoveReason, + StreamIndex, DeviceInfo, AutoPlayFailedEvent, PlayerEvent, + NetworkQuality, } from '@volcengine/rtc'; import { useDispatch } from 'react-redux'; import { useRef } from 'react'; @@ -27,15 +32,26 @@ import { updateAITalkState, setHistoryMsg, setCurrentMsg, + updateNetworkQuality, } from '@/store/slices/room'; import RtcClient, { IEventListener } from './RtcClient'; import { setMicrophoneList, updateSelectedDevice } from '@/store/slices/device'; +import Utils from '@/utils/utils'; +import { useMessageHandler } from '@/utils/handler'; const useRtcListeners = (): IEventListener => { const dispatch = useDispatch(); + const { parser } = useMessageHandler(); const playStatus = useRef<{ [key: string]: { audio: boolean; video: boolean } }>({}); + const debounceSetHistoryMsg = Utils.debounce((text: string, user: string) => { + const isAudioEnable = RtcClient.getAudioBotEnabled(); + if (isAudioEnable) { + dispatch(setHistoryMsg({ text, user })); + } + }, 600); + const handleUserJoin = (e: onUserJoinedEvent) => { const extraInfo = JSON.parse(e.userInfo.extraInfo || '{}'); const userId = extraInfo.user_id || e.userInfo.userId; @@ -48,23 +64,29 @@ const useRtcListeners = (): IEventListener => { ); }; + const handleError = (e: { errorCode: typeof VERTC.ErrorCode.DUPLICATE_LOGIN }) => { + const { errorCode } = e; + if (errorCode === VERTC.ErrorCode.DUPLICATE_LOGIN) { + console.log('踢人'); + } + }; + const handleUserLeave = (e: onUserLeaveEvent) => { dispatch(remoteUserLeave(e.userInfo)); dispatch(removeAutoPlayFail(e.userInfo)); }; const handleUserPublishStream = (e: { userId: string; mediaType: MediaType }) => { - const { userId } = e; + const { userId, mediaType } = e; const payload: IUser = { userId }; + if (mediaType === MediaType.AUDIO) { + /** 暂不需要 */ + } payload.publishAudio = true; dispatch(updateRemoteUser(payload)); }; - const handleUserUnpublishStream = (e: { - userId: string; - mediaType: MediaType; - reason: StreamRemoveReason; - }) => { + const handleUserUnpublishStream = (e: { userId: string; mediaType: MediaType; reason: StreamRemoveReason }) => { const { userId, mediaType } = e; const payload: IUser = { userId }; @@ -96,6 +118,30 @@ const useRtcListeners = (): IEventListener => { ); }; + const handleLocalAudioPropertiesReport = (e: LocalAudioPropertiesInfo[]) => { + const localAudioInfo = e.find((audioInfo) => audioInfo.streamIndex === StreamIndex.STREAM_INDEX_MAIN); + if (localAudioInfo) { + dispatch( + updateLocalUser({ + audioPropertiesInfo: localAudioInfo.audioPropertiesInfo, + }) + ); + } + }; + + const handleRemoteAudioPropertiesReport = (e: RemoteAudioPropertiesInfo[]) => { + const remoteAudioInfo = e + .filter((audioInfo) => audioInfo.streamKey.streamIndex === StreamIndex.STREAM_INDEX_MAIN) + .map((audioInfo) => ({ + userId: audioInfo.streamKey.userId, + audioPropertiesInfo: audioInfo.audioPropertiesInfo, + })); + + if (remoteAudioInfo.length) { + dispatch(updateRemoteUser(remoteAudioInfo)); + } + }; + const handleAudioDeviceStateChanged = async (device: DeviceInfo) => { const devices = await RtcClient.getDevices(); @@ -104,7 +150,7 @@ const useRtcListeners = (): IEventListener => { if (device.deviceState === 'inactive') { deviceId = devices.audioInputs?.[0].deviceId || ''; } - RtcClient.switchDevice('microphone', deviceId); + RtcClient.switchDevice(MediaType.AUDIO, deviceId); dispatch(setMicrophoneList(devices.audioInputs)); dispatch( @@ -115,6 +161,22 @@ const useRtcListeners = (): IEventListener => { } }; + const handleUserMessageReceived = (e: { userId: string; message: any }) => { + /** debounce 记录用户输入文字 */ + if (e.message) { + const msgObj = JSON.parse(e.message || '{}'); + if (msgObj.text) { + const { text: msg, definite, user_id: user } = msgObj; + if ((window as any)._debug_mode) { + dispatch(setHistoryMsg({ msg, user })); + } else { + debounceSetHistoryMsg(msg, user); + } + dispatch(setCurrentMsg({ msg, definite, user })); + } + } + }; + const handleAutoPlayFail = (event: AutoPlayFailedEvent) => { const { userId, kind } = event; let playUser = playStatus.current?.[userId] || {}; @@ -174,40 +236,37 @@ const useRtcListeners = (): IEventListener => { dispatch(updateAITalkState({ isAITalking: false })); }; + const handleNetworkQuality = (uplinkNetworkQuality: NetworkQuality, downlinkNetworkQuality: NetworkQuality) => { + dispatch( + updateNetworkQuality({ + networkQuality: Math.floor((uplinkNetworkQuality + downlinkNetworkQuality) / 2) as NetworkQuality, + }) + ); + }; + const handleRoomBinaryMessageReceived = (event: { userId: string; message: ArrayBuffer }) => { const { message } = event; - const decoder = new TextDecoder('utf-8'); - const str = decoder.decode(message); - const start = str.indexOf('{'); - const context = JSON.parse(str.substring(start, str.length)) || {}; - const data = context.data?.[0] || {}; - if (data) { - const { text: msg, definite, userId: user, paragraph } = data; - if ((window as any)._debug_mode) { - dispatch(setHistoryMsg({ msg, user, paragraph, definite })); - } else { - const isAudioEnable = RtcClient.getAudioBotEnabled(); - if (isAudioEnable) { - dispatch(setHistoryMsg({ text: msg, user, paragraph, definite })); - } - } - dispatch(setCurrentMsg({ msg, definite, user, paragraph })); - } + parser(message); }; return { + handleError, handleUserJoin, handleUserLeave, handleUserPublishStream, handleUserUnpublishStream, handleRemoteStreamStats, handleLocalStreamStats, + handleLocalAudioPropertiesReport, + handleRemoteAudioPropertiesReport, handleAudioDeviceStateChanged, + handleUserMessageReceived, handleAutoPlayFail, handlePlayerEvent, handleUserStartAudioCapture, handleUserStopAudioCapture, handleRoomBinaryMessageReceived, + handleNetworkQuality, }; }; diff --git a/src/lib/useCommon.ts b/src/lib/useCommon.ts index f3e1782..adbfec9 100644 --- a/src/lib/useCommon.ts +++ b/src/lib/useCommon.ts @@ -1,40 +1,33 @@ /** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ + import { useEffect, useState } from 'react'; import { useSelector, useDispatch } from 'react-redux'; -import { useNavigate } from 'react-router-dom'; -import { useTranslation } from 'react-i18next'; -import { message } from 'antd'; - +import { MediaType } from '@volcengine/rtc'; import Utils from '@/utils/utils'; import RtcClient from '@/lib/RtcClient'; -import { - clearCurrentMsg, - clearHistoryMsg, - localJoinRoom, - localLeaveRoom, - updateAIGCState, -} from '@/store/slices/room'; +import { clearCurrentMsg, clearHistoryMsg, localJoinRoom, localLeaveRoom, updateAIGCState, updateLocalUser } from '@/store/slices/room'; import useRtcListeners from '@/lib/listenerHooks'; import { RootState } from '@/store'; -import { - updateMediaInputs, - updateSelectedDevice, - setDevicePermissions, -} from '@/store/slices/device'; -import { resetConfig } from '@/store/slices/stream'; -import AIGCConfig from '@/config'; +import { updateMediaInputs, updateSelectedDevice, setDevicePermissions } from '@/store/slices/device'; +import logger from '@/utils/logger'; +import aigcConfig, { AI_MODEL } from '@/config'; export interface FormProps { username: string; roomId: string; - publishAudio?: boolean; + publishAudio: boolean; } +export const useVisionMode = () => { + const room = useSelector((state: RootState) => state.room); + return [AI_MODEL.VISION].includes(room.aiConfig?.Config?.LLMConfig.ModelName); +}; + export const useGetDevicePermission = () => { const [permission, setPermission] = useState<{ audio: boolean; @@ -52,24 +45,22 @@ export const useGetDevicePermission = () => { return permission; }; -export const useJoin = (): [ - boolean, - (formValues: FormProps, fromRefresh: boolean) => Promise -] => { +export const useJoin = (): [boolean, (formValues: FormProps, fromRefresh: boolean) => Promise] => { const devicePermissions = useSelector((state: RootState) => state.device.devicePermissions); const room = useSelector((state: RootState) => state.room); + const dispatch = useDispatch(); + const [joining, setJoining] = useState(false); const listeners = useRtcListeners(); - const navigate = useNavigate(); - const handleAIGCModeStart = async (roomId: string, userId: string) => { + const handleAIGCModeStart = async () => { if (room.isAIGCEnable) { - await RtcClient.stopAudioBot(roomId, userId); + await RtcClient.stopAudioBot(); dispatch(clearCurrentMsg()); - await RtcClient.startAudioBot(roomId, userId, AIGCConfig); + await RtcClient.startAudioBot(); } else { - await RtcClient.startAudioBot(roomId, userId, AIGCConfig); + await RtcClient.startAudioBot(); } dispatch(updateAIGCState({ isAIGCEnable: true })); }; @@ -78,35 +69,47 @@ export const useJoin = (): [ if (joining) { return; } + setJoining(true); - const { username, roomId } = formValues; + const isVisionMode = aigcConfig.Model === AI_MODEL.VISION; - /** - * Advised that fetching the token from your api with roomId and userId. - * Or try it by using a static token just like blow shown. - */ - const token = AIGCConfig.Token; + const token = aigcConfig.BaseConfig.Token; - /** Create RTC Engine */ + /** 1. Create RTC Engine */ await RtcClient.createEngine({ - appId: AIGCConfig.AppId, + appId: aigcConfig.BaseConfig.AppId, roomId, uid: username, - }); + } as any); - /** Set events callbacks */ + /** 2.1 Set events callbacks */ RtcClient.addEventListeners(listeners); - /** RTC starting to join room */ - await RtcClient.joinRoom(token, username); + /** 2.2 RTC starting to join room */ + await RtcClient.joinRoom(token!, username); console.log(' ------ userJoinRoom\n', `roomId: ${roomId}\n`, `uid: ${username}`); - /** Set users' devices info */ - const mediaDevices = await RtcClient.getDevices(); + /** 3. Set users' devices info */ + const mediaDevices = await RtcClient.getDevices({ + audio: true, + video: isVisionMode, + }); if (devicePermissions.audio) { - await RtcClient.startAudioCapture(); - RtcClient.setAudioVolume(30); + try { + await RtcClient.startAudioCapture(); + // RtcClient.setAudioVolume(30); + } catch (e) { + logger.debug('No permission for mic'); + } + } + + if (devicePermissions.video && isVisionMode) { + try { + await RtcClient.startVideoCapture(); + } catch (e) { + logger.debug('No permission for camera'); + } } dispatch( @@ -116,12 +119,14 @@ export const useJoin = (): [ username, userId: username, publishAudio: true, + publishVideo: devicePermissions.video && isVisionMode, }, }) ); dispatch( updateSelectedDevice({ selectedMicrophone: mediaDevices.audioInputs[0]?.deviceId, + selectedCamera: mediaDevices.videoInputs[0]?.deviceId, }) ); dispatch(updateMediaInputs(mediaDevices)); @@ -133,8 +138,8 @@ export const useJoin = (): [ roomId, publishAudio: true, }); - navigate(`/?roomId=${formValues.roomId}&userId=${formValues.username}`); - handleAIGCModeStart(formValues.roomId, formValues.username); + + handleAIGCModeStart(); } return [joining, disPatchJoin]; @@ -145,7 +150,6 @@ export const useLeave = () => { return async function () { dispatch(localLeaveRoom()); - dispatch(resetConfig()); dispatch(updateAIGCState({ isAIGCEnable: false })); await Promise.all([RtcClient.stopAudioCapture]); RtcClient.leaveRoom(); @@ -154,43 +158,74 @@ export const useLeave = () => { }; }; -export const useGetRestExperienceTime = () => { - const [time, setTime] = useState('00:30:00'); - const navigate = useNavigate(); - const { t } = useTranslation(); +export const useDeviceState = () => { + const dispatch = useDispatch(); const room = useSelector((state: RootState) => state.room); + const localUser = room.localUser; + const isAudioPublished = localUser.publishAudio; + const isVideoPublished = localUser.publishVideo; - useEffect(() => { - const timer = setInterval(async () => { - const startTime = RtcClient.audioBotStartTime; - if (startTime === 0) { - return; - } - const now = Date.now(); - const diff = now - startTime; - if (diff >= 60 * 1000 * 30) { - clearInterval(timer); - try { - navigate(`/login?roomId=${room.roomId}`); - } catch (error) { - console.error('error', error); - } - message.info(t('demoExpDone')); - } - const seconds = Math.floor(diff / 1000); - const minutes = Math.floor(seconds / 60); - const remainingSeconds = 59 - (seconds % 60); - const remainingMinutes = 29 - (minutes % 60); - const formattedTime = `00:${remainingMinutes.toString().padStart(2, '0')}:${remainingSeconds - .toString() - .padStart(2, '0')}`; - setTime(formattedTime); - }, 1000); + const queryDevices = async (type: MediaType) => { + const mediaDevices = await RtcClient.getDevices({ + audio: type === MediaType.AUDIO, + video: type === MediaType.VIDEO, + }); + if (type === MediaType.AUDIO) { + dispatch( + updateMediaInputs({ + audioInputs: mediaDevices.audioInputs, + }) + ); + dispatch( + updateSelectedDevice({ + selectedMicrophone: mediaDevices.audioInputs[0]?.deviceId, + }) + ); + } else { + dispatch( + updateMediaInputs({ + videoInputs: mediaDevices.videoInputs, + }) + ); + dispatch( + updateSelectedDevice({ + selectedCamera: mediaDevices.videoInputs[0]?.deviceId, + }) + ); + } + return mediaDevices; + }; - return () => { - clearInterval(timer); - }; - }, []); + const switchMic = (publish = true) => { + if (publish) { + !isAudioPublished ? RtcClient.publishStream(MediaType.AUDIO) : RtcClient.unpublishStream(MediaType.AUDIO); + } + queryDevices(MediaType.AUDIO); + !isAudioPublished ? RtcClient.startAudioCapture() : RtcClient.stopAudioCapture(); + dispatch( + updateLocalUser({ + publishAudio: !localUser.publishAudio, + }) + ); + }; - return time; + const switchCamera = (publish = true) => { + if (publish) { + !isVideoPublished ? RtcClient.publishStream(MediaType.VIDEO) : RtcClient.unpublishStream(MediaType.VIDEO); + } + queryDevices(MediaType.VIDEO); + !localUser.publishVideo ? RtcClient.startVideoCapture() : RtcClient.stopVideoCapture(); + dispatch( + updateLocalUser({ + publishVideo: !localUser.publishVideo, + }) + ); + }; + + return { + isAudioPublished, + isVideoPublished, + switchMic, + switchCamera, + }; }; diff --git a/src/locales/en-us.json b/src/locales/en-us.json deleted file mode 100644 index 3b9ff14..0000000 --- a/src/locales/en-us.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "OK": "OK", - "Cancel": "Cancel", - "Me": "Me", - "camera": "Camera", - "microphone": "Microphone", - "cameraEnabled": "Start Video", - "cameraDisabled": "Stop Video", - "microphoneEnabled": "Unmute", - "microphoneDisabled": "Mute", - "Delay": "Delay", - "Bitrate": "Bitrate", - "LossRate": "Packet loss", - "resolution": "Resolution", - "frameRate": "Frame rate", - "startExp": "Try it now!", - "usernamePlaceHolder": "Your name. You can use '@-_.', letters and numbers.", - "usernameError": "Sorry, only '@-_.', letters and numbers are allowed.", - "usernameLengthError": "Sorry, use 128 characters or fewer.", - "roomIdPlaceholder": "Room ID. You can use no more than 18 numbers.", - "roomIdError": "Sorry, only no more than 18 numbers are allowed.", - "Video": "Video", - "Audio": "Audio", - "realTimeData": "Call Statistics", - "Settings": "Settings", - "Mirroring": "Mirroring", - "SoundQuality": "Sound quality", - "roomID": "Room ID", - "whosScreen": "'s Screen", - "stopShare": "Stop Share", - "ShareScreen": "Share Screen", - "ClarityPreferred": "Clarity preferred", - "FluencyPreferred": "Fluency preferred", - "End": "End", - "leaveReconfirm": "Are you going to leave the room?", - "timeout": "The duration of LIVE can't exceed 15 minutes in this application.", - "isSharing": "A user in the room is screen-sharing", - "safariShare": "About to share your entire screen , are you sure?", - "aigcChat": "Video Call", - "noCameraPerm": "No Camera Permission!", - "noMicPerm": "No Microphone Permission!", - "Beautify": "Beautify", - "White": "White", - "Smooth": "Smooth", - "Sharpen": "Sharpen", - "limitUserInRoom": "The number of people in the room exceeds the limit", - "notSupported": "Current browser doesn't support", - "enableAIGC": "Start AI Chat", - "disableAIGC": "Stop AI Chat", - "AICloseSuccess": "AI Chat ended successfully", - "AIStartSuccess": "AI started successfully", - "AINotStartTips": "AI Chat not started yet, you can start chat by clicking button below", - "InterruptRightNow": "Interrupt", - "AIIsTalking": "AI saying...", - - "tone": "Tone", - "model": "Model", - "welcome": "Welcome Message", - "minTokens": "Min tokens", - "maxTokens": "Max tokens", - - "msgHistoryAlert": "Only your speaking records are temporarily displayed here for the moment.", - "dearUser": "You", - "msgHistory": "Conversation Record", - "autoPlay": "Auto Play", - - "Prompt": "Prompt", - "promptPlaceHolder": "Special information used to guide model behavior, fixing the personality, character, capabilities, and boundaries of AI.", - - "modelType": "AI Model Type", - "official": "Official", - "thirdParty": "Third-party", - - "customModelRequestUrl": "Request URL", - "customModelToken": "Token", - "customModelName": "Model Name", - "requestUrl": "URL", - "token": "Token", - - - "resettingMsg": "Currently in AI call, resetting will cause the conversation to restart", - "customModelRequestUrlRequired": "Please enter request url of remote model", - "customModelRequestUrlValidError": "Please enter the correct model request address. Only http(s) request addresses are supported.", - - "demoTitle": "RTC Demo Experience Hall", - "demoExpDone": "The experience has ended and you will be automatically exited from the room" -} diff --git a/src/locales/zh-cn.json b/src/locales/zh-cn.json deleted file mode 100644 index b57a054..0000000 --- a/src/locales/zh-cn.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "OK": "确定", - "Cancel": "取消", - "Me": "我", - "camera": "摄像头", - "microphone": "麦克风", - "cameraEnabled": "摄像头已启用", - "cameraDisabled": "摄像头已禁用", - "microphoneEnabled": "麦克风已启用", - "microphoneDisabled": "麦克风已禁用", - "Delay": "延迟", - "Bitrate": "码率", - "LossRate": "丢包率", - "resolution": "分辨率", - "frameRate": "帧率", - "startExp": "立即体验", - "usernamePlaceHolder": "请输入您的名称,仅限@-_.、英文及数字", - "usernameError": "请输入您的名称,仅限@-_.、英文及数字", - "usernameLengthError": "已达字数上限,您的名称不超过128字符", - "roomIdPlaceholder": "请输入房间号,仅限1-18位纯数字", - "roomIdError": "房间号输入错误,请输入1-18位纯数字", - "Video": "视频", - "Audio": "音频", - "realTimeData": "实时数据", - "Settings": "AI 设置", - "Mirroring": "本地镜像", - "SoundQuality": "通话音质", - "roomID": "房间ID", - "whosScreen": "的共享屏幕", - "stopShare": "结束共享", - "ShareScreen": "屏幕共享", - "ClarityPreferred": "清晰度优先", - "FluencyPreferred": "流畅度优先", - "End": "结束通话", - "leaveReconfirm": "请再次确认是否要离开房间?", - "timeout": "本产品仅用于功能体验,单次直播时长不超15分钟", - "isSharing": "房间内有用户正在屏幕共享", - "safariShare": "即将共享您的整个屏幕,是否确定共享?", - "aigcChat": "AIGC DEMO", - "noCameraPerm": "没有摄像头权限!", - "noMicPerm": "没有麦克风权限!", - "Beautify": "美颜", - "White": "美白", - "Smooth": "磨皮", - "Sharpen": "锐化", - "limitUserInRoom": "房间人数超过限制", - "notSupported": "当前浏览器不支持", - "enableAIGC": "开启 AI 通话", - "disableAIGC": "关闭 AI 通话", - "AICloseSuccess": "AI 关闭成功", - "AIStartSuccess": "AI 启用成功", - "AINotStartTips": "AI 通话尚未开启, 请点击左下方按钮开启 AI 通话", - "InterruptRightNow": "立即打断", - "AIIsTalking": "AI 正在说话...", - - "tone": "音色", - "model": "模型", - "welcome": "欢迎语", - "minTokens": "最小 Token 数", - "maxTokens": "最大 Token 数", - - "msgHistoryAlert": "此处暂时仅展示您的说话记录", - "dearUser": "您", - "msgHistory": "对话记录", - "autoPlay": "自动播放", - - "prompt": "Prompt", - "promptPlaceHolder": "用来引导模型行为的特殊信息,为 AI 固定人设、性格、能力及边界。", - - "modelType": "模型类型", - "official": "官方应用", - "thirdParty": "第三方应用", - - "customModelRequestUrl": "模型请求地址", - "customModelToken": "请求密钥", - "customModelName": "模型名称", - "requestUrl": "请求地址", - "token": "密钥", - - - "resettingMsg": "当前 AI 通话中, 重新设置将导致对话重新开始", - "customModelRequestUrlRequired": "请输入模型请求地址, 按需填入 Token 和其它参数", - "customModelRequestUrlValidError": "请输入正确的模型请求地址, 仅支持 http(s) 请求地址", - - "welcomeViewPart1": "欢迎来到对话式 AI 实时交互体验馆, 我是您的 AI 语音助手", - "welcomeViewPart2": "点击按钮,体验我的能力吧!", - "welcomeViewBubble1": "Hi, 您好!", - "welcomeViewBubble2": "Hello!", - "demoTitle": "RTC Demo 体验馆", - "demoExpDone": "体验已结束,自动为您退出房间" -} diff --git a/src/pages/Login/index.module.less b/src/pages/Login/index.module.less deleted file mode 100644 index f0464c3..0000000 --- a/src/pages/Login/index.module.less +++ /dev/null @@ -1,305 +0,0 @@ -/** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. - * SPDX-license-identifier: BSD-3-Clause - */ - @import '@/utils/utils.less'; - -@keyframes fadeInOut { - 0% { opacity: 0; } - 5% { opacity: 0.2; } - 10% { opacity: 0.4; } - 15% { opacity: 0.6; } - 20% { opacity: 0.8; } - 25% { opacity: 1; } - 30% { opacity: 1; } - 40% { opacity: 1; } - 45% { opacity: 0.8; } - 50% { opacity: 0.6; } - 55% { opacity: 0.4; } - 60% { opacity: 0.2; } - 65% { opacity: 0; } - 100% { opacity: 0; } -} - -@keyframes floatAnimationLeft { - // 0%, 100% { transform: translate(0, 0); } - // 25% { transform: translate(-2%, 2%); } - // 50% { transform: translate(2%, -3%); } - // 75% { transform: translate(-1%, 1%); } -} - -@keyframes floatAnimationRight { - // 0%, 100% { transform: translate(0, 0); } - // 25% { transform: translate(2%, 0%); } - // 50% { transform: translate(-2%, 3%); } - // 75% { transform: translate(1%, 0%); } -} - -.combinedAnimationLeft(@fadeInOutDuration, @fadeInOutDelay, @floatDuration: 3s, @floatDelay: 0s, @floatIteration: infinite) { - animation: fadeInOut @fadeInOutDuration @fadeInOutDelay ease-in-out @floatIteration, floatAnimationLeft @floatDuration @floatDelay ease-in-out @floatIteration; -} - -.combinedAnimationRight(@fadeInOutDuration, @fadeInOutDelay, @floatDuration: 3s, @floatDelay: 0s, @floatIteration: infinite) { - animation: fadeInOut @fadeInOutDuration @fadeInOutDelay ease-in-out @floatIteration, floatAnimationRight @floatDuration @floatDelay ease-in-out @floatIteration; -} - - -.animatedElement { - .combinedAnimationLeft(8s, 0.5s); -} - -.animatedElement15 { - .combinedAnimationRight(8s, 2s); -} - -.container { - width: 100%; - height: 100%; - - :global { - .ant-form-item-explain, - .ant-form-item-extra { - line-height: 24px; - } - - .ant-btn-primary[disabled] { - background-color: #4080ff; - opacity: 0.3; - border-color: #4080ff; - } - - .ant-form-item { - margin-bottom: 32px; - transition: none; - } - - .ant-form-item-with-help { - margin-bottom: 8px; - } - - .ant-form-item-has-error { - height: 72px; - } - - .ant-form-item-has-error :not(.ant-input-disabled).ant-input, - .ant-form-item-has-error :not(.ant-input-affix-wrapper-disabled).ant-input-affix-wrapper, - .ant-form-item-has-error :not(.ant-input-disabled).ant-input:hover, - .ant-form-item-has-error :not(.ant-input-affix-wrapper-disabled).ant-input-affix-wrapper:hover { - background: #1c222d; - color: #fff; - border-color: #ff4d4f; - } - - .ant-row .ant-form-item .ant-form-item-has-success { - .ant-form-item-explain { - display: none !important; - } - } - - .ant-show-help-leave-active { - min-height: 0; - display: none !important; - } - - .ant-input-affix-wrapper { - height: 48px; - border-radius: 8px; - background: #1c222d; - border-color: transparent; - color: #fff; - - &:hover { - border-color: #40a9ff; - } - - &:focus, - &-focused { - border-color: #40a9ff !important; - box-shadow: 0 0 0 2px #1890ff33 !important; - background: #1c222d; - } - - input::placeholder { - .place-holder(); - } - - button { - background: rgba(255, 255, 255, 10%); - color: #fff; - border: none; - border-radius: 4px; - } - - :global { - input { - color: #fff; - - &:focus, - &-focused { - background: #1c222d; - } - } - } - } - - .ant-input { - background: #1c222d; - - input::placeholder { - .place-holder(); - } - } - - .ant-checkbox input, - .ant-checkbox-inner { - width: 16px; - height: 16px; - border-radius: 16px; - } - } -} - -.login-input { - border-radius: 8px; - color: #fff; - height: 48px; - border-color: transparent; - - &:hover { - border-color: #40a9ff; - } - - &:focus, - &-focused { - border-color: #40a9ff !important; - box-shadow: 0 0 0 2px #1890ff33 !important; - background: #1c222d; - } -} - -.login-input::placeholder { - .place-holder(); -} - -.form-wrapper { - display: flex; - align-items: center; - width: 100%; - justify-content: center; - height: calc(100% - 50px); -} - -.main { - width: 440px; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - - .doubao { - position: relative; - width: 100%; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - margin-bottom: 36px; - - .rippleWave { - position: absolute; - top: 0; - } - - .text { - font-family: PingFang SC; - font-size: 16px; - font-weight: 500; - line-height: 24px; - letter-spacing: 0.003em; - text-align: center; - } - - .doubaoLogo { - margin-bottom: 18px; - } - - .bubble1 { - opacity: 0; - animation-fill-mode: forwards; - position: absolute; - top: 0px; - left: 36px; - } - - .bubble2 { - opacity: 0; - animation-fill-mode: forwards; - position: absolute; - top: 64px; - right: 36px; - } - } - - :global { - h1 { - color: #fff; - text-align: center; - margin: 0 0 50px 0px; - } - } -} - -.startButton { - border-radius: 8px; - height: 50px; - width: 158px; - text-shadow: none; - box-shadow: none; - border: none; - background: linear-gradient(95.87deg, #1664FF 0%, #8040FF 97.7%); - - &:hover, - &:focus, - &:active { - background: linear-gradient(95.87deg, #1664FF 0%, #8040FF 97.7%); - opacity: 0.8; - text-shadow: none; - box-shadow: none; - border: none; - } - - &::after { - display: none; - } -} - -.startButton[disabled], -.startButton[disabled]:hover { - color: #fff; - background: linear-gradient(95.87deg, #1664FF 0%, #8040FF 97.7%); - opacity: 0.8; -} - -.mediaIcon { - :global { - svg { - transform: scale(0.66) translate(0, 4px); - transform-origin: center; - } - } -} - -.settingIcon { - background: transparent; - margin-bottom: 0; - - &:hover { - background: none; - } - - :global { - svg { - transform: scale(1); - } - } -} \ No newline at end of file diff --git a/src/pages/Login/index.tsx b/src/pages/Login/index.tsx deleted file mode 100644 index 682a1ab..0000000 --- a/src/pages/Login/index.tsx +++ /dev/null @@ -1,109 +0,0 @@ -/** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. - * SPDX-license-identifier: BSD-3-Clause - */ -import { Button, Form, Input } from 'antd'; -import { useSelector } from 'react-redux'; -import { useTranslation } from 'react-i18next'; -import { v4 as uuid } from 'uuid'; - -import Header from '@/components/Header'; -import { RootState } from '@/store'; -import Utils from '@/utils/utils'; -import ResizeWrapper from '@/components/ResizeWrapper'; -import BubbleMsg from '@/components/BubbleMsg'; -import RippleWave from '@/components/RippleWave'; -import { useJoin } from '../../lib/useCommon'; - -import MagicTools from '@/assets/img/magicTool.svg'; -import DouBao from '@/assets/img/doubao.svg'; -import styles from './index.module.less'; -import Config from '@/config'; - -export interface FormProps { - username: string; - roomId: string; -} - -export default function () { - const localUser = useSelector((state: RootState) => state.room.localUser); - - /** - * Get roomId & UserId randomly - */ - const localRoomId = useSelector( - (state: RootState) => Config.RoomId || state.room.roomId || Utils.getUrlArgs()?.roomId || uuid() - ); - const username = Config.UserId || Utils.getUrlArgs()?.userId || uuid(); - - const { t } = useTranslation(); - - const [form] = Form.useForm(); - - const [joining, dispatchJoin] = useJoin(); - - const handleStart = async (formValues: FormProps) => { - if (joining) { - return; - } - dispatchJoin(formValues, false); - }; - - return ( - -
    -
    -
    -
    - - - - Logo -
    {t('welcomeViewPart1')}
    -
    {t('welcomeViewPart2')}
    -
    -
    - {/* User not need to set it, just hidden and gen it randomly */} - - {/* User not need to set it, just hidden and gen it randomly */} - - - ['roomId', 'username'].some((key) => prevValues[key] !== curValues[key]) - } - > - {({ getFieldValue }) => ( - - )} - -
    -
    -
    - - ); -} diff --git a/src/pages/MainPage/MainArea/Antechamber/InvokeButton/index.module.less b/src/pages/MainPage/MainArea/Antechamber/InvokeButton/index.module.less new file mode 100644 index 0000000..66f2e3d --- /dev/null +++ b/src/pages/MainPage/MainArea/Antechamber/InvokeButton/index.module.less @@ -0,0 +1,68 @@ +/** + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * SPDX-license-identifier: BSD-3-Clause + */ + +.wrapper { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + + .btn { + width: max-content; + height: max-content; + border-radius: 50%; + display: flex; + justify-content: center; + align-items: center; + + .icon { + position: absolute; + } + } + + .text { + margin-top: 8px; + color: rgba(115, 122, 135, 1); + } +} + +.cursor { + cursor: pointer; +} + +.cursor:hover { + opacity: 0.8; +} + +.cursor:active { + opacity: 1; +} + +.loader { + display: flex; + gap: 5px; +} + +.dot { + width: 10px; + height: 10px; + border-radius: 50%; + background-color: white; + animation: glow 0.9s infinite; +} + +@keyframes glow { + 0% { + opacity: 1; + } + 40% { + opacity: 0.7; + } + 100% { + opacity: 0.3; + } +} \ No newline at end of file diff --git a/src/pages/MainPage/MainArea/Antechamber/InvokeButton/index.tsx b/src/pages/MainPage/MainArea/Antechamber/InvokeButton/index.tsx new file mode 100644 index 0000000..4299260 --- /dev/null +++ b/src/pages/MainPage/MainArea/Antechamber/InvokeButton/index.tsx @@ -0,0 +1,29 @@ +/** + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * SPDX-license-identifier: BSD-3-Clause + */ + +import Loading from './loading'; +import style from './index.module.less'; +import CallButtonSVG from '@/assets/img/CallWrapper.svg'; +import PhoneSVG from '@/assets/img/Phone.svg'; + +interface IInvokeButtonProps extends React.HTMLAttributes { + loading?: boolean; +} + +function InvokeButton(props: IInvokeButtonProps) { + const { loading, className, ...rest } = props; + + return ( +
    +
    + call + {loading ? : phone} +
    +
    {loading ? '连接中' : '通话'}
    +
    + ); +} + +export default InvokeButton; diff --git a/src/pages/MainPage/MainArea/Antechamber/InvokeButton/loading.tsx b/src/pages/MainPage/MainArea/Antechamber/InvokeButton/loading.tsx new file mode 100644 index 0000000..620b245 --- /dev/null +++ b/src/pages/MainPage/MainArea/Antechamber/InvokeButton/loading.tsx @@ -0,0 +1,27 @@ +/** + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * SPDX-license-identifier: BSD-3-Clause + */ + +import style from './index.module.less'; + +function Loading(props: React.HTMLAttributes) { + const { className = '', ...rest } = props; + return ( +
    + {Array(3) + .fill(0) + .map((_, index) => ( +
    + ))} +
    + ); +} + +export default Loading; diff --git a/src/pages/MainPage/MainArea/Antechamber/index.module.less b/src/pages/MainPage/MainArea/Antechamber/index.module.less new file mode 100644 index 0000000..a8f96b8 --- /dev/null +++ b/src/pages/MainPage/MainArea/Antechamber/index.module.less @@ -0,0 +1,55 @@ +/** + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * SPDX-license-identifier: BSD-3-Clause + */ + +.wrapper { + position: relative; + width: 100%; + height: 100%; + border-radius: 16px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + .avatar { + /** + * height = 128px in AvatarCard.avatar + * + */ + margin-top: -128px; + /** + * width = 128px in AvatarCard.avatar + * 128px / 2 = 64px + * + */ + transform: translateX(calc(50% - 64px)); + } + + .mobile { + transform: none !important; + } + + .title { + font-size: 24px; + font-weight: 500; + line-height: 32px; + text-align: center; + margin-top: 24px; + } + + .description { + font-size: 12px; + font-weight: 400; + line-height: 20px; + text-align: center; + color: rgba(66, 70, 78, 1); + margin-top: 4px; + } + + .invoke-btn { + position: absolute; + bottom: 120px; + } +} \ No newline at end of file diff --git a/src/pages/MainPage/MainArea/Antechamber/index.tsx b/src/pages/MainPage/MainArea/Antechamber/index.tsx new file mode 100644 index 0000000..e41cd41 --- /dev/null +++ b/src/pages/MainPage/MainArea/Antechamber/index.tsx @@ -0,0 +1,41 @@ +/** + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * SPDX-license-identifier: BSD-3-Clause + */ + +import AvatarCard from '@/components/AvatarCard'; +import Utils from '@/utils/utils'; +import aigcConfig from '@/config'; +import InvokeButton from '@/pages/MainPage/MainArea/Antechamber/InvokeButton'; +import { useJoin } from '@/lib/useCommon'; +import style from './index.module.less'; + +function Antechamber() { + const [joining, dispatchJoin] = useJoin(); + const username = aigcConfig.BaseConfig.UserId; + const roomId = aigcConfig.BaseConfig.RoomId; + + const handleJoinRoom = () => { + if (!joining) { + dispatchJoin( + { + username, + roomId, + publishAudio: true, + }, + false + ); + } + }; + + return ( +
    + +
    AI 语音助手
    +
    Powered by 豆包大模型和火山引擎视频云 RTC
    + +
    + ); +} + +export default Antechamber; diff --git a/src/pages/MainPage/MainArea/Room/AudioController.tsx b/src/pages/MainPage/MainArea/Room/AudioController.tsx new file mode 100644 index 0000000..6fd6cd3 --- /dev/null +++ b/src/pages/MainPage/MainArea/Room/AudioController.tsx @@ -0,0 +1,41 @@ +/** + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * SPDX-license-identifier: BSD-3-Clause + */ + +import { useDispatch, useSelector } from 'react-redux'; +import AudioLoading from '@/components/Loading/AudioLoading'; +import { RootState } from '@/store'; +import RtcClient from '@/lib/RtcClient'; +import style from './index.module.less'; +import StopRobotBtn from '@/assets/img/StopRobotBtn.svg'; +import { setInterruptMsg } from '@/store/slices/room'; + +function AudioController(props: React.HTMLAttributes) { + const { className, ...rest } = props; + const dispatch = useDispatch(); + const room = useSelector((state: RootState) => state.room); + const volume = room.localUser.audioPropertiesInfo?.linearVolume || 0; + const isAITalking = room.isAITalking; + const isUserTalking = room.isUserTalking || volume >= 35; + + const handleInterrupt = () => { + RtcClient.commandAudioBot('interrupt'); + dispatch(setInterruptMsg()); + }; + + return ( +
    + {isAITalking ? ( +
    + StopRobotBtn + 点击打断 +
    + ) : ( +
    正在听...
    + )} + +
    + ); +} +export default AudioController; diff --git a/src/pages/MainPage/MainArea/Room/CameraArea.tsx b/src/pages/MainPage/MainArea/Room/CameraArea.tsx new file mode 100644 index 0000000..9a3f54a --- /dev/null +++ b/src/pages/MainPage/MainArea/Room/CameraArea.tsx @@ -0,0 +1,66 @@ +/** + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * SPDX-license-identifier: BSD-3-Clause + */ + +import { useDispatch, useSelector } from 'react-redux'; +import { useEffect } from 'react'; +import { MediaType } from '@volcengine/rtc'; +import { RootState } from '@/store'; +import { useVisionMode } from '@/lib/useCommon'; +import styles from './index.module.less'; +import CameraCloseNoteSVG from '@/assets/img/CameraCloseNote.svg'; +import RtcClient from '@/lib/RtcClient'; +import { updateLocalUser } from '@/store/slices/room'; + +const LocalVideoID = 'local-video-player'; + +function CameraArea(props: React.HTMLAttributes) { + const { className, ...rest } = props; + const dispatch = useDispatch(); + const room = useSelector((state: RootState) => state.room); + const isVisionMode = useVisionMode(); + const localUser = room.localUser; + const isVideoPublished = localUser.publishVideo; + + const handleOperateCamera = () => { + !localUser.publishVideo ? RtcClient.startVideoCapture() : RtcClient.stopVideoCapture(); + + !localUser.publishVideo ? RtcClient.publishStream(MediaType.VIDEO) : RtcClient.unpublishStream(MediaType.VIDEO); + + dispatch( + updateLocalUser({ + publishVideo: !localUser.publishVideo, + }) + ); + }; + + useEffect(() => { + if (isVisionMode && isVideoPublished) { + RtcClient.setLocalVideoPlayer(room.localUser.username!, LocalVideoID); + } else { + RtcClient.setLocalVideoPlayer(room.localUser.username!); + } + }, [isVisionMode, isVideoPublished]); + + return isVisionMode ? ( +
    + {isVideoPublished ? ( +
    + ) : ( +
    + close +
    + 请 + + 打开摄像头 + +
    +
    体验豆包视觉理解模型
    +
    + )} +
    + ) : null; +} + +export default CameraArea; diff --git a/src/pages/MainPage/MainArea/Room/Conversation.tsx b/src/pages/MainPage/MainArea/Room/Conversation.tsx new file mode 100644 index 0000000..bf6635f --- /dev/null +++ b/src/pages/MainPage/MainArea/Room/Conversation.tsx @@ -0,0 +1,64 @@ +/** + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * SPDX-license-identifier: BSD-3-Clause + */ + +import React, { useRef, useEffect } from 'react'; +import { useSelector } from 'react-redux'; +import { Tag, Spin } from '@arco-design/web-react'; +import { RootState } from '@/store'; +import Loading from '@/components/Loading/HorizonLoading'; +import Config from '@/config'; +import styles from './index.module.less'; + +const lines: (string | React.ReactNode)[] = []; + +function Conversation(props: React.HTMLAttributes) { + const { className, ...rest } = props; + const msgHistory = useSelector((state: RootState) => state.room.msgHistory); + const { userId } = useSelector((state: RootState) => state.room.localUser); + const { isAITalking, isUserTalking } = useSelector((state: RootState) => state.room); + const isAIReady = msgHistory.length > 0; + const containerRef = useRef(null); + + useEffect(() => { + const container = containerRef.current; + if (container) { + container.scrollTop = container.scrollHeight - container.clientHeight; + } + }, [msgHistory.length]); + + return ( +
    + {lines.map((line) => line)} + {!isAIReady ? ( +
    + + AI 准备中, 请稍侯 +
    + ) : ( + '' + )} + {msgHistory?.map(({ value, user, isInterrupted }, index) => { + const isUserMsg = user === userId; + const isRobotMsg = user === Config.BotName; + if (!isUserMsg && !isRobotMsg) { + return ''; + } + return ( +
    +
    + {value} +
    + {isAIReady && (isUserTalking || isAITalking) && index === msgHistory.length - 1 ? : ''} +
    +
    + {!isUserMsg && isInterrupted ? 已打断 : ''} +
    + ); + })} +
    + ); +} + +export default Conversation; diff --git a/src/pages/MainPage/MainArea/Room/ToolBar.tsx b/src/pages/MainPage/MainArea/Room/ToolBar.tsx new file mode 100644 index 0000000..8712827 --- /dev/null +++ b/src/pages/MainPage/MainArea/Room/ToolBar.tsx @@ -0,0 +1,54 @@ +/** + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * SPDX-license-identifier: BSD-3-Clause + */ + +import { useSelector } from 'react-redux'; +import { useState } from 'react'; +import { Drawer } from '@arco-design/web-react'; +import { useDeviceState, useLeave } from '@/lib/useCommon'; +import { RootState } from '@/store'; +import { AI_MODEL } from '@/config'; +import utils from '@/utils/utils'; +import Menu from '../../Menu'; + +import style from './index.module.less'; +import CameraOpenSVG from '@/assets/img/CameraOpen.svg'; +import CameraCloseSVG from '@/assets/img/CameraClose.svg'; +import MicOpenSVG from '@/assets/img/MicOpen.svg'; +import SettingSVG from '@/assets/img/Setting.svg'; +import MicCloseSVG from '@/assets/img/MicClose.svg'; +import LeaveRoomSVG from '@/assets/img/LeaveRoom.svg'; + +function ToolBar(props: React.HTMLAttributes) { + const { className, ...rest } = props; + const room = useSelector((state: RootState) => state.room); + const [open, setOpen] = useState(false); + const model = room.aiConfig.Config.LLMConfig?.ModelName; + const leaveRoom = useLeave(); + const { isAudioPublished, isVideoPublished, switchMic, switchCamera } = useDeviceState(); + const handleSetting = () => { + setOpen(true); + }; + return ( +
    + {utils.isMobile() ? setting : null} + switchMic(true)} className={style.btn} alt="mic" /> + {model === AI_MODEL.VISION ? switchCamera(true)} className={style.btn} alt="camera" /> : ''} + leave + {utils.isMobile() ? ( + setOpen(false)} + style={{ + width: 'max-content', + }} + > + + + ) : null} +
    + ); +} +export default ToolBar; diff --git a/src/pages/MainPage/MainArea/Room/index.module.less b/src/pages/MainPage/MainArea/Room/index.module.less new file mode 100644 index 0000000..e5978ed --- /dev/null +++ b/src/pages/MainPage/MainArea/Room/index.module.less @@ -0,0 +1,283 @@ +/** + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * SPDX-license-identifier: BSD-3-Clause + */ + +.wrapper { + position: relative; + width: 100%; + height: 100%; + border-radius: 16px; + padding: 36px 72px; + box-sizing: border-box; + + .conversation { + width: 100%; + position: relative; + height: 100%; + /** + * 100% 为容器高度 + * 128px 为上层 DouBao Card Height + * 24px 为 margin top + * 36px * 2 为容器 padding + * 128 + 24 + 36 * 2 = 224px + */ + max-height: calc(100% - 224px - 8px); + display: flex; + flex-direction: column; + padding-bottom: 12px; + // background-color: black; + overflow-x: hidden; + overflow-y: auto; + margin-top: 24px; + + .sentence { + position: relative; + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; + width: max-content; + white-space: normal; + max-width: 100%; + line-height: 28px; + + .content { + width: max-content; + } + } + + .user { + width: max-content; + border: 0px solid; + align-self: flex-end; + padding: 8px 12px 8px 12px; + border-radius: 12px 0px 12px 12px; + background: var(--background-color-bg-5, rgba(241, 243, 245, 1)); + margin-top: 12px; + margin-bottom: 12px; + } + .robot { + font-family: PingFang SC; + font-size: 14px; + font-weight: 400; + letter-spacing: 0.003em; + + border: 0px solid; + align-self: flex-start; + padding: 3px 12px 3px 0px; + } + + .loading-wrapper { + width: max-content; + display: inline-block; + + .loading { + margin-left: 8px; + width: max-content; + } + + .dot { + background-color: rgba(193, 163, 237, 1); + width: 8px; + height: 8px; + } + } + + .aiReadying { + font-family: PingFang SC; + font-size: 16px; + font-weight: 500; + color: rgba(27, 30, 61, 0.6); + text-align: center; + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; + line-height: 28px; + } + + .aiReading-spin { + margin-right: 12px; + line-height: 16px; + } + + .interruptTag { + width: max-content; + height: 22px; + padding: 0px 6px 0px 6px; + border-radius: 4px; + margin-left: 6px; + font-family: PingFang SC; + font-size: 12px; + font-weight: 400; + line-height: 22px; + letter-spacing: 0.003em; + color: var(--text-color-text-3, rgba(115, 122, 135, 1)); + background: var(--security-unknown-tag-unknown-1, rgba(241, 243, 245, 1)); + } + } + + .conversation::-webkit-scrollbar { + width: 0px; + height: 0px; + } + + .conversation::-webkit-scrollbar-thumb { + background: rgba(0,0,0,0); + border-radius: 0px; + } + + .conversation::-webkit-scrollbar-track { + background: rgba(0,0,0,0); + border-radius: 0px; + } + + .toolBar { + position: absolute; + right: 0px; + margin-right: 36px; + bottom: 36px; + } + + .controller { + position: absolute; + left: 0px; + bottom: 36px; + margin-left: 50%; + transform: translateX(-50%); + } + + .declare { + position: absolute; + bottom: 8px; + left: 12px; + color: var(--text-color-text-4, rgba(199, 204, 214, 1)); + font-size: 10px; + font-weight: 400; + line-height: 20px; + } +} + +.mobile { + padding: 12px 28px; +} + +.text { + width: 100%; + text-align: center; + color: rgba(148, 116, 255, 1); + font-size: 14px; + font-weight: 500; + line-height: 22px; +} + +.btns { + width: 100%; + display: flex; + flex-direction: row; + justify-content: flex-end; + align-items: center; + gap: 16px; + + .setting { + background-color: rgba(111, 111, 111, 0.497); + border-radius: 50%; + width: 48px; + height: 48px; + padding: 12px; + box-sizing: border-box; + cursor: pointer; + } + + .btn { + cursor: pointer; + } + + .btn:hover { + opacity: 0.8; + } + + .btn:active { + opacity: 1; + } +} + +.column { + flex-direction: column !important; + align-items: flex-end !important; +} + +.interrupt { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + background: #FFFFFF; + border-radius: 4px; + box-shadow: 0px 2px 1px 0px rgba(0, 0, 0, 0.08), 0px 0px 0px 1px rgba(221, 226, 233, 1); + border-color: #d9d9d9; + width: 81px; + height: 24px; + gap: 4px; + cursor: pointer; + user-select: none; + -webkit-user-select: none; /* Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* Internet Explorer/Edge */ + + .interrupt-text { + color: var(--text-color-text-3, rgba(115, 122, 135, 1)); + font-size: 12px; + } + + &:hover { + opacity: 0.8; + } + + &:active { + opacity: 1; + } +} + +.camera-wrapper { + position: absolute; + top: 16px; + right: 16px; + width: 320px; + height: 200px; + border-radius: 8px; + background: var(--line-color-border-2, rgba(234, 237, 241, 1)); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + border: 0.81px solid var(--line-color-border-3, rgba(221, 226, 233, 1)); + overflow: hidden; + z-index: 4; + + .camera-player { + width: 100%; + height: 100%; + border-radius: 8px; + } + + .camera-placeholder { + width: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + font-size: 12px; + color: var(--text-color-text-3, rgba(115, 122, 135, 1)); + + .camera-placeholder-close-note { + margin-bottom: 8px; + } + + .camera-open-btn { + color: var(--primary-color-primary-6, rgba(22, 100, 255, 1)); + cursor: pointer; + } + } +} \ No newline at end of file diff --git a/src/pages/MainPage/MainArea/Room/index.tsx b/src/pages/MainPage/MainArea/Room/index.tsx new file mode 100644 index 0000000..56c3656 --- /dev/null +++ b/src/pages/MainPage/MainArea/Room/index.tsx @@ -0,0 +1,28 @@ +/** + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * SPDX-license-identifier: BSD-3-Clause + */ + +import AvatarCard from '@/components/AvatarCard'; +import Conversation from './Conversation'; +import ToolBar from './ToolBar'; +import CameraArea from './CameraArea'; +import AudioController from './AudioController'; +import utils from '@/utils/utils'; +import style from './index.module.less'; +import DoubaoAvatar from '@/assets/img/DoubaoAvatar.png'; + +function Room() { + return ( +
    + + {utils.isMobile() ? null : } + + + +
    AI生成内容由大模型生成,不能完全保障真实
    +
    + ); +} + +export default Room; diff --git a/src/pages/View/Conversation/index.module.less b/src/pages/MainPage/MainArea/index.module.less similarity index 98% rename from src/pages/View/Conversation/index.module.less rename to src/pages/MainPage/MainArea/index.module.less index 194c6fb..ed60a57 100644 --- a/src/pages/View/Conversation/index.module.less +++ b/src/pages/MainPage/MainArea/index.module.less @@ -1,8 +1,9 @@ /** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ - .wrapper { + +.wrapper { width: 100%; height: 100%; background-color: white; @@ -16,9 +17,10 @@ } .doubaoIcon { - min-height: 111px; width: 111px; height: 111px; + min-height: 111px; + overflow: hidden; } .interruptTag { diff --git a/src/pages/MainPage/MainArea/index.tsx b/src/pages/MainPage/MainArea/index.tsx new file mode 100644 index 0000000..98015da --- /dev/null +++ b/src/pages/MainPage/MainArea/index.tsx @@ -0,0 +1,16 @@ +/** + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * SPDX-license-identifier: BSD-3-Clause + */ + +import { useSelector } from 'react-redux'; +import Antechamber from './Antechamber'; +import Room from './Room'; + +function MainArea() { + const room = useSelector((state: any) => state.room); + const isJoined = room.isJoined; + return isJoined ? : ; +} + +export default MainArea; diff --git a/src/pages/MainPage/Menu/components/DeviceDrawerButton/index.module.less b/src/pages/MainPage/Menu/components/DeviceDrawerButton/index.module.less new file mode 100644 index 0000000..5bddcf7 --- /dev/null +++ b/src/pages/MainPage/Menu/components/DeviceDrawerButton/index.module.less @@ -0,0 +1,38 @@ +/** + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * SPDX-license-identifier: BSD-3-Clause + */ + +.wrapper { + width: 100%; + display: flex; + flex-direction: row; + gap: 24px; + padding: 8px 16px; + + + .label { + display: flex; + flex-direction: column; + align-items: flex-start; + line-height: 16px; + gap: 12px; + + .label-text { + font-family: PingFang SC; + font-size: 14px; + font-weight: 500; + line-height: 22px; + letter-spacing: 0.003em; + text-align: left; + } + } + + .value { + display: flex; + flex-direction: column; + justify-content: center; + align-items: flex-start; + gap: 18px; + } +} \ No newline at end of file diff --git a/src/pages/MainPage/Menu/components/DeviceDrawerButton/index.tsx b/src/pages/MainPage/Menu/components/DeviceDrawerButton/index.tsx new file mode 100644 index 0000000..d5b8729 --- /dev/null +++ b/src/pages/MainPage/Menu/components/DeviceDrawerButton/index.tsx @@ -0,0 +1,85 @@ +/** + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * SPDX-license-identifier: BSD-3-Clause + */ + +import { useMemo } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import { MediaType } from '@volcengine/rtc'; +import { Switch, Select } from '@arco-design/web-react'; +import DrawerRowItem from '@/components/DrawerRowItem'; +import { RootState } from '@/store'; +import RtcClient from '@/lib/RtcClient'; +import { useDeviceState } from '@/lib/useCommon'; +import { updateSelectedDevice } from '@/store/slices/device'; +import utils from '@/utils/utils'; +import styles from './index.module.less'; + +interface IDeviceDrawerButtonProps { + type?: MediaType.AUDIO | MediaType.VIDEO; +} + +const DEVICE_NAME = { + [MediaType.AUDIO]: '麦克风', + [MediaType.VIDEO]: '摄像头', +}; + +function DeviceDrawerButton(props: IDeviceDrawerButtonProps) { + const { type = MediaType.AUDIO } = props; + const device = useDeviceState(); + const isEnable = type === MediaType.AUDIO ? device.isAudioPublished : device.isVideoPublished; + const switcher = type === MediaType.AUDIO ? device.switchMic : device.switchCamera; + const devicePermissions = useSelector((state: RootState) => state.device.devicePermissions); + const devices = useSelector((state: RootState) => state.device); + const selectedDevice = type === MediaType.AUDIO ? devices.selectedMicrophone : devices.selectedCamera; + const permission = devicePermissions?.[type === MediaType.AUDIO ? 'audio' : 'video']; + + const dispatch = useDispatch(); + const deviceList = useMemo(() => (type === MediaType.AUDIO ? devices.audioInputs : devices.videoInputs), [devices]); + + const handleDeviceChange = (value: string) => { + RtcClient.switchDevice(type, value); + if (type === MediaType.AUDIO) { + dispatch( + updateSelectedDevice({ + selectedMicrophone: value, + }) + ); + } + if (type === MediaType.VIDEO) { + dispatch( + updateSelectedDevice({ + selectedCamera: value, + }) + ); + } + }; + + return ( + +
    {DEVICE_NAME[type]}
    +
    + switcher(enable)} disabled={!permission} /> + +
    +
    + ), + }} + /> + ); +} + +export default DeviceDrawerButton; diff --git a/src/pages/MainPage/Menu/components/Interrupt/index.module.less b/src/pages/MainPage/Menu/components/Interrupt/index.module.less new file mode 100644 index 0000000..5c1219c --- /dev/null +++ b/src/pages/MainPage/Menu/components/Interrupt/index.module.less @@ -0,0 +1,23 @@ +/** + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * SPDX-license-identifier: BSD-3-Clause + */ + +.interrupt { + position: relative; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + + .label { + font-size: 13px; + font-weight: 400; + line-height: 22px; + color: var(--text-color-text-1, rgba(12, 13, 14, 1)); + + .icon { + margin-left: 4px; + } + } +} diff --git a/src/pages/MainPage/Menu/components/Interrupt/index.tsx b/src/pages/MainPage/Menu/components/Interrupt/index.tsx new file mode 100644 index 0000000..4ea7142 --- /dev/null +++ b/src/pages/MainPage/Menu/components/Interrupt/index.tsx @@ -0,0 +1,46 @@ +/** + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * SPDX-license-identifier: BSD-3-Clause + */ + +import { Popover, Switch } from '@arco-design/web-react'; +import { IconQuestionCircle } from '@arco-design/web-react/icon'; +import { useState } from 'react'; +import { useDispatch } from 'react-redux'; +import Config from '@/config'; +import styles from './index.module.less'; +import RtcClient from '@/lib/RtcClient'; +import { clearHistoryMsg } from '@/store/slices/room'; + +function Interrupt() { + const dispatch = useDispatch(); + const [switchAble, setSwitchAble] = useState(true); + const [enable, setEnable] = useState(Config.InterruptMode); + const handleChange = () => { + setSwitchAble(false); + setEnable(!enable); + Config.InterruptMode = !enable; + if (RtcClient.getAudioBotEnabled()) { + dispatch(clearHistoryMsg()); + } + RtcClient.updateAudioBot(); + setTimeout(() => { + setSwitchAble(true); + }, 3000); + }; + return ( +
    +
    + 语音打断 + + + +
    +
    + +
    +
    + ); +} + +export default Interrupt; diff --git a/src/pages/MainPage/Menu/components/Operation/index.module.less b/src/pages/MainPage/Menu/components/Operation/index.module.less new file mode 100644 index 0000000..1c46bed --- /dev/null +++ b/src/pages/MainPage/Menu/components/Operation/index.module.less @@ -0,0 +1,21 @@ +/** + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * SPDX-license-identifier: BSD-3-Clause + */ + +.device { + display: flex; + flex-direction: column; + gap: 16px; +} + +.box { + position: relative; + width: 100%; + border-radius: 16px; + background-color: white; + border: 1px solid var(--line-color-border-2, rgba(234, 237, 241, 1)); + padding: 16px 24px 16px 24px; + box-sizing: border-box; + margin-bottom: 16px; +} \ No newline at end of file diff --git a/src/pages/MainPage/Menu/components/Operation/index.tsx b/src/pages/MainPage/Menu/components/Operation/index.tsx new file mode 100644 index 0000000..860944e --- /dev/null +++ b/src/pages/MainPage/Menu/components/Operation/index.tsx @@ -0,0 +1,23 @@ +/** + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * SPDX-license-identifier: BSD-3-Clause + */ + +import { MediaType } from '@volcengine/rtc'; +import DeviceDrawerButton from '../DeviceDrawerButton'; +import { useVisionMode } from '@/lib/useCommon'; +import Interrupt from '../Interrupt'; +import styles from './index.module.less'; + +function Operation() { + const isVisionMode = useVisionMode(); + return ( +
    + + + {isVisionMode ? : ''} +
    + ); +} + +export default Operation; diff --git a/src/pages/View/Menu/index.module.less b/src/pages/MainPage/Menu/index.module.less similarity index 53% rename from src/pages/View/Menu/index.module.less rename to src/pages/MainPage/Menu/index.module.less index 158bb9e..27901a8 100644 --- a/src/pages/View/Menu/index.module.less +++ b/src/pages/MainPage/Menu/index.module.less @@ -1,9 +1,10 @@ /** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ - .wrapper { - width: max-content; + +.wrapper { + width: 200px; height: 100%; border-radius: 16px; display: flex; @@ -11,28 +12,21 @@ align-items: center; .info { - width: 100%; - border-radius: 16px; - margin-bottom: 24px; - background-color: white; - border: 1px solid var(--line-color-border-2, rgba(234, 237, 241, 1)); - padding: 16px 24px; + .bold { + font-size: 13px; + font-weight: 500; + line-height: 22px; + color: var(--text-color-text-1, rgba(12, 13, 14, 1)); + } - .titleLine { - width: 100%; + .gray { display: flex; flex-direction: row; align-items: center; - font-weight: 500; - line-height: 32px; - color: rgba(12, 13, 14, 1); - height: 22px; - - :global { - div.ant-typography, .ant-typography p { - margin-bottom: 0px; - } - } + font-size: 13px; + font-weight: 400; + line-height: 22px; + color: var(--text-color-text-3, rgba(115, 122, 135, 1)); .value { width: 65%; @@ -40,18 +34,12 @@ font-weight: 500; margin-left: 5px; } - } - .normalLine { - color: #42464E; - /* Body/body-1 regular */ - font-family: "PingFang SC"; - font-size: 12px; - font-style: normal; - font-weight: 400; - line-height: 20px; /* 166.667% */ - letter-spacing: 0.036px; - opacity: 0.5; + :global { + .arco-typography { + margin-bottom: 0px; + } + } } .buttonArea { @@ -60,35 +48,20 @@ flex-direction: row; align-items: center; justify-content: space-between; - margin-top: 16px; - - .logoutButton { - width: 48px; - border-radius: 6px; - padding: 5px 8px; - margin-right: 16px; - - color: var(--text-color-text-2, #42464E); - text-align: center; - - /* Body/body-2 medium */ - font-family: "PingFang SC"; - font-size: 13px; - font-style: normal; - font-weight: 500; - line-height: 22px; /* 169.231% */ - letter-spacing: 0.039px; - } + margin-top: 8px; .getMore { + width: 100%; color: #fff; height: 32px; - width: max-content; text-shadow: none; box-shadow: none; border: none; padding: 0px 24px; - background: linear-gradient(95.87deg, #1664FF 0%, #8040FF 97.7%); + background: linear-gradient(56.59deg, #3C73FF 15.53%, #6E41EE 62.28%, #D641EE 90.32%), + radial-gradient(203.56% 121.74% at 27.12% -21.74%, rgba(82, 182, 255, 0.2) 0%, rgba(143, 65, 238, 0) 100%), + radial-gradient(134.75% 51.95% at 26.69% 5.8%, rgba(157, 214, 255, 0.1) 0%, rgba(143, 65, 238, 0) 100%), + radial-gradient(82.39% 83.92% at 147.46% 76.45%, rgba(82, 99, 255, 0.8) 0%, rgba(143, 65, 238, 0) 100%); border-radius: 4px; display: flex; flex-direction: row; @@ -103,19 +76,15 @@ font-size: 13px; font-style: normal; font-weight: 500; + cursor: pointer; + } - &:hover, - &:active { - background: linear-gradient(95.87deg, #1664FF 0%, #8040FF 97.7%); - opacity: 0.8; - text-shadow: none; - box-shadow: none; - border: none; - } - - &::after { - display: none; - } + .getMore:hover { + opacity: 0.9; + } + + .getMore:active { + opacity: 1; } .getMore[disabled], @@ -127,7 +96,32 @@ } } - .setting { + .questions { + display: flex; + flex-direction: column; + gap: 8px; + + .title { + font-size: 13px; + font-weight: 500; + line-height: 22px; + } + + .line { + font-size: 12px; + font-weight: 400; + line-height: 20px; + color: rgba(66, 70, 78, 1); + } + } + + .device { + display: flex; + flex-direction: column; + gap: 16px; + } + + .box { position: relative; width: 100%; border-radius: 16px; @@ -135,26 +129,24 @@ border: 1px solid var(--line-color-border-2, rgba(234, 237, 241, 1)); padding: 16px 24px 16px 24px; box-sizing: border-box; - - .divider { - margin: 16px 0px; - // padding: px 0px 6px 0px; - } + margin-bottom: 16px; } .resetTime { position: relative; width: 100%; border-radius: 16px; - margin-top: 24px; - background-color: white; - border: 1px solid var(--line-color-border-2, rgba(234, 237, 241, 1)); - padding: 8px 24px 8px 24px; + padding: 0px 24px 8px 24px; box-sizing: border-box; display: flex; flex-direction: row; justify-content: center; align-items: center; + + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; .normalLine { color: #42464E; @@ -168,4 +160,24 @@ opacity: 0.8; } } +} + +.mobile-camera-wrapper { + position: relative; + width: 100%; + height: 100%; + border-radius: 16px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + margin-bottom: 16px; + + .mobile-camera { + position: relative !important; + width: 100% !important; + height: 100% !important; + top: auto !important; + right: auto !important; + } } \ No newline at end of file diff --git a/src/pages/MainPage/Menu/index.tsx b/src/pages/MainPage/Menu/index.tsx new file mode 100644 index 0000000..8edeb02 --- /dev/null +++ b/src/pages/MainPage/Menu/index.tsx @@ -0,0 +1,69 @@ +/** + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * SPDX-license-identifier: BSD-3-Clause + */ + +import VERTC from '@volcengine/rtc'; +import { Tooltip, Typography } from '@arco-design/web-react'; +import { useSelector } from 'react-redux'; +import { useVisionMode } from '@/lib/useCommon'; +import { RootState } from '@/store'; +import Operation from './components/Operation'; +import { Questions } from '@/config'; +import CameraArea from '../MainArea/Room/CameraArea'; +import utils from '@/utils/utils'; +import styles from './index.module.less'; + +function Menu() { + const room = useSelector((state: RootState) => state.room); + const scene = room.scene; + const isJoined = room?.isJoined; + const isVisionMode = useVisionMode(); + + return ( +
    + {isJoined && utils.isMobile() && isVisionMode ? ( +
    + +
    + ) : null} +
    +
    Demo Version 1.4.0
    +
    SDK Version {VERTC.getSdkVersion()}
    + {isJoined ? ( +
    + 房间ID{' '} + + + {room.roomId || '-'} + + +
    + ) : ( + '' + )} +
    + {isJoined ? ( +
    +
    你可以问各类问题,比如
    + {Questions[scene].map((question) => ( +
    + {question} +
    + ))} +
    + ) : ( + '' + )} + {isJoined ? : ''} +
    + ); +} + +export default Menu; diff --git a/src/pages/MainPage/index.module.less b/src/pages/MainPage/index.module.less new file mode 100644 index 0000000..0653f31 --- /dev/null +++ b/src/pages/MainPage/index.module.less @@ -0,0 +1,36 @@ +/** + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * SPDX-license-identifier: BSD-3-Clause + */ + +.main { + position: relative; + width: 100%; + height: calc(100% - 48px); + display: flex; + flex-direction: row; + align-items: center; + box-sizing: border-box; + + .mainArea { + position: relative; + width: calc(100% - 220px); + height: 100%; + margin-right: 2%; + background-color: white; + border-radius: 16px; + overflow: hidden; + } + + .isMobile { + width: 100% !important; + margin-right: 0% !important; + border-radius: 0px !important; + } + + .operationArea { + position: relative; + width: 200px; + height: 100%; + } +} diff --git a/src/pages/MainPage/index.tsx b/src/pages/MainPage/index.tsx new file mode 100644 index 0000000..b221f4c --- /dev/null +++ b/src/pages/MainPage/index.tsx @@ -0,0 +1,34 @@ +/** + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * SPDX-license-identifier: BSD-3-Clause + */ + +import Header from '@/components/Header'; +import ResizeWrapper from '@/components/ResizeWrapper'; +import Menu from './Menu'; +import utils from '@/utils/utils'; +import MainArea from './MainArea'; +import styles from './index.module.less'; + +export default function () { + return ( + +
    +
    +
    + +
    + {utils.isMobile() ? null : ( +
    + +
    + )} +
    + + ); +} diff --git a/src/pages/View/AutoPlayModal/index.module.less b/src/pages/View/AutoPlayModal/index.module.less deleted file mode 100644 index 5c2fdc1..0000000 --- a/src/pages/View/AutoPlayModal/index.module.less +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. - * SPDX-license-identifier: BSD-3-Clause - */ - .container { - :global { - .ant-modal-body { - min-height: 150px; - display: flex; - flex-direction: column; - justify-content: center; - } - .ant-btn { - font-weight: 700; - background-color: black; - color: white; - text-align: center; - font-size: 1em; - cursor: pointer; - border: none; - font-weight: 600; - } - } -} -.button:hover { - background-color: rgba(0, 0, 0, 0.8); - color: white; -} -.button:focus { - background-color: black; - color: white; -} -.button:active { - background-color: black; - color: white; -} diff --git a/src/pages/View/AutoPlayModal/index.tsx b/src/pages/View/AutoPlayModal/index.tsx deleted file mode 100644 index 2a85e8a..0000000 --- a/src/pages/View/AutoPlayModal/index.tsx +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. - * SPDX-license-identifier: BSD-3-Clause - */ -import React, { useEffect, useState } from 'react'; -import { Modal, Button } from 'antd'; -import { useTranslation } from 'react-i18next'; -import { useDispatch, useSelector } from 'react-redux'; -import { RootState } from '@/store'; -import RtcClient from '@/lib/RtcClient'; -import { clearAutoPlayFail } from '@/store/slices/room'; - -import styles from './index.module.less'; - -function AutoPlayModal() { - const [visible, setVisible] = useState(false); - const room = useSelector((state: RootState) => state.room); - const dispatch = useDispatch(); - const { t } = useTranslation(); - - useEffect(() => { - setVisible(!!room.autoPlayFailUser.length); - }, [room.autoPlayFailUser]); - - const handleAutoPlay = () => { - const users: string[] = room.autoPlayFailUser; - if (users && users.length) { - users.forEach((user) => { - RtcClient?.engine.play(user); - }); - } - dispatch(clearAutoPlayFail()); - }; - - return ( - - - - ); -} - -export default AutoPlayModal; diff --git a/src/pages/View/Conversation/index.tsx b/src/pages/View/Conversation/index.tsx deleted file mode 100644 index f5c3c8d..0000000 --- a/src/pages/View/Conversation/index.tsx +++ /dev/null @@ -1,180 +0,0 @@ -/** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. - * SPDX-license-identifier: BSD-3-Clause - */ -import React, { useRef, useEffect } from 'react'; -import { useSelector, useDispatch } from 'react-redux'; -import { MediaType } from '@volcengine/rtc'; -import { Button, Tag, Spin } from 'antd'; - -import { RootState } from '@/store'; -import Loading from '@/components/Loading'; -import RtcClient from '@/lib/RtcClient'; -import { setInterruptMsg, updateLocalUser } from '@/store/slices/room'; -import Config from '@/config'; - -import styles from './index.module.less'; -import WaveGIF from '@/assets/img/Wave.gif'; -import DoubaoProfileSVG from '@/assets/img/DoubaoProfile.svg'; -import MicEnabledSVG from '@/assets/img/MicEnabled.svg'; -import MicDisabledSVG from '@/assets/img/MicDisabled.svg'; -import StopRobotBtn from '@/assets/img/StopRobotBtn.svg'; - -const lines: (string | React.ReactNode)[] = [ -
    , - doubao_profile, -
    - 我是火山引擎 - RTC AI 语音助手 - , 有什么可以帮您? -
    , -
    - 您可以直接语音与我对话, 例如尝试以下几个问题进行提问: -
    , - - 火山引擎 RTC 有什么优势能力? - , - - 你能帮我解决什么问题? - , - - 你有什么爱好吗? - , -]; - -function Conversation() { - const dispatch = useDispatch(); - const msgHistory = useSelector((state: RootState) => state.room.msgHistory); - const { userId, publishAudio: isMicEnable } = useSelector( - (state: RootState) => state.room.localUser - ); - const { isAITalking, isUserTalking, roomId } = useSelector((state: RootState) => state.room); - const isAIReady = msgHistory.length > 0; - const devicePermissions = useSelector((state: RootState) => state.device.devicePermissions); - const containerRef = useRef(null); - - useEffect(() => { - const container = containerRef.current; - if (container) { - container.scrollTop = container.scrollHeight - container.clientHeight; - } - }, [msgHistory.length]); - - const handleInterrupt = () => { - RtcClient.commandAudioBot(roomId!, userId!, 'interrupt'); - dispatch(setInterruptMsg()); - }; - - const handleOpenMic = () => { - const publishType = 'publishAudio'; - - dispatch( - updateLocalUser({ - [publishType]: true, - }) - ); - - !isMicEnable - ? RtcClient.publishStream(MediaType.AUDIO) - : RtcClient.unpublishStream(MediaType.AUDIO); - }; - - return ( -
    -
    - {lines.map((line) => line)} - {!isAIReady ? ( -
    - - AI 准备中, 请稍侯 -
    - ) : ( - '' - )} - {msgHistory?.map(({ value, user, isInterrupted }, index) => { - const isUserMsg = user === userId; - const isRobotMsg = user === Config.BotName; - if (!isUserMsg && !isRobotMsg) { - return ''; - } - return ( -
    - {value} - {!isUserMsg && isInterrupted ? ( - <> - ... - 已打断 - - ) : ( - '' - )} - {isAITalking && index === msgHistory.length - 1 ? : ''} -
    - ); - })} - {isAITalking ? ( - - ) : ( - '' - )} - {isUserTalking ? ( -
    - -
    - ) : ( - '' - )} -
    -
    - -
    - -
    - {devicePermissions.audio ? ( - isMicEnable ? ( - '麦克风已开启, 您可以直接开始对话' - ) : ( -
    - 麦克风已关闭, 请先 - - 来体验 AI 语音能力 -
    - ) - ) : ( - '麦克风权限已禁用,请在浏览器设置中手动开启后重试' - )} -
    -
    -
    AI生成内容由大模型生成,不能完全保障真实
    -
    -
    - ); -} - -export default Conversation; diff --git a/src/pages/View/Menu/components/AISettingDrawerButton/FieldTitle/index.module.less b/src/pages/View/Menu/components/AISettingDrawerButton/FieldTitle/index.module.less deleted file mode 100644 index f85b4a4..0000000 --- a/src/pages/View/Menu/components/AISettingDrawerButton/FieldTitle/index.module.less +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. - * SPDX-license-identifier: BSD-3-Clause - */ - .wrapper { - width: 100%; - height: 20px; - line-height: 20px; - display: flex; - flex-direction: row; - justify-content: flex-start; - align-items: center; - margin-bottom: 16px; - - .title { - font-family: PingFang SC; - font-size: 12px; - font-weight: 400; - line-height: 20px; - letter-spacing: 0.003em; - text-align: left; - margin-left: 6px; - } -} \ No newline at end of file diff --git a/src/pages/View/Menu/components/AISettingDrawerButton/FieldTitle/index.tsx b/src/pages/View/Menu/components/AISettingDrawerButton/FieldTitle/index.tsx deleted file mode 100644 index 64d552b..0000000 --- a/src/pages/View/Menu/components/AISettingDrawerButton/FieldTitle/index.tsx +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. - * SPDX-license-identifier: BSD-3-Clause - */ -import styles from './index.module.less'; -import DotSVG from '@/assets/img/Dot.svg'; - -interface IProps { - title: string; -} - -function FieldTitle(props: IProps) { - const { title } = props; - - return ( -
    - dot -
    {title}
    -
    - ); -} - -export default FieldTitle; diff --git a/src/pages/View/Menu/components/AISettingDrawerButton/PromptGenerator/index.module.less b/src/pages/View/Menu/components/AISettingDrawerButton/PromptGenerator/index.module.less deleted file mode 100644 index ec3eb59..0000000 --- a/src/pages/View/Menu/components/AISettingDrawerButton/PromptGenerator/index.module.less +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. - * SPDX-license-identifier: BSD-3-Clause - */ - .wrapper { - width: 100%; - display: flex; - flex-direction: row; - align-items: center; - margin-bottom: 24px; - - .label { - font-family: PingFang SC; - font-size: 12px; - font-weight: 400; - line-height: 20px; - letter-spacing: 0.003em; - margin-right: 16px; - } - - .margin { - margin-left: 10px; - } - - .item { - padding: 4px 16px 4px 16px; - border-radius: 24px; - border: 1px 0px 0px 0px; - background: linear-gradient(0deg, rgba(249, 247, 255, 0.4), rgba(249, 247, 255, 0.4)), - linear-gradient(101.19deg, rgba(55, 63, 255, 0.02) 1.62%, rgba(55, 63, 255, 0) 99.44%); - - &:hover { - border: 1px 0px 0px 0px; - color: rgba(0, 0, 0, 0.85); - border-color: #d9d9d9; - background: linear-gradient(0deg, rgba(249, 247, 255, 0.9), rgba(249, 247, 255, 0.7)), - linear-gradient(11.19deg, rgba(25, 27, 98, 0.6) 1.62%, rgba(55, 63, 255, 0.1) 99.44%); - } - - &:active, - &:focus { - opacity: 1; - border: 1px 0px 0px 0px; - color: rgba(0, 0, 0, 0.85); - border-color: #d9d9d9; - } - } -} \ No newline at end of file diff --git a/src/pages/View/Menu/components/AISettingDrawerButton/PromptGenerator/index.tsx b/src/pages/View/Menu/components/AISettingDrawerButton/PromptGenerator/index.tsx deleted file mode 100644 index 36808ce..0000000 --- a/src/pages/View/Menu/components/AISettingDrawerButton/PromptGenerator/index.tsx +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. - * SPDX-license-identifier: BSD-3-Clause - */ -import { Button } from 'antd'; -import styles from './index.module.less'; -import { PROMPT_MAP } from '@/config'; - -interface IProps { - onItemClick?: (key: string) => void; - label?: string; - items?: { - label: string; - value: string; - key?: string; - }[]; -} - -function PromptGenerator(props: IProps) { - const { - onItemClick, - label = '生成人设', - items = Object.keys(PROMPT_MAP).map((type) => ({ - label: type, - value: PROMPT_MAP[type as keyof typeof PROMPT_MAP], - key: type, - })), - } = props; - - return ( -
    -
    {label}
    - {items.map(({ label, value }, index) => ( - - ))} -
    - ); -} - -export default PromptGenerator; diff --git a/src/pages/View/Menu/components/AISettingDrawerButton/index.module.less b/src/pages/View/Menu/components/AISettingDrawerButton/index.module.less deleted file mode 100644 index 92fa89c..0000000 --- a/src/pages/View/Menu/components/AISettingDrawerButton/index.module.less +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. - * SPDX-license-identifier: BSD-3-Clause - */ - .row { - width: 100%; - display: flex; - flex-direction: row; - align-items: center; - - .icon { - margin-right: 6px; - } -} - - -:global { - .ant-form-item-has-success { - margin-bottom: 12px; - } -} - -.wrapper { - width: 100%; - height: 100%; - overflow-y: auto; - overflow-x: hidden; - padding-right: 12px; - - .input { - border-radius: 12px; - line-height: 28px; - background: linear-gradient(0deg, rgba(249, 247, 255, 0.4), rgba(249, 247, 255, 0.4)), - linear-gradient(101.19deg, rgba(55, 63, 255, 0.02) 1.62%, rgba(55, 63, 255, 0) 99.44%); - } - - .third { - border-radius: 4px; - } - - .hasContent { - background: linear-gradient(0deg, rgba(249, 247, 255, 0.4), rgba(249, 247, 255, 0.4)), - linear-gradient(101.19deg, rgba(55, 63, 255, 0.02) 1.62%, rgba(55, 63, 255, 0) 99.44%); - } - - :global { - .ant-form-item-no-colon { - font-family: PingFang SC; - font-size: 13px; - font-weight: 400; - line-height: 22px; - letter-spacing: 0.003em; - text-align: left; - } - } -} -.wrapper::-webkit-scrollbar { - width: 5px; - height: 5px; -} - -.wrapper::-webkit-scrollbar-thumb { - background: rgba(0,0,0,0.3); - border-radius: 10px; -} - -.wrapper::-webkit-scrollbar-track { - background: rgba(0,0,0,0.1); - border-radius: 10px; -} - -.confirmModal { - display: flex; - flex-direction: row; - align-items: center; - align-items: center; - - img { - margin-right: 6px; - } -} \ No newline at end of file diff --git a/src/pages/View/Menu/components/AISettingDrawerButton/index.tsx b/src/pages/View/Menu/components/AISettingDrawerButton/index.tsx deleted file mode 100644 index 11649d0..0000000 --- a/src/pages/View/Menu/components/AISettingDrawerButton/index.tsx +++ /dev/null @@ -1,319 +0,0 @@ -/** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. - * SPDX-license-identifier: BSD-3-Clause - */ -import { Form, Input, Alert, Modal } from 'antd'; -import { useSelector, useDispatch } from 'react-redux'; -import DrawerRowItem from '@/components/DrawerRowItem'; -import FieldTitle from './FieldTitle'; -import RtcClient from '@/lib/RtcClient'; -import { - AI_MODEL, - ModelSourceType, - VOICE_TYPE, - AI_MODEL_MODE, - Config, - TTS_CLUSTER, - TTS_CLUSTER_MAP, - VOICE_INFO_MAP, - PROMPT, -} from '@/config'; -import { clearCurrentMsg, updateAIConfig } from '@/store/slices/room'; -import { RootState } from '@/store'; -import PromptGenerator from './PromptGenerator'; -import CheckBoxSelector from './CheckBoxSelector'; -import ButtonRadio from '@/components/ButtonRadio'; -import styles from './index.module.less'; -import ModelChangeSVG from '@/assets/img/ModelChange.svg'; -import VoiceTypeChangeSVG from '@/assets/img/VoiceTypeChange.svg'; -import AISettingSVG from '@/assets/img/AISetting.svg'; -import DoubaoModelSVG from '@/assets/img/DoubaoModel.svg'; -import InfoIconSVG from '@/assets/img/InfoIcon.svg'; - -function AISettingDrawerButton() { - const room = useSelector((state: RootState) => state.room); - const aiConfig = room.aiConfig; - const [form] = Form.useForm<{ - voiceType: VOICE_TYPE; - modelName: AI_MODEL; - welcomeSpeech: string; - prompt: PROMPT; - - modelSourceType: ModelSourceType; - - customModelUrl?: string; - customModelAPIKey?: string; - customModelName?: string; - }>(); - - const dispatch = useDispatch(); - - const handleOk = async () => { - const formValues = await form.validateFields(); - const config: { - TTSConfig: Partial; - LLMConfig: Partial & { - Url?: string; - APIKey?: string; - }; - } = { - TTSConfig: { - VoiceType: formValues.voiceType, - Cluster: TTS_CLUSTER_MAP[formValues.voiceType] || TTS_CLUSTER.TTS, - }, - LLMConfig: { - ModelName: formValues.modelName, - WelcomeSpeech: formValues.welcomeSpeech, - Mode: AI_MODEL_MODE.ARK_V3, - EndPointId: '', - SystemMessages: [formValues.prompt], - ModeSourceType: ModelSourceType.Available, - }, - }; - - if (formValues.modelSourceType === ModelSourceType.Custom) { - config.LLMConfig.ModeSourceType = ModelSourceType.Custom; - config.LLMConfig.Url = formValues.customModelUrl; - config.LLMConfig.APIKey = formValues.customModelAPIKey; - config.LLMConfig.Feature = JSON.stringify({ Http: true }); - } - - const isAudioEnable = RtcClient.getAudioBotEnabled(); - if (isAudioEnable) { - dispatch(clearCurrentMsg()); - } - await RtcClient.updateAudioBot(room.roomId!, room.localUser.userId!, config); - - dispatch( - updateAIConfig({ - Config: config, - }) - ); - }; - - const handleModelTypeChanged = (type: string) => { - if (type === ModelSourceType.Custom) { - form.setFieldValue('modelName', undefined); - } - if (type === ModelSourceType.Available) { - form.setFieldValue('modelName', AI_MODEL.DOUBAO_LITE_4K); - } - }; - - const handlePromptGenItemClick = (value: string) => { - form.setFieldValue('prompt', value); - }; - - const handleVoiceTypeChanged = (key: string) => { - form.setFieldValue('voiceType', key); - }; - - const handleConfirm = async () => { - Modal.confirm({ - title: ( -
    - info - 刷新频道以启用配置 -
    - ), - icon: null, - content: '修改 AI 配置将刷新频道, 是否确定要修改配置', - onOk: handleOk, - okButtonProps: { - style: { borderRadius: 4 }, - }, - okText: '确定', - cancelText: '取消', - cancelButtonProps: { - style: { borderRadius: 4 }, - }, - }); - }; - - const handleDrawerOpen = () => { - form.setFieldsValue({ - voiceType: aiConfig.Config.TTSConfig.VoiceType, - modelName: aiConfig.Config.LLMConfig.ModelName, - welcomeSpeech: aiConfig.Config.LLMConfig.WelcomeSpeech, - prompt: aiConfig.Config.LLMConfig.SystemMessages[0]!, - modelSourceType: aiConfig.Config.LLMConfig.ModeSourceType, - customModelUrl: aiConfig.Config.LLMConfig.Url, - customModelAPIKey: aiConfig.Config.LLMConfig.APIKey, - }); - }; - - return ( - -
    - - - - - - - - - - - {/*
    - ), - }} - /> - ); -} - -export default AISettingDrawerButton; diff --git a/src/pages/View/Menu/components/MicDrawerButton/index.module.less b/src/pages/View/Menu/components/MicDrawerButton/index.module.less deleted file mode 100644 index 955ba16..0000000 --- a/src/pages/View/Menu/components/MicDrawerButton/index.module.less +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. - * SPDX-license-identifier: BSD-3-Clause - */ - .suffix { - margin-left: 12px; - border-radius: 4px; -} - -.form { - width: 100%; -} \ No newline at end of file diff --git a/src/pages/View/Menu/components/MicDrawerButton/index.tsx b/src/pages/View/Menu/components/MicDrawerButton/index.tsx deleted file mode 100644 index 8f651cf..0000000 --- a/src/pages/View/Menu/components/MicDrawerButton/index.tsx +++ /dev/null @@ -1,100 +0,0 @@ -/** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. - * SPDX-license-identifier: BSD-3-Clause - */ -import { useMemo } from 'react'; -import { useSelector, useDispatch } from 'react-redux'; -import { MediaType } from '@volcengine/rtc'; -import { Tag, Form, Switch, Select } from 'antd'; -import DrawerRowItem from '@/components/DrawerRowItem'; -import { RootState } from '@/store'; -import RtcClient from '@/lib/RtcClient'; -import { updateLocalUser } from '@/store/slices/room'; -import { updateSelectedDevice } from '@/store/slices/device'; -import styles from './index.module.less'; -import MicEnabledSVG from '@/assets/img/MicEnabled.svg'; -import MicDisabledSVG from '@/assets/img/MicDisabled.svg'; - -function MicDrawerButton() { - const room = useSelector((state: RootState) => state.room); - const devicePermissions = useSelector((state: RootState) => state.device.devicePermissions); - const devices = useSelector((state: RootState) => state.device); - const isMicEnable = room.localUser.publishAudio; - const dispatch = useDispatch(); - const [form] = Form.useForm(); - const deviceList = useMemo(() => devices.audioInputs, [devices]); - - const handleSwitchMic = async (checked: boolean) => { - const publishType = 'publishAudio'; - - dispatch( - updateLocalUser({ - [publishType]: checked, - }) - ); - - await (!isMicEnable - ? RtcClient.publishStream(MediaType.AUDIO + 4) - : RtcClient.unpublishStream(MediaType.AUDIO)); - }; - - const handleDeviceChange = (value: string) => { - RtcClient.switchDevice('microphone', value); - dispatch( - updateSelectedDevice({ - selectedMicrophone: value, - }) - ); - }; - - return ( - - {!devicePermissions.audio ? '已禁用' : isMicEnable ? '已启用' : '已关闭'} - - } - drawer={{ - title: '麦克风设置', - footer: false, - onOpen: () => { - form.setFieldValue('enable', isMicEnable); - }, - children: ( -
    - - - - - - -
    - ), - }} - /> - ); -} - -export default MicDrawerButton; diff --git a/src/pages/View/Menu/index.tsx b/src/pages/View/Menu/index.tsx deleted file mode 100644 index 98a617a..0000000 --- a/src/pages/View/Menu/index.tsx +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. - * SPDX-license-identifier: BSD-3-Clause - */ -import { useSelector, useDispatch } from 'react-redux'; -import VERTC from '@volcengine/rtc'; -import { Typography, Tooltip, Button, Divider } from 'antd'; -import { useNavigate } from 'react-router-dom'; -import { RootState } from '@/store'; -import { localLeaveRoom } from '@/store/slices/room'; -import { resetConfig } from '@/store/slices/stream'; -import MicDrawerButton from './components/MicDrawerButton'; -import AISettingDrawerButton from './components/AISettingDrawerButton'; -import { useGetRestExperienceTime } from '@/lib/useCommon'; -import styles from './index.module.less'; - -function Menu() { - const room = useSelector((state: RootState) => state.room); - // const { t } = useTranslation(); - const navigate = useNavigate(); - const dispatch = useDispatch(); - const time = useGetRestExperienceTime(); - - const handleLogout = async () => { - dispatch(localLeaveRoom()); - dispatch(resetConfig()); - try { - navigate(`/login?roomId=${room.roomId}&userId=${room.localUser.userId}`); - } catch (error) { - console.error('error', error); - } - }; - - return ( -
    -
    -
    - 房间ID:{' '} - - - {room.roomId || '-'} - - -
    -
    Demo Version: 1.3.0
    -
    SDK Version: {VERTC.getSdkVersion()}
    -
    - - -
    -
    -
    - - - -
    -
    -
    剩余体验时长: {time}
    -
    -
    - ); -} - -export default Menu; diff --git a/src/pages/View/index.module.less b/src/pages/View/index.module.less deleted file mode 100644 index c41c782..0000000 --- a/src/pages/View/index.module.less +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. - * SPDX-license-identifier: BSD-3-Clause - */ - :global { - .ant-spin-container { - height: 100vh; - } - .ant-spin-nested-loading > div > .ant-spin { - width: 100%; - height: 100vh; - max-height: 100vh; - } -} - -.main { - width: 100%; - height: calc(100% - 48px); - display: flex; - flex-direction: row; - align-items: center; - padding: 24px 124px; - box-sizing: border-box; - - .mainArea { - width: calc(100% - 200px); - height: 100%; - margin-right: 2%; - } - - .operationArea { - width: max-content; - height: 100%; - } -} - diff --git a/src/pages/View/index.tsx b/src/pages/View/index.tsx deleted file mode 100644 index 8daf9ef..0000000 --- a/src/pages/View/index.tsx +++ /dev/null @@ -1,136 +0,0 @@ -/** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. - * SPDX-license-identifier: BSD-3-Clause - */ -import { useEffect, useMemo, useCallback, useRef } from 'react'; - -import { Spin } from 'antd'; -import { useSelector, useDispatch } from 'react-redux'; -import { useNavigate } from 'react-router-dom'; -import { RootState } from '@/store'; -import Utils from '@/utils/utils'; -import RtcClient from '@/lib/RtcClient'; - -import ResizeWrapper from '@/components/ResizeWrapper'; -import Header from '@/components/Header'; -import Conversation from './Conversation'; -import Menu from './Menu'; - -import { useGetDevicePermission, useJoin, useLeave } from '@/lib/useCommon'; -import { setDevicePermissions } from '@/store/slices/device'; -import styles from './index.module.less'; - -function View() { - const navigate = useNavigate(); - - const room = useSelector((state: RootState) => state.room); - - useGetDevicePermission(); - - const [joining, disPatchJoin] = useJoin(); - const leave = useLeave(); - - const ref = useRef({ - publishAudio: room.localUser.publishAudio, - }); - - const hasLogin = useMemo(() => Utils.checkLoginInfo(), []); - - const dispatch = useDispatch(); - - useEffect(() => { - if (!room.roomId && !hasLogin) { - const roomId = Utils.getUrlArgs()?.roomId; - const userId = Utils.getUrlArgs()?.userId; - if (roomId) { - if (userId) { - navigate(`/login?roomId=${roomId}&userId=${userId}`); - } else { - navigate(`/login?roomId=${roomId}`); - } - } else { - navigate('/login'); - } - } else if (!room.roomId) { - (async () => { - const permission = await RtcClient.checkPermission(); - dispatch(setDevicePermissions(permission)); - - if (!permission) return; - - const formValues = Utils.getLoginInfo(); - - if (false || permission) { - disPatchJoin( - { - ...formValues, - publishAudio: permission.audio && JSON.parse(formValues?.publishAudio || 'false'), - }, - true - ); - } - })(); - } - }, [room.roomId, navigate, hasLogin]); - - useEffect(() => { - const { publishAudio: prevA } = ref.current; - const { publishAudio } = room.localUser; - - if (prevA !== publishAudio) { - ref.current = { publishAudio }; - Utils.setSessionInfo({ - publishAudio: !!publishAudio, - }); - } - }, [room.localUser.publishAudio]); - - const leaveRoom = useCallback(() => { - if (!RtcClient.engine) return; - leave(); - }, []); - - useEffect(() => { - window.addEventListener('pagehide', leaveRoom); - return () => { - leaveRoom(); - window.removeEventListener('pagehide', leaveRoom); - }; - }, [leaveRoom]); - - useEffect(() => { - window.addEventListener('popstate', leaveRoom); - - return () => { - window.removeEventListener('popstate', leaveRoom); - }; - }, [leaveRoom]); - - useEffect(() => { - const stop = async () => { - await RtcClient.stopAudioBot(room.roomId!, room.localUser.userId!); - }; - window.addEventListener('beforeunload', stop); - return () => { - window.removeEventListener('beforeunload', stop); - }; - }, [RtcClient]); - - return ( - - -
    -
    -
    - -
    -
    - -
    -
    - - - ); -} - -export default View; diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts index fcfd891..8a75ed8 100644 --- a/src/react-app-env.d.ts +++ b/src/react-app-env.d.ts @@ -1,7 +1,8 @@ /** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ + /// declare module '*.less' { diff --git a/src/react-i18next-config.ts b/src/react-i18next-config.ts deleted file mode 100644 index 24bb338..0000000 --- a/src/react-i18next-config.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. - * SPDX-license-identifier: BSD-3-Clause - */ - -import i18n from 'i18next'; -import { initReactI18next } from 'react-i18next'; -import cn from './locales/zh-cn.json'; -import en from './locales/en-us.json'; - -const resources = { - cn: { - translation: cn, - }, - en: { - translation: en, - }, -}; -i18n.use(initReactI18next).init({ - resources, - fallbackLng: process.env.REACT_APP_LOCAL, -}); - -export default i18n; diff --git a/src/store/index.ts b/src/store/index.ts index 5945ae5..0fdd8f2 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -1,26 +1,21 @@ /** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ + import { configureStore } from '@reduxjs/toolkit'; import roomSlice, { RoomState } from './slices/room'; import deviceSlice, { DeviceState } from './slices/device'; -import streamSlice, { StreamState } from './slices/stream'; -import statsSlice, { StatsState } from './slices/streamStats'; export interface RootState { room: RoomState; device: DeviceState; - stream: StreamState; - streamStats: StatsState; } const store = configureStore({ reducer: { room: roomSlice, device: deviceSlice, - stream: streamSlice, - streamStats: statsSlice, }, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ diff --git a/src/store/slices/device.ts b/src/store/slices/device.ts index 984a7a2..34711c6 100644 --- a/src/store/slices/device.ts +++ b/src/store/slices/device.ts @@ -1,7 +1,8 @@ /** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ + import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { DeviceType } from '@/interface'; @@ -9,20 +10,25 @@ export const medias = [DeviceType.Microphone]; export const MediaName = { [DeviceType.Microphone]: 'microphone', + [DeviceType.Camera]: 'camera', }; export interface DeviceState { audioInputs: MediaDeviceInfo[]; + videoInputs: MediaDeviceInfo[]; selectedCamera?: string; selectedMicrophone?: string; devicePermissions: { audio: boolean; + video: boolean; }; } const initialState: DeviceState = { audioInputs: [], + videoInputs: [], devicePermissions: { audio: true, + video: true, }, }; @@ -31,7 +37,12 @@ export const DeviceSlice = createSlice({ initialState, reducers: { updateMediaInputs: (state, { payload }) => { - state.audioInputs = payload.audioInputs; + if (payload.audioInputs) { + state.audioInputs = payload.audioInputs; + } + if (payload.videoInputs) { + state.videoInputs = payload.videoInputs; + } }, updateSelectedDevice: (state, { payload }) => { if (payload.selectedCamera) { @@ -50,13 +61,13 @@ export const DeviceSlice = createSlice({ state, action: PayloadAction<{ audio: boolean; + video: boolean; }> ) => { state.devicePermissions = action.payload; }, }, }); -export const { updateMediaInputs, updateSelectedDevice, setMicrophoneList, setDevicePermissions } = - DeviceSlice.actions; +export const { updateMediaInputs, updateSelectedDevice, setMicrophoneList, setDevicePermissions } = DeviceSlice.actions; export default DeviceSlice.reducer; diff --git a/src/store/slices/room.ts b/src/store/slices/room.ts index 2f9691c..831aae0 100644 --- a/src/store/slices/room.ts +++ b/src/store/slices/room.ts @@ -1,16 +1,18 @@ /** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ + import { createSlice } from '@reduxjs/toolkit'; -import { AudioPropertiesInfo, LocalAudioStats, RemoteAudioStats } from '@volcengine/rtc'; -import config from '@/config'; +import { AudioPropertiesInfo, LocalAudioStats, NetworkQuality, RemoteAudioStats } from '@volcengine/rtc'; +import config, { SCENE } from '@/config'; import utils from '@/utils/utils'; export interface IUser { username?: string; userId?: string; publishAudio?: boolean; + publishVideo?: boolean; publishScreen?: boolean; audioStats?: RemoteAudioStats; audioPropertiesInfo?: AudioPropertiesInfo; @@ -35,40 +37,52 @@ export interface RoomState { localUser: LocalUser; remoteUsers: IUser[]; autoPlayFailUser: string[]; + /** + * @brief 是否已加房 + */ + isJoined: boolean; + /** + * @brief 选择的模式 + */ + scene: SCENE; /** - * @brief Whether AI enabled + * @brief AI 通话是否启用 */ isAIGCEnable: boolean; /** - * @brief Whether AI saying + * @brief AI 是否正在说话 */ isAITalking: boolean; /** - * @brief Whether user saying + * @brief 用户是否正在说话 */ isUserTalking: boolean; /** - * @brief AI basic configuration + * @brief AI 基础配置 */ - aiConfig: ReturnType; + aiConfig: ReturnType; + /** + * @brief 网络质量 + */ + networkQuality: NetworkQuality; /** - * @brief conversation + * @brief 对话记录 */ msgHistory: Msg[]; /** - * @brief Current conversation + * @brief 当前的对话 */ currentConversation: { [user: string]: { /** - * @brief real time conversation content + * @brief 实时对话内容 */ msg: string; /** - * @brief Whether the current real-time conversation content can be defined as a "question" + * @brief 当前实时对话内容是否能被定义为 "问题" */ definite: boolean; }; @@ -77,16 +91,20 @@ export interface RoomState { const initialState: RoomState = { time: -1, + scene: SCENE.INTELLIGENT_ASSISTANT, remoteUsers: [], localUser: { publishAudio: true, + publishVideo: true, }, autoPlayFailUser: [], - isAIGCEnable: utils.getAudioBotEnabled(), + isJoined: false, + isAIGCEnable: false, isAITalking: false, isUserTalking: false, + networkQuality: NetworkQuality.UNKNOWN, - aiConfig: config.getAIGCConfig(), + aiConfig: config.aigcConfig, msgHistory: [], currentConversation: {}, @@ -109,14 +127,17 @@ export const roomSlice = createSlice({ ) => { state.roomId = payload.roomId; state.localUser = payload.user; + state.isJoined = true; }, localLeaveRoom: (state) => { state.roomId = undefined; state.time = -1; state.localUser = { publishAudio: true, + publishVideo: true, }; state.remoteUsers = []; + state.isJoined = false; }, remoteUserJoin: (state, { payload }) => { state.remoteUsers.push(payload); @@ -126,13 +147,21 @@ export const roomSlice = createSlice({ state.remoteUsers.splice(findIndex, 1); }, - updateLocalUser: (state, { payload }: { payload: LocalUser }) => { + updateScene: (state, { payload }) => { + state.scene = payload.scene; + }, + + updateLocalUser: (state, { payload }: { payload: Partial }) => { state.localUser = { ...state.localUser, ...payload, }; }, + updateNetworkQuality: (state, { payload }) => { + state.networkQuality = payload.networkQuality; + }, + updateRemoteUser: (state, { payload }: { payload: IUser | IUser[] }) => { if (!Array.isArray(payload)) { payload = [payload]; @@ -196,14 +225,11 @@ export const roomSlice = createSlice({ if (state.isUserTalking !== userTalking) { state.isUserTalking = userTalking; } - if (payload.user === state.localUser.userId) { - return; - } } - /** If the current speaker is a user, and the previous record is AI, and it is not a sentence, then interrupt */ + /** 如果当前说话人是用户, 并且上一条记录是 AI 的话, 并且不成语句, 则是打断 */ if (userTalking) { const lastMsg = state.msgHistory[state.msgHistory.length - 1]; - const isAI = lastMsg.user !== state.localUser.userId; + const isAI = lastMsg.user === config.BotName; if (!lastMsg.paragraph && isAI) { lastMsg.isInterrupted = true; state.msgHistory[state.msgHistory.length - 1] = lastMsg; @@ -254,6 +280,8 @@ export const { clearHistoryMsg, clearCurrentMsg, setInterruptMsg, + updateNetworkQuality, + updateScene, } = roomSlice.actions; export default roomSlice.reducer; diff --git a/src/store/slices/stream.ts b/src/store/slices/stream.ts deleted file mode 100644 index f1cf11c..0000000 --- a/src/store/slices/stream.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. - * SPDX-license-identifier: BSD-3-Clause - */ -import { createSlice } from '@reduxjs/toolkit'; -import { AudioProfileType } from '@volcengine/rtc'; -import { AudioProfile } from '@/config'; - -export interface StreamState { - audioProfile: AudioProfileType; -} -const initialState: StreamState = { - audioProfile: AudioProfile[0].type, -}; - -export const streamSlice = createSlice({ - name: 'stream', - initialState, - reducers: { - updateAudioProfile: (state, { payload }) => { - state.audioProfile = payload.audioProfile; - }, - updateAllStreamConfig: (state, { payload }) => { - state.audioProfile = payload.audioProfile; - }, - resetConfig: (state) => { - state.audioProfile = initialState.audioProfile; - }, - }, -}); - -export const { updateAudioProfile, updateAllStreamConfig, resetConfig } = streamSlice.actions; - -export default streamSlice.reducer; diff --git a/src/store/slices/streamStats.ts b/src/store/slices/streamStats.ts deleted file mode 100644 index 561f065..0000000 --- a/src/store/slices/streamStats.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. - * SPDX-license-identifier: BSD-3-Clause - */ -import { createSlice } from '@reduxjs/toolkit'; -import { LocalStreamStats, RemoteStreamStats } from '@volcengine/rtc'; - -export interface StatsState { - [key: string]: RemoteStreamStats | LocalStreamStats; -} -const initialState: StatsState = {}; - -export const statsSlice = createSlice({ - name: 'stats', - initialState, - reducers: { - updateLocal: (state, { payload }) => { - state.local = payload; - }, - updateRemote: (state, { payload }) => { - state[payload.userId] = payload; - }, - }, -}); - -export const { updateLocal, updateRemote } = statsSlice.actions; - -export default statsSlice.reducer; diff --git a/src/theme.less b/src/theme.less index cd5951a..8b6d20f 100644 --- a/src/theme.less +++ b/src/theme.less @@ -1,5 +1,6 @@ /** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ + @primary-color: #1664ff; diff --git a/src/utils/handler.ts b/src/utils/handler.ts new file mode 100644 index 0000000..beb53a1 --- /dev/null +++ b/src/utils/handler.ts @@ -0,0 +1,104 @@ +/** + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * SPDX-license-identifier: BSD-3-Clause + */ + +import { useDispatch } from 'react-redux'; +import logger from './logger'; +import { setCurrentMsg, setHistoryMsg } from '@/store/slices/room'; +import RtcClient from '@/lib/RtcClient'; +import Utils from '@/utils/utils'; + +export type AnyRecord = Record; + +export enum MESSAGE_TYPE { + BRIEF = 'conv', + SUBTITLE = 'subv', + FUNCTION_CALL = 'func', +} + +export enum AGENT_BRIEF { + UNKNOWN, + LISTENING, + THINKING, + SPEAKING, + INTERRUPTED, + FINISHED, +} + +export const MessageTypeCode = { + [MESSAGE_TYPE.SUBTITLE]: 1, + [MESSAGE_TYPE.FUNCTION_CALL]: 2, + [MESSAGE_TYPE.BRIEF]: 3, +}; + +export const useMessageHandler = () => { + const dispatch = useDispatch(); + + const maps = { + /** + * @brief 接收状态变化信息 + * @note https://www.volcengine.com/docs/6348/1415216 + */ + [MESSAGE_TYPE.BRIEF]: (parsed: AnyRecord) => { + const { Stage } = parsed || {}; + const { Code, Description } = Stage || {}; + logger.debug(Code, Description); + }, + /** + * @brief 字幕 + * @note https://www.volcengine.com/docs/6348/1337284 + */ + [MESSAGE_TYPE.SUBTITLE]: (parsed: AnyRecord) => { + const data = parsed.data?.[0] || {}; + /** debounce 记录用户输入文字 */ + if (data) { + const { text: msg, definite, userId: user, paragraph } = data; + logger.debug('handleRoomBinaryMessageReceived', data); + if ((window as any)._debug_mode) { + dispatch(setHistoryMsg({ msg, user, paragraph, definite })); + } else { + const isAudioEnable = RtcClient.getAudioBotEnabled(); + if (isAudioEnable) { + dispatch(setHistoryMsg({ text: msg, user, paragraph, definite })); + } + } + dispatch(setCurrentMsg({ msg, definite, user, paragraph })); + } + }, + /** + * @brief Function calling + * @note https://www.volcengine.com/docs/6348/1359441 + */ + [MESSAGE_TYPE.FUNCTION_CALL]: (parsed: AnyRecord) => { + const name: string = parsed?.tool_calls?.[0]?.function?.name; + console.log('[Function Call] - Called by sendUserBinaryMessage'); + const map: Record = { + getcurrentweather: '今天下雪, 最低气温零下10度', + musicplayer: '查询到李四的歌曲, 名称是千里之内', + sendmessage: '发送成功', + }; + + RtcClient.engine.sendUserBinaryMessage( + 'RobotMan_', + Utils.string2tlv( + JSON.stringify({ + ToolCallID: parsed?.tool_calls?.[0]?.id, + Content: map[name.toLocaleLowerCase().replaceAll('_', '')], + }) + ) + ); + }, + }; + + return { + parser: (buffer: ArrayBuffer) => { + try { + const { type, value } = Utils.tlv2String(buffer); + maps[type as MESSAGE_TYPE]?.(JSON.parse(value)); + } catch (e) { + logger.debug('parse error', e); + } + }, + }; +}; diff --git a/src/utils/logger.ts b/src/utils/logger.ts new file mode 100644 index 0000000..84e008b --- /dev/null +++ b/src/utils/logger.ts @@ -0,0 +1,24 @@ +/** + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * SPDX-license-identifier: BSD-3-Clause + */ + +class Logger { + public debug(...args: any[]) { + console.debug(...args); + } + + public log(...args: any[]) { + console.log(...args); + } + + public error(...args: any[]) { + console.error(...args); + } + + public warn(...args: any[]) { + console.warn(...args); + } +} + +export default new Logger(); diff --git a/src/utils/utils.less b/src/utils/utils.less index 5c0ab73..77b2211 100644 --- a/src/utils/utils.less +++ b/src/utils/utils.less @@ -1,8 +1,9 @@ /** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ - @meetingBackgroundColor: #1e2128; + +@meetingBackgroundColor: #1e2128; .flex-box { display: flex; diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 6a2b64d..396da0d 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,10 +1,12 @@ /** - * Copyright 2022 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. + * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause */ + import { v4 as uuidv4 } from 'uuid'; import config from '@/config'; import { Msg, RoomState } from '@/store/slices/room'; +import RtcClient from '@/lib/RtcClient'; interface UserBaseInfo { deviceId?: string; @@ -30,13 +32,11 @@ class Utils { getDeviceId = (): string => this.userBaseInfo.deviceId!; - getAudioBotEnabled = (): boolean => !!sessionStorage.getItem('audioBotEnabled') || false; - setLoginToken = (token: string): void => { this.userBaseInfo.login_token = token; }; - getLoginToken = (): string | null => config.Token; + getLoginToken = (): string | null => config.BaseConfig.Token; formatTime = (time: number): string => { if (time < 0) { @@ -91,10 +91,7 @@ class Utils { let hasLogin = true; if (!_roomId || !_uid) { hasLogin = false; - } else if ( - !/^[0-9a-zA-Z_\-@.]{1,128}$/.test(_roomId) || - !/^[0-9a-zA-Z_\-@.]{1,128}$/.test(_uid) - ) { + } else if (!/^[0-9a-zA-Z_\-@.]{1,128}$/.test(_roomId) || !/^[0-9a-zA-Z_\-@.]{1,128}$/.test(_uid)) { hasLogin = false; } return hasLogin; @@ -138,13 +135,70 @@ class Utils { if (arr.length) { const last = arr.at(-1)!; const { user, value, isInterrupted } = last; - if (user === added.user && (added.value.startsWith(value) || value.startsWith(added.value))) { + if ((added.user === RtcClient.basicInfo.user_id && last.user === added.user) || (user === added.user && added.value.startsWith(value) && value.trim())) { arr.pop(); added.isInterrupted = isInterrupted; } } arr.push(added); }; + + /** + * @brief 将字符串包装成 TLV + */ + string2tlv(str: string) { + const type = 'func'; + const typeBuffer = new Uint8Array(4); + + for (let i = 0; i < type.length; i++) { + typeBuffer[i] = type.charCodeAt(i); + } + + const lengthBuffer = new Uint32Array(1); + const valueBuffer = new TextEncoder().encode(str); + + lengthBuffer[0] = valueBuffer.length; + + const tlvBuffer = new Uint8Array(typeBuffer.length + 4 + valueBuffer.length); + + tlvBuffer.set(typeBuffer, 0); + + tlvBuffer[4] = (lengthBuffer[0] >> 24) & 0xff; + tlvBuffer[5] = (lengthBuffer[0] >> 16) & 0xff; + tlvBuffer[6] = (lengthBuffer[0] >> 8) & 0xff; + tlvBuffer[7] = lengthBuffer[0] & 0xff; + + tlvBuffer.set(valueBuffer, 8); + return tlvBuffer.buffer; + } + + /** + * @brief TLV 数据格式转换成字符串 + * @note TLV 数据格式 + * | magic number | length(big-endian) | value | + * @param {ArrayBufferLike} tlvBuffer + * @returns + */ + tlv2String(tlvBuffer: ArrayBufferLike) { + const typeBuffer = new Uint8Array(tlvBuffer, 0, 4); + const lengthBuffer = new Uint8Array(tlvBuffer, 4, 4); + const valueBuffer = new Uint8Array(tlvBuffer, 8); + + let type = ''; + for (let i = 0; i < typeBuffer.length; i++) { + type += String.fromCharCode(typeBuffer[i]); + } + + const length = (lengthBuffer[0] << 24) | (lengthBuffer[1] << 16) | (lengthBuffer[2] << 8) | lengthBuffer[3]; + + const value = new TextDecoder().decode(valueBuffer.subarray(0, length)); + + return { type, value }; + } + + isMobile() { + return /Mobi|Android|iPhone|iPad|Windows Phone/i.test(window.navigator.userAgent); + } } export default new Utils(); diff --git a/yarn.lock b/yarn.lock index f59e808..7d8dc66 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,40 +10,6 @@ "@jridgewell/gen-mapping" "^0.1.0" "@jridgewell/trace-mapping" "^0.3.9" -"@ant-design/colors@^6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@ant-design/colors/-/colors-6.0.0.tgz#9b9366257cffcc47db42b9d0203bb592c13c0298" - integrity sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ== - dependencies: - "@ctrl/tinycolor" "^3.4.0" - -"@ant-design/icons-svg@^4.2.1": - version "4.2.1" - resolved "https://registry.yarnpkg.com/@ant-design/icons-svg/-/icons-svg-4.2.1.tgz#8630da8eb4471a4aabdaed7d1ff6a97dcb2cf05a" - integrity sha512-EB0iwlKDGpG93hW8f85CTJTs4SvMX7tt5ceupvhALp1IF44SeUFOMhKUOYqpsoYWQKAOuTRDMqn75rEaKDp0Xw== - -"@ant-design/icons@^4.7.0": - version "4.7.0" - resolved "https://registry.yarnpkg.com/@ant-design/icons/-/icons-4.7.0.tgz#8c3cbe0a556ba92af5dc7d1e70c0b25b5179af0f" - integrity sha512-aoB4Z7JA431rt6d4u+8xcNPPCrdufSRMUOpxa1ab6mz1JCQZOEVolj2WVs/tDFmN62zzK30mNelEsprLYsSF3g== - dependencies: - "@ant-design/colors" "^6.0.0" - "@ant-design/icons-svg" "^4.2.1" - "@babel/runtime" "^7.11.2" - classnames "^2.2.6" - rc-util "^5.9.4" - -"@ant-design/react-slick@~0.29.1": - version "0.29.2" - resolved "https://registry.yarnpkg.com/@ant-design/react-slick/-/react-slick-0.29.2.tgz#53e6a7920ea3562eebb304c15a7fc2d7e619d29c" - integrity sha512-kgjtKmkGHa19FW21lHnAfyyH9AAoh35pBdcJ53rHmQ3O+cfFHGHnUbj/HFrRNJ5vIts09FKJVAD8RpaC+RaWfA== - dependencies: - "@babel/runtime" "^7.10.4" - classnames "^2.2.5" - json2mq "^0.2.0" - lodash "^4.17.21" - resize-observer-polyfill "^1.5.1" - "@apideck/better-ajv-errors@^0.3.1": version "0.3.6" resolved "https://registry.yarnpkg.com/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.6.tgz#957d4c28e886a64a8141f7522783be65733ff097" @@ -53,6 +19,33 @@ jsonpointer "^5.0.0" leven "^3.1.0" +"@arco-design/color@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@arco-design/color/-/color-0.4.0.tgz#52ddb40d318ee6df1057ca8c653cc1675023928f" + integrity sha512-s7p9MSwJgHeL8DwcATaXvWT3m2SigKpxx4JA1BGPHL4gfvaQsmQfrLBDpjOJFJuJ2jG2dMt3R3P8Pm9E65q18g== + dependencies: + color "^3.1.3" + +"@arco-design/web-react@^2.65.0": + version "2.65.0" + resolved "https://registry.yarnpkg.com/@arco-design/web-react/-/web-react-2.65.0.tgz#bcba6417cf763860ee2e72051b7079bd9d6d8c9d" + integrity sha512-IzSHoKZ+2hYwf7gGYaISrw4RMzEzw9DoOeA8fggxikrwKs8BrzwwfpxTpJ4MGekerhbFDmereCrYNJnVnNrigA== + dependencies: + "@arco-design/color" "^0.4.0" + "@babel/runtime" "^7.5.5" + b-tween "^0.3.3" + b-validate "^1.4.2" + compute-scroll-into-view "^1.0.17" + dayjs "^1.10.5" + lodash "^4.17.21" + number-precision "^1.3.1" + react-focus-lock "^2.13.2" + react-is "^18.2.0" + react-transition-group "^4.3.0" + resize-observer-polyfill "^1.5.1" + scroll-into-view-if-needed "^2.2.20" + shallowequal "^1.1.0" + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.18.6", "@babel/code-frame@^7.8.3": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" @@ -1059,7 +1052,14 @@ core-js-pure "^3.20.2" regenerator-runtime "^0.13.4" -"@babel/runtime@^7.10.1", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.4", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.2", "@babel/runtime@^7.18.0", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.9", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.12.13", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.0.tgz#8600c2f595f277c60815256418b85356a65173c1" + integrity sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw== + dependencies: + regenerator-runtime "^0.14.0" + +"@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.2", "@babel/runtime@^7.18.9", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.9.tgz#b4fcfce55db3d2e5e080d2490f608a3b9f407f4a" integrity sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw== @@ -1220,11 +1220,6 @@ resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz#1bfafe4b7ed0f3e4105837e056e0a89b108ebe36" integrity sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg== -"@ctrl/tinycolor@^3.4.0": - version "3.4.1" - resolved "https://registry.yarnpkg.com/@ctrl/tinycolor/-/tinycolor-3.4.1.tgz#75b4c27948c81e88ccd3a8902047bcd797f38d32" - integrity sha512-ej5oVy6lykXsvieQtqZxCOaLT+xD4+QNarq78cIYISHmZXshCvROLudpQN3lfL8G0NL7plMSSK+zlyvCaIJ4Iw== - "@eslint/eslintrc@^1.3.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.0.tgz#29f92c30bb3e771e4a2048c95fa6855392dfac4f" @@ -2513,56 +2508,6 @@ ansi-styles@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== -antd@^4.21.7: - version "4.22.2" - resolved "https://registry.yarnpkg.com/antd/-/antd-4.22.2.tgz#8b022c7d234f9038f9aa018e837544bc1caace8c" - integrity sha512-3zyJ0Z9LaloCII0OLv7GUjKvigrmj44whPrNqrbLhcdc2O2oJNGtlPUf5bkrn98RIZSQ+EjXOuTlUSRbDjvLJA== - dependencies: - "@ant-design/colors" "^6.0.0" - "@ant-design/icons" "^4.7.0" - "@ant-design/react-slick" "~0.29.1" - "@babel/runtime" "^7.18.3" - "@ctrl/tinycolor" "^3.4.0" - classnames "^2.2.6" - copy-to-clipboard "^3.2.0" - lodash "^4.17.21" - memoize-one "^6.0.0" - moment "^2.29.2" - rc-cascader "~3.6.0" - rc-checkbox "~2.3.0" - rc-collapse "~3.3.0" - rc-dialog "~8.9.0" - rc-drawer "~5.1.0-alpha.1" - rc-dropdown "~4.0.0" - rc-field-form "~1.27.0" - rc-image "~5.7.0" - rc-input "~0.0.1-alpha.5" - rc-input-number "~7.3.5" - rc-mentions "~1.9.0" - rc-menu "~9.6.0" - rc-motion "^2.6.1" - rc-notification "~4.6.0" - rc-pagination "~3.1.17" - rc-picker "~2.6.10" - rc-progress "~3.3.2" - rc-rate "~2.9.0" - rc-resize-observer "^1.2.0" - rc-segmented "~2.1.0" - rc-select "~14.1.1" - rc-slider "~10.0.0" - rc-steps "~4.1.0" - rc-switch "~3.2.0" - rc-table "~7.25.3" - rc-tabs "~11.16.0" - rc-textarea "~0.3.0" - rc-tooltip "~5.2.0" - rc-tree "~5.6.5" - rc-tree-select "~5.4.0" - rc-trigger "^5.2.10" - rc-upload "~4.3.0" - rc-util "^5.22.5" - scroll-into-view-if-needed "^2.2.25" - anymatch@^3.0.3, anymatch@~3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" @@ -2622,11 +2567,6 @@ array-includes@^3.1.4, array-includes@^3.1.5: get-intrinsic "^1.1.1" is-string "^1.0.7" -array-tree-filter@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/array-tree-filter/-/array-tree-filter-2.1.0.tgz#873ac00fec83749f255ac8dd083814b4f6329190" - integrity sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw== - array-union@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" @@ -2683,11 +2623,6 @@ astral-regex@^2.0.0: resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== -async-validator@^4.1.0: - version "4.2.5" - resolved "https://registry.yarnpkg.com/async-validator/-/async-validator-4.2.5.tgz#c96ea3332a521699d0afaaceed510a54656c6339" - integrity sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg== - async@^3.2.3: version "3.2.4" resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" @@ -2732,6 +2667,16 @@ axobject-query@^2.2.0: resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" integrity sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA== +b-tween@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/b-tween/-/b-tween-0.3.3.tgz#7a93ed199c98cd41a33ba4c711a0fa7e86db3fa2" + integrity sha512-oEHegcRpA7fAuc9KC4nktucuZn2aS8htymCPcP3qkEGPqiBH+GfqtqoG2l7LxHngg6O0HFM7hOeOYExl1Oz4ZA== + +b-validate@^1.4.2: + version "1.5.3" + resolved "https://registry.yarnpkg.com/b-validate/-/b-validate-1.5.3.tgz#f6ac83b70caccbabf1c2eee42a0739bd228f79e6" + integrity sha512-iCvCkGFskbaYtfQ0a3GmcQCHl/Sv1GufXFGuUQ+FE+WJa7A/espLOuFIn09B944V8/ImPj71T4+rTASxO2PAuA== + babel-jest@^27.4.2, babel-jest@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-27.5.1.tgz#a1bf8d61928edfefd21da27eb86a695bfd691444" @@ -3139,11 +3084,6 @@ cjs-module-lexer@^1.0.0: resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== -classnames@2.x, classnames@^2.2.1, classnames@^2.2.3, classnames@^2.2.5, classnames@^2.2.6, classnames@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e" - integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA== - clean-css@^5.2.2: version "5.3.1" resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.3.1.tgz#d0610b0b90d125196a2894d35366f734e5d7aa32" @@ -3186,7 +3126,7 @@ collect-v8-coverage@^1.0.0: resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== -color-convert@^1.9.0: +color-convert@^1.9.0, color-convert@^1.9.3: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== @@ -3205,11 +3145,27 @@ color-name@1.1.3: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== -color-name@^1.1.4, color-name@~1.1.4: +color-name@^1.0.0, color-name@^1.1.4, color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +color-string@^1.6.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" + integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + +color@^3.1.3: + version "3.2.1" + resolved "https://registry.yarnpkg.com/color/-/color-3.2.1.tgz#3544dc198caf4490c3ecc9a790b54fe9ff45e164" + integrity sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA== + dependencies: + color-convert "^1.9.3" + color-string "^1.6.0" + colord@^2.9.1, colord@^2.9.2: version "2.9.2" resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.2.tgz#25e2bacbbaa65991422c07ea209e2089428effb1" @@ -3282,6 +3238,11 @@ compute-scroll-into-view@^1.0.17: resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.17.tgz#6a88f18acd9d42e9cf4baa6bec7e0522607ab7ab" integrity sha512-j4dx+Fb0URmzbwwMUrhqWM2BEWHdFGx+qZ9qqASHRPqvTYdqvWnHg0H1hIbcyLnvgnoNAVMlwkepyqM3DaIFUg== +compute-scroll-into-view@^1.0.20: + version "1.0.20" + resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz#1768b5522d1172754f5d0c9b02de3af6be506a43" + integrity sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -3333,13 +3294,6 @@ copy-anything@^2.0.1: dependencies: is-what "^3.14.1" -copy-to-clipboard@^3.2.0: - version "3.3.1" - resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz#115aa1a9998ffab6196f93076ad6da3b913662ae" - integrity sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw== - dependencies: - toggle-selection "^1.0.6" - core-js-compat@^3.21.0, core-js-compat@^3.22.1: version "3.24.1" resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.24.1.tgz#d1af84a17e18dfdd401ee39da9996f9a7ba887de" @@ -3636,15 +3590,10 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" -date-fns@2.x: - version "2.29.1" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.1.tgz#9667c2615525e552b5135a3116b95b1961456e60" - integrity sha512-dlLD5rKaKxpFdnjrs+5azHDFOPEu4ANy/LTh04A1DTzMM7qoajmKCBc8pkKRFT41CNzw+4gQh79X5C+Jq27HAw== - -dayjs@1.x: - version "1.11.4" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.4.tgz#3b3c10ca378140d8917e06ebc13a4922af4f433e" - integrity sha512-Zj/lPM5hOvQ1Bf7uAvewDaUcsJoI6JmNqmHhHl3nyumwe0XHwt8sWdOVAPACJzCebL8gQCi+K49w7iKWnGwX9g== +dayjs@^1.10.5: + version "1.11.13" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.13.tgz#92430b0139055c3ebb60150aa13e860a4b5a366c" + integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg== debug@2.6.9, debug@^2.6.0, debug@^2.6.9: version "2.6.9" @@ -3750,6 +3699,11 @@ detect-newline@^3.0.0: resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== +detect-node-es@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493" + integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ== + detect-node@^2.0.4: version "2.1.0" resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" @@ -3825,11 +3779,6 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -dom-align@^1.7.0: - version "1.12.3" - resolved "https://registry.yarnpkg.com/dom-align/-/dom-align-1.12.3.tgz#a36d02531dae0eefa2abb0c4db6595250526f103" - integrity sha512-Gj9hZN3a07cbR6zviMUBOMPdWxYhbMI+x+WS0NAIu2zFZmbK8ys9R79g+iG9qLnlCwpFoaB+fKy8Pdv470GsPA== - dom-converter@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" @@ -3837,6 +3786,14 @@ dom-converter@^0.2.0: dependencies: utila "~0.4" +dom-helpers@^5.0.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" + integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== + dependencies: + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" + dom-serializer@0: version "0.2.2" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" @@ -4633,6 +4590,13 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.6.tgz#022e9218c637f9f3fc9c35ab9c9193f05add60b2" integrity sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ== +focus-lock@^1.3.5: + version "1.3.6" + resolved "https://registry.yarnpkg.com/focus-lock/-/focus-lock-1.3.6.tgz#955eec1e10591d56f679258edb94aedb11d691cd" + integrity sha512-Ik/6OCk9RQQ0T5Xw+hKNLWrjSMtv51dD4GRmJjbD5a58TIEpI5a5iXagKVl3Z5UuyslMCA8Xwnu76jQob62Yhg== + dependencies: + tslib "^2.0.3" + follow-redirects@^1.0.0: version "1.15.1" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.1.tgz#0ca6a452306c9b276e4d3127483e29575e207ad5" @@ -5244,6 +5208,11 @@ is-arrayish@^0.2.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== +is-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + is-bigint@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" @@ -6064,13 +6033,6 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== -json2mq@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/json2mq/-/json2mq-0.2.0.tgz#b637bd3ba9eabe122c83e9720483aeb10d2c904a" - integrity sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA== - dependencies: - string-convert "^0.2.0" - json5@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" @@ -6370,11 +6332,6 @@ memfs@^3.1.2, memfs@^3.4.3: dependencies: fs-monkey "^1.0.3" -memoize-one@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045" - integrity sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw== - meow@^9.0.0: version "9.0.0" resolved "https://registry.yarnpkg.com/meow/-/meow-9.0.0.tgz#cd9510bc5cac9dee7d03c73ee1f9ad959f4ea364" @@ -6502,11 +6459,6 @@ mkdirp@~0.5.1: dependencies: minimist "^1.2.6" -moment@^2.24.0, moment@^2.29.2: - version "2.29.4" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108" - integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== - ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -6638,6 +6590,11 @@ nth-check@^2.0.1: dependencies: boolbase "^1.0.0" +number-precision@^1.3.1: + version "1.6.0" + resolved "https://registry.yarnpkg.com/number-precision/-/number-precision-1.6.0.tgz#e309d28f80871d36ac9f6ecd974e13afb1ec0de0" + integrity sha512-05OLPgbgmnixJw+VvEh18yNPUo3iyp4BEWJcrLu4X9W05KmMifN7Mu5exYvQXqxxeNWhvIF+j3Rij+HmddM/hQ== + nwsapi@^2.2.0: version "2.2.1" resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.1.tgz#10a9f268fbf4c461249ebcfe38e359aa36e2577c" @@ -7625,7 +7582,7 @@ prompts@^2.0.1, prompts@^2.4.2: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@^15.7.2, prop-types@^15.8.1: +prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -7713,374 +7670,6 @@ raw-body@2.5.1: iconv-lite "0.4.24" unpipe "1.0.0" -rc-align@^4.0.0: - version "4.0.12" - resolved "https://registry.yarnpkg.com/rc-align/-/rc-align-4.0.12.tgz#065b5c68a1cc92a00800c9239320d9fdf5f16207" - integrity sha512-3DuwSJp8iC/dgHzwreOQl52soj40LchlfUHtgACOUtwGuoFIOVh6n/sCpfqCU8kO5+iz6qR0YKvjgB8iPdE3aQ== - dependencies: - "@babel/runtime" "^7.10.1" - classnames "2.x" - dom-align "^1.7.0" - lodash "^4.17.21" - rc-util "^5.3.0" - resize-observer-polyfill "^1.5.1" - -rc-cascader@~3.6.0: - version "3.6.1" - resolved "https://registry.yarnpkg.com/rc-cascader/-/rc-cascader-3.6.1.tgz#2e94fb3ed770ffd71d87ebcf17a9b44a6442e76f" - integrity sha512-+GmN2Z0IybKT45t0Z94jkjmsOHGxAliobR2tzt05/Gw0AKBYLHX5bdvsVXR7abPnarYyYzZ/cWe8CoFgDjAFNw== - dependencies: - "@babel/runtime" "^7.12.5" - array-tree-filter "^2.1.0" - classnames "^2.3.1" - rc-select "~14.1.0" - rc-tree "~5.6.3" - rc-util "^5.6.1" - -rc-checkbox@~2.3.0: - version "2.3.2" - resolved "https://registry.yarnpkg.com/rc-checkbox/-/rc-checkbox-2.3.2.tgz#f91b3678c7edb2baa8121c9483c664fa6f0aefc1" - integrity sha512-afVi1FYiGv1U0JlpNH/UaEXdh6WUJjcWokj/nUN2TgG80bfG+MDdbfHKlLcNNba94mbjy2/SXJ1HDgrOkXGAjg== - dependencies: - "@babel/runtime" "^7.10.1" - classnames "^2.2.1" - -rc-collapse@~3.3.0: - version "3.3.1" - resolved "https://registry.yarnpkg.com/rc-collapse/-/rc-collapse-3.3.1.tgz#fc66d4c9cfeaf41e932b2de6da2d454874aee55a" - integrity sha512-cOJfcSe3R8vocrF8T+PgaHDrgeA1tX+lwfhwSj60NX9QVRidsILIbRNDLD6nAzmcvVC5PWiIRiR4S1OobxdhCg== - dependencies: - "@babel/runtime" "^7.10.1" - classnames "2.x" - rc-motion "^2.3.4" - rc-util "^5.2.1" - shallowequal "^1.1.0" - -rc-dialog@~8.9.0: - version "8.9.0" - resolved "https://registry.yarnpkg.com/rc-dialog/-/rc-dialog-8.9.0.tgz#04dc39522f0321ed2e06018d4a7e02a4c32bd3ea" - integrity sha512-Cp0tbJnrvPchJfnwIvOMWmJ4yjX3HWFatO6oBFD1jx8QkgsQCR0p8nUWAKdd3seLJhEC39/v56kZaEjwp9muoQ== - dependencies: - "@babel/runtime" "^7.10.1" - classnames "^2.2.6" - rc-motion "^2.3.0" - rc-util "^5.21.0" - -rc-drawer@~5.1.0-alpha.1: - version "5.1.0-alpha.2" - resolved "https://registry.yarnpkg.com/rc-drawer/-/rc-drawer-5.1.0-alpha.2.tgz#402b37d65f43dd9e3dd2fba3ace2c88aba979fd3" - integrity sha512-tDT2WG9j4zLUL1kw7BJ1u+cCDypqgonwVO3OwIzksyqeqeLKWRJCwRSxwLYDsuVeCoXFndCGZ7qmRUG83z6mOQ== - dependencies: - "@babel/runtime" "^7.10.1" - classnames "^2.2.6" - rc-motion "^2.6.1" - rc-util "^5.21.2" - -rc-dropdown@~4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/rc-dropdown/-/rc-dropdown-4.0.1.tgz#f65d9d3d89750241057db59d5a75e43cd4576b68" - integrity sha512-OdpXuOcme1rm45cR0Jzgfl1otzmU4vuBVb+etXM8vcaULGokAKVpKlw8p6xzspG7jGd/XxShvq+N3VNEfk/l5g== - dependencies: - "@babel/runtime" "^7.18.3" - classnames "^2.2.6" - rc-trigger "^5.3.1" - rc-util "^5.17.0" - -rc-field-form@~1.27.0: - version "1.27.1" - resolved "https://registry.yarnpkg.com/rc-field-form/-/rc-field-form-1.27.1.tgz#11d61ccb43679e71fdbbff0d821326202554df84" - integrity sha512-RShegnwFu6TH8tl2olCxn+B4Wyh5EiQH8c/7wucbkLNyue05YiH5gomUAg1vbZjp71yFKwegClctsEG5CNBWAA== - dependencies: - "@babel/runtime" "^7.18.0" - async-validator "^4.1.0" - rc-util "^5.8.0" - -rc-image@~5.7.0: - version "5.7.0" - resolved "https://registry.yarnpkg.com/rc-image/-/rc-image-5.7.0.tgz#e1a3b21099feb3fb9bf8ef3ce12c3fc11a8c1148" - integrity sha512-v6dzSgYfYrH4liKmOZKZZO+x21sJ9KPXNinBfkAoQg2Ihcd5QZ+P/JjB7v60X981XTPGjegy8U17Z8VUX4V36g== - dependencies: - "@babel/runtime" "^7.11.2" - classnames "^2.2.6" - rc-dialog "~8.9.0" - rc-util "^5.0.6" - -rc-input-number@~7.3.5: - version "7.3.6" - resolved "https://registry.yarnpkg.com/rc-input-number/-/rc-input-number-7.3.6.tgz#54d66bd3fdaef0abfded4c734a12ac6d9461ebab" - integrity sha512-Se62oMOBn9HwF/gSag+YtAYyKZsjJzEsqmyAJHAnAvPfjZJOu7dLMlQRwBbTtELbKXM/Y5Fztcq8CW2Y9f49qA== - dependencies: - "@babel/runtime" "^7.10.1" - classnames "^2.2.5" - rc-util "^5.23.0" - -rc-input@~0.0.1-alpha.5: - version "0.0.1-alpha.7" - resolved "https://registry.yarnpkg.com/rc-input/-/rc-input-0.0.1-alpha.7.tgz#53e3f13871275c21d92b51f80b698f389ad45dd3" - integrity sha512-eozaqpCYWSY5LBMwlHgC01GArkVEP+XlJ84OMvdkwUnJBSv83Yxa15pZpn7vACAj84uDC4xOA2CoFdbLuqB08Q== - dependencies: - "@babel/runtime" "^7.11.1" - classnames "^2.2.1" - rc-util "^5.18.1" - -rc-mentions@~1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/rc-mentions/-/rc-mentions-1.9.0.tgz#0ddff6ab442dce58efa7b9f5ae561b0051173334" - integrity sha512-CSC2t8WxK8daS8lylJcquzCgKz4bXLAVNrSHXlTI8fNUy4toot8Sv79wT4fcP/bYgdt07/e3RXzkZfX6xjYYow== - dependencies: - "@babel/runtime" "^7.10.1" - classnames "^2.2.6" - rc-menu "~9.6.0" - rc-textarea "^0.3.0" - rc-trigger "^5.0.4" - rc-util "^5.22.5" - -rc-menu@~9.6.0: - version "9.6.0" - resolved "https://registry.yarnpkg.com/rc-menu/-/rc-menu-9.6.0.tgz#3263a729a81ae49cfdadee112e97d3c702922829" - integrity sha512-d26waws42U/rVwW/+rOE2FN9pX6wUc9bDy38vVQYoie6gE85auWIpl5oChGlnW6nE2epnTwUsgWl8ipOPgmnUA== - dependencies: - "@babel/runtime" "^7.10.1" - classnames "2.x" - rc-motion "^2.4.3" - rc-overflow "^1.2.0" - rc-trigger "^5.1.2" - rc-util "^5.12.0" - shallowequal "^1.1.0" - -rc-motion@^2.0.0, rc-motion@^2.0.1, rc-motion@^2.2.0, rc-motion@^2.3.0, rc-motion@^2.3.4, rc-motion@^2.4.3, rc-motion@^2.4.4, rc-motion@^2.6.1: - version "2.6.2" - resolved "https://registry.yarnpkg.com/rc-motion/-/rc-motion-2.6.2.tgz#3d31f97e41fb8e4f91a4a4189b6a98ac63342869" - integrity sha512-4w1FaX3dtV749P8GwfS4fYnFG4Rb9pxvCYPc/b2fw1cmlHJWNNgOFIz7ysiD+eOrzJSvnLJWlNQQncpNMXwwpg== - dependencies: - "@babel/runtime" "^7.11.1" - classnames "^2.2.1" - rc-util "^5.21.0" - -rc-notification@~4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/rc-notification/-/rc-notification-4.6.0.tgz#4e76fc2d0568f03cc93ac18c9e20763ebe29fa46" - integrity sha512-xF3MKgIoynzjQAO4lqsoraiFo3UXNYlBfpHs0VWvwF+4pimen9/H1DYLN2mfRWhHovW6gRpla73m2nmyIqAMZQ== - dependencies: - "@babel/runtime" "^7.10.1" - classnames "2.x" - rc-motion "^2.2.0" - rc-util "^5.20.1" - -rc-overflow@^1.0.0, rc-overflow@^1.2.0: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc-overflow/-/rc-overflow-1.2.8.tgz#40f140fabc244118543e627cdd1ef750d9481a88" - integrity sha512-QJ0UItckWPQ37ZL1dMEBAdY1dhfTXFL9k6oTTcyydVwoUNMnMqCGqnRNA98axSr/OeDKqR6DVFyi8eA5RQI/uQ== - dependencies: - "@babel/runtime" "^7.11.1" - classnames "^2.2.1" - rc-resize-observer "^1.0.0" - rc-util "^5.19.2" - -rc-pagination@~3.1.17: - version "3.1.17" - resolved "https://registry.yarnpkg.com/rc-pagination/-/rc-pagination-3.1.17.tgz#91e690aa894806e344cea88ea4a16d244194a1bd" - integrity sha512-/BQ5UxcBnW28vFAcP2hfh+Xg15W0QZn8TWYwdCApchMH1H0CxiaUUcULP8uXcFM1TygcdKWdt3JqsL9cTAfdkQ== - dependencies: - "@babel/runtime" "^7.10.1" - classnames "^2.2.1" - -rc-picker@~2.6.10: - version "2.6.10" - resolved "https://registry.yarnpkg.com/rc-picker/-/rc-picker-2.6.10.tgz#8d0a473c079388bdb2d7358a2a54c7d5095893b4" - integrity sha512-9wYtw0DFWs9FO92Qh2D76P0iojUr8ZhLOtScUeOit6ks/F+TBLrOC1uze3IOu+u9gbDAjmosNWLKbBzx/Yuv2w== - dependencies: - "@babel/runtime" "^7.10.1" - classnames "^2.2.1" - date-fns "2.x" - dayjs "1.x" - moment "^2.24.0" - rc-trigger "^5.0.4" - rc-util "^5.4.0" - shallowequal "^1.1.0" - -rc-progress@~3.3.2: - version "3.3.3" - resolved "https://registry.yarnpkg.com/rc-progress/-/rc-progress-3.3.3.tgz#eb9bffbacab1534f2542f9f6861ce772254362b1" - integrity sha512-MDVNVHzGanYtRy2KKraEaWeZLri2ZHWIRyaE1a9MQ2MuJ09m+Wxj5cfcaoaR6z5iRpHpA59YeUxAlpML8N4PJw== - dependencies: - "@babel/runtime" "^7.10.1" - classnames "^2.2.6" - rc-util "^5.16.1" - -rc-rate@~2.9.0: - version "2.9.2" - resolved "https://registry.yarnpkg.com/rc-rate/-/rc-rate-2.9.2.tgz#4a58965d1ecf91896ebae01d458b59056df0b4ea" - integrity sha512-SaiZFyN8pe0Fgphv8t3+kidlej+cq/EALkAJAc3A0w0XcPaH2L1aggM8bhe1u6GAGuQNAoFvTLjw4qLPGRKV5g== - dependencies: - "@babel/runtime" "^7.10.1" - classnames "^2.2.5" - rc-util "^5.0.1" - -rc-resize-observer@^1.0.0, rc-resize-observer@^1.1.0, rc-resize-observer@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/rc-resize-observer/-/rc-resize-observer-1.2.0.tgz#9f46052f81cdf03498be35144cb7c53fd282c4c7" - integrity sha512-6W+UzT3PyDM0wVCEHfoW3qTHPTvbdSgiA43buiy8PzmeMnfgnDeb9NjdimMXMl3/TcrvvWl5RRVdp+NqcR47pQ== - dependencies: - "@babel/runtime" "^7.10.1" - classnames "^2.2.1" - rc-util "^5.15.0" - resize-observer-polyfill "^1.5.1" - -rc-segmented@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/rc-segmented/-/rc-segmented-2.1.0.tgz#0e0afe646c1a0e44a0e18785f518c42633ec8efc" - integrity sha512-hUlonro+pYoZcwrH6Vm56B2ftLfQh046hrwif/VwLIw1j3zGt52p5mREBwmeVzXnSwgnagpOpfafspzs1asjGw== - dependencies: - "@babel/runtime" "^7.11.1" - classnames "^2.2.1" - rc-motion "^2.4.4" - rc-util "^5.17.0" - -rc-select@~14.1.0, rc-select@~14.1.1: - version "14.1.9" - resolved "https://registry.yarnpkg.com/rc-select/-/rc-select-14.1.9.tgz#9c9eceff00920ad8a0a53b77b76afc177ffe5ab4" - integrity sha512-DK01+Q7oCWr5jVPiEp/BTQ8xCB4rI4LfXzZtSmBWJhOMuibyZD1Vlz/DlVKCUFmtBM4SzG4/SltGHoGlcbCqiw== - dependencies: - "@babel/runtime" "^7.10.1" - classnames "2.x" - rc-motion "^2.0.1" - rc-overflow "^1.0.0" - rc-trigger "^5.0.4" - rc-util "^5.16.1" - rc-virtual-list "^3.2.0" - -rc-slider@~10.0.0: - version "10.0.1" - resolved "https://registry.yarnpkg.com/rc-slider/-/rc-slider-10.0.1.tgz#7058c68ff1e1aa4e7c3536e5e10128bdbccb87f9" - integrity sha512-igTKF3zBet7oS/3yNiIlmU8KnZ45npmrmHlUUio8PNbIhzMcsh+oE/r2UD42Y6YD2D/s+kzCQkzQrPD6RY435Q== - dependencies: - "@babel/runtime" "^7.10.1" - classnames "^2.2.5" - rc-util "^5.18.1" - shallowequal "^1.1.0" - -rc-steps@~4.1.0: - version "4.1.4" - resolved "https://registry.yarnpkg.com/rc-steps/-/rc-steps-4.1.4.tgz#0ba82db202d59ca52d0693dc9880dd145b19dc23" - integrity sha512-qoCqKZWSpkh/b03ASGx1WhpKnuZcRWmvuW+ZUu4mvMdfvFzVxblTwUM+9aBd0mlEUFmt6GW8FXhMpHkK3Uzp3w== - dependencies: - "@babel/runtime" "^7.10.2" - classnames "^2.2.3" - rc-util "^5.0.1" - -rc-switch@~3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/rc-switch/-/rc-switch-3.2.2.tgz#d001f77f12664d52595b4f6fb425dd9e66fba8e8" - integrity sha512-+gUJClsZZzvAHGy1vZfnwySxj+MjLlGRyXKXScrtCTcmiYNPzxDFOxdQ/3pK1Kt/0POvwJ/6ALOR8gwdXGhs+A== - dependencies: - "@babel/runtime" "^7.10.1" - classnames "^2.2.1" - rc-util "^5.0.1" - -rc-table@~7.25.3: - version "7.25.3" - resolved "https://registry.yarnpkg.com/rc-table/-/rc-table-7.25.3.tgz#a2941d4fde4c181e687e97a294faca8e4122e26d" - integrity sha512-McsLJ2rg8EEpRBRYN4Pf9gT7ZNYnjvF9zrBpUBBbUX/fxk+eGi5ff1iPIhMyiHsH71/BmTUzX9nc9XqupD0nMg== - dependencies: - "@babel/runtime" "^7.10.1" - classnames "^2.2.5" - rc-resize-observer "^1.1.0" - rc-util "^5.22.5" - shallowequal "^1.1.0" - -rc-tabs@~11.16.0: - version "11.16.1" - resolved "https://registry.yarnpkg.com/rc-tabs/-/rc-tabs-11.16.1.tgz#7c57b6a092d9d0e2df54413b0319f195c27214a9" - integrity sha512-bR7Dap23YyfzZQwtKomhiFEFzZuE7WaKWo+ypNRSGB9PDKSc6tM12VP8LWYkvmmQHthgwP0WRN8nFbSJWuqLYw== - dependencies: - "@babel/runtime" "^7.11.2" - classnames "2.x" - rc-dropdown "~4.0.0" - rc-menu "~9.6.0" - rc-resize-observer "^1.0.0" - rc-util "^5.5.0" - -rc-textarea@^0.3.0, rc-textarea@~0.3.0: - version "0.3.7" - resolved "https://registry.yarnpkg.com/rc-textarea/-/rc-textarea-0.3.7.tgz#987142891efdedb774883c07e2f51b318fde5a11" - integrity sha512-yCdZ6binKmAQB13hc/oehh0E/QRwoPP1pjF21aHBxlgXO3RzPF6dUu4LG2R4FZ1zx/fQd2L1faktulrXOM/2rw== - dependencies: - "@babel/runtime" "^7.10.1" - classnames "^2.2.1" - rc-resize-observer "^1.0.0" - rc-util "^5.7.0" - shallowequal "^1.1.0" - -rc-tooltip@~5.2.0: - version "5.2.2" - resolved "https://registry.yarnpkg.com/rc-tooltip/-/rc-tooltip-5.2.2.tgz#e5cafa8ecebf78108936a0bcb93c150fa81ac93b" - integrity sha512-jtQzU/18S6EI3lhSGoDYhPqNpWajMtS5VV/ld1LwyfrDByQpYmw/LW6U7oFXXLukjfDHQ7Ju705A82PRNFWYhg== - dependencies: - "@babel/runtime" "^7.11.2" - classnames "^2.3.1" - rc-trigger "^5.0.0" - -rc-tree-select@~5.4.0: - version "5.4.0" - resolved "https://registry.yarnpkg.com/rc-tree-select/-/rc-tree-select-5.4.0.tgz#c94b961aca68689f5ee3a43e33881cf693d195ef" - integrity sha512-reRbOqC7Ic/nQocJAJeCl4n6nJUY3NoqiwRXKvhjgZJU7NGr9vIccXEsY+Lghkw5UMpPoxGsIJB0jiAvM18XYA== - dependencies: - "@babel/runtime" "^7.10.1" - classnames "2.x" - rc-select "~14.1.0" - rc-tree "~5.6.1" - rc-util "^5.16.1" - -rc-tree@~5.6.1, rc-tree@~5.6.3, rc-tree@~5.6.5: - version "5.6.6" - resolved "https://registry.yarnpkg.com/rc-tree/-/rc-tree-5.6.6.tgz#c04253d8f8345ec52fc196dec2be06c7e708125b" - integrity sha512-HI/q4D4AHOp48OZcBUvJFWkI5OfnZivvGYI0xzI0dy0Mita2KcTGZv7/Yl6Aq3bL3od3x5AqAXq/7qxR3x4Kkg== - dependencies: - "@babel/runtime" "^7.10.1" - classnames "2.x" - rc-motion "^2.0.1" - rc-util "^5.16.1" - rc-virtual-list "^3.4.8" - -rc-trigger@^5.0.0, rc-trigger@^5.0.4, rc-trigger@^5.1.2, rc-trigger@^5.2.10, rc-trigger@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/rc-trigger/-/rc-trigger-5.3.1.tgz#acafadf3eaf384e7f466c303bfa0f34c8137d7b8" - integrity sha512-5gaFbDkYSefZ14j2AdzucXzlWgU2ri5uEjkHvsf1ynRhdJbKxNOnw4PBZ9+FVULNGFiDzzlVF8RJnR9P/xrnKQ== - dependencies: - "@babel/runtime" "^7.18.3" - classnames "^2.2.6" - rc-align "^4.0.0" - rc-motion "^2.0.0" - rc-util "^5.19.2" - -rc-upload@~4.3.0: - version "4.3.4" - resolved "https://registry.yarnpkg.com/rc-upload/-/rc-upload-4.3.4.tgz#83ff7d3867631c37adbfd72ea3d1fd7e97ca84af" - integrity sha512-uVbtHFGNjHG/RyAfm9fluXB6pvArAGyAx8z7XzXXyorEgVIWj6mOlriuDm0XowDHYz4ycNK0nE0oP3cbFnzxiQ== - dependencies: - "@babel/runtime" "^7.18.3" - classnames "^2.2.5" - rc-util "^5.2.0" - -rc-util@^5.0.1, rc-util@^5.0.6, rc-util@^5.12.0, rc-util@^5.15.0, rc-util@^5.16.1, rc-util@^5.17.0, rc-util@^5.18.1, rc-util@^5.19.2, rc-util@^5.2.0, rc-util@^5.2.1, rc-util@^5.20.1, rc-util@^5.21.0, rc-util@^5.21.2, rc-util@^5.22.5, rc-util@^5.23.0, rc-util@^5.3.0, rc-util@^5.4.0, rc-util@^5.5.0, rc-util@^5.6.1, rc-util@^5.7.0, rc-util@^5.8.0, rc-util@^5.9.4: - version "5.23.0" - resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.23.0.tgz#a583b1ec3e1832a80eced7a700a494af0b590743" - integrity sha512-lgm6diJ/pLgyfoZY59Vz7sW4mSoQCgozqbBye9IJ7/mb5w5h4T7h+i2JpXAx/UBQxscBZe68q0sP7EW+qfkKUg== - dependencies: - "@babel/runtime" "^7.18.3" - react-is "^16.12.0" - shallowequal "^1.1.0" - -rc-virtual-list@^3.2.0, rc-virtual-list@^3.4.8: - version "3.4.8" - resolved "https://registry.yarnpkg.com/rc-virtual-list/-/rc-virtual-list-3.4.8.tgz#c24c10c6940546b7e2a5e9809402c6716adfd26c" - integrity sha512-qSN+Rv4i/E7RCTvTMr1uZo7f3crJJg/5DekoCagydo9zsXrxj07zsFSxqizqW+ldGA16lwa8So/bIbV9Ofjddg== - dependencies: - classnames "^2.2.6" - rc-resize-observer "^1.0.0" - rc-util "^5.15.0" - react-app-polyfill@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/react-app-polyfill/-/react-app-polyfill-3.0.0.tgz#95221e0a9bd259e5ca6b177c7bb1cb6768f68fd7" @@ -8093,6 +7682,13 @@ react-app-polyfill@^3.0.0: regenerator-runtime "^0.13.9" whatwg-fetch "^3.6.2" +react-clientside-effect@^1.2.6: + version "1.2.7" + resolved "https://registry.yarnpkg.com/react-clientside-effect/-/react-clientside-effect-1.2.7.tgz#78eb62e3be36208d4d8d5b2668ae630a32deca73" + integrity sha512-gce9m0Pk/xYYMEojRI9bgvqQAkl6hm7ozQvqWPyQx+kULiatdHgkNM1QG4DQRx5N9BAzWSCJmt9mMV8/KsdgVg== + dependencies: + "@babel/runtime" "^7.12.13" + react-dev-utils@^12.0.1: version "12.0.1" resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-12.0.1.tgz#ba92edb4a1f379bd46ccd6bcd4e7bc398df33e73" @@ -8141,6 +7737,18 @@ react-fast-compare@^3.1.1: resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49" integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ== +react-focus-lock@^2.13.2: + version "2.13.5" + resolved "https://registry.yarnpkg.com/react-focus-lock/-/react-focus-lock-2.13.5.tgz#68b01618ef3a4717746a02e223afe9d86a69a95e" + integrity sha512-HjHuZFFk2+j6ZT3LDQpyqffue541HrxUG/OFchCEwis9nstgNg0rREVRAxHBcB1lHJ5Fsxtx1qya/5xFwxDb4g== + dependencies: + "@babel/runtime" "^7.0.0" + focus-lock "^1.3.5" + prop-types "^15.6.2" + react-clientside-effect "^1.2.6" + use-callback-ref "^1.3.2" + use-sidecar "^1.1.2" + react-helmet@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-6.1.0.tgz#a750d5165cb13cf213e44747502652e794468726" @@ -8159,7 +7767,7 @@ react-i18next@^11.18.3: "@babel/runtime" "^7.14.5" html-parse-stringify "^3.0.1" -react-is@^16.12.0, react-is@^16.13.1, react-is@^16.7.0: +react-is@^16.13.1, react-is@^16.7.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -8174,6 +7782,11 @@ react-is@^18.0.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== +react-is@^18.2.0: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + react-redux@^8.0.2: version "8.0.2" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.0.2.tgz#bc2a304bb21e79c6808e3e47c50fe1caf62f7aad" @@ -8266,6 +7879,16 @@ react-side-effect@^2.1.0: resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-2.1.2.tgz#dc6345b9e8f9906dc2eeb68700b615e0b4fe752a" integrity sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw== +react-transition-group@^4.3.0: + version "4.4.5" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" + integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== + dependencies: + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + react@^18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" @@ -8372,6 +7995,11 @@ regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.9: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== +regenerator-runtime@^0.14.0: + version "0.14.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" + integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== + regenerator-transform@^0.15.0: version "0.15.0" resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.0.tgz#cbd9ead5d77fae1a48d957cf889ad0586adb6537" @@ -8639,12 +8267,12 @@ schema-utils@^4.0.0: ajv-formats "^2.1.1" ajv-keywords "^5.0.0" -scroll-into-view-if-needed@^2.2.25: - version "2.2.29" - resolved "https://registry.yarnpkg.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.29.tgz#551791a84b7e2287706511f8c68161e4990ab885" - integrity sha512-hxpAR6AN+Gh53AdAimHM6C8oTN1ppwVZITihix+WqalywBeFcQ6LdQP5ABNl26nX8GTEL7VT+b8lKpdqq65wXg== +scroll-into-view-if-needed@^2.2.20: + version "2.2.31" + resolved "https://registry.yarnpkg.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz#d3c482959dc483e37962d1521254e3295d0d1587" + integrity sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA== dependencies: - compute-scroll-into-view "^1.0.17" + compute-scroll-into-view "^1.0.20" select-hose@^2.0.0: version "2.0.0" @@ -8782,6 +8410,13 @@ signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== + dependencies: + is-arrayish "^0.3.1" + sisteransi@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" @@ -8945,11 +8580,6 @@ statuses@2.0.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== -string-convert@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/string-convert/-/string-convert-0.2.1.tgz#6982cc3049fbb4cd85f8b24568b9d9bf39eeff97" - integrity sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A== - string-length@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" @@ -9377,11 +9007,6 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -toggle-selection@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32" - integrity sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ== - toidentifier@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" @@ -9454,6 +9079,11 @@ tslib@^1.8.1: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== +tslib@^2.0.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + tslib@^2.0.3, tslib@^2.3.0: version "2.4.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" @@ -9615,6 +9245,21 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +use-callback-ref@^1.3.2: + version "1.3.3" + resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.3.3.tgz#98d9fab067075841c5b2c6852090d5d0feabe2bf" + integrity sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg== + dependencies: + tslib "^2.0.0" + +use-sidecar@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.1.3.tgz#10e7fd897d130b896e2c546c63a5e8233d00efdb" + integrity sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ== + dependencies: + detect-node-es "^1.1.0" + tslib "^2.0.0" + use-sync-external-store@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"