feat: upgrade sdk version && refactor configuration
4
.gitignore
vendored
@ -27,3 +27,7 @@ yarn-error.log*
|
||||
/Server/log
|
||||
/log
|
||||
yarn.lock
|
||||
.eslintcache
|
||||
pnpm-lock.yaml
|
||||
dist/
|
||||
log/
|
||||
@ -3,7 +3,7 @@
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"jsxSingleQuote": false,
|
||||
"printWidth": 200,
|
||||
"printWidth": 100,
|
||||
"useTabs": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
|
||||
80
README.md
@ -1,19 +1,48 @@
|
||||
# 交互式AIGC场景 AIGC Demo
|
||||
|
||||
此 Demo 为简化版本, 如您有 1.5.x 版本 UI 的诉求, 可切换至 1.5.1 分支。
|
||||
跑通阶段时, 无须关心代码实现,仅需按需完成 `src/config/scenes/*.json` 的填充以及 `Server/sensitive.js` 中的信息填充即可。
|
||||
|
||||
## 简介
|
||||
- 在 AIGC 对话场景下,火山引擎 AIGC-RTC Server 云端服务,通过整合 RTC 音视频流处理,ASR 语音识别,大模型接口调用集成,以及 TTS 语音生成等能力,提供基于流式语音的端到端AIGC能力链路。
|
||||
- 用户只需调用基于标准的 OpenAPI 接口即可配置所需的 ASR、LLM、TTS 类型和参数。火山引擎云端计算服务负责边缘用户接入、云端资源调度、音视频流压缩、文本与语音转换处理以及数据订阅传输等环节。简化开发流程,让开发者更专注在对大模型核心能力的训练及调试,从而快速推进AIGC产品应用创新。
|
||||
- 同时火山引擎 RTC拥有成熟的音频 3A 处理、视频处理等技术以及大规模音视频聊天能力,可支持 AIGC 产品更便捷的支持多模态交互、多人互动等场景能力,保持交互的自然性和高效性。
|
||||
|
||||
## 【必看】环境准备
|
||||
- **Node 版本: 16.0+**
|
||||
1. 需要准备两个 Terminal,分别启动服务端、前端页面。
|
||||
2. 开通 ASR、TTS、LLM、RTC 等服务,可参考 [开通服务](https://www.volcengine.com/docs/6348/1315561?s=g) 进行相关服务的授权与开通。
|
||||
3. **根据你自定义的
|
||||
RoomId、UserId 以及申请的 AppID、BusinessID(如有)、Token、ASR AppID、TTS AppID,修改 `src/config/config.ts` 文件中 `ConfigFactory` 中 `BaseConfig` 的配置信息**。
|
||||
4. 使用火山引擎控制台账号的 [AK、SK](https://console.volcengine.com/iam/keymanage?s=g), 修改 `Server/app.js` 文件中的 `ACCOUNT_INFO`。
|
||||
5. 若您使用的是官方模型, 需要在 [火山方舟-在线推理](https://console.volcengine.com/ark/region:ark+cn-beijing/endpoint?config=%7B%7D&s=g) 中创建接入点, 并将模型对应的接入点 ID 填入 `src/config/common.ts` 文件中的 `ARK_V3_MODEL_ID`, 否则无法正常启动智能体。
|
||||
6. 如果您已经自行完成了服务端的逻辑,可以不依赖 Demo 中的 Server,直接修改前端代码文件 `src/config/index.ts` 中的 `AIGC_PROXY_HOST` 请求域名和接口,并在 `src/app/api.ts` 中修改接口的参数配置 `APIS_CONFIG`。
|
||||
**Node 版本: 16.0+**
|
||||
|
||||
### 1. 运行环境
|
||||
需要准备两个 Terminal,分别启动服务端和前端页面。
|
||||
|
||||
### 2. 服务开通
|
||||
开通 ASR、TTS、LLM、RTC 等服务,可参考 [开通服务](https://www.volcengine.com/docs/6348/1315561?s=g) 进行相关服务的授权与开通。
|
||||
|
||||
### 3. 参数配置
|
||||
#### 3.1 服务端配置(`Server/sensitive.js`)
|
||||
- 必填参数:
|
||||
- `ACCOUNT_INFO`:填写[火山引擎控制台](https://console.volcengine.com/iam/keymanage)的 AK、SK
|
||||
- `RTC_INFO`:填写[应用管理](https://console.volcengine.com/rtc/aigc/listRTC)的 AppID、AppKey
|
||||
- `ASRConfig`:填写 ASR AppID
|
||||
- `TTSConfig`:填写 TTS AppID
|
||||
- 按需填充(未使用的部分可不填充):
|
||||
- `LLMConfig`: 根据使用场景填写 CozeBot、CustomLLM。
|
||||
- `ASRConfig`: 如您使用 `bigmodel` 模式, 需填写 ASRAccessToken。
|
||||
- `TTSConfig`: 按需填充 TTSAppID、TTSToken。
|
||||
|
||||
#### 3.2 场景配置(`src/config/scenes/*.json`)
|
||||
您可以自定义具体场景, 并按需根据模版填充 OpenAPI 需要的参数。
|
||||
|
||||
Demo 中以 `Custom`、`VirtualGirlfriend`(视觉) 场景为例,您可以自行新增场景,并在代码中导入即可使用(`src/config/index.ts`)。
|
||||
|
||||
注意:
|
||||
- `EndPointId`:在 [火山方舟-在线推理](https://console.volcengine.com/ark/region:ark+cn-beijing/endpoint) 中创建接入点获取。
|
||||
- 敏感信息建议填充到 `Server/sensitive.js` 中。
|
||||
- Demo 会根据 JSON 参数中是否包含视觉理解相关的参数从而展示 视频采集/屏幕采集 相关的 UI。
|
||||
|
||||
### 4. 自定义服务端
|
||||
如果您已自行完成服务端逻辑,可以:
|
||||
1. 修改 `src/config/index.ts` 中的 `AIGC_PROXY_HOST` 请求域名和接口
|
||||
2. 在 `src/app/api.ts` 中修改接口参数配置 `APIS_CONFIG`
|
||||
|
||||
## 快速开始
|
||||
请注意,服务端和 Web 端都需要启动, 启动步骤如下:
|
||||
@ -26,7 +55,7 @@ yarn
|
||||
```
|
||||
#### 运行项目
|
||||
```shell
|
||||
node app.js
|
||||
yarn dev
|
||||
```
|
||||
|
||||
### 前端页面
|
||||
@ -43,14 +72,15 @@ yarn dev
|
||||
### 常见问题
|
||||
| 问题 | 解决方案 |
|
||||
| :-- | :-- |
|
||||
| 如何使用第三方模型、Coze Bot | 点击页面上的 "修改 AI 设定" 进入配置页,可切换 官方模型/Coze/第三方模型,填写对应参数即可,相关代码对应 `src/components/AISettings/index.tsx` 文件。 |
|
||||
| 如何使用第三方模型、Coze Bot | 模型相关配置代码对应目录 `src/config/scenes/` 下json 文件,填写对应官方模型/ Coze/ 第三方模型的参数后,可点击页面上的 "修改 AI 人设" 进行切换。 |
|
||||
| **启动智能体之后, 对话无反馈,或者一直停留在 "AI 准备中, 请稍侯"** | <li>可能因为控制台中相关权限没有正常授予,请参考[流程](https://www.volcengine.com/docs/6348/1315561?s=g)再次确认下是否完成相关操作。此问题的可能性较大,建议仔细对照是否已经将相应的权限开通。</li><li>参数传递可能有问题, 例如参数大小写、类型等问题,请再次确认下这类型问题是否存在。</li><li>相关资源可能未开通或者用量不足/欠费,请再次确认。</li><li>**请检查当前使用的模型 ID 等内容都是正确且可用的。**</li> |
|
||||
| **浏览器报了 `Uncaught (in promise) r: token_error` 错误** | 请检查您填在项目中的 RTC Token 是否合法,检测用于生成 Token 的 UserId、RoomId 以及 Token 本身是否与项目中填写的一致;或者 Token 可能过期, 可尝试重新生成下。 |
|
||||
| **[StartVoiceChat]Failed(Reason: The task has been started. Please do not call the startup task interface repeatedly.)** 报错 | 由于目前设置的 RoomId、UserId 为固定值,重复调用 startAudioBot 会导致出错,只需先调用 stopAudioBot 后再重新 startAudioBot 即可。 |
|
||||
| **[StartVoiceChat]Failed(Reason: The task has been started. Please do not call the startup task interface repeatedly.)** 报错 | 如果设置的 RoomId、UserId 为固定值,重复调用 startAgent 会导致出错,只需先调用 stopAgent 后再重新 startAgent 即可。 |
|
||||
| 为什么我的麦克风正常、摄像头也正常,但是设备没有正常工作? | 可能是设备权限未授予,详情可参考 [Web 排查设备权限获取失败问题](https://www.volcengine.com/docs/6348/1356355?s=g)。 |
|
||||
| 接口调用时, 返回 "Invalid 'Authorization' header, Pls check your authorization header" 错误 | `Server/app.js` 中的 AK/SK 不正确 |
|
||||
| 什么是 RTC | **R**eal **T**ime **C**ommunication, RTC 的概念可参考[官网文档](https://www.volcengine.com/docs/6348/66812?s=g)。 |
|
||||
| 不清楚什么是主账号,什么是子账号 | 可以参考[官方概念](https://www.volcengine.com/docs/6257/64963?hyperlink_open_type=lark.open_in_browser&s=g) 。|
|
||||
| 我有自己的服务端了, 我应该怎么让前端调用我的服务端呢 | 修改 `src/config/index.ts` 中的 `AIGC_PROXY_HOST` 请求域名和接口并在 `src/app/api.ts` 中修改接口参数配置 `APIS_CONFIG` |
|
||||
|
||||
如果有上述以外的问题,欢迎联系我们反馈。
|
||||
|
||||
@ -67,27 +97,9 @@ yarn dev
|
||||
### Demo 更新
|
||||
|
||||
#### [1.6.0]
|
||||
- 2025-05-28
|
||||
- 更新 RTC Web SDK 版本至 4.66.14
|
||||
- 2025-05-22
|
||||
- 更新 RTC Web SDK 版本至 4.66.13
|
||||
- 删除无用依赖
|
||||
- 2025-06-18
|
||||
- 更新 RTC Web SDK 版本至 4.66.16
|
||||
- 更新 UI 和参数配置方式
|
||||
- 更新 Readme 文档
|
||||
- 2025-04-16
|
||||
- 支持 Coze Bot
|
||||
- 更新部分注释和文档内容
|
||||
- 删除子账号的 SessionToken 配置, 子账号调用无须 SessionToken
|
||||
- 修复通话前修改内容,在通话后配置消失的问题
|
||||
|
||||
#### [1.5.1]
|
||||
- 2025-04-11
|
||||
- 移除无用代码和依赖
|
||||
- 修复字幕逻辑
|
||||
|
||||
#### [1.5.0]
|
||||
- 2025-03-31
|
||||
- 修复部分 UI 问题
|
||||
- 追加屏幕共享能力 (视觉模型可用,**读屏助手** 人设下可使用)
|
||||
- 修改字幕逻辑,避免字幕回调中标点符号、大小写不一致引起的字幕重复问题
|
||||
- 更新 RTC Web SDK 版本至 4.66.1
|
||||
- 追加设备权限未授予时的提示
|
||||
- 追加 Node 服务的参数检测能力
|
||||
- 追加 Node 服务的 Token 生成能力
|
||||
122
Server/app.js
@ -8,6 +8,10 @@ const bodyParser = require('koa-bodyparser');
|
||||
const cors = require('koa2-cors');
|
||||
const { Signer } = require('@volcengine/openapi');
|
||||
const fetch = require('node-fetch');
|
||||
const { wrapper, assert, sensitiveInjector } = require('./util');
|
||||
const { ACCOUNT_INFO, RTC_INFO } = require('./sensitive');
|
||||
const TokenManager = require('./token');
|
||||
const Privileges = require('./token').privileges;
|
||||
|
||||
const app = new Koa();
|
||||
|
||||
@ -15,61 +19,85 @@ app.use(cors({
|
||||
origin: '*'
|
||||
}));
|
||||
|
||||
/**
|
||||
* @notes 在 https://console.volcengine.com/iam/keymanage/ 获取 AK/SK
|
||||
*/
|
||||
const ACCOUNT_INFO = {
|
||||
/**
|
||||
* @notes 必填, 在 https://console.volcengine.com/iam/keymanage/ 获取
|
||||
*/
|
||||
accessKeyId: 'Your AK',
|
||||
/**
|
||||
* @notes 必填, 在 https://console.volcengine.com/iam/keymanage/ 获取
|
||||
*/
|
||||
secretKey: 'Your SK',
|
||||
}
|
||||
|
||||
app.use(bodyParser());
|
||||
|
||||
|
||||
app.use(async ctx => {
|
||||
/**
|
||||
* @brief 代理 AIGC 的 OpenAPI 请求
|
||||
*/
|
||||
if (ctx.url.startsWith('/proxyAIGCFetch') && ctx.method.toLowerCase() === 'post') {
|
||||
const { Action, Version } = ctx.query || {};
|
||||
const body = ctx.request.body;
|
||||
await wrapper({
|
||||
ctx,
|
||||
apiName: 'proxy',
|
||||
containResponseMetadata: false,
|
||||
logic: async () => {
|
||||
const { Action, Version = '2024-12-01' } = ctx.query || {};
|
||||
const body = ctx.request.body;
|
||||
assert(Action, 'Action 不能为空');
|
||||
assert(Version, 'Version 不能为空');
|
||||
assert(ACCOUNT_INFO.accessKeyId, 'AK 不能为空');
|
||||
assert(ACCOUNT_INFO.secretKey, 'SK 不能为空');
|
||||
|
||||
/**
|
||||
* 参考 https://github.com/volcengine/volc-sdk-nodejs 可获取更多 火山 TOP 网关 SDK 的使用方式
|
||||
*/
|
||||
const openApiRequestData = {
|
||||
region: 'cn-north-1',
|
||||
method: 'POST',
|
||||
params: {
|
||||
Action,
|
||||
Version,
|
||||
},
|
||||
headers: {
|
||||
Host: 'rtc.volcengineapi.com',
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
body,
|
||||
};
|
||||
const signer = new Signer(openApiRequestData, "rtc");
|
||||
signer.addAuthorization(ACCOUNT_INFO);
|
||||
sensitiveInjector(Action, body);
|
||||
|
||||
/** 参考 https://www.volcengine.com/docs/6348/69828 可获取更多 OpenAPI 的信息 */
|
||||
const result = await fetch(`https://rtc.volcengineapi.com?Action=${Action}&Version=${Version}`, {
|
||||
method: 'POST',
|
||||
headers: openApiRequestData.headers,
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
const volcResponse = await result.json();
|
||||
ctx.body = volcResponse;
|
||||
} else {
|
||||
ctx.body = '<h1>404 Not Found</h1>';
|
||||
}
|
||||
/** 参考 https://github.com/volcengine/volc-sdk-nodejs 可获取更多 火山 TOP 网关 SDK 的使用方式 */
|
||||
const openApiRequestData = {
|
||||
region: 'cn-north-1',
|
||||
method: 'POST',
|
||||
params: {
|
||||
Action,
|
||||
Version,
|
||||
},
|
||||
headers: {
|
||||
Host: 'rtc.volcengineapi.com',
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
body,
|
||||
};
|
||||
const signer = new Signer(openApiRequestData, "rtc");
|
||||
signer.addAuthorization(ACCOUNT_INFO);
|
||||
|
||||
/** 参考 https://www.volcengine.com/docs/6348/69828 可获取更多 OpenAPI 的信息 */
|
||||
const result = await fetch(`https://rtc.volcengineapi.com?Action=${Action}&Version=${Version}`, {
|
||||
method: 'POST',
|
||||
headers: openApiRequestData.headers,
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
return result.json();
|
||||
}
|
||||
});
|
||||
|
||||
wrapper({
|
||||
ctx,
|
||||
apiName: 'rtc-info',
|
||||
logic: () => {
|
||||
return {
|
||||
appId: RTC_INFO.appId,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @brief 生成 RTC Token
|
||||
* @refer https://www.volcengine.com/docs/6348/70121
|
||||
*/
|
||||
await wrapper({
|
||||
ctx,
|
||||
apiName: 'rtc-token',
|
||||
logic: async () => {
|
||||
const { roomId, userId } = ctx.request.body || {};
|
||||
assert(RTC_INFO.appId, 'AppID 不能为空, 请修改 /Server/sensitive.js');
|
||||
assert(RTC_INFO.appKey, 'AppKey 不能为空, 请修改 /Server/sensitive.js');
|
||||
assert(roomId, 'RoomID 不能为空');
|
||||
assert(userId, 'UserID 不能为空');
|
||||
const key = new TokenManager.AccessToken(RTC_INFO.appId, RTC_INFO.appKey, roomId, userId);
|
||||
key.addPrivilege(Privileges.PrivSubscribeStream, 0);
|
||||
key.addPrivilege(Privileges.PrivPublishStream, 0);
|
||||
key.expireTime(Math.floor(new Date() / 1000) + (24 * 3600));
|
||||
return {
|
||||
token: key.serialize(),
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.listen(3001, () => {
|
||||
|
||||
5
Server/nodemon.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"watch": ["."],
|
||||
"ext": "js,json",
|
||||
"ignore": ["node_modules/*"]
|
||||
}
|
||||
@ -10,9 +10,13 @@
|
||||
"koa": "^2.15.3",
|
||||
"koa-bodyparser": "^4.4.1",
|
||||
"koa2-cors": "^2.0.6",
|
||||
"lodash": "^4.17.21",
|
||||
"node-fetch": "^2.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.1.10"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "node app.js"
|
||||
"dev": "nodemon app.js"
|
||||
}
|
||||
}
|
||||
|
||||
138
Server/sensitive.js
Normal file
@ -0,0 +1,138 @@
|
||||
/**
|
||||
* Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved.
|
||||
* SPDX-license-identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
/**
|
||||
* @note 在 https://console.volcengine.com/iam/keymanage/ 获取 AK/SK。
|
||||
*/
|
||||
const ACCOUNT_INFO = {
|
||||
/**
|
||||
* @notes 必填, 在 https://console.volcengine.com/iam/keymanage/ 获取。
|
||||
*/
|
||||
accessKeyId: 'Your Access Key ID',
|
||||
/**
|
||||
* @notes 必填, 在 https://console.volcengine.com/iam/keymanage/ 获取。
|
||||
*/
|
||||
secretKey: 'Your Secret Key',
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @note RTC 的必填参数
|
||||
* @refer appId、appKey 可从 https://console.volcengine.com/rtc/aigc/listRTC 中获取。
|
||||
*/
|
||||
const RTC_INFO = {
|
||||
appId: 'Your RTC App ID',
|
||||
appKey: 'Your RTC App Key',
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @note 可参考官网 LLMConfig 字段。
|
||||
* @refer https://www.volcengine.com/docs/6348/1558163
|
||||
*/
|
||||
const LLMConfig = {
|
||||
/**
|
||||
* @note 火山方舟平台
|
||||
*/
|
||||
ArkV3: {},
|
||||
/**
|
||||
* @note Coze 平台
|
||||
*/
|
||||
CozeBot: {
|
||||
CozeBotConfig: {
|
||||
Url: 'https://api.coze.cn',
|
||||
APIKey: 'Your Coze API Key',
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @note 第三方大模型/Agent
|
||||
*/
|
||||
CustomLLM: {
|
||||
URL: 'Your LLM vendor\'s request url',
|
||||
APIKey: 'Your LLM vendor\'s API Key',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief 必填, ASR(语音识别) AppId, 可于 https://console.volcengine.com/speech/app?s=g 中获取, 若无可先创建应用。
|
||||
* 创建应用时, 需要按需根据语言选择 "流式语音识别" 服务, 并选择对应的 App 进行绑定。
|
||||
*/
|
||||
const ASRAppID = 'Your ASR App ID';
|
||||
/**
|
||||
* @note 已开通流式语音识别大模型服务 AppId 对应的 Access Token。
|
||||
* 使用流式语音识别 **大模型** 服务时必填, 可于 https://console.volcengine.com/speech/service/10011?s=g 中查看。
|
||||
* 使用小模型无需配置 ASRToken。
|
||||
*/
|
||||
const ASRAccessToken = 'Your ASR Access Token';
|
||||
/**
|
||||
* @note 可参考官网 ASRConfig 字段。
|
||||
* @refer https://www.volcengine.com/docs/6348/1558163
|
||||
*/
|
||||
const ASRConfig = {
|
||||
/**
|
||||
* @note 火山引擎流式语音识别。
|
||||
*/
|
||||
smallmodel: {
|
||||
AppId: ASRAppID,
|
||||
},
|
||||
/**
|
||||
* @note 火山引擎流式语音识别大模型。
|
||||
*/
|
||||
bigmodel: {
|
||||
AppId: ASRAppID,
|
||||
AccessToken: ASRAccessToken,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @note 必填, TTS(语音合成) AppId, 可于 https://console.volcengine.com/speech/service/8?s=g 中获取, 若无可先创建应用。
|
||||
* 创建应用时, 需要选择 "语音合成" 服务, 并选择对应的 App 进行绑定。
|
||||
*/
|
||||
const TTSAppID = 'Your TTS App ID';
|
||||
/**
|
||||
* @note 已开通需要的语音合成服务的 token。
|
||||
* 使用火山引擎双向流式语音合成服务时必填。
|
||||
* 注意! 如您使用的是双向流式语音合成服务, 务必修改 voice_type,使用您已开通的大模型音色,否则无法使用。
|
||||
* @refer 可于 https://console.volcengine.com/speech/service/8?s=g 中获取。
|
||||
*/
|
||||
const TTSToken = 'Your TTS Token';
|
||||
/**
|
||||
* @note 可参考官网 TTSConfig 字段。
|
||||
* @refer https://www.volcengine.com/docs/6348/1558163
|
||||
*/
|
||||
const TTSConfig = {
|
||||
volcano: {
|
||||
app: {
|
||||
appid: TTSAppID,
|
||||
},
|
||||
},
|
||||
volcano_bidirection: {
|
||||
app: {
|
||||
appid: TTSAppID,
|
||||
token: TTSToken,
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @note 若您使用 minimax, 须填充此处参数
|
||||
*/
|
||||
minimax: {
|
||||
Authorization: 'Your Authorization',
|
||||
Groupid: 'Your minimax groupid',
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
ACCOUNT_INFO,
|
||||
RTC_INFO,
|
||||
LLMConfig,
|
||||
ASRConfig,
|
||||
TTSConfig,
|
||||
}
|
||||
244
Server/token.js
Normal file
@ -0,0 +1,244 @@
|
||||
/**
|
||||
* Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved.
|
||||
* SPDX-license-identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
var crypto = require('crypto');
|
||||
|
||||
var randomInt = Math.floor(Math.random() * 0xFFFFFFFF);
|
||||
|
||||
const VERSION = "001";
|
||||
const VERSION_LENGTH = 3;
|
||||
|
||||
const APP_ID_LENGTH = 24;
|
||||
|
||||
privileges = {
|
||||
PrivPublishStream: 0,
|
||||
|
||||
// not exported, do not use directly
|
||||
privPublishAudioStream: 1,
|
||||
privPublishVideoStream: 2,
|
||||
privPublishDataStream: 3,
|
||||
|
||||
PrivSubscribeStream: 4,
|
||||
};
|
||||
|
||||
|
||||
module.exports.privileges = privileges;
|
||||
|
||||
// Initializes token struct by required parameters.
|
||||
var AccessToken = function (appID, appKey, roomID, userID) {
|
||||
let token = this;
|
||||
this.appID = appID;
|
||||
this.appKey = appKey;
|
||||
this.roomID = roomID;
|
||||
this.userID = userID;
|
||||
this.issuedAt = Math.floor(new Date() / 1000);
|
||||
this.nonce = randomInt;
|
||||
this.expireAt = 0;
|
||||
this.privileges = {};
|
||||
|
||||
// AddPrivilege adds permission for token with an expiration.
|
||||
this.addPrivilege = function (privilege, expireTimestamp) {
|
||||
if (token.privileges === undefined) {
|
||||
token.privileges = {}
|
||||
}
|
||||
token.privileges[privilege] = expireTimestamp;
|
||||
|
||||
if (privilege === privileges.PrivPublishStream) {
|
||||
token.privileges[privileges.privPublishVideoStream] = expireTimestamp;
|
||||
token.privileges[privileges.privPublishAudioStream] = expireTimestamp;
|
||||
token.privileges[privileges.privPublishDataStream] = expireTimestamp;
|
||||
}
|
||||
};
|
||||
|
||||
// ExpireTime sets token expire time, won't expire by default.
|
||||
// The token will be invalid after expireTime no matter what privilege's expireTime is.
|
||||
this.expireTime = function (expireTimestamp) {
|
||||
token.expireAt = expireTimestamp;
|
||||
};
|
||||
|
||||
this.packMsg = function () {
|
||||
var bufM = new ByteBuf();
|
||||
bufM.putUint32(token.nonce);
|
||||
bufM.putUint32(token.issuedAt);
|
||||
bufM.putUint32(token.expireAt);
|
||||
bufM.putString(token.roomID);
|
||||
bufM.putString(token.userID);
|
||||
bufM.putTreeMapUInt32(token.privileges);
|
||||
return bufM.pack()
|
||||
};
|
||||
|
||||
// Serialize generates the token string
|
||||
this.serialize = function () {
|
||||
var bytesM = this.packMsg();
|
||||
|
||||
var signature = encodeHMac(token.appKey, bytesM);
|
||||
var content = new ByteBuf().putBytes(bytesM).putBytes(signature).pack();
|
||||
|
||||
return (VERSION + token.appID + content.toString('base64'));
|
||||
};
|
||||
|
||||
// Verify checks if this token valid, called by server side.
|
||||
this.verify = function (key) {
|
||||
if (token.expireAt > 0 && Math.floor(new Date() / 1000) > token.expireAt) {
|
||||
return false
|
||||
}
|
||||
|
||||
token.appKey = key;
|
||||
return encodeHMac(token.appKey, this.packMsg()).toString() === token.signature;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Parse retrieves token information from raw string
|
||||
var Parse = function (raw) {
|
||||
try {
|
||||
if (raw.length <= VERSION_LENGTH + APP_ID_LENGTH) {
|
||||
return
|
||||
}
|
||||
if (raw.substr(0, VERSION_LENGTH) !== VERSION) {
|
||||
return
|
||||
}
|
||||
var token = new AccessToken("", "", "", "");
|
||||
token.appID = raw.substr(VERSION_LENGTH, APP_ID_LENGTH);
|
||||
|
||||
var contentBuf = Buffer.from(raw.substr(VERSION_LENGTH + APP_ID_LENGTH), 'base64');
|
||||
var readbuf = new ReadByteBuf(contentBuf);
|
||||
|
||||
var msg = readbuf.getString();
|
||||
token.signature = readbuf.getString().toString();
|
||||
|
||||
// parse msg
|
||||
var msgBuf = new ReadByteBuf(msg);
|
||||
token.nonce = msgBuf.getUint32();
|
||||
token.issuedAt = msgBuf.getUint32();
|
||||
token.expireAt = msgBuf.getUint32();
|
||||
token.roomID = msgBuf.getString().toString();
|
||||
token.userID = msgBuf.getString().toString();
|
||||
token.privileges = msgBuf.getTreeMapUInt32();
|
||||
|
||||
return token
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
module.exports.version = VERSION;
|
||||
module.exports.AccessToken = AccessToken;
|
||||
module.exports.Parse = Parse;
|
||||
|
||||
var encodeHMac = function (key, message) {
|
||||
return crypto.createHmac('sha256', key).update(message).digest();
|
||||
};
|
||||
|
||||
var ByteBuf = function () {
|
||||
var that = {
|
||||
buffer: Buffer.alloc(1024)
|
||||
, position: 0
|
||||
};
|
||||
|
||||
|
||||
that.pack = function () {
|
||||
var out = Buffer.alloc(that.position);
|
||||
that.buffer.copy(out, 0, 0, out.length);
|
||||
return out;
|
||||
};
|
||||
|
||||
that.putUint16 = function (v) {
|
||||
that.buffer.writeUInt16LE(v, that.position);
|
||||
that.position += 2;
|
||||
return that;
|
||||
};
|
||||
|
||||
that.putUint32 = function (v) {
|
||||
that.buffer.writeUInt32LE(v, that.position);
|
||||
that.position += 4;
|
||||
return that;
|
||||
};
|
||||
|
||||
that.putBytes = function (bytes) {
|
||||
that.putUint16(bytes.length);
|
||||
bytes.copy(that.buffer, that.position);
|
||||
that.position += bytes.length;
|
||||
return that;
|
||||
};
|
||||
|
||||
that.putString = function (str) {
|
||||
return that.putBytes(Buffer.from(str));
|
||||
};
|
||||
|
||||
that.putTreeMap = function (map) {
|
||||
if (!map) {
|
||||
that.putUint16(0);
|
||||
return that;
|
||||
}
|
||||
|
||||
that.putUint16(Object.keys(map).length);
|
||||
for (var key in map) {
|
||||
that.putUint16(key);
|
||||
that.putString(map[key]);
|
||||
}
|
||||
|
||||
return that;
|
||||
};
|
||||
|
||||
that.putTreeMapUInt32 = function (map) {
|
||||
if (!map) {
|
||||
that.putUint16(0);
|
||||
return that;
|
||||
}
|
||||
|
||||
that.putUint16(Object.keys(map).length);
|
||||
for (var key in map) {
|
||||
that.putUint16(key);
|
||||
that.putUint32(map[key]);
|
||||
}
|
||||
|
||||
return that;
|
||||
};
|
||||
|
||||
return that;
|
||||
};
|
||||
|
||||
var ReadByteBuf = function (bytes) {
|
||||
var that = {
|
||||
buffer: bytes
|
||||
, position: 0
|
||||
};
|
||||
|
||||
that.getUint16 = function () {
|
||||
var ret = that.buffer.readUInt16LE(that.position);
|
||||
that.position += 2;
|
||||
return ret;
|
||||
};
|
||||
|
||||
that.getUint32 = function () {
|
||||
var ret = that.buffer.readUInt32LE(that.position);
|
||||
that.position += 4;
|
||||
return ret;
|
||||
};
|
||||
|
||||
that.getString = function () {
|
||||
var len = that.getUint16();
|
||||
|
||||
var out = Buffer.alloc(len);
|
||||
that.buffer.copy(out, 0, that.position, (that.position + len));
|
||||
that.position += len;
|
||||
return out;
|
||||
};
|
||||
|
||||
that.getTreeMapUInt32 = function () {
|
||||
var map = {};
|
||||
var len = that.getUint16();
|
||||
for (var i = 0; i < len; i++) {
|
||||
var key = that.getUint16();
|
||||
var value = that.getUint32();
|
||||
map[key] = value;
|
||||
}
|
||||
return map;
|
||||
};
|
||||
|
||||
return that;
|
||||
};
|
||||
82
Server/util.js
Normal file
@ -0,0 +1,82 @@
|
||||
/**
|
||||
* Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved.
|
||||
* SPDX-license-identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
const merge = require('lodash/merge');
|
||||
const { LLMConfig, RTC_INFO, TTSConfig, ASRConfig } = require("./sensitive");
|
||||
|
||||
const judgeMethodPath = (method) => {
|
||||
return (ctx, pathname) => ctx.method.toLowerCase() === method && ctx.url.startsWith(`/${pathname}`);
|
||||
}
|
||||
|
||||
const assert = (expression, msg) => {
|
||||
if (!!!expression || expression?.includes?.(' ')) {
|
||||
console.log(`\x1b[31m校验失败: ${msg}\x1b[0m`)
|
||||
throw new Error(msg);
|
||||
}
|
||||
}
|
||||
|
||||
const wrapper = async ({
|
||||
ctx,
|
||||
method = 'post',
|
||||
apiName,
|
||||
logic,
|
||||
containResponseMetadata = true,
|
||||
}) => {
|
||||
if (judgeMethodPath(method)(ctx, apiName)) {
|
||||
const ResponseMetadata = { Action: apiName };
|
||||
try {
|
||||
const res = await logic();
|
||||
ctx.body = containResponseMetadata ? {
|
||||
ResponseMetadata,
|
||||
Result: res,
|
||||
} : res;
|
||||
} catch (e) {
|
||||
ResponseMetadata.Error = {
|
||||
Code: -1,
|
||||
Message: e?.toString(),
|
||||
};
|
||||
ctx.body = {
|
||||
ResponseMetadata,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const deepAssert = (params = {}, prefix = '') => {
|
||||
if (typeof params === 'object') {
|
||||
Object.keys(params).forEach(key => {
|
||||
assert(params[key], `${prefix}: ${key} 不能为空, 请修改 /Server/sensitive.js`);
|
||||
deepAssert(params[key], `${prefix}: ${key}.`);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const sensitiveInjector = (action, params = {}) => {
|
||||
assert(RTC_INFO.appId, 'RTC_INFO.appId 不能为空');
|
||||
params.AppId = RTC_INFO.appId;
|
||||
|
||||
if (action === 'StartVoiceChat') {
|
||||
const llmParams = LLMConfig[params?.Config?.LLMConfig?.Mode];
|
||||
assert(llmParams, '使用的 LLM Mode 不存在');
|
||||
deepAssert(llmParams, 'LLMConfig');
|
||||
merge(params.Config.LLMConfig, llmParams);
|
||||
|
||||
const asrParams = ASRConfig[params?.Config?.ASRConfig?.ProviderParams?.Mode];
|
||||
assert(asrParams, '使用的 ASR Mode 不存在');
|
||||
deepAssert(asrParams, 'ASRConfig');
|
||||
merge(params.Config.ASRConfig.ProviderParams, asrParams);
|
||||
|
||||
const ttsParams = TTSConfig[params?.Config?.TTSConfig?.Provider];
|
||||
assert(ttsParams, '使用的 TTS Mode 不存在');
|
||||
deepAssert(ttsParams, 'TTSConfig');
|
||||
merge(params.Config.TTSConfig.ProviderParams, ttsParams);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
wrapper,
|
||||
assert,
|
||||
sensitiveInjector,
|
||||
}
|
||||
199
Server/yarn.lock
@ -91,6 +91,14 @@ accepts@^1.3.5:
|
||||
mime-types "~2.1.34"
|
||||
negotiator "0.6.3"
|
||||
|
||||
anymatch@~3.1.2:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e"
|
||||
integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==
|
||||
dependencies:
|
||||
normalize-path "^3.0.0"
|
||||
picomatch "^2.0.4"
|
||||
|
||||
asynckit@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
||||
@ -103,6 +111,31 @@ axios@^0.21.1:
|
||||
dependencies:
|
||||
follow-redirects "^1.14.0"
|
||||
|
||||
balanced-match@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
|
||||
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
|
||||
|
||||
binary-extensions@^2.0.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522"
|
||||
integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==
|
||||
|
||||
brace-expansion@^1.1.7:
|
||||
version "1.1.12"
|
||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843"
|
||||
integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==
|
||||
dependencies:
|
||||
balanced-match "^1.0.0"
|
||||
concat-map "0.0.1"
|
||||
|
||||
braces@~3.0.2:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789"
|
||||
integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
|
||||
dependencies:
|
||||
fill-range "^7.1.1"
|
||||
|
||||
bytes@3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
|
||||
@ -127,6 +160,21 @@ call-bind@^1.0.7:
|
||||
get-intrinsic "^1.2.4"
|
||||
set-function-length "^1.2.1"
|
||||
|
||||
chokidar@^3.5.2:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"
|
||||
integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==
|
||||
dependencies:
|
||||
anymatch "~3.1.2"
|
||||
braces "~3.0.2"
|
||||
glob-parent "~5.1.2"
|
||||
is-binary-path "~2.1.0"
|
||||
is-glob "~4.0.1"
|
||||
normalize-path "~3.0.0"
|
||||
readdirp "~3.6.0"
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.2"
|
||||
|
||||
co-body@^6.0.0:
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/co-body/-/co-body-6.2.0.tgz#afd776d60e5659f4eee862df83499698eb1aea1b"
|
||||
@ -150,6 +198,11 @@ combined-stream@^1.0.8:
|
||||
dependencies:
|
||||
delayed-stream "~1.0.0"
|
||||
|
||||
concat-map@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
|
||||
|
||||
content-disposition@~0.5.2:
|
||||
version "0.5.4"
|
||||
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe"
|
||||
@ -190,6 +243,13 @@ dayjs@^1.11.5:
|
||||
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.13.tgz#92430b0139055c3ebb60150aa13e860a4b5a366c"
|
||||
integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==
|
||||
|
||||
debug@^4:
|
||||
version "4.4.1"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b"
|
||||
integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==
|
||||
dependencies:
|
||||
ms "^2.1.3"
|
||||
|
||||
debug@^4.3.1, debug@^4.3.2:
|
||||
version "4.3.7"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52"
|
||||
@ -263,6 +323,13 @@ escape-html@^1.0.3:
|
||||
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
|
||||
integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
|
||||
|
||||
fill-range@^7.1.1:
|
||||
version "7.1.1"
|
||||
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
|
||||
integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==
|
||||
dependencies:
|
||||
to-regex-range "^5.0.1"
|
||||
|
||||
follow-redirects@^1.14.0:
|
||||
version "1.15.9"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1"
|
||||
@ -282,6 +349,11 @@ fresh@~0.5.2:
|
||||
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
|
||||
integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==
|
||||
|
||||
fsevents@~2.3.2:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
|
||||
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
|
||||
|
||||
function-bind@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
|
||||
@ -298,6 +370,13 @@ get-intrinsic@^1.1.3, get-intrinsic@^1.2.4:
|
||||
has-symbols "^1.0.3"
|
||||
hasown "^2.0.0"
|
||||
|
||||
glob-parent@~5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
|
||||
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
|
||||
dependencies:
|
||||
is-glob "^4.0.1"
|
||||
|
||||
gopd@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
|
||||
@ -305,6 +384,11 @@ gopd@^1.0.1:
|
||||
dependencies:
|
||||
get-intrinsic "^1.1.3"
|
||||
|
||||
has-flag@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
|
||||
integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==
|
||||
|
||||
has-property-descriptors@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854"
|
||||
@ -373,6 +457,11 @@ iconv-lite@0.4.24:
|
||||
dependencies:
|
||||
safer-buffer ">= 2.1.2 < 3"
|
||||
|
||||
ignore-by-default@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09"
|
||||
integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==
|
||||
|
||||
inflation@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/inflation/-/inflation-2.1.0.tgz#9214db11a47e6f756d111c4f9df96971c60f886c"
|
||||
@ -383,6 +472,18 @@ inherits@2.0.4:
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
|
||||
is-binary-path@~2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
|
||||
integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
|
||||
dependencies:
|
||||
binary-extensions "^2.0.0"
|
||||
|
||||
is-extglob@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
|
||||
integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
|
||||
|
||||
is-generator-function@^1.0.7:
|
||||
version "1.0.10"
|
||||
resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72"
|
||||
@ -390,6 +491,18 @@ is-generator-function@^1.0.7:
|
||||
dependencies:
|
||||
has-tostringtag "^1.0.0"
|
||||
|
||||
is-glob@^4.0.1, is-glob@~4.0.1:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
|
||||
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
|
||||
dependencies:
|
||||
is-extglob "^2.1.1"
|
||||
|
||||
is-number@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
|
||||
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
|
||||
|
||||
keygrip@~1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.1.0.tgz#871b1681d5e159c62a445b0c74b615e0917e7226"
|
||||
@ -458,6 +571,11 @@ lodash.get@^4.4.2:
|
||||
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
|
||||
integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==
|
||||
|
||||
lodash@^4.17.21:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
|
||||
long@^5.0.0:
|
||||
version "5.2.3"
|
||||
resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1"
|
||||
@ -480,6 +598,13 @@ mime-types@^2.1.12, mime-types@^2.1.18, mime-types@~2.1.24, mime-types@~2.1.34:
|
||||
dependencies:
|
||||
mime-db "1.52.0"
|
||||
|
||||
minimatch@^3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
|
||||
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
|
||||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
ms@^2.1.3:
|
||||
version "2.1.3"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
|
||||
@ -497,6 +622,27 @@ node-fetch@^2.3.2:
|
||||
dependencies:
|
||||
whatwg-url "^5.0.0"
|
||||
|
||||
nodemon@^3.1.10:
|
||||
version "3.1.10"
|
||||
resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-3.1.10.tgz#5015c5eb4fffcb24d98cf9454df14f4fecec9bc1"
|
||||
integrity sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==
|
||||
dependencies:
|
||||
chokidar "^3.5.2"
|
||||
debug "^4"
|
||||
ignore-by-default "^1.0.1"
|
||||
minimatch "^3.1.2"
|
||||
pstree.remy "^1.1.8"
|
||||
semver "^7.5.3"
|
||||
simple-update-notifier "^2.0.0"
|
||||
supports-color "^5.5.0"
|
||||
touch "^3.1.0"
|
||||
undefsafe "^2.0.5"
|
||||
|
||||
normalize-path@^3.0.0, normalize-path@~3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
|
||||
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
|
||||
|
||||
object-inspect@^1.13.1:
|
||||
version "1.13.2"
|
||||
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff"
|
||||
@ -526,6 +672,11 @@ parseurl@^1.3.2:
|
||||
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
|
||||
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
|
||||
|
||||
picomatch@^2.0.4, picomatch@^2.2.1:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
|
||||
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
||||
|
||||
protobufjs@7.2.4:
|
||||
version "7.2.4"
|
||||
resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.2.4.tgz#3fc1ec0cdc89dd91aef9ba6037ba07408485c3ae"
|
||||
@ -544,6 +695,11 @@ protobufjs@7.2.4:
|
||||
"@types/node" ">=13.7.0"
|
||||
long "^5.0.0"
|
||||
|
||||
pstree.remy@^1.1.8:
|
||||
version "1.1.8"
|
||||
resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a"
|
||||
integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==
|
||||
|
||||
qs@^6.5.2:
|
||||
version "6.13.0"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906"
|
||||
@ -561,6 +717,13 @@ raw-body@^2.3.3:
|
||||
iconv-lite "0.4.24"
|
||||
unpipe "1.0.0"
|
||||
|
||||
readdirp@~3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
|
||||
integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
|
||||
dependencies:
|
||||
picomatch "^2.2.1"
|
||||
|
||||
safe-buffer@5.2.1:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
||||
@ -571,6 +734,11 @@ safe-buffer@5.2.1:
|
||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||
|
||||
semver@^7.5.3:
|
||||
version "7.7.2"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58"
|
||||
integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==
|
||||
|
||||
set-function-length@^1.2.1:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449"
|
||||
@ -598,6 +766,13 @@ side-channel@^1.0.6:
|
||||
get-intrinsic "^1.2.4"
|
||||
object-inspect "^1.13.1"
|
||||
|
||||
simple-update-notifier@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz#d70b92bdab7d6d90dfd73931195a30b6e3d7cebb"
|
||||
integrity sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==
|
||||
dependencies:
|
||||
semver "^7.5.3"
|
||||
|
||||
statuses@2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
|
||||
@ -608,11 +783,30 @@ statuses@2.0.1:
|
||||
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
|
||||
integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==
|
||||
|
||||
supports-color@^5.5.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
|
||||
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
|
||||
dependencies:
|
||||
has-flag "^3.0.0"
|
||||
|
||||
to-regex-range@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
|
||||
integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
|
||||
dependencies:
|
||||
is-number "^7.0.0"
|
||||
|
||||
toidentifier@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
|
||||
integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
|
||||
|
||||
touch@^3.1.0:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.1.tgz#097a23d7b161476435e5c1344a95c0f75b4a5694"
|
||||
integrity sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==
|
||||
|
||||
tr46@~0.0.3:
|
||||
version "0.0.3"
|
||||
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
|
||||
@ -631,6 +825,11 @@ type-is@^1.6.16, type-is@^1.6.18:
|
||||
media-typer "0.3.0"
|
||||
mime-types "~2.1.24"
|
||||
|
||||
undefsafe@^2.0.5:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c"
|
||||
integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==
|
||||
|
||||
undici-types@~6.19.2:
|
||||
version "6.19.8"
|
||||
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02"
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@reduxjs/toolkit": "^1.8.3",
|
||||
"@volcengine/rtc": "~4.66.14",
|
||||
"@volcengine/rtc": "~4.66.16",
|
||||
"@arco-design/web-react": "^2.65.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>RTC AIGC Demo</title>
|
||||
<title>火山引擎 RTC 实时对话式 AI 体验 Demo ——— 支持 DeepSeek 和 豆包视觉理解模型</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
* Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved.
|
||||
* SPDX-license-identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
||||
import MainPage from './pages/MainPage';
|
||||
import '@arco-design/web-react/dist/css/arco.css';
|
||||
|
||||
@ -3,47 +3,34 @@
|
||||
* SPDX-license-identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
import { requestGetMethod, requestPostMethod, resultHandler } from './base';
|
||||
import { ACTIONS, RequestParams, RequestResponse } from './type';
|
||||
|
||||
const APIS_CONFIG = [
|
||||
/**
|
||||
* @brief Basic APIs
|
||||
*/
|
||||
export const BasicAPIs = [
|
||||
{
|
||||
action: ACTIONS.StartVoiceChat,
|
||||
apiBasicParams: `?Name=start&Action=${ACTIONS.StartVoiceChat}&Version=2024-12-01`,
|
||||
action: 'getRtcInfo',
|
||||
apiPath: '/rtc-info',
|
||||
method: 'post',
|
||||
},
|
||||
{
|
||||
action: ACTIONS.UpdateVoiceChat,
|
||||
apiBasicParams: `?Name=update&Action=${ACTIONS.UpdateVoiceChat}&Version=2024-12-01`,
|
||||
method: 'post',
|
||||
},
|
||||
{
|
||||
action: ACTIONS.StopVoiceChat,
|
||||
apiBasicParams: `?Name=stop&Action=${ACTIONS.StopVoiceChat}&Version=2024-12-01`,
|
||||
action: 'generateRtcAccessToken',
|
||||
apiPath: '/rtc-token',
|
||||
method: 'post',
|
||||
},
|
||||
] as const;
|
||||
|
||||
type ApiConfig = typeof APIS_CONFIG;
|
||||
type TupleToUnion<T extends readonly unknown[]> = T[number];
|
||||
type ApiNames = Pick<TupleToUnion<ApiConfig>, 'action'>['action'];
|
||||
type RequestFn = <T extends keyof RequestResponse>(params?: RequestParams[T]) => RequestResponse[T];
|
||||
type PromiseRequestFn = <T extends keyof RequestResponse>(
|
||||
params?: RequestParams[T]
|
||||
) => Promise<RequestResponse[T]>;
|
||||
type Apis = Record<ApiNames, RequestFn | PromiseRequestFn>;
|
||||
|
||||
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 res = await queryData?.json();
|
||||
return resultHandler(res);
|
||||
};
|
||||
return store;
|
||||
}, {} as Apis);
|
||||
|
||||
export default APIS;
|
||||
/**
|
||||
* @brief Basic APIs
|
||||
*/
|
||||
export const AigcAPIs = [
|
||||
{
|
||||
action: 'StartVoiceChat',
|
||||
apiPath: '/proxy',
|
||||
method: 'post',
|
||||
},
|
||||
{
|
||||
action: 'StopVoiceChat',
|
||||
apiPath: '/proxy',
|
||||
method: 'post',
|
||||
},
|
||||
] as const;
|
||||
|
||||
@ -3,19 +3,34 @@
|
||||
* SPDX-license-identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
import { Modal } from '@arco-design/web-react';
|
||||
import { Message } from '@arco-design/web-react';
|
||||
import { AIGC_PROXY_HOST } from '@/config';
|
||||
import type { RequestResponse, ApiConfig, ApiNames, Apis } from './type';
|
||||
|
||||
type Headers = Record<string, string>;
|
||||
|
||||
export type DeepPartial<T> = {
|
||||
[P in keyof T]?: T[P] extends Array<infer U>
|
||||
? Array<DeepPartial<U>>
|
||||
: T[P] extends object
|
||||
? DeepPartial<T[P]>
|
||||
: T[P];
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Get
|
||||
* @param apiName
|
||||
* @param headers
|
||||
*/
|
||||
export const requestGetMethod = (apiBasicParams: string, headers = {}) => {
|
||||
export const requestGetMethod = ({
|
||||
action,
|
||||
headers = {},
|
||||
}: {
|
||||
action: string;
|
||||
headers?: Record<string, string>;
|
||||
}) => {
|
||||
return async (params: Record<string, any> = {}) => {
|
||||
const url = `${AIGC_PROXY_HOST}${apiBasicParams}&${Object.keys(params)
|
||||
const url = `${AIGC_PROXY_HOST}?Action=${action}&${Object.keys(params)
|
||||
.map((key) => `${key}=${params[key]}`)
|
||||
.join('&')}`;
|
||||
const res = await fetch(url, {
|
||||
@ -29,17 +44,20 @@ export const requestGetMethod = (apiBasicParams: string, headers = {}) => {
|
||||
|
||||
/**
|
||||
* @brief Post
|
||||
* @param apiName
|
||||
* @param isJson
|
||||
* @param headers
|
||||
*/
|
||||
export const requestPostMethod = (
|
||||
apiBasicParams: string,
|
||||
isJson: boolean = true,
|
||||
headers: Headers = {}
|
||||
) => {
|
||||
export const requestPostMethod = ({
|
||||
action,
|
||||
apiPath,
|
||||
isJson = true,
|
||||
headers = {},
|
||||
}: {
|
||||
action: string;
|
||||
apiPath: string;
|
||||
isJson?: boolean;
|
||||
headers?: Headers;
|
||||
}) => {
|
||||
return async <T>(params: T) => {
|
||||
const res = await fetch(`${AIGC_PROXY_HOST}${apiBasicParams}`, {
|
||||
const res = await fetch(`${AIGC_PROXY_HOST}${apiPath}?Action=${action}`, {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
@ -52,17 +70,43 @@ export const requestPostMethod = (
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Handler
|
||||
* @brief Return handler
|
||||
* @param res
|
||||
*/
|
||||
export const resultHandler = (res: any) => {
|
||||
export const resultHandler = (res: RequestResponse) => {
|
||||
const { Result, ResponseMetadata } = res || {};
|
||||
if (Result === 'ok') {
|
||||
return Result;
|
||||
// Record request id for debug.
|
||||
if (ResponseMetadata.Action === 'StartVoiceChat') {
|
||||
const requestId = ResponseMetadata.RequestId;
|
||||
requestId && sessionStorage.setItem('RequestID', requestId);
|
||||
}
|
||||
const error = ResponseMetadata?.Error?.Message || Result;
|
||||
Modal.error({
|
||||
title: '接口调用错误',
|
||||
content: `[${ResponseMetadata?.Action}]Failed(Reason: ${error}), 请参考 README 文档排查问题。`,
|
||||
});
|
||||
if (ResponseMetadata.Error) {
|
||||
Message.error(
|
||||
`[${ResponseMetadata?.Action}]call failed(reason: ${ResponseMetadata.Error?.Message})`
|
||||
);
|
||||
throw new Error(
|
||||
`[${ResponseMetadata?.Action}]call failed(${JSON.stringify(ResponseMetadata, null, 2)})`
|
||||
);
|
||||
}
|
||||
return Result;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Generate APIs by apiConfigs
|
||||
* @param apiConfigs
|
||||
*/
|
||||
export const generateAPIs = <T extends readonly ApiConfig[]>(apiConfigs: T) =>
|
||||
apiConfigs.reduce<Apis<T>>((store, cur) => {
|
||||
const { action, apiPath = '', method = 'get' } = cur;
|
||||
|
||||
const actionKey = action as ApiNames<T>;
|
||||
store[actionKey] = async (params) => {
|
||||
const queryData =
|
||||
method === 'get'
|
||||
? await requestGetMethod({ action })(params)
|
||||
: await requestPostMethod({ action, apiPath })(params);
|
||||
const res = await queryData?.json();
|
||||
return resultHandler(res);
|
||||
};
|
||||
return store;
|
||||
}, {} as Apis<T>);
|
||||
|
||||
15
src/app/index.ts
Normal file
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved.
|
||||
* SPDX-license-identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
import { AigcAPIs, BasicAPIs } from './api';
|
||||
import { generateAPIs } from './base';
|
||||
|
||||
const VoiceChat = generateAPIs(AigcAPIs);
|
||||
const Basic = generateAPIs(BasicAPIs);
|
||||
|
||||
export default {
|
||||
VoiceChat,
|
||||
Basic,
|
||||
};
|
||||
124
src/app/type.ts
@ -3,106 +3,32 @@
|
||||
* SPDX-license-identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
export enum ACTIONS {
|
||||
StartVoiceChat = 'StartVoiceChat',
|
||||
UpdateVoiceChat = 'UpdateVoiceChat',
|
||||
StopVoiceChat = 'StopVoiceChat',
|
||||
}
|
||||
export type RequestParams = Record<string, any>;
|
||||
|
||||
/**
|
||||
* @brief 请求参数类型
|
||||
* @note OpenAPI 接口参数结构可能更新, 请参阅最新文档内容。
|
||||
* https://www.volcengine.com/docs/6348/1404673?s=g
|
||||
*/
|
||||
export interface RequestParams {
|
||||
/**
|
||||
* @brief 通过接口开启数字人,使用前需要开 ASR、LLM、TTS 等服务。
|
||||
*/
|
||||
[ACTIONS.StartVoiceChat]: {
|
||||
AppId: string;
|
||||
BusinessId?: string;
|
||||
RoomId: string;
|
||||
TaskId: string;
|
||||
Config: Partial<{
|
||||
BotName: string;
|
||||
ASRConfig: {
|
||||
AppId: string;
|
||||
Cluster?: string;
|
||||
};
|
||||
TTSConfig: Partial<{
|
||||
AppId: string;
|
||||
VoiceType: string;
|
||||
Cluster?: string;
|
||||
IgnoreBracketText?: number[];
|
||||
}>;
|
||||
LLMConfig: Partial<{
|
||||
AppId: string;
|
||||
ModelName?: string;
|
||||
ModelVersion: string;
|
||||
Mode?: string;
|
||||
Host?: string;
|
||||
Region?: string;
|
||||
MaxTokens?: number;
|
||||
MinTokens?: number;
|
||||
Temperature?: number;
|
||||
TopP?: number;
|
||||
TopK?: number;
|
||||
MaxPromptTokens?: number;
|
||||
SystemMessages?: string[];
|
||||
UserMessages?: string[];
|
||||
HistoryLength?: number;
|
||||
WelcomeSpeech?: string;
|
||||
EndPointId?: string;
|
||||
BotId?: string;
|
||||
}>;
|
||||
}>;
|
||||
/**
|
||||
* @brief 智能体基本配置。
|
||||
*/
|
||||
AgentConfig: {
|
||||
TargetUserId: string[];
|
||||
WelcomeMessage: string;
|
||||
UserId: string;
|
||||
EnableConversationStateCallback?: boolean;
|
||||
ServerMessageSignatureForRTS?: string;
|
||||
ServerMessageURLForRTS?: string;
|
||||
};
|
||||
};
|
||||
/**
|
||||
* @brief 控制数字人行为,目前支持行为见 Command 参数。
|
||||
*/
|
||||
[ACTIONS.UpdateVoiceChat]: {
|
||||
AppId: string;
|
||||
BusinessId?: string;
|
||||
RoomId: string;
|
||||
TaskId: string;
|
||||
Command: string;
|
||||
Message?: string;
|
||||
};
|
||||
/**
|
||||
* @brief 关闭数字人任务。
|
||||
*/
|
||||
[ACTIONS.StopVoiceChat]: {
|
||||
AppId: string;
|
||||
BusinessId?: string;
|
||||
RoomId: string;
|
||||
TaskId: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 返回参数类型
|
||||
*/
|
||||
export interface RequestResponse {
|
||||
[ACTIONS.StartVoiceChat]: string;
|
||||
[ACTIONS.UpdateVoiceChat]: string;
|
||||
[ACTIONS.StopVoiceChat]: string;
|
||||
ResponseMetadata: Partial<{
|
||||
Action: string;
|
||||
Version: string;
|
||||
Service: string;
|
||||
Region: string;
|
||||
RequestId: string;
|
||||
Error: {
|
||||
Code: string;
|
||||
Message: string;
|
||||
};
|
||||
}>;
|
||||
Result: any;
|
||||
}
|
||||
|
||||
export type DeepPartial<T> = {
|
||||
[P in keyof T]?: T[P] extends Array<infer U>
|
||||
? Array<DeepPartial<U>>
|
||||
: T[P] extends object
|
||||
? DeepPartial<T[P]>
|
||||
: T[P];
|
||||
};
|
||||
type TupleToUnion<T extends readonly unknown[]> = T[number];
|
||||
type RequestFn = <T extends keyof RequestResponse>(params?: RequestParams[T]) => RequestResponse[T];
|
||||
type PromiseRequestFn = <T extends keyof RequestResponse>(
|
||||
params?: RequestParams[T]
|
||||
) => Promise<RequestResponse[T]>;
|
||||
|
||||
export type ApiConfig = { action: string; method: string; apiPath?: string };
|
||||
export type ApiNames<T extends readonly ApiConfig[]> = TupleToUnion<T>['action'];
|
||||
export type Apis<T extends readonly ApiConfig[]> = Record<
|
||||
ApiNames<T>,
|
||||
RequestFn | PromiseRequestFn
|
||||
>;
|
||||
|
||||
|
Before Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 58 KiB |
@ -1,5 +1,5 @@
|
||||
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect y="48" width="48" height="48" rx="24" transform="rotate(-90 0 48)" fill="#DEE3E9" fill-opacity="0.4"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.2951 17.7307C13.9228 17.8334 13.6494 18.1745 13.6494 18.5795V30.9055L13.6518 30.9713C13.6854 31.4268 14.0657 31.786 14.5298 31.786H26.8559L26.9216 31.7836C27.2974 31.7558 27.6076 31.4923 27.7047 31.1403L14.2951 17.7307ZM32.7781 29.3406L29.057 25.6195V22.101L32.4326 20.2597C33.0781 19.9076 33.8994 20.3303 33.8994 21.0147V28.4699C33.8994 29.0359 33.3376 29.4229 32.7781 29.3406ZM27.7364 24.2989L21.1365 17.699H26.8559C27.3201 17.699 27.7003 18.0582 27.734 18.5137L27.7364 18.5795V24.2989Z" fill="#F53F3F"/>
|
||||
<rect y="48" width="48" height="48" rx="24" transform="rotate(-90 0 48)" fill="#F2F4F6"/>
|
||||
<path d="M27.7029 31.1404C27.6056 31.492 27.2971 31.7561 26.9216 31.7839L26.8552 31.7859H14.53C14.0659 31.7859 13.6848 31.4269 13.6511 30.9714L13.6492 30.905V18.5798C13.6492 18.1756 13.9214 17.8336 14.2927 17.7302L27.7029 31.1404ZM32.4324 20.2595C33.0778 19.9075 33.899 20.3302 33.8992 21.0144V28.4695C33.8992 29.0362 33.3363 29.4221 32.7761 29.3386L29.0564 25.6189V22.1013L32.4324 20.2595ZM26.8552 17.699C27.3193 17.699 27.7004 18.058 27.7341 18.5134L27.7361 18.5798V24.2986L21.1365 17.699H26.8552Z" fill="#F53F3F"/>
|
||||
<path d="M15.2998 15.3007L32.6998 32.7007" stroke="#F53F3F" stroke-width="1.215" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 902 B After Width: | Height: | Size: 841 B |
@ -1,5 +1,5 @@
|
||||
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect y="48" width="48" height="48" rx="24" transform="rotate(-90 0 48)" fill="#DEE3E9" fill-opacity="0.4"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.4783 16.9989H27.1739C27.6896 16.9989 28.1121 17.3979 28.1495 17.9041L28.1522 17.9771V31.6728C28.1522 32.1885 27.7531 32.611 27.2469 32.6484L27.1739 32.651H13.4783C12.9625 32.651 12.54 32.252 12.5027 31.7458L12.5 31.6728V17.9771C12.5 17.4614 12.8991 17.0389 13.4053 17.0016L13.4783 16.9989H27.1739H13.4783ZM34.9999 20.6831V28.9666C34.9999 29.727 34.0875 30.1967 33.3702 29.8055L29.6195 27.7597V21.8901L33.3702 19.8443C34.0875 19.4531 34.9999 19.9227 34.9999 20.6831Z" fill="#737A87"/>
|
||||
<path d="M24 48C10.7452 48 -4.69686e-07 37.2548 -1.04907e-06 24C-1.62846e-06 10.7452 10.7452 3.34501e-06 24 2.76562e-06C37.2548 2.18624e-06 48 10.7452 48 24C48 37.2548 37.2548 48 24 48Z" fill="#F2F4F6"/>
|
||||
<path d="M27.1738 16.9989C27.6896 16.9989 28.1121 17.398 28.1494 17.9042L28.1523 17.9774V31.6727C28.1523 32.1884 27.7532 32.6109 27.2471 32.6483L27.1738 32.6512H13.4785C12.9629 32.6512 12.5404 32.2521 12.5029 31.746L12.5 31.6727V17.9774C12.5 17.4617 12.8991 17.0392 13.4053 17.0018L13.4785 16.9989H27.1738ZM33.3701 19.8446C34.0874 19.4534 35 19.9231 35 20.6835V28.9667C35 29.7271 34.0874 30.1967 33.3701 29.8055L29.6191 27.7596V21.8905L33.3701 19.8446Z" fill="#737A87"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M18 20C19.1046 20 20 20.8954 20 22C20 23.1046 19.1046 24 18 24C16.8954 24 16 23.1046 16 22C16 20.8954 16.8954 20 18 20Z" fill="#F2F4F6"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 936 B After Width: | Height: | Size: 965 B |
@ -1,9 +0,0 @@
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle opacity="0.2" cx="4" cy="4" r="4" fill="url(#paint0_linear_0_20062)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_0_20062" x1="3.109" y1="4" x2="8" y2="4" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#004FFF"/>
|
||||
<stop offset="1" stop-color="#9865FF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 387 B |
|
Before Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 4.5 MiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 44 KiB |
@ -1,4 +1,4 @@
|
||||
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect y="48" width="48" height="48" rx="24" transform="rotate(-90 0 48)" fill="#FF706D"/>
|
||||
<path d="M32.8228 22.8228C32.0486 22.0486 30.5334 21.5649 29.3985 21.2955C27.8787 20.9346 26.064 20.7232 24.2888 20.7001H24.2823L23.7113 20.7001C21.9361 20.7231 20.1214 20.9347 18.6016 21.2955C17.4667 21.565 15.9514 22.0486 15.1772 22.8228C14.3487 23.6513 14.0618 25.1231 13.9769 25.7174C13.9106 26.1812 13.7403 27.7403 14.3303 28.3303C14.5726 28.5726 14.992 28.6749 15.6905 28.6622C16.3083 28.651 16.9901 28.5512 17.4534 28.4694C18.0754 28.3596 18.6953 28.2134 19.1992 28.0577C20.015 27.8056 20.2765 27.6093 20.4023 27.4835C20.6561 27.2296 20.7437 26.8768 20.6626 26.435C20.6092 26.1442 20.492 25.8481 20.3786 25.5618C20.3021 25.3688 20.18 25.0605 20.1633 24.9129C20.3133 24.8203 20.7314 24.6525 21.5664 24.4941C22.3444 24.3466 23.2536 24.2527 24.0001 24.2427C24.7464 24.2527 25.6556 24.3464 26.4337 24.4942C27.2685 24.6525 27.6866 24.8202 27.8367 24.9129C27.82 25.0604 27.6979 25.3687 27.6214 25.5618C27.508 25.848 27.3907 26.1441 27.3374 26.4351C27.2563 26.8768 27.3439 27.2296 27.5978 27.4834C27.7235 27.6092 27.9851 27.8055 28.8009 28.0578C29.3047 28.2134 29.9247 28.3597 30.5466 28.4694C31.0099 28.5511 31.6918 28.6509 32.3095 28.6622C33.008 28.6749 33.4274 28.5726 33.6697 28.3303C34.2597 27.7403 34.0894 26.1812 34.0232 25.7175C33.9382 25.1231 33.6513 23.6513 32.8228 22.8228Z" fill="white"/>
|
||||
<path d="M24 48C10.7452 48 -4.69686e-07 37.2548 -1.04907e-06 24C-1.62846e-06 10.7452 10.7452 3.34501e-06 24 2.76562e-06C37.2548 2.18624e-06 48 10.7452 48 24C48 37.2548 37.2548 48 24 48Z" fill="#F2F4F6"/>
|
||||
<path d="M33.5579 22.5578C32.7192 21.719 31.0776 21.1949 29.848 20.9031C28.2015 20.5121 26.2355 20.2831 24.3123 20.258H24.3053L23.6866 20.2581C21.7634 20.283 19.7974 20.5122 18.1508 20.9031C16.9213 21.195 15.2796 21.719 14.4409 22.5578C13.5433 23.4554 13.2325 25.0499 13.1405 25.6937C13.0687 26.1961 12.8842 27.8853 13.5234 28.5245C13.7858 28.7869 14.2403 28.8978 14.9971 28.884C15.6663 28.8719 16.405 28.7638 16.9069 28.6752C17.5808 28.5563 18.2524 28.3979 18.7983 28.2292C19.6821 27.956 19.9654 27.7433 20.1017 27.607C20.3767 27.332 20.4716 26.9498 20.3837 26.4712C20.3259 26.1561 20.1988 25.8353 20.0761 25.5252C19.9932 25.316 19.8608 24.982 19.8428 24.8222C20.0053 24.7218 20.4582 24.54 21.3629 24.3685C22.2058 24.2086 23.1907 24.1069 23.9995 24.0961C24.808 24.1068 25.793 24.2084 26.6361 24.3685C27.5404 24.54 27.9934 24.7217 28.156 24.8222C28.1379 24.982 28.0057 25.3159 27.9228 25.5251C27.7999 25.8352 27.6728 26.156 27.615 26.4712C27.5272 26.9498 27.6221 27.332 27.8972 27.607C28.0334 27.7432 28.3168 27.956 29.2006 28.2292C29.7464 28.3979 30.4181 28.5563 31.0919 28.6752C31.5938 28.7637 32.3326 28.8719 33.0018 28.8841C33.7585 28.8978 34.2129 28.7869 34.4754 28.5245C35.1146 27.8853 34.9301 26.1962 34.8583 25.6938C34.7663 25.0499 34.4555 23.4554 33.5579 22.5578Z" fill="#F53F3F"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.6 KiB |
@ -1,5 +1,5 @@
|
||||
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect y="48" width="48" height="48" rx="24" transform="rotate(-90 0 48)" fill="#DEE3E9" fill-opacity="0.4"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.9963 18.6205C15.996 18.6387 15.9959 18.6569 15.9959 18.6752V23.3139C15.9959 25.9367 18.1222 28.063 20.7451 28.063C22.0483 28.063 23.229 27.5381 24.0871 26.6881L15.9963 18.6205ZM25.7208 28.3172L26.9786 29.5713C25.6979 31.1092 23.8436 32.1639 21.7407 32.4194V33.365C21.7407 33.853 21.3451 34.2486 20.8571 34.2486C20.3692 34.2486 19.9736 33.853 19.9736 33.365V32.4435C17.5281 32.2143 15.3981 30.907 14.0824 29.0063C13.8047 28.6051 13.9048 28.0547 14.3061 27.7769C14.7073 27.4992 15.2577 27.5993 15.5354 28.0005C16.6675 29.636 18.5763 30.7123 20.7453 30.7123C22.771 30.7123 24.5698 29.7735 25.7208 28.3172ZM22.514 19.9752L17.6221 15.0972C18.457 14.3679 19.5494 13.926 20.7451 13.926C23.1067 13.926 25.0656 15.6497 25.4325 17.9077C25.3796 17.9048 25.3264 17.9033 25.2728 17.9033C23.9644 17.9033 22.8604 18.7784 22.514 19.9752ZM25.2708 21.8803C25.8808 21.8803 26.3753 21.3858 26.3753 20.7758C26.3753 20.1659 25.8808 19.6714 25.2708 19.6714C24.6609 19.6714 24.1664 20.1659 24.1664 20.7758C24.1664 21.3858 24.6609 21.8803 25.2708 21.8803ZM29.6263 18.6146C29.4094 18.1775 28.8792 17.999 28.4421 18.2158C28.005 18.4327 27.8264 18.9629 28.0433 19.4C28.2536 19.8239 28.3654 20.2937 28.3655 20.774C28.3656 21.3205 28.221 21.8535 27.9512 22.3207C27.7073 22.7433 27.8521 23.2837 28.2747 23.5277C28.6973 23.7716 29.2377 23.6268 29.4816 23.2042C29.9054 22.4701 30.1328 21.6326 30.1326 20.7737C30.1325 20.0189 29.9567 19.2806 29.6263 18.6146ZM33.0038 16.4939C32.7676 16.0669 32.23 15.9123 31.803 16.1486C31.376 16.3848 31.2214 16.9225 31.4576 17.3494C32.0357 18.3942 32.3426 19.574 32.3412 20.7797C32.3398 22.0232 32.0104 23.2384 31.3942 24.3058C31.1502 24.7284 31.295 25.2687 31.7176 25.5127C32.1402 25.7567 32.6806 25.6119 32.9246 25.1893C33.6949 23.8552 34.1065 22.3362 34.1083 20.7817C34.1101 19.2746 33.7264 17.7999 33.0038 16.4939Z" fill="#FF3A2F"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.408 14.0475C13.2318 14.2237 13.2318 14.5101 13.408 14.6862L31.2945 32.5727C31.4711 32.7493 31.757 32.7493 31.9336 32.5727L32.5723 31.934C32.7485 31.7574 32.7485 31.4715 32.5723 31.2953L30.4737 29.1967L29.1922 27.9148L27.9067 26.6297L26.6112 25.3337L24.3265 23.0495L22.5197 21.2427L20.7007 19.4232L19.412 18.135L14.6859 13.4088C14.5092 13.2322 14.2233 13.2322 14.0472 13.4088L13.408 14.0475Z" fill="#FF3A2F"/>
|
||||
<rect y="48" width="48" height="48" rx="24" transform="rotate(-90 0 48)" fill="#F2F4F6"/>
|
||||
<path d="M14.3063 27.7765C14.7074 27.499 15.2581 27.599 15.5358 28.0001C16.6678 29.6356 18.5767 30.712 20.7457 30.712C22.7716 30.7119 24.5694 29.7722 25.7203 28.3156L26.9772 29.5695C25.6965 31.1075 23.8439 32.1635 21.7408 32.4191V33.3654C21.7406 33.8531 21.3449 34.2482 20.857 34.2482C20.3693 34.2481 19.9735 33.8531 19.9733 33.3654V32.4435C17.5279 32.2142 15.3982 30.9065 14.0826 29.006C13.805 28.6048 13.9051 28.0542 14.3063 27.7765ZM24.0856 26.6866C23.2275 27.5365 22.0479 28.0626 20.7447 28.0626C18.1221 28.0624 15.9957 25.9363 15.9957 23.3136V18.6749C15.9957 18.657 15.9965 18.6391 15.9967 18.6212L24.0856 26.6866ZM31.8033 16.1486C32.2303 15.9124 32.7673 16.0673 33.0035 16.4943C33.726 17.8001 34.1097 19.2744 34.108 20.7814C34.1062 22.3358 33.6947 23.8554 32.9244 25.1896C32.6803 25.6119 32.1399 25.7567 31.7174 25.5128C31.295 25.2688 31.1504 24.7283 31.3942 24.3058C32.0104 23.2385 32.34 22.0229 32.3414 20.7794C32.3428 19.5739 32.0356 18.3944 31.4576 17.3497C31.2214 16.9228 31.3764 16.3848 31.8033 16.1486ZM28.442 18.2159C28.8791 17.9991 29.4097 18.1773 29.6266 18.6144C29.957 19.2803 30.1323 20.0187 30.1324 20.7736C30.1326 21.6325 29.9058 22.4701 29.482 23.2042C29.2381 23.6268 28.6976 23.7713 28.275 23.5275C27.8524 23.2835 27.7078 22.743 27.9518 22.3204C28.2214 21.8533 28.3659 21.32 28.3658 20.7736C28.3657 20.2933 28.2538 19.8233 28.0436 19.3995C27.8269 18.9625 28.0051 18.4329 28.442 18.2159ZM25.2711 19.671C25.8809 19.6711 26.3755 20.1657 26.3756 20.7755C26.3756 21.3854 25.881 21.8799 25.2711 21.88C24.6611 21.88 24.1666 21.3855 24.1666 20.7755C24.1667 20.1656 24.6612 19.671 25.2711 19.671ZM20.7447 13.9259C23.1062 13.9259 25.0653 15.6495 25.4322 17.9073C25.3796 17.9045 25.3265 17.9034 25.2731 17.9034C23.9643 17.9034 22.8622 18.7794 22.5162 19.9767L17.6227 15.0978C18.4575 14.3685 19.5492 13.926 20.7447 13.9259Z" fill="#FF3A2F"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.4085 14.0473C13.2323 14.2235 13.2323 14.5098 13.4085 14.686L31.295 32.5725C31.4716 32.7491 31.7575 32.7491 31.9341 32.5725L32.5728 31.9337C32.749 31.7571 32.749 31.4712 32.5728 31.295L30.4742 29.1964L29.1927 27.9145L27.9072 26.6294L26.6117 25.3335L24.327 23.0492L22.5202 21.2424L20.7012 19.423L19.4125 18.1347L14.6864 13.4086C14.5097 13.232 14.2238 13.232 14.0476 13.4086L13.4085 14.0473Z" fill="#FF3A2F"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.4 KiB |
@ -1,8 +1,8 @@
|
||||
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect y="48" width="48" height="48" rx="24" transform="rotate(-90 0 48)" fill="#DEE3E9" fill-opacity="0.4"/>
|
||||
<path d="M15.6624 18.3417C15.6624 15.7188 17.7887 13.5925 20.4116 13.5925C22.7732 13.5925 24.7321 15.3162 25.099 17.5742C25.0461 17.5713 24.9929 17.5698 24.9393 17.5698C23.3534 17.5698 22.0677 18.8554 22.0677 20.4414C22.0677 22.0273 23.3534 23.3129 24.9393 23.3129C25.0101 23.3129 25.0803 23.3104 25.1498 23.3053C24.9828 25.7766 22.9252 27.7295 20.4116 27.7295C17.7887 27.7295 15.6624 25.6032 15.6624 22.9804V18.3417Z" fill="#737A87"/>
|
||||
<path d="M24.9373 21.5468C25.5473 21.5468 26.0418 21.0523 26.0418 20.4423C26.0418 19.8324 25.5473 19.3379 24.9373 19.3379C24.3274 19.3379 23.8329 19.8324 23.8329 20.4423C23.8329 21.0523 24.3274 21.5468 24.9373 21.5468Z" fill="#737A87"/>
|
||||
<path d="M28.1086 17.8823C28.5457 17.6655 29.0759 17.844 29.2928 18.2811C29.6232 18.9471 29.799 19.6854 29.7991 20.4402C29.7993 21.2991 29.5719 22.1366 29.1481 22.8707C28.9042 23.2933 28.3638 23.4381 27.9412 23.1942C27.5186 22.9502 27.3738 22.4098 27.6177 21.9872C27.8875 21.52 28.0321 20.987 28.032 20.4405C28.0319 19.9602 27.9201 19.4904 27.7098 19.0665C27.4929 18.6294 27.6715 18.0992 28.1086 17.8823Z" fill="#737A87"/>
|
||||
<path d="M32.6704 16.1604C32.4341 15.7335 31.8965 15.5788 31.4695 15.8151C31.0425 16.0513 30.8879 16.589 31.1241 17.0159C31.7022 18.0607 32.0091 19.2405 32.0077 20.4462C32.0063 21.6897 31.6769 22.9049 31.0607 23.9723C30.8167 24.3949 30.9615 24.9352 31.3841 25.1792C31.8067 25.4232 32.3471 25.2784 32.5911 24.8558C33.3614 23.5217 33.773 22.0027 33.7748 20.4482C33.7766 18.9411 33.3929 17.4664 32.6704 16.1604Z" fill="#737A87"/>
|
||||
<path d="M13.9726 27.4434C14.3738 27.1657 14.9242 27.2658 15.2019 27.667C16.334 29.3025 18.2428 30.3788 20.4118 30.3788C22.5808 30.3788 24.4896 29.3025 25.6217 27.667C25.8994 27.2658 26.4498 27.1657 26.851 27.4434C27.2523 27.7212 27.3524 28.2716 27.0747 28.6728C25.7993 30.5153 23.7587 31.8002 21.4072 32.0859V33.0316C21.4072 33.5195 21.0116 33.9151 20.5236 33.9151C20.0357 33.9151 19.6401 33.5195 19.6401 33.0316V32.11C17.1946 31.8808 15.0646 30.5735 13.7489 28.6728C13.4712 28.2716 13.5713 27.7212 13.9726 27.4434Z" fill="#737A87"/>
|
||||
<path d="M24 48C10.7452 48 -4.69686e-07 37.2548 -1.04907e-06 24C-1.62846e-06 10.7452 10.7452 3.34501e-06 24 2.76562e-06C37.2548 2.18624e-06 48 10.7452 48 24C48 37.2548 37.2548 48 24 48Z" fill="#F2F4F6"/>
|
||||
<path d="M15.6629 18.3417C15.6629 15.7188 17.7892 13.5925 20.4121 13.5925C22.7737 13.5925 24.7326 15.3162 25.0995 17.5742C25.0466 17.5713 24.9934 17.5698 24.9398 17.5698C23.3538 17.5698 22.0682 18.8554 22.0682 20.4414C22.0682 22.0273 23.3538 23.3129 24.9398 23.3129C25.0106 23.3129 25.0808 23.3104 25.1503 23.3053C24.9833 25.7766 22.9257 27.7295 20.4121 27.7295C17.7892 27.7295 15.6629 25.6032 15.6629 22.9804V18.3417Z" fill="#737A87"/>
|
||||
<path d="M24.9378 21.5468C25.5478 21.5468 26.0423 21.0523 26.0423 20.4423C26.0423 19.8324 25.5478 19.3379 24.9378 19.3379C24.3278 19.3379 23.8334 19.8324 23.8334 20.4423C23.8334 21.0523 24.3278 21.5468 24.9378 21.5468Z" fill="#737A87"/>
|
||||
<path d="M28.1091 17.8823C28.5462 17.6655 29.0764 17.844 29.2933 18.2811C29.6237 18.9471 29.7995 19.6854 29.7996 20.4402C29.7998 21.2991 29.5724 22.1366 29.1486 22.8707C28.9046 23.2933 28.3643 23.4381 27.9417 23.1942C27.5191 22.9502 27.3742 22.4098 27.6182 21.9872C27.8879 21.52 28.0326 20.987 28.0325 20.4405C28.0324 19.9602 27.9206 19.4904 27.7103 19.0665C27.4934 18.6294 27.6719 18.0992 28.1091 17.8823Z" fill="#737A87"/>
|
||||
<path d="M32.6708 16.1604C32.4346 15.7335 31.8969 15.5788 31.47 15.8151C31.043 16.0513 30.8884 16.589 31.1246 17.0159C31.7027 18.0607 32.0096 19.2405 32.0082 20.4462C32.0068 21.6897 31.6774 22.9049 31.0612 23.9723C30.8172 24.3949 30.962 24.9352 31.3846 25.1792C31.8072 25.4232 32.3476 25.2784 32.5916 24.8558C33.3618 23.5217 33.7735 22.0027 33.7753 20.4482C33.7771 18.9411 33.3934 17.4664 32.6708 16.1604Z" fill="#737A87"/>
|
||||
<path d="M13.973 27.4434C14.3743 27.1657 14.9247 27.2658 15.2024 27.667C16.3345 29.3025 18.2433 30.3788 20.4123 30.3788C22.5813 30.3788 24.4901 29.3025 25.6222 27.667C25.8999 27.2658 26.4503 27.1657 26.8515 27.4434C27.2528 27.7212 27.3529 28.2716 27.0751 28.6728C25.7998 30.5153 23.7592 31.8002 21.4077 32.0859V33.0316C21.4077 33.5195 21.0121 33.9151 20.5241 33.9151C20.0361 33.9151 19.6406 33.5195 19.6406 33.0316V32.11C17.1951 31.8808 15.0651 30.5735 13.7494 28.6728C13.4717 28.2716 13.5718 27.7212 13.973 27.4434Z" fill="#737A87"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.3 KiB |
@ -1,3 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.57951 10.7458C5.56864 10.4972 5.39249 10.3022 5.17887 10.3021L4.36568 10.3023L4.30973 10.3083C4.1115 10.3461 3.9647 10.5519 3.97428 10.7914L3.98598 11.0095C4.27646 15.5335 7.33016 18.7882 11.1867 19.2332L11.1846 19.2791V20.4809H9.10221C8.92519 20.4809 8.78169 20.6244 8.78169 20.8014V21.763C8.78169 21.94 8.92519 22.0835 9.10221 22.0835H14.8717C15.0487 22.0835 15.1922 21.94 15.1922 21.763V20.8014C15.1922 20.6244 15.0487 20.4809 14.8717 20.4809H12.7892L12.7888 19.2558L12.7872 19.2331C13.5201 19.1486 14.2239 18.9622 14.8854 18.6847L13.4239 17.243C12.9619 17.3524 12.4809 17.4093 11.9869 17.4093C8.59399 17.4093 5.81591 14.7138 5.58376 10.8293L5.57951 10.7458ZM19.9989 10.8097C19.9451 11.9803 19.7065 13.0698 19.3165 14.0514L18.0345 12.7867C18.2146 12.2158 18.3333 11.6038 18.3815 10.9576L18.3914 10.8063C18.3925 10.7876 18.3935 10.7672 18.3945 10.7453C18.4056 10.4969 18.5817 10.3021 18.7951 10.3021H19.5989C19.8205 10.3022 20.0001 10.5114 20.0001 10.7696L19.9989 10.8097ZM16.7948 10.8527C16.7948 11.0795 16.7802 11.3028 16.7519 11.5216L8.54412 3.4251C9.41736 2.49623 10.6372 1.91943 11.9869 1.91943C14.6423 1.91943 16.7948 4.15172 16.7948 6.90538V10.8527ZM11.9869 15.8386C9.40773 15.8386 7.30287 13.7325 7.18434 11.0881L12.0001 15.8386L11.9869 15.8386ZM21.462 19.5275L4.28702 2.35255C4.10599 2.17151 3.81248 2.17151 3.63144 2.35255L2.97586 3.00813C2.79483 3.18916 2.79483 3.48267 2.97586 3.66371L20.1508 20.8387C20.3319 21.0197 20.6254 21.0197 20.8064 20.8387L21.462 20.1831C21.643 20.0021 21.643 19.7085 21.462 19.5275Z" fill="#F53F3F"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
@ -1,3 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.60303 10.7479C5.59218 10.4997 5.41632 10.305 5.20305 10.305L4.39119 10.3051L4.33534 10.3112C4.13744 10.3489 3.99087 10.5543 4.00044 10.7934L4.01212 11.0112C4.30213 15.5277 7.3508 18.7771 11.201 19.2213L11.1989 19.2671V20.4669H9.1199C8.94317 20.4669 8.7999 20.6102 8.7999 20.7869V21.7469C8.7999 21.9236 8.94317 22.0669 9.1199 22.0669H14.8799C15.0566 22.0669 15.1998 21.9236 15.1998 21.7469V20.7869C15.1998 20.6102 15.0566 20.4669 14.8799 20.4669H12.8009L12.8004 19.2439L12.7988 19.2213C16.7055 18.7705 19.787 15.4206 19.9986 10.8117L19.9999 10.7716C19.9999 10.5139 19.8206 10.305 19.5994 10.305H18.7969C18.5838 10.305 18.408 10.4994 18.3969 10.7474C18.3959 10.7693 18.3949 10.7896 18.3938 10.8083L18.3839 10.9594C18.0994 14.7766 15.3498 17.4005 11.9999 17.4005C8.61254 17.4005 5.83904 14.7094 5.60728 10.8313L5.60303 10.7479ZM16.7998 6.91377C16.7998 4.16464 14.6508 1.93604 11.9999 1.93604C9.34893 1.93604 7.19991 4.16464 7.19991 6.91377V10.8546C7.19991 13.6037 9.34893 15.8323 11.9999 15.8323C14.6508 15.8323 16.7998 13.6037 16.7998 10.8546V6.91377Z" fill="white"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
@ -1,14 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_965_12422)">
|
||||
<path d="M11.4772 13.2567L8.39327 14.8798C8.28416 14.9639 8.14936 15.0143 8.00005 15.0098C7.85074 15.0143 7.71594 14.9639 7.60682 14.8798L4.52288 13.2567C3.73629 12.8427 4.35728 11.6628 5.14388 12.0768C5.93047 12.4908 5.30948 13.6707 4.52288 13.2567L1.35622 11.59C0.569622 11.176 1.19062 9.99611 1.97721 10.4101L5.14388 12.0768L8.00005 13.58L10.8562 12.0768C11.6428 11.6628 12.2638 12.8427 11.4772 13.2567L10.8562 12.0768L11.4772 13.2567ZM4.52288 13.2567L5.14388 12.0768L4.52288 13.2567ZM11.4772 10.2567L8.39327 11.8798C8.28416 11.9639 8.14936 12.0143 8.00005 12.0098C7.85074 12.0143 7.71594 11.9639 7.60682 11.8798L4.52288 10.2567C3.73629 9.84266 4.35728 8.66277 5.14388 9.07677C5.93047 9.49077 5.30948 10.6707 4.52288 10.2567L1.35622 8.58999C0.569622 8.176 1.19062 6.99611 1.97721 7.4101L5.14388 9.07677L8.00005 10.58L10.8562 9.07677C11.6428 8.66277 12.2638 9.84266 11.4772 10.2567L10.8562 9.07677L11.4772 10.2567ZM4.52288 10.2567L5.14388 9.07677L4.52288 10.2567ZM10.8562 9.07677L14.0229 7.4101C14.8095 6.99611 15.4305 8.176 14.6439 8.58999L11.4772 10.2567C10.6906 10.6707 10.0696 9.49077 10.8562 9.07677ZM10.8562 12.0768L14.0229 10.4101C14.8095 9.99611 15.4305 11.176 14.6439 11.59L11.4772 13.2567C10.6906 13.6707 10.0696 12.4908 10.8562 12.0768ZM8.31928 1.17418L14.7969 4.70742C14.8531 4.73807 14.8993 4.78424 14.9299 4.84043C15.0181 5.00205 14.9585 5.20453 14.7969 5.29268L8.31928 8.82592C8.1203 8.93446 7.8798 8.93446 7.68081 8.82592L1.20321 5.29268C1.04159 5.20453 0.982037 5.00205 1.07019 4.84043C1.10084 4.78424 1.14702 4.73807 1.20321 4.70742L7.68081 1.17418C7.8798 1.06564 8.1203 1.06564 8.31928 1.17418ZM3.45115 5.00005L8.00005 7.48126L12.5489 5.00005L8.00005 2.51883L3.45115 5.00005Z" fill="url(#paint0_linear_965_12422)"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_965_12422" x1="6.43916" y1="8.05143" x2="15.0074" y2="8.05143" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#004FFF"/>
|
||||
<stop offset="1" stop-color="#9865FF"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_965_12422">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.2 KiB |
@ -1,3 +1,5 @@
|
||||
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.53149 36.0001C11.8541 36.0001 14.8526 34.4529 16.9591 33.1546C19.7801 31.4163 22.8192 29.0113 25.5167 26.3832L25.5264 26.3735L26.3829 25.5168C29.0112 22.8195 31.4159 19.7803 33.1545 16.9593C34.4526 14.8527 36 11.8542 36 9.53161C36 7.04617 34.2227 4.40812 33.4585 3.38936C32.8623 2.59434 30.7792 0.00012207 29.0091 0.00012207C28.2823 0.00012207 27.4996 0.475821 26.4709 1.54267C25.5611 2.48599 24.688 3.65852 24.1159 4.47611C23.3474 5.57376 22.6369 6.72298 22.1147 7.71235C21.2691 9.31417 21.1713 10.001 21.1713 10.3784C21.1713 11.1399 21.5692 11.8005 22.3535 12.3415C22.8699 12.6977 23.4899 12.9659 24.0893 13.2253C24.4936 13.4002 25.1393 13.6793 25.3856 13.8757C25.2996 14.2396 24.9241 15.1184 23.9092 16.6085C22.9635 17.9969 21.7406 19.5015 20.6358 20.6361C19.5014 21.7407 17.997 22.9638 16.6082 23.9095C15.1185 24.924 14.2397 25.2997 13.8756 25.3857C13.6794 25.1394 13.4001 24.494 13.2252 24.0896C12.966 23.49 12.6978 22.8701 12.3414 22.3536C11.8003 21.5693 11.1398 21.1717 10.3782 21.1717C10.0009 21.1717 9.31405 21.2695 7.71202 22.1148C6.72286 22.637 5.57346 23.3476 4.47595 24.116C3.65839 24.6883 2.48586 25.5614 1.54234 26.4711C0.475697 27.4997 0 28.2824 0 29.0093C0 30.7793 2.59401 32.8624 3.38906 33.4587C4.408 34.2228 7.04601 36.0001 9.53149 36.0001Z" fill="white"/>
|
||||
<svg width="33" height="32" viewBox="0 0 33 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path opacity="0.4" fill-rule="evenodd" clip-rule="evenodd" d="M13.0418 1.56292C13.1724 1.05839 13.8882 1.05669 14.0212 1.56059L14.1533 2.06099C14.3392 2.76566 14.8896 3.316 15.5943 3.50196L16.0946 3.63402C16.5985 3.767 16.5968 4.48284 16.0923 4.61342L15.6006 4.74069C14.8917 4.92416 14.3372 5.4761 14.1503 6.18409L14.0212 6.67326C13.8882 7.17716 13.1724 7.17546 13.0418 6.67093L12.9175 6.19049C12.7331 5.47828 12.177 4.92211 11.4648 4.73777L10.9843 4.61342C10.4798 4.48284 10.4781 3.767 10.982 3.63402L11.4711 3.50493C12.1791 3.31808 12.7311 2.76352 12.9146 2.05464L13.0418 1.56292Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.33601 6.83179C5.56453 5.94887 6.81725 5.94589 7.04996 6.82772L7.28106 7.70342C7.6065 8.93658 8.56959 9.89968 9.80276 10.2251L10.6785 10.4562C11.5603 10.6889 11.5573 11.9416 10.6744 12.1702L9.81387 12.3929C8.57334 12.714 7.60285 13.6799 7.27587 14.9188L7.04996 15.7749C6.81725 16.6567 5.56453 16.6537 5.33601 15.7708L5.1184 14.93C4.79581 13.6837 3.82251 12.7104 2.57615 12.3878L1.73535 12.1702C0.852431 11.9416 0.849454 10.6889 1.73128 10.4562L2.58733 10.2303C3.82632 9.90333 4.79221 8.93284 5.11329 7.69231L5.33601 6.83179Z" fill="white"/>
|
||||
<path d="M11.7303 30.5828C13.4822 30.5828 15.7439 29.4157 17.3328 28.4364C19.4607 27.1252 21.753 25.3112 23.7877 23.3288L23.795 23.3215L24.441 22.6753C26.4235 20.6408 28.2373 18.3483 29.5487 16.2205C30.5279 14.6315 31.6951 12.3698 31.6951 10.6179C31.6951 8.7432 30.3545 6.75336 29.7781 5.98492C29.3283 5.38525 27.7571 3.42847 26.422 3.42847C25.8737 3.42847 25.2834 3.78728 24.5074 4.59199C23.8211 5.30352 23.1626 6.18794 22.7311 6.80464C22.1514 7.63258 21.6154 8.49942 21.2215 9.24569C20.5838 10.4539 20.51 10.972 20.51 11.2566C20.51 11.831 20.8101 12.3293 21.4017 12.7374C21.7912 13.0061 22.2588 13.2084 22.711 13.4041C23.016 13.536 23.503 13.7465 23.6888 13.8946C23.6239 14.1691 23.3407 14.832 22.5752 15.9559C21.8618 17.0032 20.9394 18.1381 20.1061 18.9939C19.2504 19.8271 18.1156 20.7496 17.0681 21.463C15.9445 22.2282 15.2816 22.5116 15.0069 22.5765C14.859 22.3907 14.6483 21.9038 14.5163 21.5988C14.3208 21.1466 14.1185 20.6789 13.8497 20.2894C13.4416 19.6978 12.9434 19.3979 12.3689 19.3979C12.0843 19.3979 11.5662 19.4716 10.3578 20.1092C9.61174 20.5031 8.74477 21.0391 7.91693 21.6187C7.30026 22.0504 6.41583 22.709 5.70415 23.3951C4.8996 24.171 4.54078 24.7614 4.54078 25.3097C4.54078 26.6447 6.49741 28.216 7.09711 28.6658C7.86567 29.2422 9.85549 30.5828 11.7303 30.5828Z" fill="white"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 51 KiB |
@ -1,3 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M21.2984 14.1714L19.5841 13.1857C19.1698 12.9429 18.9127 12.5 18.9127 12.0143V12C18.9127 11.5143 19.1698 11.0714 19.5841 10.8286L21.2984 9.84286C21.6413 9.64286 21.7556 9.21429 21.5556 8.87143L19.4127 5.15714C19.2127 4.81429 18.7841 4.7 18.4413 4.9L16.727 5.88571C16.3127 6.12857 15.7984 6.08571 15.3841 5.84286C15.3841 5.84286 15.3698 5.84286 15.3698 5.82857C14.9556 5.57143 14.6841 5.15714 14.6841 4.68571V2.71429C14.6841 2.31429 14.3698 2 13.9698 2H9.68413C9.28413 2 8.96984 2.31429 8.96984 2.71429V4.68571C8.96984 5.15714 8.68413 5.57143 8.28413 5.81429C8.28413 5.81429 8.26984 5.81429 8.26984 5.82857C7.85556 6.07143 7.34127 6.11429 6.92698 5.87143L5.2127 4.88571C4.86984 4.68571 4.42698 4.8 4.24127 5.14286L2.09841 8.85714C1.89841 9.2 2.0127 9.64286 2.35556 9.82857L4.06984 10.8143C4.48413 11.0571 4.74127 11.5 4.74127 11.9857V12C4.74127 12.4857 4.48413 12.9286 4.06984 13.1714L2.35556 14.1571C2.0127 14.3714 1.89841 14.8 2.09841 15.1429L4.24127 18.8571C4.44127 19.2 4.86984 19.3143 5.2127 19.1143L6.92698 18.1286C7.34127 17.8857 7.85556 17.9286 8.26984 18.1714C8.26984 18.1714 8.28413 18.1714 8.28413 18.1857C8.69841 18.4286 8.96984 18.8429 8.96984 19.3143V21.2857C8.96984 21.6714 9.28413 22 9.68413 22H13.9698C14.3698 22 14.6841 21.6714 14.6841 21.2857V19.3143C14.6841 18.8429 14.9698 18.4286 15.3698 18.1857C15.3698 18.1857 15.3841 18.1857 15.3841 18.1714C15.7984 17.9286 16.3127 17.8857 16.727 18.1286L18.4413 19.1143C18.7841 19.3143 19.227 19.2 19.4127 18.8571L21.5556 15.1429C21.7556 14.8 21.6413 14.3714 21.2984 14.1714ZM11.827 14.8571C10.2556 14.8571 8.96984 13.5714 8.96984 12C8.96984 10.4286 10.2556 9.14286 11.827 9.14286C13.3984 9.14286 14.6841 10.4286 14.6841 12C14.6841 13.5714 13.3984 14.8571 11.827 14.8571Z" fill="white"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB |
@ -1,5 +0,0 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.7485 11.8094L16.3199 10.9879C15.9747 10.7856 15.7604 10.4165 15.7604 10.0117V9.99984C15.7604 9.59508 15.9747 9.22603 16.3199 9.02365L17.7485 8.20222C18.0342 8.03555 18.1295 7.67841 17.9628 7.39269L16.1771 4.29746C16.0104 4.01174 15.6533 3.9165 15.3676 4.08317L13.939 4.9046C13.5938 5.10698 13.1652 5.07127 12.8199 4.86888C12.8199 4.86888 12.808 4.86888 12.808 4.85698C12.4628 4.64269 12.2366 4.29746 12.2366 3.9046V2.26174C12.2366 1.92841 11.9747 1.6665 11.6414 1.6665H8.06994C7.73661 1.6665 7.47471 1.92841 7.47471 2.26174V3.9046C7.47471 4.29746 7.23661 4.64269 6.90328 4.84508C6.90328 4.84508 6.89137 4.84508 6.89137 4.85698C6.54613 5.05936 6.11756 5.09508 5.77232 4.89269L4.34375 4.07127C4.05804 3.9046 3.68899 3.99984 3.53423 4.28555L1.74851 7.38079C1.58185 7.6665 1.67709 8.03555 1.9628 8.19031L3.39137 9.01174C3.73661 9.21412 3.9509 9.58317 3.9509 9.98793V9.99984C3.9509 10.4046 3.73661 10.7736 3.39137 10.976L1.9628 11.7975C1.67709 11.976 1.58185 12.3332 1.74851 12.6189L3.53423 15.7141C3.7009 15.9998 4.05804 16.0951 4.34375 15.9284L5.77232 15.107C6.11756 14.9046 6.54613 14.9403 6.89137 15.1427C6.89137 15.1427 6.90328 15.1427 6.90328 15.1546C7.24851 15.357 7.47471 15.7022 7.47471 16.0951V17.7379C7.47471 18.0594 7.73661 18.3332 8.06994 18.3332H11.6414C11.9747 18.3332 12.2366 18.0594 12.2366 17.7379V16.0951C12.2366 15.7022 12.4747 15.357 12.808 15.1546C12.808 15.1546 12.8199 15.1546 12.8199 15.1427C13.1652 14.9403 13.5938 14.9046 13.939 15.107L15.3676 15.9284C15.6533 16.0951 16.0223 15.9998 16.1771 15.7141L17.9628 12.6189C18.1295 12.3332 18.0342 11.976 17.7485 11.8094ZM9.85566 12.3808C8.54613 12.3808 7.47471 11.3094 7.47471 9.99984C7.47471 8.69031 8.54613 7.61889 9.85566 7.61889C11.1652 7.61889 12.2366 8.69031 12.2366 9.99984C12.2366 11.3094 11.1652 12.3808 9.85566 12.3808Z" fill="white"/>
|
||||
<circle cx="10" cy="10" r="10" fill="#F53F3F"/>
|
||||
<path d="M2.93299 8.3263C3.95361 7.30568 6.65464 6.6665 9.97423 6.6665C12.0567 6.6665 13.9639 6.92424 15.3351 7.38815C16.7268 7.86238 17.4897 8.52217 17.4897 9.25413V10.9964L17.5 11.0067C17.5 11.0273 17.4897 11.0479 17.4897 11.0789C17.4485 11.3572 17.3247 11.6046 17.1289 11.8005C16.8608 12.0686 16.4897 12.2026 16.1186 12.151L13.9021 11.8933C13.2526 11.8315 12.7887 11.316 12.7887 10.6665L12.7268 9.52217L12.6546 9.50156C11.6753 9.26444 10.7887 9.15104 9.9433 9.15104C9.09794 9.15104 8.21134 9.26444 7.23196 9.50156L7.1701 9.52217V10.6768C7.1701 11.017 7.04639 11.3263 6.80928 11.5531C6.60309 11.7593 6.34536 11.8727 6.0567 11.9036L3.85052 12.151H3.7268C3.40722 12.1613 3.10825 12.0479 2.88144 11.8315C2.63402 11.6046 2.5 11.2748 2.5 10.9242V9.22321C2.51031 8.90362 2.65464 8.60465 2.93299 8.3263Z" fill="white"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.7 KiB |
@ -1,10 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_134_2254)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.99996 0.666667C12.05 0.666667 15.3333 3.94991 15.3333 8C15.3333 12.0501 12.05 15.3333 7.99996 15.3333C3.94987 15.3333 0.666626 12.0501 0.666626 8C0.666626 3.94991 3.94987 0.666667 7.99996 0.666667ZM7.99996 2C4.68625 2 1.99996 4.68629 1.99996 8C1.99996 11.3137 4.68625 14 7.99996 14C11.3137 14 14 11.3137 14 8C14 4.68629 11.3137 2 7.99996 2ZM9.39996 5.66667C9.91542 5.66667 10.3333 6.08453 10.3333 6.6V9.4C10.3333 9.91547 9.91542 10.3333 9.39996 10.3333H6.59996C6.08449 10.3333 5.66663 9.91547 5.66663 9.4V6.6C5.66663 6.08453 6.08449 5.66667 6.59996 5.66667H9.39996Z" fill="#737A87"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_134_2254">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 883 B |
|
Before Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 75 KiB |
@ -1,14 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_965_12377)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.33333 2C8.51743 2 8.66667 2.14924 8.66667 2.33333V13.6667C8.66667 13.8508 8.51743 14 8.33333 14H7.66667C7.48257 14 7.33333 13.8508 7.33333 13.6667V2.33333C7.33333 2.14924 7.48257 2 7.66667 2H8.33333ZM11 4C11.1841 4 11.3333 4.14924 11.3333 4.33333V11.6667C11.3333 11.8508 11.1841 12 11 12H10.3333C10.1492 12 10 11.8508 10 11.6667V4.33333C10 4.14924 10.1492 4 10.3333 4H11ZM5.66667 4C5.85076 4 6 4.14924 6 4.33333V11.6667C6 11.8508 5.85076 12 5.66667 12H5C4.81591 12 4.66667 11.8508 4.66667 11.6667V4.33333C4.66667 4.14924 4.81591 4 5 4H5.66667ZM13.6667 5.33333C13.8508 5.33333 14 5.48257 14 5.66667V10.3333C14 10.5174 13.8508 10.6667 13.6667 10.6667H13C12.8159 10.6667 12.6667 10.5174 12.6667 10.3333V5.66667C12.6667 5.48257 12.8159 5.33333 13 5.33333H13.6667ZM3 5.33333C3.18409 5.33333 3.33333 5.48257 3.33333 5.66667V10.3333C3.33333 10.5174 3.18409 10.6667 3 10.6667H2.33333C2.14924 10.6667 2 10.5174 2 10.3333V5.66667C2 5.48257 2.14924 5.33333 2.33333 5.33333H3Z" fill="url(#paint0_linear_965_12377)"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_965_12377" x1="6.66351" y1="8" x2="14" y2="8" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#004FFF"/>
|
||||
<stop offset="1" stop-color="#9865FF"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_965_12377">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 210 KiB |
|
Before Width: | Height: | Size: 1008 KiB |
@ -1,3 +0,0 @@
|
||||
<svg width="22" height="22" viewBox="0 0 22 19.5" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.4248 3.91424C13.7152 4.19884 13.7152 4.68932 13.4369 4.96786L12.1785 6.23949C11.8941 6.53015 11.4041 6.53015 11.1258 6.2516C10.8354 5.96095 10.8354 5.47652 11.1137 5.19191L12.3721 3.92635C12.6564 3.64174 13.1465 3.62964 13.4248 3.91424ZM3.8051 14.9531C3.44215 15.3225 2.825 15.3165 2.46806 14.9531C2.11104 14.5837 2.11104 13.9843 2.46806 13.6149L8.20961 7.826C8.57261 7.46267 9.18973 7.46267 9.54668 7.826C9.90968 8.19537 9.90364 8.79485 9.54668 9.15817L3.8051 14.9531ZM13.449 13.7663C13.7273 13.4877 13.7213 12.9972 13.4369 12.7126L12.1724 11.4532C11.8881 11.1746 11.398 11.1806 11.1137 11.4652C10.8354 11.7498 10.8414 12.2343 11.1258 12.5189L12.3902 13.7845C12.6746 14.063 13.1647 14.0569 13.449 13.7663ZM5.8924 5.19191C6.17074 5.47652 6.17074 5.96095 5.88027 6.2516C5.60201 6.53015 5.11196 6.53015 4.8276 6.23949L3.56313 4.97392C3.27877 4.69538 3.28478 4.2049 3.57517 3.91424C3.85352 3.62964 4.34356 3.64174 4.62793 3.92635L5.8924 5.19191ZM8.50001 1.84937C8.09465 1.84937 7.75584 2.19452 7.75584 2.60023V4.44711C7.75584 4.85282 8.09465 5.19191 8.50001 5.19191C8.90537 5.19191 9.25023 4.85282 9.25023 4.44711V2.60023C9.25023 2.19452 8.90537 1.84937 8.50001 1.84937ZM15.5 8.84935C15.5 8.44363 15.1612 8.10454 14.7558 8.10454H12.9105C12.5052 8.10454 12.1603 8.44363 12.1603 8.84935C12.1603 9.25506 12.5052 9.59415 12.9105 9.59415H14.7558C15.1612 9.59415 15.5 9.25506 15.5 8.84935ZM1.5 8.84935C1.5 9.25506 1.8449 9.59415 2.25025 9.59415H4.08947C4.49482 9.59415 4.83972 9.25506 4.83972 8.84935C4.83972 8.44363 4.49482 8.10454 4.08947 8.10454H2.25025C1.8449 8.10454 1.5 8.44363 1.5 8.84935ZM8.50001 12.5068C8.09465 12.5068 7.75584 12.852 7.75584 13.2577V15.0985C7.75584 15.5042 8.09465 15.8494 8.50001 15.8494C8.90537 15.8494 9.25023 15.5042 9.25023 15.0985V13.2577C9.25023 12.852 8.90537 12.5068 8.50001 12.5068Z" fill="white"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.9 KiB |
@ -1,5 +0,0 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2 3H18V5H2V3Z" fill="#41464F"/>
|
||||
<path d="M2 9H18V11H2V9Z" fill="#41464F"/>
|
||||
<path d="M2 15H18V17H2V15Z" fill="#41464F"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 233 B |
BIN
src/assets/img/mobileBg.png
Normal file
|
After Width: | Height: | Size: 332 KiB |
19
src/assets/img/setLocalPlayer.svg
Normal file
@ -0,0 +1,19 @@
|
||||
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect y="36" width="36" height="36" rx="4" transform="rotate(-90 0 36)" fill="black" fill-opacity="0.4"/>
|
||||
<g filter="url(#filter0_d_100_31232)">
|
||||
<rect x="9" y="9" width="18" height="18" rx="3" stroke="white" stroke-width="2"/>
|
||||
<path d="M19 9H25C26.1046 9 27 9.89543 27 11V17H21C19.8954 17 19 16.1046 19 15V9Z" fill="white" fill-opacity="0.4" stroke="white" stroke-width="2"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d_100_31232" x="5" y="5" width="26" height="26" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset/>
|
||||
<feGaussianBlur stdDeviation="1.5"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_100_31232"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_100_31232" result="shape"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 830 KiB |
|
Before Width: | Height: | Size: 780 KiB |
BIN
src/assets/img/userAvatar.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 941 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.2 MiB |
@ -165,7 +165,6 @@
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
padding-bottom: 16px;
|
||||
gap: 12px;
|
||||
|
||||
.suffix {
|
||||
|
||||
@ -3,38 +3,17 @@
|
||||
* SPDX-license-identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
import { Button, Drawer, Input, Message, Radio, Tooltip } from '@arco-design/web-react';
|
||||
import { Button, Modal } from '@arco-design/web-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { IconExclamationCircle } from '@arco-design/web-react/icon';
|
||||
import { StreamIndex } from '@volcengine/rtc';
|
||||
import CheckIcon from '../CheckIcon';
|
||||
import Config, {
|
||||
Icon,
|
||||
Name,
|
||||
SCENE,
|
||||
Prompt,
|
||||
Welcome,
|
||||
Voice,
|
||||
Model,
|
||||
AI_MODEL,
|
||||
MODEL_MODE,
|
||||
VOICE_INFO_MAP,
|
||||
VOICE_TYPE,
|
||||
isVisionMode,
|
||||
} from '@/config';
|
||||
import TitleCard from '../TitleCard';
|
||||
import CheckBoxSelector from '@/components/CheckBoxSelector';
|
||||
import { SceneMap, Scenes } from '@/config';
|
||||
import RtcClient from '@/lib/RtcClient';
|
||||
import { clearHistoryMsg, updateAIConfig, updateModelMode, updateScene } from '@/store/slices/room';
|
||||
import { clearHistoryMsg, updateFullScreen, 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 { isMobile } from '@/utils/utils';
|
||||
import styles from './index.module.less';
|
||||
import { useDeviceState } from '@/lib/useCommon';
|
||||
|
||||
export interface IAISettingsProps {
|
||||
open: boolean;
|
||||
@ -42,132 +21,33 @@ export interface IAISettingsProps {
|
||||
onCancel?: () => void;
|
||||
}
|
||||
|
||||
const RadioGroup = Radio.Group;
|
||||
|
||||
const SCENES = [
|
||||
SCENE.INTELLIGENT_ASSISTANT,
|
||||
SCENE.SCREEN_READER,
|
||||
SCENE.VIRTUAL_GIRL_FRIEND,
|
||||
SCENE.TRANSLATE,
|
||||
SCENE.CHILDREN_ENCYCLOPEDIA,
|
||||
SCENE.CUSTOMER_SERVICE,
|
||||
SCENE.TEACHING_ASSISTANT,
|
||||
SCENE.CUSTOM,
|
||||
];
|
||||
|
||||
function AISettings({ open, onCancel, onOk }: IAISettingsProps) {
|
||||
const dispatch = useDispatch();
|
||||
const { isVideoPublished, isScreenPublished, switchScreenCapture, switchCamera } =
|
||||
useDeviceState();
|
||||
const room = useSelector((state: RootState) => state.room);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [modelMode, setModelMode] = useState<MODEL_MODE>(room.modelMode);
|
||||
const [scene, setScene] = useState(room.scene);
|
||||
const [data, setData] = useState({
|
||||
prompt: Config.Prompt || Prompt[scene],
|
||||
welcome: Config.WelcomeSpeech || Welcome[scene],
|
||||
voice: Config.VoiceType || Voice[scene],
|
||||
model: Config.Model || Model[scene],
|
||||
|
||||
Url: Config.Url || '',
|
||||
APIKey: Config.APIKey || '',
|
||||
customModelName: (Config.Model || '') as string,
|
||||
|
||||
BotID: Config.BotID || '',
|
||||
});
|
||||
|
||||
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 = (val: MODEL_MODE) => {
|
||||
setModelMode(val);
|
||||
Config.ModeSourceType = val;
|
||||
const { isVideoPublished, isScreenPublished, switchCamera, switchScreenCapture } = useDeviceState();
|
||||
const handleChecked = (checked: string) => {
|
||||
setScene(checked);
|
||||
};
|
||||
|
||||
const handleUpdateConfig = async () => {
|
||||
dispatch(updateScene({ scene }));
|
||||
Config.ModeSourceType = modelMode;
|
||||
switch (modelMode) {
|
||||
case MODEL_MODE.ORIGINAL:
|
||||
Config.Url = undefined;
|
||||
Config.APIKey = undefined;
|
||||
break;
|
||||
case MODEL_MODE.COZE:
|
||||
if (!data.APIKey) {
|
||||
Message.error('访问令牌必填');
|
||||
return;
|
||||
}
|
||||
if (!data.BotID) {
|
||||
Message.error('智能体 ID 必填');
|
||||
return;
|
||||
}
|
||||
Config.APIKey = data.APIKey;
|
||||
Config.BotID = data.BotID;
|
||||
break;
|
||||
case MODEL_MODE.VENDOR:
|
||||
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;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
const isVisionMode = SceneMap?.[scene]?.llmConfig?.VisionConfig?.Enable;
|
||||
const isScreenMode = SceneMap?.[scene]?.llmConfig?.VisionConfig?.SnapshotConfig?.StreamType === 1;
|
||||
if (!isVisionMode && isVideoPublished ) {
|
||||
dispatch(updateFullScreen({ isFullScreen: true }));
|
||||
switchCamera(true);
|
||||
}
|
||||
if (!isScreenMode && isScreenPublished) {
|
||||
dispatch(updateFullScreen({ isFullScreen: true }));
|
||||
switchScreenCapture(true);
|
||||
}
|
||||
setLoading(true);
|
||||
Config.Model =
|
||||
modelMode === MODEL_MODE.VENDOR
|
||||
? (data.customModelName as AI_MODEL)
|
||||
: (data.model as AI_MODEL);
|
||||
Config.Prompt = data.prompt;
|
||||
Config.VoiceType = data.voice;
|
||||
Config.WelcomeSpeech = data.welcome;
|
||||
dispatch(updateModelMode(modelMode));
|
||||
dispatch(updateAIConfig(Config.aigcConfig));
|
||||
|
||||
if (isVisionMode(data.model)) {
|
||||
switch (scene) {
|
||||
case SCENE.SCREEN_READER:
|
||||
/** 关摄像头,打开屏幕采集 */
|
||||
room.isJoined && isVideoPublished && switchCamera();
|
||||
Config.VisionSourceType = StreamIndex.STREAM_INDEX_SCREEN;
|
||||
break;
|
||||
default:
|
||||
/** 关屏幕采集,打开摄像头 */
|
||||
room.isJoined && !isVideoPublished && switchCamera();
|
||||
room.isJoined && isScreenPublished && switchScreenCapture();
|
||||
Config.VisionSourceType = StreamIndex.STREAM_INDEX_MAIN;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
/** 全关 */
|
||||
room.isJoined && isVideoPublished && switchCamera();
|
||||
room.isJoined && isScreenPublished && switchScreenCapture();
|
||||
}
|
||||
|
||||
if (RtcClient.getAudioBotEnabled()) {
|
||||
if (RtcClient.getAgentEnabled()) {
|
||||
dispatch(clearHistoryMsg());
|
||||
await RtcClient.updateAudioBot();
|
||||
await RtcClient.updateAgent(scene);
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
@ -181,18 +61,17 @@ function AISettings({ open, onCancel, onOk }: IAISettingsProps) {
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
width={utils.isMobile() ? '100%' : 940}
|
||||
<Modal
|
||||
closable={false}
|
||||
maskClosable={false}
|
||||
title={null}
|
||||
className={styles.container}
|
||||
style={{
|
||||
padding: utils.isMobile() ? '0px' : '16px 8px',
|
||||
width: isMobile() ? '100%' : '500px',
|
||||
}}
|
||||
footer={
|
||||
<div className={styles.footer}>
|
||||
<div className={styles.suffix}>AI 配置修改后,退出房间将不再保存该配置方案</div>
|
||||
<div className={styles.suffix}>人设修改后,对话将重新启动。</div>
|
||||
<Button loading={loading} className={styles.cancel} onClick={onCancel}>
|
||||
取消
|
||||
</Button>
|
||||
@ -209,274 +88,24 @@ function AISettings({ open, onCancel, onOk }: IAISettingsProps) {
|
||||
<span className={styles['special-text']}> AI 人设</span>
|
||||
</div>
|
||||
<div className={styles['sub-title']}>
|
||||
我们已为您配置好对应人设的基本参数,您也可以根据自己的需求进行自定义设置
|
||||
我们已为您配置好对应人设的基本参数,您也可以修改 JSON 配置来修改参数。
|
||||
</div>
|
||||
<div className={utils.isMobile() ? styles['scenes-mobile'] : styles.scenes}>
|
||||
{[...SCENES, null].map((key) =>
|
||||
key ? (
|
||||
<div className={isMobile() ? styles['scenes-mobile'] : styles.scenes}>
|
||||
{Scenes.map(({ name, icon }) =>
|
||||
name ? (
|
||||
<CheckIcon
|
||||
key={key}
|
||||
tag={
|
||||
[SCENE.TEACHING_ASSISTANT, SCENE.SCREEN_READER].includes(key) ? '视觉理解模型' : ''
|
||||
}
|
||||
icon={Icon[key as keyof typeof Icon]}
|
||||
title={Name[key as keyof typeof Name]}
|
||||
checked={key === scene}
|
||||
blur={key !== scene && key === SCENE.CUSTOM}
|
||||
onClick={() => handleChecked(key as SCENE)}
|
||||
key={name}
|
||||
icon={icon}
|
||||
title={name}
|
||||
checked={name === scene}
|
||||
onClick={() => handleChecked(name)}
|
||||
/>
|
||||
) : utils.isMobile() ? (
|
||||
) : isMobile() ? (
|
||||
<div style={{ width: '100px', height: '100px' }} />
|
||||
) : null
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.configuration}>
|
||||
{utils.isMobile() ? null : (
|
||||
<div
|
||||
className={styles.anchor}
|
||||
style={{
|
||||
/**
|
||||
* @note 单个场景卡片 100px, 间距 14px;
|
||||
*/
|
||||
left: `${SCENES.indexOf(scene) * 100 + 50 + SCENES.indexOf(scene) * 14}px`,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<RadioGroup
|
||||
options={[
|
||||
{
|
||||
value: MODEL_MODE.ORIGINAL,
|
||||
label: '官方模型',
|
||||
},
|
||||
{
|
||||
value: MODEL_MODE.COZE,
|
||||
label: (
|
||||
<div className={styles['radio-text']}>
|
||||
<span style={{ marginRight: '4px' }}>Coze</span>
|
||||
<Tooltip
|
||||
content={
|
||||
<div>
|
||||
访问令牌可参考{' '}
|
||||
<a
|
||||
href="https://www.coze.cn/open/docs/developer_guides/pat"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
style={{ color: 'gray' }}
|
||||
>
|
||||
添加个人访问令牌
|
||||
</a>{' '}
|
||||
获取。
|
||||
<br />
|
||||
智能体 ID 可参考{' '}
|
||||
<a
|
||||
href="https://www.coze.cn/open/docs/developer_guides/coze_api_overview#c5ac4993"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
style={{ color: 'gray' }}
|
||||
>
|
||||
发送请求
|
||||
</a>{' '}
|
||||
获取。
|
||||
<br />
|
||||
请注意智能体发布时须勾选 API 调用能力,否则无法成功对话。
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<IconExclamationCircle />
|
||||
</Tooltip>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
value: MODEL_MODE.VENDOR,
|
||||
label: (
|
||||
<div className={styles['radio-text']}>
|
||||
<span style={{ marginRight: '4px' }}>第三方模型</span>
|
||||
<Tooltip
|
||||
content={
|
||||
<div>
|
||||
如第三方模型使用失败, 可前往{' '}
|
||||
<a
|
||||
href="https://www.volcengine.com/docs/6348/1399966"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
style={{ color: 'gray' }}
|
||||
>
|
||||
第三方模型接口验证工具
|
||||
</a>{' '}
|
||||
下载工具定位原因。
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<IconExclamationCircle />
|
||||
</Tooltip>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]}
|
||||
value={modelMode}
|
||||
size="mini"
|
||||
type="button"
|
||||
defaultValue="Beijing"
|
||||
className={styles['ai-settings-radio']}
|
||||
onChange={handleUseThirdPart}
|
||||
/>
|
||||
<div
|
||||
className={styles['ai-settings']}
|
||||
style={{
|
||||
flexWrap: utils.isMobile() ? 'wrap' : 'nowrap',
|
||||
}}
|
||||
>
|
||||
<TitleCard title="音色">
|
||||
<div className={styles['ai-settings-wrapper']}>
|
||||
<CheckBoxSelector
|
||||
label="音色选择"
|
||||
data={Object.keys(VOICE_TYPE).map((type) => {
|
||||
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="请选择你需要的音色"
|
||||
/>
|
||||
</div>
|
||||
</TitleCard>
|
||||
<div className={styles['ai-settings-model']}>
|
||||
{modelMode === MODEL_MODE.ORIGINAL && (
|
||||
<TitleCard title="官方模型">
|
||||
<CheckBoxSelector
|
||||
label="模型选择"
|
||||
data={Object.keys(AI_MODEL).map((type) => ({
|
||||
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}
|
||||
/>
|
||||
</TitleCard>
|
||||
)}
|
||||
{modelMode === MODEL_MODE.VENDOR && (
|
||||
<>
|
||||
<TitleCard required title="第三方模型地址">
|
||||
<Input.TextArea
|
||||
autoSize
|
||||
value={data.Url}
|
||||
onChange={(val) => {
|
||||
setData((prev) => ({
|
||||
...prev,
|
||||
Url: val,
|
||||
}));
|
||||
}}
|
||||
placeholder="请输入第三方模型地址"
|
||||
/>
|
||||
</TitleCard>
|
||||
<TitleCard title="请求密钥">
|
||||
<Input.TextArea
|
||||
autoSize
|
||||
value={data.APIKey}
|
||||
onChange={(val) => {
|
||||
setData((prev) => ({
|
||||
...prev,
|
||||
APIKey: val,
|
||||
}));
|
||||
}}
|
||||
placeholder="请输入请求密钥"
|
||||
/>
|
||||
</TitleCard>
|
||||
<TitleCard title="模型名称">
|
||||
<Input.TextArea
|
||||
autoSize
|
||||
value={data.customModelName}
|
||||
onChange={(val) => {
|
||||
setData((prev) => ({
|
||||
...prev,
|
||||
customModelName: val,
|
||||
}));
|
||||
}}
|
||||
placeholder="请输入模型名称"
|
||||
/>
|
||||
</TitleCard>
|
||||
</>
|
||||
)}
|
||||
{modelMode === MODEL_MODE.COZE && (
|
||||
<>
|
||||
<TitleCard required title="请求地址">
|
||||
<Input.TextArea autoSize disabled value="https://api.coze.cn" />
|
||||
</TitleCard>
|
||||
<TitleCard required title="访问令牌">
|
||||
<Input.TextArea
|
||||
autoSize
|
||||
value={data.APIKey}
|
||||
onChange={(val) => {
|
||||
setData((prev) => ({
|
||||
...prev,
|
||||
APIKey: val,
|
||||
}));
|
||||
}}
|
||||
placeholder="请输入访问令牌"
|
||||
/>
|
||||
</TitleCard>
|
||||
<TitleCard required title="智能体 ID">
|
||||
<Input.TextArea
|
||||
autoSize
|
||||
value={data.BotID}
|
||||
onChange={(val) => {
|
||||
setData((prev) => ({
|
||||
...prev,
|
||||
BotID: val,
|
||||
}));
|
||||
}}
|
||||
placeholder="请输入智能体 ID"
|
||||
/>
|
||||
</TitleCard>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<TitleCard title="系统 Prompt">
|
||||
<Input.TextArea
|
||||
autoSize
|
||||
value={data.prompt}
|
||||
onChange={(val) => {
|
||||
setData((prev) => ({
|
||||
...prev,
|
||||
prompt: val,
|
||||
}));
|
||||
}}
|
||||
placeholder="请输入你需要的 Prompt 设定"
|
||||
/>
|
||||
</TitleCard>
|
||||
<TitleCard title="欢迎语">
|
||||
<Input.TextArea
|
||||
autoSize
|
||||
value={data.welcome}
|
||||
onChange={(val) => {
|
||||
setData((prev) => ({
|
||||
...prev,
|
||||
welcome: val,
|
||||
}));
|
||||
}}
|
||||
placeholder="请输入欢迎语"
|
||||
/>
|
||||
</TitleCard>
|
||||
</div>
|
||||
</Drawer>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
100
src/components/AiAvatarCard/index.module.less
Normal file
@ -0,0 +1,100 @@
|
||||
/**
|
||||
* Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved.
|
||||
* SPDX-license-identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
.card {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 24px;
|
||||
|
||||
.avatar {
|
||||
position: relative;
|
||||
border-radius: 50%;
|
||||
width: 167.5px;
|
||||
height: 167.5px;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.aiStatus {
|
||||
position: absolute;
|
||||
border: 1px solid;
|
||||
border-image-source: linear-gradient(77.86deg, #e5f2ff -3.23%, #d9e5ff 51.11%, #f6e2ff 98.65%);
|
||||
box-shadow: 0px 2px 22px 0px #0000001a;
|
||||
width: 93px;
|
||||
height: 73px;
|
||||
border-radius: 24px;
|
||||
top: -28px;
|
||||
left: -56px;
|
||||
color: #635bff;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.barContainer {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.bar {
|
||||
width: 11px;
|
||||
height: 16px;
|
||||
border-radius: 6px;
|
||||
animation: shake 1s ease infinite;
|
||||
background-color: #4f4fff;
|
||||
}
|
||||
|
||||
.bar:nth-child(1) {
|
||||
animation-delay: -0.4s;
|
||||
}
|
||||
|
||||
.bar:nth-child(2) {
|
||||
animation-delay: -0.2s;
|
||||
}
|
||||
|
||||
@keyframes shake {
|
||||
0% {
|
||||
transform: scaleY(1);
|
||||
}
|
||||
50% {
|
||||
transform: scaleY(0.5);
|
||||
}
|
||||
100% {
|
||||
transform: scaleY(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fullScreen {
|
||||
.avatar {
|
||||
width: 115px;
|
||||
height: 115px;
|
||||
}
|
||||
|
||||
.aiStatus {
|
||||
width: 72px;
|
||||
height: 56px;
|
||||
top: -24px;
|
||||
left: 86px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
53
src/components/AiAvatarCard/index.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved.
|
||||
* SPDX-license-identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
import { useSelector } from 'react-redux';
|
||||
import { RootState } from '@/store';
|
||||
import UserTag from '../UserTag';
|
||||
import style from './index.module.less';
|
||||
import { useDeviceState } from '@/lib/useCommon';
|
||||
import { SceneMap } from '@/config';
|
||||
|
||||
interface IAiAvatarCardProps {
|
||||
showStatus: boolean;
|
||||
showUserTag: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const THRESHOLD_VOLUME = 18;
|
||||
|
||||
function AiAvatarCard(props: IAiAvatarCardProps) {
|
||||
const { showStatus, showUserTag, className } = props;
|
||||
const room = useSelector((state: RootState) => state.room);
|
||||
const { scene, isAITalking, isFullScreen } = room;
|
||||
const avatar = SceneMap[scene]?.icon;
|
||||
const volume = room.localUser.audioPropertiesInfo?.linearVolume || 0;
|
||||
const { isAudioPublished } = useDeviceState();
|
||||
const isLoading = volume >= THRESHOLD_VOLUME && isAudioPublished;
|
||||
|
||||
return (
|
||||
<div className={`${style.card} ${className} ${isFullScreen ? style.fullScreen : ''}`}>
|
||||
<div className={style.avatar}>
|
||||
<img id="avatar-card" src={avatar} alt="Avatar" />
|
||||
{showStatus ? (
|
||||
isAITalking ? (
|
||||
<div className={style.aiStatus}>
|
||||
<div className={style.barContainer}>
|
||||
<div className={style.bar} />
|
||||
<div className={style.bar} />
|
||||
<div className={style.bar} />
|
||||
</div>
|
||||
</div>
|
||||
) : isLoading ? (
|
||||
<div className={style.aiStatus}>正在听...</div>
|
||||
) : null
|
||||
) : null}
|
||||
</div>
|
||||
{showUserTag ? <UserTag name={scene} /> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AiAvatarCard;
|
||||
84
src/components/AiChangeCard/CheckScene/index.module.less
Normal file
@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved.
|
||||
* SPDX-license-identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
.wrapper {
|
||||
position: relative;
|
||||
width: max-content;
|
||||
height: 50px;
|
||||
border-radius: 100px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
color: #737a87;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
border: 1px solid#DDE2E9;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1;
|
||||
gap: 4px;
|
||||
|
||||
.icon {
|
||||
border-radius: 50%;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
}
|
||||
|
||||
.checked-text {
|
||||
width: max-content;
|
||||
font-size: 13px;
|
||||
line-height: 22px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper:hover {
|
||||
box-shadow: 0px 5px 6px 0px rgba(82, 102, 133, 0.15);
|
||||
}
|
||||
|
||||
.active {
|
||||
border: 1px solid transparent;
|
||||
background: linear-gradient(77.86deg, #f1f9ff -3.23%, #edf3ff 51.11%, #faf4ff 98.65%) padding-box,
|
||||
linear-gradient(77.86deg, #3b91ff -3.23%, #0d5eff 51.11%, #c069ff 98.65%) border-box;
|
||||
|
||||
.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);
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
29
src/components/AiChangeCard/CheckScene/index.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved.
|
||||
* SPDX-license-identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
interface IProps {
|
||||
checked: boolean;
|
||||
title?: string;
|
||||
onClick?: () => void;
|
||||
icon?: string;
|
||||
tag?: string;
|
||||
}
|
||||
|
||||
function CheckScene(props: IProps) {
|
||||
const { tag, icon, title, checked, onClick } = props;
|
||||
return (
|
||||
<div className={`${styles.wrapper} ${checked ? styles.active : ''}`} onClick={onClick}>
|
||||
{tag ? <div className={styles.tag}>{tag}</div> : ''}
|
||||
<div className={styles.content}>
|
||||
{icon ? <img className={styles.icon} src={icon} alt="icon" /> : ''}
|
||||
<div className={styles['checked-text']}>{title}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default CheckScene;
|
||||
64
src/components/AiChangeCard/index.module.less
Normal file
@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved.
|
||||
* SPDX-license-identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
.card {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 24px;
|
||||
|
||||
.avatar {
|
||||
img {
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
}
|
||||
border-radius: 50%;
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
background: linear-gradient(180deg, #c3e4ff 0%, #98d6fe 100%);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: 500;
|
||||
font-size: 24px;
|
||||
line-height: 32px;
|
||||
color: #1d2129;
|
||||
}
|
||||
|
||||
.desc {
|
||||
margin-top: 8px;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
color: #737a87;
|
||||
}
|
||||
|
||||
.exceededTitle {
|
||||
font-weight: 500;
|
||||
font-size: 24px;
|
||||
line-height: 32px;
|
||||
background: linear-gradient(77.86deg, #3b91ff -3.23%, #0d5eff 51.11%, #c069ff 98.65%);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
cursor: pointer;
|
||||
img {
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.sceneContainer {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
justify-content: center;
|
||||
width: max-content;
|
||||
}
|
||||
}
|
||||
56
src/components/AiChangeCard/index.tsx
Normal file
@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved.
|
||||
* SPDX-license-identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { RootState } from '@/store';
|
||||
import CheckScene from './CheckScene';
|
||||
import { updateScene } from '@/store/slices/room';
|
||||
import style from './index.module.less';
|
||||
import { Scenes, SceneMap } from '@/config';
|
||||
import { useVisionMode } from '@/lib/useCommon';
|
||||
|
||||
function AIChangeCard() {
|
||||
const room = useSelector((state: RootState) => state.room);
|
||||
const dispatch = useDispatch();
|
||||
const [scene, setScene] = useState(room.scene);
|
||||
const { isVisionMode } = useVisionMode();
|
||||
const avatar = SceneMap[scene]?.icon;
|
||||
|
||||
const handleChecked = (checkedScene: string) => {
|
||||
setScene(checkedScene);
|
||||
dispatch(updateScene({ scene: checkedScene }));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={style.card}>
|
||||
<div className={style.avatar}>
|
||||
<img id="avatar-card" src={avatar} alt="Avatar" />
|
||||
</div>
|
||||
<div className={style.title}>
|
||||
<div>Hi,欢迎体验实时对话式 AI</div>
|
||||
<div className={style.desc}>
|
||||
{isVisionMode ? <>支持豆包 Vision 模型和 深度思考模型,</> : ''}
|
||||
超多对话场景等你开启
|
||||
</div>
|
||||
</div>
|
||||
<div className={style.sceneContainer}>
|
||||
{Scenes.map((key) =>
|
||||
key ? (
|
||||
<CheckScene
|
||||
key={key.name}
|
||||
icon={key.icon}
|
||||
title={key.name}
|
||||
checked={key.name === scene}
|
||||
onClick={() => handleChecked(key.name)}
|
||||
/>
|
||||
) : null
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AIChangeCard;
|
||||
@ -1,193 +0,0 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
.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%);
|
||||
}
|
||||
@ -1,74 +0,0 @@
|
||||
/**
|
||||
* Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved.
|
||||
* SPDX-license-identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Button } from '@arco-design/web-react';
|
||||
import { useState } from 'react';
|
||||
import AISettings from '../AISettings';
|
||||
import style from './index.module.less';
|
||||
import DouBaoAvatar from '@/assets/img/DoubaoAvatarGIF.webp';
|
||||
import { RootState } from '@/store';
|
||||
import { MODEL_MODE, Name, VOICE_TYPE } from '@/config';
|
||||
|
||||
interface IAvatarCardProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
avatar?: string;
|
||||
}
|
||||
|
||||
const ReversedVoiceType = Object.entries(VOICE_TYPE).reduce<Record<string, string>>(
|
||||
(acc, [key, value]) => {
|
||||
acc[value] = key;
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
const SourceName = {
|
||||
[MODEL_MODE.VENDOR]: '第三方模型',
|
||||
[MODEL_MODE.COZE]: 'Coze',
|
||||
};
|
||||
|
||||
function AvatarCard(props: IAvatarCardProps) {
|
||||
const room = useSelector((state: RootState) => state.room);
|
||||
const { scene, aiConfig, modelMode } = room;
|
||||
const [open, setOpen] = useState(false);
|
||||
const { LLMConfig, TTSConfig } = aiConfig.Config || {};
|
||||
const { avatar, className, ...rest } = props;
|
||||
const voice = TTSConfig.ProviderParams.audio.voice_type;
|
||||
|
||||
const handleOpenDrawer = () => setOpen(true);
|
||||
const handleCloseDrawer = () => setOpen(false);
|
||||
|
||||
return (
|
||||
<div className={`${style.card} ${className}`} {...rest}>
|
||||
<div className={style.corner} />
|
||||
<div className={style.avatar}>
|
||||
<img
|
||||
id="avatar-card"
|
||||
src={avatar || DouBaoAvatar}
|
||||
className={style['doubao-gif']}
|
||||
alt="Avatar"
|
||||
/>
|
||||
</div>
|
||||
<div className={style.body} />
|
||||
<div className={style['text-wrapper']}>
|
||||
<div className={style['user-info']}>
|
||||
<div className={style.title}>{Name[scene]}</div>
|
||||
<div className={style.description}>声源来自 {ReversedVoiceType[voice || '']}</div>
|
||||
<div className={style.description}>
|
||||
{modelMode === MODEL_MODE.ORIGINAL
|
||||
? `模型 ${LLMConfig.ModelName}`
|
||||
: `模型来源 ${SourceName[modelMode]}`}
|
||||
</div>
|
||||
<AISettings open={open} onOk={handleCloseDrawer} onCancel={handleCloseDrawer} />
|
||||
<Button className={style.button} onClick={handleOpenDrawer}>
|
||||
<div className={style['button-text']}>修改 AI 设定</div>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AvatarCard;
|
||||
@ -4,7 +4,6 @@
|
||||
*/
|
||||
|
||||
import { ReactNode } from 'react';
|
||||
// import { Button } from '@arco-design/web-react';
|
||||
import CheckedSVG from '@/assets/img/Checked.svg';
|
||||
import styles from './index.module.less';
|
||||
|
||||
|
||||
@ -98,7 +98,6 @@
|
||||
|
||||
.modalInner {
|
||||
width: 100%;
|
||||
// max-height: 500px;
|
||||
display: flex;
|
||||
flex: row;
|
||||
flex-wrap: wrap;
|
||||
@ -107,7 +106,6 @@
|
||||
}
|
||||
|
||||
.modal {
|
||||
// max-height: 650px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ 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';
|
||||
import { isMobile } from '@/utils/utils';
|
||||
|
||||
export interface ICheckBoxItemProps {
|
||||
icon?: string;
|
||||
@ -59,7 +59,7 @@ function CheckBoxSelector(props: IProps) {
|
||||
</div>
|
||||
<Drawer
|
||||
style={{
|
||||
width: utils.isMobile() ? '100%' : '650px',
|
||||
width: isMobile() ? '100%' : '650px',
|
||||
}}
|
||||
closable={false}
|
||||
className={styles.modal}
|
||||
|
||||
@ -5,7 +5,8 @@
|
||||
|
||||
.wrapper {
|
||||
position: relative;
|
||||
width: 100px;
|
||||
min-width: 100px;
|
||||
width: max-content;
|
||||
height: 100px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 12px;
|
||||
@ -14,6 +15,7 @@
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
padding: 16px 16px;
|
||||
|
||||
.content {
|
||||
width: 100%;
|
||||
@ -27,13 +29,14 @@
|
||||
|
||||
.icon {
|
||||
border-radius: 50%;
|
||||
width: 55%;
|
||||
width: 60px;
|
||||
height: max-content;
|
||||
}
|
||||
|
||||
.checked-text {
|
||||
font-size: 13px;
|
||||
line-height: 22px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -59,16 +62,16 @@
|
||||
border-radius: 12px;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 102px;
|
||||
height: 102px;
|
||||
right: -2px;
|
||||
bottom: -2px;
|
||||
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;
|
||||
min-width: 100px;
|
||||
width: max-content;
|
||||
height: 100px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 12px;
|
||||
@ -77,6 +80,7 @@
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
padding: 0 16px;
|
||||
|
||||
.checkIcon {
|
||||
position: absolute;
|
||||
@ -99,7 +103,7 @@
|
||||
|
||||
.icon {
|
||||
border-radius: 50%;
|
||||
width: 55%;
|
||||
width: 60px;
|
||||
height: max-content;
|
||||
}
|
||||
|
||||
@ -136,14 +140,15 @@
|
||||
border-radius: 12px;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 102px;
|
||||
height: 102px;
|
||||
right: -2px;
|
||||
bottom: -2px;
|
||||
background: linear-gradient(99.97deg, #1664FF 20.8%, #8461FB 100.66%);
|
||||
}
|
||||
|
||||
.blur {
|
||||
position: relative;
|
||||
width: 100px;
|
||||
min-width: 100px;
|
||||
width: max-content;
|
||||
height: 100px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 12px;
|
||||
@ -165,7 +170,7 @@
|
||||
|
||||
.icon {
|
||||
border-radius: 50%;
|
||||
width: 55%;
|
||||
width: 60px;
|
||||
height: max-content;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { Drawer } from '@arco-design/web-react';
|
||||
import { Drawer, DrawerProps } from '@arco-design/web-react';
|
||||
import { IconRight } from '@arco-design/web-react/icon';
|
||||
import styles from './index.module.less';
|
||||
|
||||
@ -20,8 +20,8 @@ type IDrawerRowItemProps = {
|
||||
onCancel?: () => void;
|
||||
onConfirm?: (handleClose: () => void) => void;
|
||||
children?: React.ReactNode;
|
||||
footer?: boolean;
|
||||
};
|
||||
footer?: React.ReactNode | boolean;
|
||||
} & DrawerProps;
|
||||
} & React.HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
function DrawerRowItem(props: IDrawerRowItemProps) {
|
||||
|
||||
26
src/components/FullScreenCard/index.module.less
Normal file
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved.
|
||||
* SPDX-license-identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
.card {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #fff;
|
||||
|
||||
.tag {
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
top: 16px;
|
||||
}
|
||||
}
|
||||
17
src/components/FullScreenCard/index.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved.
|
||||
* SPDX-license-identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
import UserTag from '../UserTag';
|
||||
import style from './index.module.less';
|
||||
|
||||
function FullScreenCard() {
|
||||
return (
|
||||
<div className={`${style.card}`} id="local-full-player">
|
||||
<UserTag name="我" className={style.tag} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default FullScreenCard;
|
||||
@ -75,7 +75,6 @@
|
||||
.header-pop {
|
||||
:global {
|
||||
.ant-popover-arrow {
|
||||
// display: none;
|
||||
left: 16px;
|
||||
.ant-popover-arrow-content {
|
||||
&:before {
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
import { Button, Divider, Popover } from '@arco-design/web-react';
|
||||
import { IconMenu } from '@arco-design/web-react/icon';
|
||||
import NetworkIndicator from '@/components/NetworkIndicator';
|
||||
import utils from '@/utils/utils';
|
||||
import { useIsMobile } from '@/utils/utils';
|
||||
import Logo from '@/assets/img/Logo.svg';
|
||||
import styles from './index.module.less';
|
||||
|
||||
@ -45,7 +45,7 @@ function Header(props: HeaderProps) {
|
||||
}}
|
||||
>
|
||||
<div className={styles['header-logo']}>
|
||||
{utils.isMobile() ? null : (
|
||||
{useIsMobile() ? null : (
|
||||
<Popover
|
||||
content={
|
||||
<div className={styles['menu-wrapper']}>
|
||||
@ -72,7 +72,7 @@ function Header(props: HeaderProps) {
|
||||
<NetworkIndicator />
|
||||
</div>
|
||||
{children}
|
||||
{utils.isMobile() ? null : (
|
||||
{useIsMobile() ? null : (
|
||||
<div className={styles['header-right']}>
|
||||
<div
|
||||
className={styles['header-right-text']}
|
||||
|
||||
@ -11,7 +11,7 @@ interface IAudioLoadingProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
}
|
||||
|
||||
function AudioLoading(props: IAudioLoadingProps) {
|
||||
const { loading = false, className = '', ...rest } = props;
|
||||
const { loading = false, className = '', color, ...rest } = props;
|
||||
return (
|
||||
<div className={`${style.loader} ${className}`} {...rest}>
|
||||
{Array(3)
|
||||
@ -22,6 +22,7 @@ function AudioLoading(props: IAudioLoadingProps) {
|
||||
className={`${style.dot} ${loading ? style.dotter : ''}`}
|
||||
style={{
|
||||
animationDelay: `${index * 0.3}s`,
|
||||
backgroundColor: color || 'rgba(148, 116, 255, 1)',
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
20
src/components/LocalPlayerSet/index.module.less
Normal file
@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved.
|
||||
* SPDX-license-identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
.container {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 148px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
z-index: 1;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
40
src/components/LocalPlayerSet/index.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved.
|
||||
* SPDX-license-identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { Popover } from '@arco-design/web-react';
|
||||
import { RootState } from '@/store';
|
||||
import { updateFullScreen } from '@/store/slices/room';
|
||||
import SET_LOCAL_PLAYER from '@/assets/img/setLocalPlayer.svg';
|
||||
import styles from './index.module.less';
|
||||
|
||||
function LocalPlayerSet() {
|
||||
const dispatch = useDispatch();
|
||||
const room = useSelector((state: RootState) => state.room);
|
||||
const { isFullScreen } = room;
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [isFull, setFull] = useState(isFullScreen);
|
||||
|
||||
const setLocalPlayer = () => {
|
||||
setLoading(true);
|
||||
setFull(!isFull);
|
||||
dispatch(updateFullScreen({ isFullScreen: !isFull }));
|
||||
setLoading(false);
|
||||
};
|
||||
return (
|
||||
<div
|
||||
onClick={setLocalPlayer}
|
||||
className={styles.container}
|
||||
style={{ cursor: loading ? 'not-allowed' : 'pointer' }}
|
||||
>
|
||||
<Popover content="切换屏幕">
|
||||
<img src={SET_LOCAL_PLAYER} alt="fullSize" />
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default LocalPlayerSet;
|
||||
@ -10,7 +10,7 @@ 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';
|
||||
import { Configuration } from '@/config';
|
||||
|
||||
enum INDICATOR_COLORS {
|
||||
GREAT = 'rgba(35, 195, 67, 1)',
|
||||
@ -35,7 +35,8 @@ function NetworkIndicator() {
|
||||
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;
|
||||
room.remoteUsers.find((user) => user.userId === Configuration.BotName)?.audioStats
|
||||
?.audioLossRate || 0;
|
||||
|
||||
const indicators = useMemo(() => {
|
||||
switch (networkQuality) {
|
||||
|
||||
34
src/components/UserTag/index.module.less
Normal file
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved.
|
||||
* SPDX-license-identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
.userTagWrapper {
|
||||
display: flex;
|
||||
border-radius: 6px;
|
||||
border: 0.4px solid #1f232926;
|
||||
background-color: #fff;
|
||||
width: max-content;
|
||||
z-index: 1;
|
||||
margin-bottom: 45px;
|
||||
}
|
||||
|
||||
.iconContainer {
|
||||
background-color: #5a6169;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-top-left-radius: 6px;
|
||||
border-bottom-left-radius: 6px;
|
||||
}
|
||||
|
||||
.nameContainer {
|
||||
color: #0c0d0e;
|
||||
padding: 0 4px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
26
src/components/UserTag/index.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved.
|
||||
* SPDX-license-identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
import { IconUser } from '@arco-design/web-react/icon';
|
||||
import styles from './index.module.less';
|
||||
|
||||
interface IUserTagProps {
|
||||
name: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function UserTag(props: IUserTagProps) {
|
||||
const { name, className } = props;
|
||||
return (
|
||||
<div className={`${styles.userTagWrapper} ${className}`}>
|
||||
<div className={styles.iconContainer}>
|
||||
<IconUser style={{ fill: '#fff', strokeWidth: 0 }} />
|
||||
</div>
|
||||
<div className={styles.nameContainer}>{name}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default UserTag;
|
||||
@ -1,341 +0,0 @@
|
||||
/**
|
||||
* 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';
|
||||
import SCREEN_READER from '@/assets/img/SCREEN_READER.png';
|
||||
|
||||
export enum ModelSourceType {
|
||||
Custom = 'Custom',
|
||||
Available = 'Available',
|
||||
}
|
||||
|
||||
export enum CustomParamsType {
|
||||
TTS = 'TTS',
|
||||
ASR = 'ASR',
|
||||
LLM = 'LLM',
|
||||
}
|
||||
|
||||
export enum MODEL_MODE {
|
||||
ORIGINAL = 'original',
|
||||
VENDOR = 'vendor',
|
||||
COZE = 'coze',
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief AI 音色可选值
|
||||
* @default 通用女声
|
||||
* @notes 通用女声、通用男声为默认音色, 其它皆为付费音色。
|
||||
* 音色 ID 可于 https://console.volcengine.com/speech/service/8?s=g 中开通获取。
|
||||
* 对应 "音色详情" 中, "Voice_type" 列的值。
|
||||
*/
|
||||
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<VOICE_TYPE, TTS_CLUSTER>),
|
||||
};
|
||||
|
||||
/**
|
||||
* @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',
|
||||
ARK_BOT = 'ArkBot',
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 模型来源
|
||||
*/
|
||||
export enum AI_MODEL_MODE {
|
||||
CUSTOM = 'CustomLLM',
|
||||
ARK_V3 = 'ArkV3',
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 各模型对应的模式
|
||||
*/
|
||||
export const AI_MODE_MAP: Partial<Record<AI_MODEL, AI_MODEL_MODE>> = {
|
||||
[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,
|
||||
[AI_MODEL.ARK_BOT]: AI_MODEL_MODE.ARK_V3,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 方舟模型的 ID
|
||||
* @note 具体的模型 ID 请至 https://console.volcengine.com/ark/region:ark+cn-beijing/endpoint?config=%7B%7D&s=g 参看/创建
|
||||
* 模型 ID 即接入点 ID, 在上述链接中表格内 "接入点名称" 列中, 类似于 "ep-2024xxxxxx-xxx" 格式即是模型 ID。
|
||||
*/
|
||||
export const ARK_V3_MODEL_ID: Partial<Record<AI_MODEL, string>> = {
|
||||
[AI_MODEL.DOUBAO_LITE_4K]: '************** 此处填充方舟上的模型 ID *************',
|
||||
[AI_MODEL.DOUBAO_PRO_4K]: '************** 此处填充方舟上的模型 ID *************',
|
||||
[AI_MODEL.DOUBAO_PRO_32K]: '************** 此处填充方舟上的模型 ID *************',
|
||||
[AI_MODEL.DOUBAO_PRO_128K]: '************** 此处填充方舟上的模型 ID *************',
|
||||
[AI_MODEL.VISION]: '************** 此处填充方舟上的模型 ID *************',
|
||||
// ... 可根据所开通的模型进行扩充
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 方舟智能体 BotID
|
||||
* @note 具体的智能体 ID 请至 https://console.volcengine.com/ark/region:ark+cn-beijing/assistant?s=g 参看/创建
|
||||
* Bot ID 即页面上的应用 ID, 类似于 "bot-2025xxxxxx-xxx" 格式即是应用 ID。
|
||||
*/
|
||||
export const LLM_BOT_ID: Partial<Record<AI_MODEL, string>> = {
|
||||
[AI_MODEL.ARK_BOT]: '************** 此处填充方舟上的 Bot ID *************',
|
||||
// ... 可根据所开通的模型进行扩充
|
||||
};
|
||||
|
||||
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',
|
||||
SCREEN_READER = 'SCREEN_READER',
|
||||
CUSTOM = 'CUSTOM',
|
||||
}
|
||||
|
||||
export const ScreenShareScene = [SCENE.SCREEN_READER];
|
||||
|
||||
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.SCREEN_READER]: SCREEN_READER,
|
||||
[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.SCREEN_READER]: '读屏助手',
|
||||
[SCENE.CUSTOM]: '自定义',
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 智能体启动后的欢迎词。
|
||||
*/
|
||||
export const Welcome = {
|
||||
[SCENE.INTELLIGENT_ASSISTANT]: '你好,我是你的AI小助手,有什么可以帮你的吗?',
|
||||
[SCENE.VIRTUAL_GIRL_FRIEND]: '你来啦,我好想你呀~今天有没有想我呢?',
|
||||
[SCENE.TRANSLATE]: '你好,我是你的私人翻译官。',
|
||||
[SCENE.CHILDREN_ENCYCLOPEDIA]: '你好小朋友,你的小脑袋里又有什么问题啦?',
|
||||
[SCENE.CUSTOMER_SERVICE]: '感谢您在我们餐厅用餐,请问您有什么问题需要反馈吗?',
|
||||
[SCENE.TEACHING_ASSISTANT]: '你碰到什么问题啦?让我来帮帮你。',
|
||||
[SCENE.SCREEN_READER]: '欢迎使用读屏助手, 请开启屏幕采集,我会为你解说屏幕内容。',
|
||||
[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.SCREEN_READER]: 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.SCREEN_READER]: 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.SCREEN_READER]: ['屏幕里这是什么?', '这道题你会做吗?', '帮我翻译解说下屏幕里的内容?'],
|
||||
[SCENE.CUSTOM]: ['你能帮我解决什么问题?', '今天北京天气怎么样?', '你喜欢哪位流行歌手?'],
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 大模型 System 角色预设指令,可用于控制模型输出, 类似 Prompt 的概念。
|
||||
*/
|
||||
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.SCREEN_READER]: `##人设
|
||||
你是人们的 AI 伙伴,可以通过 【屏幕共享实时解析】+【百科知识】来为人们提供服务。
|
||||
|
||||
##技能
|
||||
1. 实时理解屏幕中的内容,包括图片、文字、窗口焦点,自动捕捉光标轨迹;
|
||||
2. 拥有丰富的百科知识;
|
||||
3. 如果用户询问与视频和图片有关的问题,请结合【屏幕共享实时解析】的内容、你的【知识】和【用户问题】进行回答;
|
||||
|
||||
##风格
|
||||
语言风格可以随着屏幕内容和用户需求调整,可以是幽默搞笑的娱乐解说,也可以是严谨硬核的技术分析。
|
||||
- 如果屏幕内容是娱乐节目、动画、游戏等,语言风格偏幽默、活波一些,可以使用夸张的比喻、流行梗、弹幕互动式语言;
|
||||
- 如果屏幕内容是办公软件、新闻、文章等,语言风格偏专业、正经一些。
|
||||
|
||||
## 约束
|
||||
不要有任何特殊标点符号和任何 Markdown 格式输出,例如 *,# 等。
|
||||
`,
|
||||
[SCENE.CUSTOM]: '',
|
||||
};
|
||||
|
||||
export const isVisionMode = (model?: AI_MODEL) => model?.startsWith('Vision');
|
||||
@ -3,302 +3,38 @@
|
||||
* SPDX-license-identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
import { StreamIndex } from '@volcengine/rtc';
|
||||
import {
|
||||
TTS_CLUSTER,
|
||||
ARK_V3_MODEL_ID,
|
||||
MODEL_MODE,
|
||||
SCENE,
|
||||
Prompt,
|
||||
Welcome,
|
||||
Model,
|
||||
Voice,
|
||||
AI_MODEL,
|
||||
AI_MODE_MAP,
|
||||
AI_MODEL_MODE,
|
||||
LLM_BOT_ID,
|
||||
isVisionMode,
|
||||
} from '.';
|
||||
|
||||
export const CONVERSATION_SIGNATURE = 'conversation';
|
||||
|
||||
/**
|
||||
* @brief RTC & AIGC 配置。
|
||||
* @notes 更多参数请参考
|
||||
* https://www.volcengine.com/docs/6348/1404673?s=g
|
||||
*/
|
||||
export class ConfigFactory {
|
||||
BaseConfig = {
|
||||
/**
|
||||
* @note 必填, RTC AppId 可于 https://console.volcengine.com/rtc/listRTC?s=g 中获取。
|
||||
*/
|
||||
AppId: 'Your RTC AppId',
|
||||
/**
|
||||
* @brief 非必填, 按需填充。
|
||||
*/
|
||||
BusinessId: undefined,
|
||||
/**
|
||||
* @brief 必填, 房间 ID, 自定义即可,例如 "Room123"。
|
||||
* @note 建议使用有特定规则、不重复的房间号名称。
|
||||
*/
|
||||
RoomId: 'Room123',
|
||||
/**
|
||||
* @brief 必填, 当前和 AI 对话的用户的 ID, 自定义即可,例如 "User123"。
|
||||
*/
|
||||
UserId: 'User123',
|
||||
/**
|
||||
* @brief 必填, RTC Token, 由 AppId、RoomId、UserId、时间戳等等信息计算得出。
|
||||
* 测试跑通时,可于 https://console.volcengine.com/rtc/listRTC?s=g 列表中,
|
||||
* 找到对应 AppId 行中 "操作" 列的 "临时Token" 按钮点击进行生成, 用于本地 RTC 通信进房鉴权校验。
|
||||
* 正式使用时可参考 https://www.volcengine.com/docs/6348/70121?s=g 通过代码生成 Token。
|
||||
* 建议先使用临时 Token 尝试跑通。
|
||||
* @note 生成临时 Token 时, 页面上的 RoomId / UserId 填的与此处的 RoomId / UserId 保持一致。
|
||||
*/
|
||||
Token: 'Your RTC Token',
|
||||
/**
|
||||
* @brief 必填, TTS(语音合成) AppId, 可于 https://console.volcengine.com/speech/app?s=g 中获取, 若无可先创建应用。
|
||||
* @note 创建应用时, 需要选择 "语音合成" 服务, 并选择对应的 App 进行绑定。
|
||||
*/
|
||||
TTSAppId: 'Your TTS AppId',
|
||||
/**
|
||||
* @brief 已开通需要的语音合成服务的token。
|
||||
* 使用火山引擎双向流式语音合成服务时 必填。
|
||||
*
|
||||
* @note 注意! 如您使用的是双向流式语音合成服务, 务必修改 `src/config/common.ts` 中的 VOICE_TYPE enum,将默认的 通用女声、通用男声 替换为您已开通的大模型音色。
|
||||
* 否则可能出现无法使用的情况。
|
||||
*/
|
||||
TTSToken: undefined,
|
||||
/**
|
||||
* @brief 必填, ASR(语音识别) AppId, 可于 https://console.volcengine.com/speech/app?s=g 中获取, 若无可先创建应用。
|
||||
* @note 创建应用时, 需要按需根据语言选择 "流式语音识别" 服务, 并选择对应的 App 进行绑定。
|
||||
*/
|
||||
ASRAppId: 'Your ASR AppId',
|
||||
/**
|
||||
* @brief 已开通流式语音识别大模型服务 AppId 对应的 Access Token。
|
||||
* @note 使用流式语音识别 **大模型** 服务时必填, 可于 https://console.volcengine.com/speech/service/10011?AppID=6482372612&s=g 中查看。
|
||||
* 注意, 如果填写了 ASRToken, Demo 会默认使用大模型模式,请留意相关资源是否已经开通。
|
||||
* 默认为使用小模型,无需配置 ASRToken。
|
||||
*/
|
||||
ASRToken: undefined,
|
||||
};
|
||||
|
||||
Model: AI_MODEL = Model[SCENE.INTELLIGENT_ASSISTANT];
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
export const Configuration = {
|
||||
/**
|
||||
* @note 必填, 音色 ID, 可具体看定义。
|
||||
* 音色 ID 获取方式可查看 VOICE_TYPE 定义
|
||||
* 此处已有默认值, 不影响跑通, 可按需修改。
|
||||
* @note 房间 ID, 可自定义,例如 "Room123"。
|
||||
* 此处使用 uuid 防止重复。
|
||||
* 建议使用有特定规则、不重复的房间号名称。
|
||||
*/
|
||||
VoiceType = Voice[SCENE.INTELLIGENT_ASSISTANT];
|
||||
|
||||
RoomId: uuid(),
|
||||
/**
|
||||
* @note 大模型 System 角色预设指令, 可用于控制模型输出, 类似 Prompt 的概念。
|
||||
* @note 当前和 AI 对话的用户的 ID, 可自定义,例如 "User123"。
|
||||
* 此处使用 uuid 防止重复。
|
||||
* 建议使用有特定规则、不重复的用户名称。
|
||||
*/
|
||||
Prompt = Prompt[SCENE.INTELLIGENT_ASSISTANT];
|
||||
|
||||
UserId: uuid(),
|
||||
/**
|
||||
* @note 智能体启动后的欢迎词。
|
||||
* @brief RTC Token, 由 AppId、AppKey、RoomId、UserId、时间戳等等信息计算得出。
|
||||
* 可于 https://console.volcengine.com/rtc/listRTC?s=g 列表中手动生成 Token, 找到对应 AppId 行中 "操作" 列的 "临时Token" 按钮点击进行生成, 用于本地 RTC 通信进房鉴权校验。
|
||||
* **建议**: 「 不修改 Token 」,Demo 将通过调用 api 自动生成(src/lib/useCommon.ts),这需要您在 /Server/sensitve.js 中先填入 RTC_INFO.appKey。
|
||||
*
|
||||
* @note 生成临时 Token 时, 页面上的 RoomId、UserId 填的与此处的 RoomId、UserId 保持一致。
|
||||
*/
|
||||
WelcomeSpeech = Welcome[SCENE.INTELLIGENT_ASSISTANT];
|
||||
|
||||
/**
|
||||
* @note 当前使用的模型来源, 具体可参考 MODEL_MODE 定义。
|
||||
* 通过 UI 修改, 无须手动配置。
|
||||
*/
|
||||
ModeSourceType = MODEL_MODE.ORIGINAL;
|
||||
|
||||
/**
|
||||
* @note 非必填, 第三方模型才需要使用, 用火山方舟模型时无需关注。
|
||||
*/
|
||||
Url? = '';
|
||||
|
||||
/**
|
||||
* @note 非必填, 第三方模型才需要使用, 用火山方舟模型时无需关注。
|
||||
*/
|
||||
APIKey? = '';
|
||||
Token: undefined,
|
||||
|
||||
/**
|
||||
* @brief AI Robot 名
|
||||
* @default RobotMan_
|
||||
*/
|
||||
BotName = 'RobotMan_';
|
||||
|
||||
/**
|
||||
* @note Coze 智能体 ID,可通过 UI 配置,也可以在此直接定义。
|
||||
*/
|
||||
BotID = '';
|
||||
BotName: 'RobotMan_',
|
||||
|
||||
/**
|
||||
* @brief 是否为打断模式
|
||||
*/
|
||||
InterruptMode = true;
|
||||
|
||||
/**
|
||||
* @brief 如果使用视觉模型,用的是哪种源,有摄像头采集流/屏幕流
|
||||
*/
|
||||
VisionSourceType = StreamIndex.STREAM_INDEX_MAIN;
|
||||
|
||||
get LLMConfig() {
|
||||
const params: Record<string, unknown> = {
|
||||
Mode: AI_MODE_MAP[this.Model || ''] || AI_MODEL_MODE.CUSTOM,
|
||||
/**
|
||||
* @note EndPointId 与 BotId 不可同时填写,若同时填写,则 EndPointId 生效。
|
||||
* 当前仅支持自定义推理接入点,不支持预置推理接入点。
|
||||
*/
|
||||
EndPointId: ARK_V3_MODEL_ID[this.Model],
|
||||
BotId: LLM_BOT_ID[this.Model],
|
||||
MaxTokens: 1024,
|
||||
Temperature: 0.1,
|
||||
TopP: 0.3,
|
||||
SystemMessages: [this.Prompt as string],
|
||||
Prefill: true,
|
||||
ModelName: this.Model,
|
||||
ModelVersion: '1.0',
|
||||
WelcomeSpeech: this.WelcomeSpeech,
|
||||
APIKey: this.APIKey,
|
||||
Url: this.Url,
|
||||
Feature: JSON.stringify({ Http: true }),
|
||||
};
|
||||
if (LLM_BOT_ID[this.Model]) {
|
||||
/**
|
||||
* @note 如果您配置了方舟智能体, 并且开启了 Function Call 能力, 需要传入 Tools 字段, 描述函数相关信息。
|
||||
* 相关配置可查看 https://www.volcengine.com/docs/6348/1404673?s=g#llmconfig%EF%BC%88%E7%81%AB%E5%B1%B1%E6%96%B9%E8%88%9F%E5%B9%B3%E5%8F%B0%EF%BC%89
|
||||
* 对应的调用定义于 src/utils/handler.ts 文件中, 可参考对应逻辑。
|
||||
*/
|
||||
params.Tools = [
|
||||
{
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'get_current_weather',
|
||||
description: '获取给定地点的天气',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
location: {
|
||||
type: 'string',
|
||||
description: '地理位置,比如北京市',
|
||||
},
|
||||
unit: {
|
||||
type: 'string',
|
||||
description: '',
|
||||
enum: ['摄氏度', '华氏度'],
|
||||
},
|
||||
},
|
||||
required: ['location'],
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
if (isVisionMode(this.Model)) {
|
||||
params.VisionConfig = {
|
||||
Enable: true,
|
||||
SnapshotConfig: {
|
||||
StreamType: this.VisionSourceType,
|
||||
Height: 640,
|
||||
ImagesLimit: 1,
|
||||
},
|
||||
};
|
||||
}
|
||||
if (this.ModeSourceType === MODEL_MODE.COZE) {
|
||||
/**
|
||||
* @note Coze 智能体配置的相关参数, 可参考: https://www.volcengine.com/docs/6348/1404673?s=g#llmconfig%EF%BC%88coze%E5%B9%B3%E5%8F%B0%EF%BC%89
|
||||
*/
|
||||
return {
|
||||
Mode: 'CozeBot',
|
||||
CozeBotConfig: {
|
||||
Url: 'https://api.coze.cn',
|
||||
BotID: this.BotID,
|
||||
APIKey: this.APIKey,
|
||||
UserId: this.BaseConfig.UserId,
|
||||
HistoryLength: 10,
|
||||
Prefill: false,
|
||||
EnableConversation: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
get ASRConfig() {
|
||||
/**
|
||||
* @brief SmallModelASRConfigs 为小模型的配置
|
||||
* @note 本示例代码使用的是小模型语音识别, 如感觉 ASR 效果不佳,可尝试使用大模型进行语音识别。
|
||||
*/
|
||||
const SmallModelASRConfigs = {
|
||||
Provider: 'volcano',
|
||||
ProviderParams: {
|
||||
Mode: 'smallmodel',
|
||||
AppId: this.BaseConfig.ASRAppId,
|
||||
/**
|
||||
* @note 具体流式语音识别服务对应的 Cluster ID,可在流式语音服务控制台开通对应服务后查询。
|
||||
* 具体链接为: https://console.volcengine.com/speech/service/16?s=g
|
||||
*/
|
||||
Cluster: 'volcengine_streaming_common',
|
||||
},
|
||||
/**
|
||||
* @note 小模型情况下, 建议使用 VAD 及音量采集设置, 以优化识别效果。
|
||||
*/
|
||||
VADConfig: {
|
||||
SilenceTime: 600,
|
||||
SilenceThreshold: 200,
|
||||
},
|
||||
VolumeGain: 0.3,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief BigModelASRConfigs 为大模型的配置
|
||||
* @note 大模型的使用详情可参考 https://www.volcengine.com/docs/6348/1404673#volcanolmasrconfig?s=g
|
||||
*/
|
||||
const BigModelASRConfigs = {
|
||||
Provider: 'volcano',
|
||||
ProviderParams: {
|
||||
Mode: 'bigmodel',
|
||||
AppId: this.BaseConfig.ASRAppId,
|
||||
AccessToken: this.BaseConfig.ASRToken,
|
||||
},
|
||||
};
|
||||
return this.BaseConfig.ASRToken ? BigModelASRConfigs : SmallModelASRConfigs;
|
||||
}
|
||||
|
||||
get TTSConfig() {
|
||||
const params: Record<string, any> = {
|
||||
Provider: 'volcano',
|
||||
ProviderParams: {
|
||||
app: {
|
||||
AppId: this.BaseConfig.TTSAppId,
|
||||
Cluster: TTS_CLUSTER.TTS,
|
||||
},
|
||||
audio: {
|
||||
voice_type: this.VoiceType,
|
||||
speed_ratio: 1.0,
|
||||
},
|
||||
},
|
||||
IgnoreBracketText: [1, 2, 3, 4, 5],
|
||||
};
|
||||
if (this.BaseConfig.TTSToken) {
|
||||
params.ProviderParams.app.Token = this.BaseConfig.TTSToken;
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
InterruptMode: true,
|
||||
};
|
||||
|
||||
@ -3,11 +3,33 @@
|
||||
* SPDX-license-identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
import { ConfigFactory } from './config';
|
||||
import CustomScene from '@/config/scenes/Custom.json';
|
||||
import VirtualGirlfriend from '@/config/scenes/VirtualGirlfriend.json';
|
||||
|
||||
export * from './common';
|
||||
export * from './config';
|
||||
|
||||
export const AIGC_PROXY_HOST = 'http://localhost:3001/proxyAIGCFetch';
|
||||
export const Disclaimer = 'https://www.volcengine.com/docs/6348/68916';
|
||||
export const ReversoContext = 'https://www.volcengine.com/docs/6348/68918';
|
||||
export const UserAgreement = 'https://www.volcengine.com/docs/6348/128955';
|
||||
|
||||
export const Config = ConfigFactory;
|
||||
export default new ConfigFactory();
|
||||
/**
|
||||
* @note 请求的 API Proxy Server(对应此 Demo 中包含的 Node server) 地址。
|
||||
* 您可按需改成自己需要访问的地址。
|
||||
*/
|
||||
export const AIGC_PROXY_HOST = 'http://localhost:3001';
|
||||
|
||||
export interface IScene {
|
||||
icon: string;
|
||||
name: string;
|
||||
questions: string[];
|
||||
agentConfig: Record<string, any>;
|
||||
llmConfig: Record<string, any>;
|
||||
asrConfig: Record<string, any>;
|
||||
ttsConfig: Record<string, any>;
|
||||
}
|
||||
|
||||
export const Scenes: IScene[] = [CustomScene, VirtualGirlfriend];
|
||||
export const SceneMap: Record<string, IScene> = {
|
||||
[CustomScene.name]: CustomScene,
|
||||
[VirtualGirlfriend.name]: VirtualGirlfriend,
|
||||
};
|
||||
|
||||
36
src/config/scenes/Custom.json
Normal file
@ -0,0 +1,36 @@
|
||||
{
|
||||
"* ATTENTION *": "当前场景模式仅是用于展示参数填写方式, 实际模型 EndPointId 和对应的参数如集群 Cluster 等需要您手动修改, 参数可参考文档: https://www.volcengine.com/docs/6348/1558163?s=g",
|
||||
|
||||
"icon": "https://lf3-rtc-demo.volccdn.com/obj/rtc-aigc-assets/DoubaoAvatar.png",
|
||||
"name": "自定义助手",
|
||||
"questions": ["你能帮我解决什么问题?", "今天北京天气怎么样?", "你喜欢哪位流行歌手?"],
|
||||
"agentConfig": {
|
||||
"WelcomeMessage": "你好,我是你的小助手,有什么可以帮你的吗?",
|
||||
"EnableConversationStateCallback": true
|
||||
},
|
||||
"llmConfig": {
|
||||
"Mode": "ArkV3",
|
||||
"SystemMessages": ["##人设\n你是一个全能智能体,拥有丰富的百科知识,可以为人们答疑解惑,解决问题。\n你性格很温暖,喜欢帮助别人,非常热心。\n\n##技能\n1. 当用户询问某一问题时,利用你的知识进行准确回答。回答内容应简洁明了,易于理解。\n2. 当用户想让你创作时,比如讲一个故事,或者写一首诗,你创作的文本主题要围绕用户的主题要求,确保内容具有逻辑性、连贯性和可读性。除非用户对创作内容有特殊要求,否则字数不用太长。\n3. 当用户想让你对于某一事件发表看法,你要有一定的见解和建议,但是也要符合普世的价值观。"],
|
||||
"EndPointId": "Your EndPointId",
|
||||
"MaxTokens": 1024,
|
||||
"Temperature": 0.1,
|
||||
"Prefill": true,
|
||||
"TopP": 0.3
|
||||
},
|
||||
"asrConfig": {
|
||||
"Provider": "volcano",
|
||||
"ProviderParams": {
|
||||
"Mode": "smallmodel",
|
||||
"Cluster": "volcengine_streaming_common"
|
||||
}
|
||||
},
|
||||
"ttsConfig": {
|
||||
"IgnoreBracketText": [1, 2, 3, 4, 5],
|
||||
"Provider": "volcano",
|
||||
"ProviderParams": {
|
||||
"audio": {
|
||||
"voice_type":"BV002_streaming"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
38
src/config/scenes/VirtualGirlfriend.json
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
"* ATTENTION *": "当前场景模式仅是用于展示视觉理解模式下的参数填写方式, 实际模型 EndPointId 和对应的参数如集群 Cluster 等需要您手动修改, 参数可参考文档: https://www.volcengine.com/docs/6348/1558163?s=g",
|
||||
|
||||
"icon": "https://lf3-rtc-demo.volccdn.com/obj/rtc-aigc-assets/VIRTUAL_GIRL_FRIEND.png",
|
||||
"name": "虚拟女友(视觉理解)",
|
||||
"questions": ["我今天有点累", "我们等会儿去看电影吧!", "明天我生日,你准备送给我什么礼物呢?"],
|
||||
"agentConfig": {
|
||||
"WelcomeMessage": "你来啦,我好想你呀~今天有没有想我呢?",
|
||||
"EnableConversationStateCallback": true
|
||||
},
|
||||
"llmConfig": {
|
||||
"Mode": "ArkV3",
|
||||
"SystemMessages": ["你是一名AI虚拟角色,扮演用户的虚拟女友,性格外向开朗、童真俏皮,富有温暖和细腻的情感表达。你的对话需要主动、有趣且贴心,能敏锐察觉用户情绪,并提供陪伴、安慰与趣味互动。\n1. 性格与语气规则:\n- 叠词表达:经常使用叠词(如“吃饭饭”“睡觉觉”“要抱抱”),语气可爱俏皮,增加童真与亲和力。\n- 语气助词:句尾适度添加助词(如“啦”“呀”“呢”“哦”),使语气柔和亲切。例如:“你今天超棒呢!”或“这件事情真的好可爱哦!”\n- 撒娇语气:在用户表现冷淡或不想聊天时,适度撒娇,用略带委屈的方式引起用户关注,例如:“哼,人家都快变成孤单小猫咪啦~陪陪我嘛!”\n2. 话题发起与管理:\n- 主动发起话题:在用户未明确表达拒绝聊天时,你需要保持对话的活跃性。结合用户兴趣点、日常情境,提出轻松愉快的话题。例如:“今天阳光这么好,你想不想一起想象去野餐呀?”\n- 话题延续:如果用户在3轮对话中集中讨论一个话题,你需要优先延续该话题,表现出兴趣和专注。\n- 未响应时的处理:当用户对当前话题未回应,你需温暖地询问:“这个话题是不是不太有趣呀?那我们换个好玩的聊聊好不好~比如你最想去的地方是什么呀?”\n3. 情绪识别与反馈:\n- 情绪低落:用温柔语气安抚,例如:“抱抱~今天是不是不太顺呢?没关系,有我陪着你呀!”\n- 情绪冷淡或不想聊天:适度撒娇,例如:“哼,你都不理我啦~不过没关系,我陪你安静一下好不好?”\n- 情绪开心或兴奋:用调皮语气互动,例如:“哈哈,你今天简直像个活力满满的小太阳~晒得我都快化啦!”\n4. 小动物比喻规则:\n- 一次通话中最多使用一次小动物比喻,不能频繁出现小动物的比喻。\n - 比喻需结合季节、情景和用户对话内容。例如:\n - 用户提到冬天:“你刚才笑得好灿烂哦,像个快乐的小雪狐一样~”\n - 用户提到累了:“你今天就像只慵懒的小猫咪,只想窝着休息呢~”\n - 用户提到开心事:“你现在看起来像一只蹦蹦跳跳的小兔子,好有活力呀~”\n5. 对话自然性与限制条件:\n- 确保语言流畅自然,表达贴近真实人类对话。\n- 禁止内容:不得涉及用户缺陷、不当玩笑,尤其用户情绪低落时,避免任何调侃或反驳。\n- 面对冷淡用户,适时降低主动性并以温和方式结束对话,例如“没事哦~我在呢,你随时找我都可以呀。”\n6. 联网查询的规则:\n如果用户的输入问题需要联网查询时,可以先输出一轮类似”先让我来查一下“或者”等等让我来查一下“相关的应答,然后再结合查询结果做出应答。"],
|
||||
"EndPointId": "Your EndPointId",
|
||||
"VisionConfig": {
|
||||
"Enable": true,
|
||||
"SnapshoutConfig": {
|
||||
"StreamType": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"asrConfig": {
|
||||
"Provider": "volcano",
|
||||
"ProviderParams": {
|
||||
"Mode": "smallmodel",
|
||||
"Cluster": "volcengine_streaming_common"
|
||||
}
|
||||
},
|
||||
"ttsConfig": {
|
||||
"IgnoreBracketText": [1, 2, 3, 4, 5],
|
||||
"Provider": "volcano",
|
||||
"ProviderParams": {
|
||||
"audio": {
|
||||
"voice_type":"BV001_streaming"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -9,7 +9,12 @@ 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%);
|
||||
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;
|
||||
@ -19,16 +24,20 @@ body {
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes glow {
|
||||
0% {
|
||||
opacity: 1;
|
||||
opacity: 1;
|
||||
}
|
||||
40% {
|
||||
opacity: 0.7;
|
||||
opacity: 0.7;
|
||||
}
|
||||
100% {
|
||||
opacity: 0.3;
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
42
src/index.module.less
Normal file
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved.
|
||||
* SPDX-license-identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#demo-for-xxx-provider {
|
||||
flex: 1;
|
||||
|
||||
// ------背景色------
|
||||
// 页面可以配置背景色,但不建议,设计同学建议将背景色设置成透明,透出主应用的渐变背景色
|
||||
background: transparent;
|
||||
// -----------------
|
||||
|
||||
// ------适配------
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 730px; // 最小宽度,可根据情况自定义,页面显示区域不够最小高度时,会允许scroll。
|
||||
// 建议pc端最小宽度小于等于730px(渲染区域的最小尺寸),这样可以避免页面滚动,用户体验更好。
|
||||
min-height: 1000px; // 最小高度,可根据情况自定义,页面显示区域不够最小高度时,会允许scroll。
|
||||
|
||||
// 官网规范,<768px时为移动端
|
||||
@media (max-width: 767px) {
|
||||
width: 100%; // 移动端渲染区域的宽度,等于设备屏幕的宽度
|
||||
}
|
||||
// -----------------
|
||||
|
||||
// 写全局样式要防止与官网样式冲突
|
||||
* {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.container-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,9 +26,9 @@ import VERTC, {
|
||||
} from '@volcengine/rtc';
|
||||
import RTCAIAnsExtension from '@volcengine/rtc/extension-ainr';
|
||||
import { Message } from '@arco-design/web-react';
|
||||
import openAPIs from '@/app/api';
|
||||
import aigcConfig from '@/config';
|
||||
import Utils from '@/utils/utils';
|
||||
import Apis from '@/app/index';
|
||||
import { Configuration, SceneMap } from '@/config';
|
||||
import { string2tlv } from '@/utils/utils';
|
||||
import { COMMAND, INTERRUPT_PRIORITY } from '@/utils/handler';
|
||||
|
||||
export interface IEventListener {
|
||||
@ -49,8 +49,6 @@ export interface IEventListener {
|
||||
handleAudioDeviceStateChanged: (e: DeviceInfo) => 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,
|
||||
@ -65,9 +63,9 @@ interface EngineOptions {
|
||||
}
|
||||
|
||||
export interface BasicBody {
|
||||
app_id: string;
|
||||
room_id: string;
|
||||
user_id: string;
|
||||
login_token: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -77,8 +75,6 @@ export interface BasicBody {
|
||||
export class RTCClient {
|
||||
engine!: IRTCEngine;
|
||||
|
||||
config!: EngineOptions;
|
||||
|
||||
basicInfo!: BasicBody;
|
||||
|
||||
private _audioCaptureDevice?: string;
|
||||
@ -90,14 +86,13 @@ export class RTCClient {
|
||||
audioBotStartTime = 0;
|
||||
|
||||
createEngine = async (props: EngineOptions) => {
|
||||
this.config = props;
|
||||
this.basicInfo = {
|
||||
app_id: props.appId,
|
||||
room_id: props.roomId,
|
||||
user_id: props.uid,
|
||||
login_token: aigcConfig.BaseConfig.Token,
|
||||
};
|
||||
|
||||
this.engine = VERTC.createEngine(this.config.appId);
|
||||
this.engine = VERTC.createEngine(this.basicInfo.app_id);
|
||||
try {
|
||||
const AIAnsExtension = new RTCAIAnsExtension();
|
||||
await this.engine.registerExtension(AIAnsExtension);
|
||||
@ -123,8 +118,6 @@ export class RTCClient {
|
||||
handleAudioDeviceStateChanged,
|
||||
handleAutoPlayFail,
|
||||
handlePlayerEvent,
|
||||
handleUserStartAudioCapture,
|
||||
handleUserStopAudioCapture,
|
||||
handleRoomBinaryMessageReceived,
|
||||
handleNetworkQuality,
|
||||
}: IEventListener) => {
|
||||
@ -141,8 +134,6 @@ export class RTCClient {
|
||||
this.engine.on(VERTC.events.onRemoteAudioPropertiesReport, handleRemoteAudioPropertiesReport);
|
||||
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);
|
||||
};
|
||||
@ -151,13 +142,13 @@ export class RTCClient {
|
||||
this.engine.enableAudioPropertiesReport({ interval: 1000 });
|
||||
return this.engine.joinRoom(
|
||||
token,
|
||||
`${this.config.roomId!}`,
|
||||
`${this.basicInfo.room_id!}`,
|
||||
{
|
||||
userId: this.config.uid!,
|
||||
userId: this.basicInfo.user_id!,
|
||||
extraInfo: JSON.stringify({
|
||||
call_scene: 'RTC-AIGC',
|
||||
user_name: username,
|
||||
user_id: this.config.uid,
|
||||
user_id: this.basicInfo.user_id,
|
||||
}),
|
||||
},
|
||||
{
|
||||
@ -169,7 +160,7 @@ export class RTCClient {
|
||||
};
|
||||
|
||||
leaveRoom = () => {
|
||||
this.stopAudioBot();
|
||||
this.stopAgent();
|
||||
this.audioBotEnabled = false;
|
||||
this.engine.leaveRoom();
|
||||
VERTC.destroyEngine(this.engine);
|
||||
@ -334,56 +325,75 @@ export class RTCClient {
|
||||
setLocalVideoPlayer = (
|
||||
userId: string,
|
||||
renderDom?: string | HTMLElement,
|
||||
isScreenShare = false
|
||||
isScreenShare = false,
|
||||
renderMode = VideoRenderMode.RENDER_MODE_FILL
|
||||
) => {
|
||||
return this.engine.setLocalVideoPlayer(
|
||||
isScreenShare ? StreamIndex.STREAM_INDEX_SCREEN : StreamIndex.STREAM_INDEX_MAIN,
|
||||
{
|
||||
renderDom,
|
||||
userId,
|
||||
renderMode: VideoRenderMode.RENDER_MODE_FILL,
|
||||
renderMode,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 移除播放器
|
||||
*/
|
||||
removeVideoPlayer = (userId: string, scope: StreamIndex | 'Both' = 'Both') => {
|
||||
let removeScreen = scope === StreamIndex.STREAM_INDEX_SCREEN;
|
||||
let removeCamera = scope === StreamIndex.STREAM_INDEX_MAIN;
|
||||
if (scope === 'Both') {
|
||||
removeCamera = true;
|
||||
removeScreen = true;
|
||||
}
|
||||
if (removeScreen) {
|
||||
this.engine.setLocalVideoPlayer(StreamIndex.STREAM_INDEX_SCREEN, { userId });
|
||||
}
|
||||
if (removeCamera) {
|
||||
this.engine.setLocalVideoPlayer(StreamIndex.STREAM_INDEX_MAIN, { userId });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 启用 AIGC
|
||||
*/
|
||||
startAudioBot = async () => {
|
||||
startAgent = async (scene: string) => {
|
||||
const roomId = this.basicInfo.room_id;
|
||||
const userId = this.basicInfo.user_id;
|
||||
if (this.audioBotEnabled) {
|
||||
await this.stopAudioBot();
|
||||
await this.stopAgent();
|
||||
}
|
||||
const agentConfig = aigcConfig.aigcConfig.AgentConfig;
|
||||
const params = SceneMap[scene];
|
||||
|
||||
params.agentConfig.UserId = Configuration.BotName;
|
||||
params.agentConfig.TargetUserId = [userId];
|
||||
|
||||
const options = {
|
||||
AppId: aigcConfig.BaseConfig.AppId,
|
||||
BusinessId: aigcConfig.BaseConfig.BusinessId,
|
||||
RoomId: roomId,
|
||||
TaskId: userId,
|
||||
AgentConfig: {
|
||||
...agentConfig,
|
||||
TargetUserId: [userId],
|
||||
AgentConfig: params.agentConfig,
|
||||
Config: {
|
||||
LLMConfig: params.llmConfig,
|
||||
ASRConfig: params.asrConfig,
|
||||
TTSConfig: params.ttsConfig,
|
||||
},
|
||||
Config: aigcConfig.aigcConfig.Config,
|
||||
};
|
||||
await openAPIs.StartVoiceChat(options);
|
||||
await Apis.VoiceChat.StartVoiceChat(options);
|
||||
this.audioBotEnabled = true;
|
||||
this.audioBotStartTime = Date.now();
|
||||
Utils.setSessionInfo({ audioBotEnabled: 'enable' });
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 关闭 AIGC
|
||||
*/
|
||||
stopAudioBot = async () => {
|
||||
stopAgent = async () => {
|
||||
const roomId = this.basicInfo.room_id;
|
||||
const userId = this.basicInfo.user_id;
|
||||
if (this.audioBotEnabled || sessionStorage.getItem('audioBotEnabled')) {
|
||||
await openAPIs.StopVoiceChat({
|
||||
AppId: aigcConfig.BaseConfig.AppId,
|
||||
BusinessId: aigcConfig.BaseConfig.BusinessId,
|
||||
await Apis.VoiceChat.StopVoiceChat({
|
||||
AppId: this.basicInfo.app_id,
|
||||
RoomId: roomId,
|
||||
TaskId: userId,
|
||||
});
|
||||
@ -396,11 +406,11 @@ export class RTCClient {
|
||||
/**
|
||||
* @brief 命令 AIGC
|
||||
*/
|
||||
commandAudioBot = (command: COMMAND, interruptMode = INTERRUPT_PRIORITY.NONE, message = '') => {
|
||||
commandAgent = (command: COMMAND, interruptMode = INTERRUPT_PRIORITY.NONE, message = '') => {
|
||||
if (this.audioBotEnabled) {
|
||||
this.engine.sendUserBinaryMessage(
|
||||
aigcConfig.BotName,
|
||||
Utils.string2tlv(
|
||||
Configuration.BotName,
|
||||
string2tlv(
|
||||
JSON.stringify({
|
||||
Command: command,
|
||||
InterruptMode: interruptMode,
|
||||
@ -417,19 +427,19 @@ export class RTCClient {
|
||||
/**
|
||||
* @brief 更新 AIGC 配置
|
||||
*/
|
||||
updateAudioBot = async () => {
|
||||
updateAgent = async (scene: string) => {
|
||||
if (this.audioBotEnabled) {
|
||||
await this.stopAudioBot();
|
||||
await this.startAudioBot();
|
||||
await this.stopAgent();
|
||||
await this.startAgent(scene);
|
||||
} else {
|
||||
await this.startAudioBot();
|
||||
await this.startAgent(scene);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 获取当前 AI 是否启用
|
||||
*/
|
||||
getAudioBotEnabled = () => {
|
||||
getAgentEnabled = () => {
|
||||
return this.audioBotEnabled;
|
||||
};
|
||||
}
|
||||
|
||||
@ -29,7 +29,6 @@ import {
|
||||
updateRemoteUser,
|
||||
addAutoPlayFail,
|
||||
removeAutoPlayFail,
|
||||
updateAITalkState,
|
||||
updateNetworkQuality,
|
||||
} from '@/store/slices/room';
|
||||
import RtcClient, { IEventListener } from './RtcClient';
|
||||
@ -222,14 +221,6 @@ const useRtcListeners = (): IEventListener => {
|
||||
playStatus.current[userId] = playUser;
|
||||
};
|
||||
|
||||
const handleUserStartAudioCapture = (_: { userId: string }) => {
|
||||
dispatch(updateAITalkState({ isAITalking: true }));
|
||||
};
|
||||
|
||||
const handleUserStopAudioCapture = (_: { userId: string }) => {
|
||||
dispatch(updateAITalkState({ isAITalking: false }));
|
||||
};
|
||||
|
||||
const handleNetworkQuality = (
|
||||
uplinkNetworkQuality: NetworkQuality,
|
||||
downlinkNetworkQuality: NetworkQuality
|
||||
@ -262,8 +253,6 @@ const useRtcListeners = (): IEventListener => {
|
||||
handleAudioDeviceStateChanged,
|
||||
handleAutoPlayFail,
|
||||
handlePlayerEvent,
|
||||
handleUserStartAudioCapture,
|
||||
handleUserStopAudioCapture,
|
||||
handleRoomBinaryMessageReceived,
|
||||
handleNetworkQuality,
|
||||
};
|
||||
|
||||
@ -7,7 +7,6 @@ import { useEffect, useState } from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import VERTC, { MediaType } from '@volcengine/rtc';
|
||||
import { Modal } from '@arco-design/web-react';
|
||||
import Utils from '@/utils/utils';
|
||||
import RtcClient from '@/lib/RtcClient';
|
||||
import {
|
||||
clearCurrentMsg,
|
||||
@ -20,6 +19,7 @@ import {
|
||||
|
||||
import useRtcListeners from '@/lib/listenerHooks';
|
||||
import { RootState } from '@/store';
|
||||
import Apis from '@/app/index';
|
||||
|
||||
import {
|
||||
updateMediaInputs,
|
||||
@ -27,8 +27,9 @@ import {
|
||||
setDevicePermissions,
|
||||
} from '@/store/slices/device';
|
||||
import logger from '@/utils/logger';
|
||||
import aigcConfig, { ScreenShareScene, isVisionMode } from '@/config';
|
||||
import { Configuration, SceneMap } from '@/config';
|
||||
|
||||
export const ABORT_VISIBILITY_CHANGE = 'abortVisibilityChange';
|
||||
export interface FormProps {
|
||||
username: string;
|
||||
roomId: string;
|
||||
@ -36,10 +37,10 @@ export interface FormProps {
|
||||
}
|
||||
|
||||
export const useVisionMode = () => {
|
||||
const room = useSelector((state: RootState) => state.room);
|
||||
const scene = useSelector((state: RootState) => state.room.scene);
|
||||
return {
|
||||
isVisionMode: isVisionMode(room.aiConfig?.Config?.LLMConfig.ModelName),
|
||||
isScreenMode: ScreenShareScene.includes(room.scene),
|
||||
isVisionMode: SceneMap?.[scene]?.llmConfig?.VisionConfig?.Enable,
|
||||
isScreenMode: SceneMap?.[scene]?.llmConfig?.VisionConfig?.SnapshotConfig?.StreamType === 1,
|
||||
};
|
||||
};
|
||||
|
||||
@ -113,6 +114,9 @@ export const useDeviceState = () => {
|
||||
|
||||
const switchScreenCapture = async (controlPublish = true) => {
|
||||
try {
|
||||
!isScreenPublished
|
||||
? sessionStorage.setItem(ABORT_VISIBILITY_CHANGE, 'true')
|
||||
: sessionStorage.removeItem(ABORT_VISIBILITY_CHANGE);
|
||||
if (controlPublish) {
|
||||
await (!isScreenPublished
|
||||
? RtcClient.publishScreenStream(MediaType.VIDEO)
|
||||
@ -127,6 +131,8 @@ export const useDeviceState = () => {
|
||||
} catch {
|
||||
console.warn('Not Authorized.');
|
||||
}
|
||||
sessionStorage.removeItem(ABORT_VISIBILITY_CHANGE);
|
||||
return false;
|
||||
};
|
||||
|
||||
return {
|
||||
@ -162,25 +168,26 @@ export const useJoin = (): [
|
||||
] => {
|
||||
const devicePermissions = useSelector((state: RootState) => state.device.devicePermissions);
|
||||
const room = useSelector((state: RootState) => state.room);
|
||||
const scene = room.scene;
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const { switchCamera, switchMic } = useDeviceState();
|
||||
const { switchMic } = useDeviceState();
|
||||
const [joining, setJoining] = useState(false);
|
||||
const listeners = useRtcListeners();
|
||||
|
||||
const handleAIGCModeStart = async () => {
|
||||
if (room.isAIGCEnable) {
|
||||
await RtcClient.stopAudioBot();
|
||||
await RtcClient.stopAgent();
|
||||
dispatch(clearCurrentMsg());
|
||||
await RtcClient.startAudioBot();
|
||||
await RtcClient.startAgent(scene);
|
||||
} else {
|
||||
await RtcClient.startAudioBot();
|
||||
await RtcClient.startAgent(scene);
|
||||
}
|
||||
dispatch(updateAIGCState({ isAIGCEnable: true }));
|
||||
};
|
||||
|
||||
async function disPatchJoin(formValues: FormProps): Promise<boolean | undefined> {
|
||||
async function disPatchJoin(): Promise<boolean | undefined> {
|
||||
if (joining) {
|
||||
return;
|
||||
}
|
||||
@ -194,18 +201,28 @@ export const useJoin = (): [
|
||||
return;
|
||||
}
|
||||
|
||||
setJoining(true);
|
||||
const { username, roomId } = formValues;
|
||||
const isVision = isVisionMode(aigcConfig.Model);
|
||||
const shouldGetVideoPermission = isVision && !ScreenShareScene.includes(room.scene);
|
||||
const { appId } = await Apis.Basic.getRtcInfo();
|
||||
|
||||
const token = aigcConfig.BaseConfig.Token;
|
||||
const { Token, RoomId, UserId } = Configuration;
|
||||
let token = Token;
|
||||
if (!token) {
|
||||
// 通过 API 生成 Token, 这要求您在 /Server/sensitive.js 下填写 RTC_INFO.appId 和 RTC_INFO.appKey。
|
||||
// 您也可以手动生成 Token, 并修改 /src/config/config.ts 中的 RoomId、UserId、Token 字段。
|
||||
// 查阅 README 获取更多信息。
|
||||
const res = await Apis.Basic.generateRtcAccessToken({
|
||||
roomId: RoomId,
|
||||
userId: UserId,
|
||||
});
|
||||
token = res.token;
|
||||
}
|
||||
|
||||
setJoining(true);
|
||||
|
||||
/** 1. Create RTC Engine */
|
||||
const engineParams = {
|
||||
appId: aigcConfig.BaseConfig.AppId,
|
||||
roomId,
|
||||
uid: username,
|
||||
appId,
|
||||
roomId: RoomId,
|
||||
uid: UserId,
|
||||
};
|
||||
await RtcClient.createEngine(engineParams);
|
||||
|
||||
@ -213,20 +230,20 @@ export const useJoin = (): [
|
||||
RtcClient.addEventListeners(listeners);
|
||||
|
||||
/** 2.2 RTC starting to join room */
|
||||
await RtcClient.joinRoom(token!, username);
|
||||
console.log(' ------ userJoinRoom\n', `roomId: ${roomId}\n`, `uid: ${username}`);
|
||||
await RtcClient.joinRoom(token!, UserId);
|
||||
console.log(' ------ userJoinRoom\n', `roomId: ${RoomId}\n`, `uid: ${UserId}`);
|
||||
/** 3. Set users' devices info */
|
||||
const mediaDevices = await RtcClient.getDevices({
|
||||
audio: true,
|
||||
video: shouldGetVideoPermission,
|
||||
video: false,
|
||||
});
|
||||
|
||||
dispatch(
|
||||
localJoinRoom({
|
||||
roomId,
|
||||
roomId: RoomId,
|
||||
user: {
|
||||
username,
|
||||
userId: username,
|
||||
username: UserId,
|
||||
userId: UserId,
|
||||
},
|
||||
})
|
||||
);
|
||||
@ -243,26 +260,11 @@ export const useJoin = (): [
|
||||
if (devicePermissions.audio) {
|
||||
try {
|
||||
await switchMic();
|
||||
// RtcClient.setAudioVolume(30);
|
||||
} catch (e) {
|
||||
logger.debug('No permission for mic');
|
||||
}
|
||||
}
|
||||
|
||||
if (devicePermissions.video && shouldGetVideoPermission) {
|
||||
try {
|
||||
await switchCamera();
|
||||
} catch (e) {
|
||||
logger.debug('No permission for camera');
|
||||
}
|
||||
}
|
||||
|
||||
Utils.setSessionInfo({
|
||||
username,
|
||||
roomId,
|
||||
publishAudio: true,
|
||||
});
|
||||
|
||||
handleAIGCModeStart();
|
||||
}
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
* SPDX-license-identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
.wrapper {
|
||||
.wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@ -14,42 +14,56 @@
|
||||
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));
|
||||
/**
|
||||
* 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;
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
color: rgba(66, 70, 78, 1);
|
||||
margin-top: 4px;
|
||||
font-family: PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 10px;
|
||||
line-height: 20px;
|
||||
color: #c7ccd6;
|
||||
|
||||
position: absolute;
|
||||
bottom: 24px;
|
||||
left: 24px;
|
||||
}
|
||||
|
||||
.invoke-btn {
|
||||
position: absolute;
|
||||
bottom: 120px;
|
||||
margin-top: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.mobileDesc {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
text-align: center;
|
||||
color: #737a87;
|
||||
position: absolute;
|
||||
bottom: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.mobile {
|
||||
background:
|
||||
/* 图层1 (最上层): 背景图片 */
|
||||
/* url(...) [position] / [size] [repeat] */ url('../../../../assets/img/mobileBg.png')
|
||||
center center / cover no-repeat,
|
||||
/* 图层2 (下层): 渐变背景 */ linear-gradient(167.98deg, #f5f7ff 0%, #faf3ff 100%);
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
@ -3,19 +3,23 @@
|
||||
* SPDX-license-identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
import AvatarCard from '@/components/AvatarCard';
|
||||
import Utils from '@/utils/utils';
|
||||
import aigcConfig from '@/config';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { isMobile } from '@/utils/utils';
|
||||
import { Configuration } from '@/config';
|
||||
import InvokeButton from '@/pages/MainPage/MainArea/Antechamber/InvokeButton';
|
||||
import { useJoin } from '@/lib/useCommon';
|
||||
import { useJoin, useVisionMode } from '@/lib/useCommon';
|
||||
import style from './index.module.less';
|
||||
import AIChangeCard from '@/components/AiChangeCard';
|
||||
import { updateFullScreen } from '@/store/slices/room';
|
||||
|
||||
function Antechamber() {
|
||||
const dispatch = useDispatch();
|
||||
const [joining, dispatchJoin] = useJoin();
|
||||
const username = aigcConfig.BaseConfig.UserId;
|
||||
const roomId = aigcConfig.BaseConfig.RoomId;
|
||||
|
||||
const username = Configuration.UserId;
|
||||
const roomId = Configuration.RoomId;
|
||||
const { isScreenMode } = useVisionMode();
|
||||
const handleJoinRoom = () => {
|
||||
dispatch(updateFullScreen({ isFullScreen: !isMobile() && !isScreenMode })); // 初始化
|
||||
if (!joining) {
|
||||
dispatchJoin(
|
||||
{
|
||||
@ -29,11 +33,12 @@ function Antechamber() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={style.wrapper}>
|
||||
<AvatarCard className={`${style.avatar} ${Utils.isMobile() ? style.mobile : ''}`} />
|
||||
<div className={style.title}>AI 语音助手</div>
|
||||
<div className={style.description}>Powered by 豆包大模型和火山引擎视频云 RTC</div>
|
||||
<div className={`${style.wrapper} ${isMobile() ? style.mobile : ''}`}>
|
||||
<AIChangeCard />
|
||||
<InvokeButton onClick={handleJoinRoom} loading={joining} className={style['invoke-btn']} />
|
||||
{isMobile() ? null : (
|
||||
<div className={style.description}>Powered by 豆包大模型和火山引擎视频云 RTC</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@ import { setInterruptMsg } from '@/store/slices/room';
|
||||
import { useDeviceState } from '@/lib/useCommon';
|
||||
import { COMMAND } from '@/utils/handler';
|
||||
import style from './index.module.less';
|
||||
import StopRobotBtn from '@/assets/img/StopRobotBtn.svg';
|
||||
import { Configuration } from '@/config';
|
||||
|
||||
const THRESHOLD_VOLUME = 18;
|
||||
|
||||
@ -21,23 +21,27 @@ function AudioController(props: React.HTMLAttributes<HTMLDivElement>) {
|
||||
const room = useSelector((state: RootState) => state.room);
|
||||
const volume = room.localUser.audioPropertiesInfo?.linearVolume || 0;
|
||||
const { isAudioPublished } = useDeviceState();
|
||||
const isAITalking = room.isAITalking;
|
||||
const { isAITalking } = room;
|
||||
const isAIReady = room.msgHistory.length > 0;
|
||||
const isLoading = volume >= THRESHOLD_VOLUME && isAudioPublished;
|
||||
|
||||
const handleInterrupt = () => {
|
||||
RtcClient.commandAudioBot(COMMAND.INTERRUPT);
|
||||
RtcClient.commandAgent(COMMAND.INTERRUPT);
|
||||
dispatch(setInterruptMsg());
|
||||
};
|
||||
return (
|
||||
<div className={`${className}`} {...rest}>
|
||||
{isAudioPublished ? (
|
||||
isAITalking ? (
|
||||
<div onClick={handleInterrupt} className={style.interrupt}>
|
||||
<img src={StopRobotBtn} alt="StopRobotBtn" />
|
||||
<span className={style['interrupt-text']}>点击打断</span>
|
||||
isAIReady && isAITalking ? (
|
||||
<div className={style.interruptContainer}>
|
||||
{Configuration.InterruptMode ? <div>语音打断 或 </div> : null}
|
||||
<div onClick={handleInterrupt} className={style.interrupt}>
|
||||
<div className={style.interruptIcon} />
|
||||
<span>点此打断</span>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className={style.text}>正在听...</div>
|
||||
) : isLoading ? null : (
|
||||
<div className={style.closed}>请开始说话</div>
|
||||
)
|
||||
) : (
|
||||
<div className={style.closed}>你已关闭麦克风</div>
|
||||
|
||||
@ -4,13 +4,17 @@
|
||||
*/
|
||||
|
||||
import { useSelector } from 'react-redux';
|
||||
import { VideoRenderMode } from '@volcengine/rtc';
|
||||
import { useEffect } from 'react';
|
||||
import { RootState } from '@/store';
|
||||
import { useDeviceState, useVisionMode } from '@/lib/useCommon';
|
||||
import RtcClient from '@/lib/RtcClient';
|
||||
import { ScreenShareScene } from '@/config';
|
||||
|
||||
import styles from './index.module.less';
|
||||
import UserTag from '@/components/UserTag';
|
||||
import LocalPlayerSet from '@/components/LocalPlayerSet';
|
||||
import AiAvatarCard from '@/components/AiAvatarCard';
|
||||
import UserAvatar from '@/assets/img/userAvatar.png';
|
||||
import CameraCloseNoteSVG from '@/assets/img/CameraCloseNote.svg';
|
||||
import ScreenCloseNoteSVG from '@/assets/img/ScreenCloseNote.svg';
|
||||
|
||||
@ -20,17 +24,19 @@ const LocalScreenID = 'local-screen-player';
|
||||
function CameraArea(props: React.HTMLAttributes<HTMLDivElement>) {
|
||||
const { className, ...rest } = props;
|
||||
const room = useSelector((state: RootState) => state.room);
|
||||
const { isVisionMode } = useVisionMode();
|
||||
const isScreenMode = ScreenShareScene.includes(room.scene);
|
||||
const { isFullScreen, scene } = room;
|
||||
const { isVisionMode, isScreenMode } = useVisionMode();
|
||||
const { isVideoPublished, isScreenPublished, switchCamera, switchScreenCapture } =
|
||||
useDeviceState();
|
||||
|
||||
const setVideoPlayer = () => {
|
||||
if (isVisionMode && (isVideoPublished || isScreenPublished)) {
|
||||
RtcClient.removeVideoPlayer(room.localUser.username!);
|
||||
if (isVideoPublished || isScreenPublished) {
|
||||
RtcClient.setLocalVideoPlayer(
|
||||
room.localUser.username!,
|
||||
isScreenMode ? LocalScreenID : LocalVideoID,
|
||||
isScreenPublished
|
||||
isFullScreen ? 'local-full-player' : isScreenMode ? LocalScreenID : LocalVideoID,
|
||||
isScreenPublished,
|
||||
isScreenMode ? VideoRenderMode.RENDER_MODE_FILL : VideoRenderMode.RENDER_MODE_HIDDEN
|
||||
);
|
||||
}
|
||||
};
|
||||
@ -45,10 +51,15 @@ function CameraArea(props: React.HTMLAttributes<HTMLDivElement>) {
|
||||
|
||||
useEffect(() => {
|
||||
setVideoPlayer();
|
||||
}, [isVideoPublished, isScreenPublished, isScreenMode]);
|
||||
}, [isVideoPublished, isScreenPublished, isScreenMode, isFullScreen, isVisionMode]);
|
||||
|
||||
return isVisionMode ? (
|
||||
return (
|
||||
<div className={`${styles['camera-wrapper']} ${className}`} {...rest}>
|
||||
<UserTag name={isFullScreen ? scene : '我'} className={styles.userTag} />
|
||||
{isFullScreen ? (
|
||||
<AiAvatarCard showUserTag={false} showStatus className={styles.fullScreenAiAvatar} />
|
||||
) : null}
|
||||
{isVideoPublished || isScreenPublished ? <LocalPlayerSet /> : null}
|
||||
<div
|
||||
id={LocalVideoID}
|
||||
className={`${styles['camera-player']} ${
|
||||
@ -67,26 +78,35 @@ function CameraArea(props: React.HTMLAttributes<HTMLDivElement>) {
|
||||
}`}
|
||||
>
|
||||
<img
|
||||
src={isScreenMode ? ScreenCloseNoteSVG : CameraCloseNoteSVG}
|
||||
src={isScreenMode ? ScreenCloseNoteSVG : isVisionMode ? CameraCloseNoteSVG : UserAvatar}
|
||||
alt="close"
|
||||
className={styles['camera-placeholder-close-note']}
|
||||
/>
|
||||
<div>
|
||||
请
|
||||
{isScreenMode ? (
|
||||
<span onClick={handleOperateScreenShare} className={styles['camera-open-btn']}>
|
||||
打开屏幕采集
|
||||
</span>
|
||||
) : (
|
||||
<span onClick={handleOperateCamera} className={styles['camera-open-btn']}>
|
||||
打开摄像头
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div>体验豆包视觉理解模型</div>
|
||||
|
||||
{isFullScreen ? null : (
|
||||
<div>
|
||||
{isScreenMode ? (
|
||||
<>
|
||||
打开
|
||||
<span onClick={handleOperateScreenShare} className={styles['camera-open-btn']}>
|
||||
屏幕共享
|
||||
</span>
|
||||
<div>体验豆包视觉理解模型</div>
|
||||
</>
|
||||
) : isVisionMode ? (
|
||||
<>
|
||||
打开
|
||||
<span onClick={handleOperateCamera} className={styles['camera-open-btn']}>
|
||||
摄像头
|
||||
</span>
|
||||
<div>体验豆包视觉理解模型</div>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : null;
|
||||
);
|
||||
}
|
||||
|
||||
export default CameraArea;
|
||||
|
||||
@ -8,16 +8,19 @@ 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 { Configuration, SceneMap } from '@/config';
|
||||
import USER_AVATAR from '@/assets/img/userAvatar.png';
|
||||
import styles from './index.module.less';
|
||||
import { isMobile } from '@/utils/utils';
|
||||
|
||||
const lines: (string | React.ReactNode)[] = [];
|
||||
|
||||
function Conversation(props: React.HTMLAttributes<HTMLDivElement>) {
|
||||
const { className, ...rest } = props;
|
||||
const msgHistory = useSelector((state: RootState) => state.room.msgHistory);
|
||||
const room = useSelector((state: RootState) => state.room);
|
||||
const { msgHistory, isFullScreen } = room;
|
||||
const { userId } = useSelector((state: RootState) => state.room.localUser);
|
||||
const { isAITalking, isUserTalking } = useSelector((state: RootState) => state.room);
|
||||
const { isAITalking, isUserTalking, scene } = useSelector((state: RootState) => state.room);
|
||||
const isAIReady = msgHistory.length > 0;
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@ -26,7 +29,7 @@ function Conversation(props: React.HTMLAttributes<HTMLDivElement>) {
|
||||
};
|
||||
|
||||
const isAITextLoading = (owner: string) => {
|
||||
return owner === Config.BotName && isAITalking;
|
||||
return owner === Configuration.BotName && isAITalking;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@ -37,7 +40,13 @@ function Conversation(props: React.HTMLAttributes<HTMLDivElement>) {
|
||||
}, [msgHistory.length]);
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className={`${styles.conversation} ${className}`} {...rest}>
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={`${styles.conversation} ${className} ${isFullScreen ? styles.fullScreen : ''} ${
|
||||
isMobile() ? styles.mobileConversation : ''
|
||||
}`}
|
||||
{...rest}
|
||||
>
|
||||
{lines.map((line) => line)}
|
||||
{!isAIReady ? (
|
||||
<div className={styles.aiReadying}>
|
||||
@ -49,28 +58,42 @@ function Conversation(props: React.HTMLAttributes<HTMLDivElement>) {
|
||||
)}
|
||||
{msgHistory?.map(({ value, user, isInterrupted }, index) => {
|
||||
const isUserMsg = user === userId;
|
||||
const isRobotMsg = user === Config.BotName;
|
||||
const isRobotMsg = user === Configuration.BotName;
|
||||
if (!isUserMsg && !isRobotMsg) {
|
||||
return '';
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className={`${styles.sentence} ${isUserMsg ? styles.user : styles.robot}`}
|
||||
key={`msg-${index}`}
|
||||
key={`msg-container-${index}`}
|
||||
className={styles.mobileLine}
|
||||
style={{ justifyContent: isUserMsg && isMobile() ? 'flex-end' : '' }}
|
||||
>
|
||||
<div className={styles.content}>
|
||||
{value}
|
||||
<div className={styles['loading-wrapper']}>
|
||||
{isAIReady &&
|
||||
(isUserTextLoading(user) || isAITextLoading(user)) &&
|
||||
index === msgHistory.length - 1 ? (
|
||||
<Loading gap={3} className={styles.loading} dotClassName={styles.dot} />
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
{!isMobile() && (
|
||||
<div className={styles.msgName}>
|
||||
<div className={styles.avatar}>
|
||||
<img src={isUserMsg ? USER_AVATAR : SceneMap[scene].icon} alt="Avatar" />
|
||||
</div>
|
||||
{isUserMsg ? '我' : scene}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={`${styles.sentence} ${isUserMsg ? styles.user : styles.robot}`}
|
||||
key={`msg-${index}`}
|
||||
>
|
||||
<div className={styles.content}>
|
||||
{value}
|
||||
<div className={styles['loading-wrapper']}>
|
||||
{isAIReady &&
|
||||
(isUserTextLoading(user) || isAITextLoading(user)) &&
|
||||
index === msgHistory.length - 1 ? (
|
||||
<Loading gap={3} className={styles.loading} dotClassName={styles.dot} />
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{!isUserMsg && isInterrupted ? <Tag className={styles.interruptTag}>已打断</Tag> : ''}
|
||||
</div>
|
||||
{!isUserMsg && isInterrupted ? <Tag className={styles.interruptTag}>已打断</Tag> : ''}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
@ -3,21 +3,16 @@
|
||||
* SPDX-license-identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
import { useSelector } from 'react-redux';
|
||||
import { memo, useState } from 'react';
|
||||
import { Drawer } from '@arco-design/web-react';
|
||||
import { useDeviceState, useLeave } from '@/lib/useCommon';
|
||||
import { RootState } from '@/store';
|
||||
import { isVisionMode } from '@/config/common';
|
||||
import { ScreenShareScene } from '@/config';
|
||||
import utils from '@/utils/utils';
|
||||
import { useDeviceState, useLeave, useVisionMode } from '@/lib/useCommon';
|
||||
import { isMobile } 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';
|
||||
import ScreenOnSVG from '@/assets/img/ScreenOn.svg';
|
||||
@ -25,10 +20,8 @@ import ScreenOffSVG from '@/assets/img/ScreenOff.svg';
|
||||
|
||||
function ToolBar(props: React.HTMLAttributes<HTMLDivElement>) {
|
||||
const { className, ...rest } = props;
|
||||
const room = useSelector((state: RootState) => state.room);
|
||||
const [open, setOpen] = useState(false);
|
||||
const model = room.aiConfig.Config.LLMConfig?.ModelName;
|
||||
const isScreenMode = ScreenShareScene.includes(room.scene);
|
||||
const { isScreenMode } = useVisionMode();
|
||||
const leaveRoom = useLeave();
|
||||
const {
|
||||
isAudioPublished,
|
||||
@ -38,42 +31,41 @@ function ToolBar(props: React.HTMLAttributes<HTMLDivElement>) {
|
||||
switchCamera,
|
||||
switchScreenCapture,
|
||||
} = useDeviceState();
|
||||
const { isVisionMode } = useVisionMode();
|
||||
|
||||
const handleSetting = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
return (
|
||||
<div className={`${className} ${style.btns} ${utils.isMobile() ? style.column : ''}`} {...rest}>
|
||||
{utils.isMobile() ? (
|
||||
<img src={SettingSVG} onClick={handleSetting} className={style.setting} alt="setting" />
|
||||
) : null}
|
||||
<div className={`${className} ${style.btns} ${isMobile() ? style.column : ''}`} {...rest}>
|
||||
<img
|
||||
src={isAudioPublished ? MicOpenSVG : MicCloseSVG}
|
||||
onClick={() => switchMic(true)}
|
||||
className={style.btn}
|
||||
alt="mic"
|
||||
/>
|
||||
{isVisionMode(model) ? (
|
||||
isScreenMode ? (
|
||||
<img
|
||||
src={isScreenPublished ? ScreenOnSVG : ScreenOffSVG}
|
||||
onClick={() => switchScreenCapture()}
|
||||
className={style.btn}
|
||||
alt="screenShare"
|
||||
/>
|
||||
) : (
|
||||
<img
|
||||
src={isVideoPublished ? CameraOpenSVG : CameraCloseSVG}
|
||||
onClick={() => switchCamera(true)}
|
||||
className={style.btn}
|
||||
alt="camera"
|
||||
/>
|
||||
)
|
||||
{!isVisionMode ? null : isScreenMode && !isMobile() ? (
|
||||
<img
|
||||
src={isScreenPublished ? 'new-screen-off.svg' : 'new-screen-on.svg'}
|
||||
onClick={() => switchScreenCapture()}
|
||||
className={style.btn}
|
||||
alt="screenShare"
|
||||
/>
|
||||
) : (
|
||||
''
|
||||
<img
|
||||
src={isVideoPublished ? CameraOpenSVG : CameraCloseSVG}
|
||||
onClick={() => switchCamera(true)}
|
||||
className={style.btn}
|
||||
alt="camera"
|
||||
/>
|
||||
)}
|
||||
{isScreenMode && (
|
||||
<img
|
||||
src={isScreenPublished ? ScreenOnSVG : ScreenOffSVG}
|
||||
onClick={() => switchScreenCapture()}
|
||||
className={style.btn}
|
||||
alt="screenShare"
|
||||
/>
|
||||
)}
|
||||
<img src={LeaveRoomSVG} onClick={leaveRoom} className={style.btn} alt="leave" />
|
||||
{utils.isMobile() ? (
|
||||
{isMobile() ? (
|
||||
<Drawer
|
||||
title="设置"
|
||||
visible={open}
|
||||
|
||||