From 540ecf261e6b57775d2591e15aac5d8060c835ba Mon Sep 17 00:00:00 2001 From: "quemingyi.wudong" Date: Thu, 16 Jan 2025 14:10:55 +0800 Subject: [PATCH] chore: update comment & readme --- README.md | 26 ++++---- package.json | 2 +- src/app/api.ts | 9 ++- src/app/base.ts | 10 ++- src/app/type.ts | 6 +- src/components/AISettings/index.tsx | 29 +++++++-- src/components/AvatarCard/index.tsx | 11 +++- src/components/BubbleMsg/index.tsx | 6 +- src/components/ButtonRadio/index.tsx | 10 ++- src/components/CheckBox/index.tsx | 16 ++++- src/components/CheckBoxSelector/index.tsx | 18 +++++- src/components/DrawerRowItem/index.tsx | 10 ++- src/components/Header/index.tsx | 14 +++- src/components/NetworkIndicator/index.tsx | 11 +++- src/config/common.ts | 48 ++++++++++---- src/config/config.ts | 64 +++++++++++++++++-- src/lib/RtcClient.ts | 12 +++- src/lib/listenerHooks.ts | 19 ++++-- src/lib/useCommon.ts | 28 ++++++-- .../Antechamber/InvokeButton/index.tsx | 6 +- .../MainPage/MainArea/Room/CameraArea.tsx | 10 ++- .../MainPage/MainArea/Room/Conversation.tsx | 11 +++- src/pages/MainPage/MainArea/Room/ToolBar.tsx | 22 ++++++- .../components/DeviceDrawerButton/index.tsx | 15 ++++- src/store/slices/device.ts | 3 +- src/store/slices/room.ts | 7 +- src/utils/utils.ts | 15 +++-- 27 files changed, 353 insertions(+), 85 deletions(-) diff --git a/README.md b/README.md index 0d11586..8f9b4bd 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,6 @@ - 用户只需调用基于标准的 OpenAPI 接口即可配置所需的 ASR、LLM、TTS 类型和参数。火山引擎云端计算服务负责边缘用户接入、云端资源调度、音视频流压缩、文本与语音转换处理以及数据订阅传输等环节。简化开发流程,让开发者更专注在对大模型核心能力的训练及调试,从而快速推进AIGC产品应用创新。 - 同时火山引擎 RTC拥有成熟的音频 3A 处理、视频处理等技术以及大规模音视频聊天能力,可支持 AIGC 产品更便捷的支持多模态交互、多人互动等场景能力,保持交互的自然性和高效性。 -# 快速开始 ## 【必看】环境准备 - Node 版本: 16.0+ 1. 需要准备两个 Terminal,分别启动服务端、前端页面。 @@ -15,40 +14,43 @@ RoomId、UserId 以及申请的 AppID、BusinessID(如有)、Token、ASR AppID 4. 您需要在 [火山方舟-在线推理](https://console.volcengine.com/ark/region:ark+cn-beijing/endpoint?config=%7B%7D) 中创建接入点, 并将模型对应的接入点 ID 填入 `src/config/common.ts` 文件中的 `ARK_V3_MODEL_ID`, 否则无法正常启动智能体。 5. 如果您已经自行完成了服务端的逻辑,可以不依赖 Demo 中的 Server,直接修改前端代码文件 `src/config/index.ts` 中的 `AIGC_PROXY_HOST` 请求域名和接口,并在 `src/app/api.ts` 中修改接口的参数配置 `APIS_CONFIG`。 -## 服务端 +## 快速开始 +### 服务端 进到项目根目录 -### 安装依赖 +#### 安装依赖 ```shell cd Server yarn ``` -### 运行项目 +#### 运行项目 ```shell node app.js ``` -## 前端页面 +### 前端页面 进到项目根目录 -### 安装依赖 +#### 安装依赖 ```shell yarn ``` -### 运行项目 +#### 运行项目 ```shell yarn dev ``` -## 常见问题 +### 常见问题 | 问题 | 解决方案 | | :-- | :-- | | `Server/app.js` 中的 `sessionToken` 是什么,该怎么填,为什么要填 | `sessionToken` 是火山引擎子账号发起 OpenAPI 请求时所必须携带的临时 Token,获取方式可参考 [此文章末尾](https://www.volcengine.com/docs/6348/1315561)。 | -| 什么是主账号,什么是子账号 | 可以参考[官方概念](https://www.volcengine.com/docs/6257/64963?hyperlink_open_type=lark.open_in_browser) 。| -| 启动智能体之后, 说话无反馈 |
  • 可能是因为参数传递的有问题, 例如参数大小写、类型等问题,请再次确认下这类型问题是否存在。
  • 另一方面,可能是因为控制台中相关权限没有正常授予,请参考[流程](https://www.volcengine.com/docs/6348/1315561)再次确认下是否完成相关操作。
  • 相关资源可能未开通或者用量不足,请再次确认。
  • | +| 不清楚什么是主账号,什么是子账号 | 可以参考[官方概念](https://www.volcengine.com/docs/6257/64963?hyperlink_open_type=lark.open_in_browser) 。| +| **启动智能体之后, 对话无反馈** |
  • 参数传递可能有问题, 例如参数大小写、类型等问题,请再次确认下这类型问题是否存在。
  • 另一方面,可能是因为控制台中相关权限没有正常授予,请参考[流程](https://www.volcengine.com/docs/6348/1315561)再次确认下是否完成相关操作。
  • 相关资源可能未开通或者用量不足,请再次确认。
  • 请检查本地的网络/带宽情况
  • | +| **浏览器报了 `Uncaught (in promise) r: token_error` 错误** | 请检查您填在项目中的 RTC Token 是否合法,检测用于生成 Token 的 UserId、RoomId 是否与项目中填写的一致。 | +| 什么是 RTC | **R**eal **T**ime **C**ommunication, RTC 的概念可参考[官网文档](https://www.volcengine.com/docs/6348/66812)。 | | 为什么我的麦克风正常、摄像头也正常,但是设备没有正常工作? | 可能是设备权限未授予,详情可参考 [Web 排查设备权限获取失败问题](https://www.volcengine.com/docs/6348/1356355)。 | -如果有上述以外的问题,欢迎联系我们反馈。 +如果有上述以外的问题,也可以参考[问题反馈收集](https://bytedance.larkoffice.com/docx/FM51drJNFoSFcAxciXYcZkpmnBl),或者联系我们帮忙排查处理。 -## 相关文档 +### 相关文档 - [场景介绍](https://www.volcengine.com/docs/6348/1310537) - [Demo 体验](https://www.volcengine.com/docs/6348/1310559) - [场景搭建方案](https://www.volcengine.com/docs/6348/1310560) diff --git a/package.json b/package.json index 8d05a81..c11111d 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "test": "craco test", "eject": "react-scripts eject", "prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'", - "eslint": "eslint src/ --fix --quiet --ext .js,.jsx,.ts,.tsx", + "eslint": "eslint src/ --fix --cache --quiet --ext .js,.jsx,.ts,.tsx", "stylelint": "stylelint 'src/**/*.less' --fix", "pre-commit": "npm run eslint && npm run stylelint", "echo": "node message.js" diff --git a/src/app/api.ts b/src/app/api.ts index 2a7d006..e736ea8 100644 --- a/src/app/api.ts +++ b/src/app/api.ts @@ -28,13 +28,18 @@ type ApiConfig = typeof APIS_CONFIG; type TupleToUnion = T[number]; type ApiNames = Pick, 'action'>['action']; type RequestFn = (params?: RequestParams[T]) => RequestResponse[T]; -type PromiseRequestFn = (params?: RequestParams[T]) => Promise; +type PromiseRequestFn = ( + params?: RequestParams[T] +) => Promise; type Apis = Record; const APIS = APIS_CONFIG.reduce((store, cur) => { const { action, apiBasicParams, method = 'get' } = cur; store[action] = async (params) => { - const queryData = method === 'get' ? await requestGetMethod(apiBasicParams)(params) : await requestPostMethod(apiBasicParams)(params); + const queryData = + method === 'get' + ? await requestGetMethod(apiBasicParams)(params) + : await requestPostMethod(apiBasicParams)(params); const res = await queryData?.json(); return resultHandler(res); }; diff --git a/src/app/base.ts b/src/app/base.ts index d813d1f..9bba1aa 100644 --- a/src/app/base.ts +++ b/src/app/base.ts @@ -33,7 +33,11 @@ export const requestGetMethod = (apiBasicParams: string, headers = {}) => { * @param isJson * @param headers */ -export const requestPostMethod = (apiBasicParams: string, isJson: boolean = true, headers: Headers = {}) => { +export const requestPostMethod = ( + apiBasicParams: string, + isJson: boolean = true, + headers: Headers = {} +) => { return async (params: T) => { const res = await fetch(`${AIGC_PROXY_HOST}${apiBasicParams}`, { method: 'post', @@ -57,5 +61,7 @@ export const resultHandler = (res: any) => { return Result; } Message.error(`[${ResponseMetadata?.Action}]Failed(Reason: ${ResponseMetadata?.Error?.Code})`); - throw new Error(`[${ResponseMetadata?.Action}]Failed(${JSON.stringify(ResponseMetadata, null, 2)})`); + throw new Error( + `[${ResponseMetadata?.Action}]Failed(${JSON.stringify(ResponseMetadata, null, 2)})` + ); }; diff --git a/src/app/type.ts b/src/app/type.ts index 4efb669..a2ece20 100644 --- a/src/app/type.ts +++ b/src/app/type.ts @@ -100,5 +100,9 @@ export interface RequestResponse { } export type DeepPartial = { - [P in keyof T]?: T[P] extends Array ? Array> : T[P] extends object ? DeepPartial : T[P]; + [P in keyof T]?: T[P] extends Array + ? Array> + : T[P] extends object + ? DeepPartial + : T[P]; }; diff --git a/src/components/AISettings/index.tsx b/src/components/AISettings/index.tsx index 1459015..8831d01 100644 --- a/src/components/AISettings/index.tsx +++ b/src/components/AISettings/index.tsx @@ -8,7 +8,19 @@ import { useEffect, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { IconSwap } from '@arco-design/web-react/icon'; import CheckIcon from '../CheckIcon'; -import Config, { Icon, Name, SCENE, Prompt, Welcome, Voice, Model, AI_MODEL, ModelSourceType, VOICE_INFO_MAP, VOICE_TYPE } from '@/config'; +import Config, { + Icon, + Name, + SCENE, + Prompt, + Welcome, + Voice, + Model, + AI_MODEL, + ModelSourceType, + VOICE_INFO_MAP, + VOICE_TYPE, +} from '@/config'; import TitleCard from '../TitleCard'; import CheckBoxSelector from '@/components/CheckBoxSelector'; import RtcClient from '@/lib/RtcClient'; @@ -156,7 +168,9 @@ function AISettings() { 选择你所需要的 AI 人设 -
    我们已为您配置好对应人设的基本参数,您也可以根据自己的需求进行自定义设置
    +
    + 我们已为您配置好对应人设的基本参数,您也可以根据自己的需求进行自定义设置 +
    {SCENES.map((key) => ( ({ - key: AI_MODEL[type as keyof typeof AI_MODEL], - label: type.replaceAll('_', ' '), - icon: DoubaoModelSVG, - }))} + 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="请选择你需要的模型" diff --git a/src/components/AvatarCard/index.tsx b/src/components/AvatarCard/index.tsx index d845437..0ccccc4 100644 --- a/src/components/AvatarCard/index.tsx +++ b/src/components/AvatarCard/index.tsx @@ -29,13 +29,20 @@ function AvatarCard(props: IAvatarCardProps) {
    - Avatar + Avatar
    {Name[scene]}
    -
    声源来自 {ReversedVoiceType[TTSConfig?.VoiceType || '']}
    +
    + 声源来自 {ReversedVoiceType[TTSConfig?.VoiceType || '']} +
    模型 {LLMConfig.ModelName}
    diff --git a/src/components/BubbleMsg/index.tsx b/src/components/BubbleMsg/index.tsx index 9a7c69c..db8df20 100644 --- a/src/components/BubbleMsg/index.tsx +++ b/src/components/BubbleMsg/index.tsx @@ -22,7 +22,11 @@ function BubbleMsg(props: IBubbleMsgProps) { return (
    - Logo + Logo
    {text}
    ); diff --git a/src/components/ButtonRadio/index.tsx b/src/components/ButtonRadio/index.tsx index 8cda676..1e46776 100644 --- a/src/components/ButtonRadio/index.tsx +++ b/src/components/ButtonRadio/index.tsx @@ -18,7 +18,8 @@ interface IProps { function ButtonRadio(props: IProps) { const { value, onChange, options } = props; - const selected = useMemo(() => options.find((item) => item.key === value), [value]) || options?.[0]; + const selected = + useMemo(() => options.find((item) => item.key === value), [value]) || options?.[0]; const handleClick = (key: string) => { onChange?.(key); }; @@ -26,7 +27,12 @@ function ButtonRadio(props: IProps) { return (
    {options.map(({ label, key }) => ( - ))} diff --git a/src/components/CheckBox/index.tsx b/src/components/CheckBox/index.tsx index 697b730..a0176e8 100644 --- a/src/components/CheckBox/index.tsx +++ b/src/components/CheckBox/index.tsx @@ -20,7 +20,16 @@ interface IProps { } function CheckBox(props: IProps) { - const { noStyle, className = '', icon = '', checked, label, description, suffix, onClick } = props; + const { + noStyle, + className = '', + icon = '', + checked, + label, + description, + suffix, + onClick, + } = props; if (noStyle) { return ( @@ -35,7 +44,10 @@ function CheckBox(props: IProps) { } return ( -
    +
    {icon ? icon : ''}
    {label}
    diff --git a/src/components/CheckBoxSelector/index.tsx b/src/components/CheckBoxSelector/index.tsx index ad90f51..4194e30 100644 --- a/src/components/CheckBoxSelector/index.tsx +++ b/src/components/CheckBoxSelector/index.tsx @@ -42,7 +42,13 @@ function CheckBoxSelector(props: IProps) { <>
    {selectedOne ? ( - + ) : (
    {placeHolder}
    )} @@ -78,7 +84,15 @@ function CheckBoxSelector(props: IProps) { >
    {data.map((item) => ( - setSelected(item.key)} /> + setSelected(item.key)} + /> ))}
    diff --git a/src/components/DrawerRowItem/index.tsx b/src/components/DrawerRowItem/index.tsx index 88c5b0e..156c5b9 100644 --- a/src/components/DrawerRowItem/index.tsx +++ b/src/components/DrawerRowItem/index.tsx @@ -54,7 +54,15 @@ function DrawerRowItem(props: IDrawerRowItemProps) {
    - +
    {drawer?.children}
    diff --git a/src/components/Header/index.tsx b/src/components/Header/index.tsx index f34dec5..ab4accf 100644 --- a/src/components/Header/index.tsx +++ b/src/components/Header/index.tsx @@ -74,12 +74,22 @@ function Header(props: HeaderProps) { {children} {utils.isMobile() ? null : (
    -
    window.open('https://www.volcengine.com/product/veRTC/ConversationalAI', '_blank')}> +
    + window.open('https://www.volcengine.com/product/veRTC/ConversationalAI', '_blank') + } + > 官网链接
    window.open('https://www.volcengine.com/contact/product?t=%E5%AF%B9%E8%AF%9D%E5%BC%8Fai&source=%E4%BA%A7%E5%93%81%E5%92%A8%E8%AF%A2', '_blank')} + onClick={() => + window.open( + 'https://www.volcengine.com/contact/product?t=%E5%AF%B9%E8%AF%9D%E5%BC%8Fai&source=%E4%BA%A7%E5%93%81%E5%92%A8%E8%AF%A2', + '_blank' + ) + } > 联系我们
    diff --git a/src/components/NetworkIndicator/index.tsx b/src/components/NetworkIndicator/index.tsx index 6795e9a..d52cdd0 100644 --- a/src/components/NetworkIndicator/index.tsx +++ b/src/components/NetworkIndicator/index.tsx @@ -34,7 +34,8 @@ function NetworkIndicator() { const networkQuality = room.networkQuality; const delay = room.localUser.audioStats?.rtt; const audioLossRateUpper = room.localUser.audioStats?.audioLossRate || 0; - const audioLossRateLower = room.remoteUsers.find((user) => user.userId === Config.BotName)?.audioStats?.audioLossRate || 0; + const audioLossRateLower = + room.remoteUsers.find((user) => user.userId === Config.BotName)?.audioStats?.audioLossRate || 0; const indicators = useMemo(() => { switch (networkQuality) { @@ -75,11 +76,15 @@ function NetworkIndicator() {
    - {`${audioLossRateUpper}` ? (audioLossRateUpper * 100)?.toFixed(0) : '- '}% + + {`${audioLossRateUpper}` ? (audioLossRateUpper * 100)?.toFixed(0) : '- '}% +
    - {`${audioLossRateLower}` ? (audioLossRateLower * 100)?.toFixed(0) : '- '}% + + {`${audioLossRateLower}` ? (audioLossRateLower * 100)?.toFixed(0) : '- '}% +
    diff --git a/src/config/common.ts b/src/config/common.ts index 98c0819..7fce11a 100644 --- a/src/config/common.ts +++ b/src/config/common.ts @@ -26,7 +26,9 @@ export enum CustomParamsType { /** * @brief AI 音色可选值 * @default 通用女声 - * @notes 通用女声、通用男声为默认音色, 其它皆为付费音色 + * @notes 通用女声、通用男声为默认音色, 其它皆为付费音色。 + * 音色 ID 可于 https://console.volcengine.com/speech/service/8 中开通获取。 + * 对应 "音色详情" 中, "Voice_type" 列的值。 */ export enum VOICE_TYPE { '通用女声' = 'BV001_streaming', @@ -102,18 +104,14 @@ export const AI_MODE_MAP: Partial> = { /** * @brief 豆包模型的 ID + * @note 具体的模型 ID 请至 https://console.volcengine.com/ark/region:ark+cn-beijing/endpoint 参看/创建 + * 模型 ID 即接入点 ID, 在上述链接中表格内 "接入点名称" 列中, 类似于 "ep-2024xxxxxx-xxx" 格式即是模型 ID。 */ export const ARK_V3_MODEL_ID: Partial> = { - /** - * @note 具体的 ID 请至 https://console.volcengine.com/ark/region:ark+cn-beijing/endpoint 参看/创建 - */ [AI_MODEL.DOUBAO_LITE_4K]: '************** 此处填充方舟上的模型 ID *************', [AI_MODEL.DOUBAO_PRO_4K]: '************** 此处填充方舟上的模型 ID *************', [AI_MODEL.DOUBAO_PRO_32K]: '************** 此处填充方舟上的模型 ID *************', [AI_MODEL.DOUBAO_PRO_128K]: '************** 此处填充方舟上的模型 ID *************', - /** - * @note 视觉模型, 可至火山方舟开通使用 - */ [AI_MODEL.VISION]: '************** 此处填充方舟上的模型 ID *************', // ... 可根据所开通的模型进行扩充 }; @@ -155,6 +153,9 @@ export const Name = { [SCENE.CUSTOM]: '自定义', }; +/** + * @brief 智能体启动后的欢迎词。 + */ export const Welcome = { [SCENE.INTELLIGENT_ASSISTANT]: '你好,我是你的AI小助手,有什么可以帮你的吗?', [SCENE.VIRTUAL_GIRL_FRIEND]: '你来啦,我好想你呀~今天有没有想我呢?', @@ -186,15 +187,38 @@ export const Voice = { }; export const Questions = { - [SCENE.INTELLIGENT_ASSISTANT]: ['最近有什么好看的电影推荐吗?', '上海有什么好玩的地方吗?', '能给我讲一个故事吗?'], - [SCENE.VIRTUAL_GIRL_FRIEND]: ['我今天有点累。', '我们等会儿去看电影吧!', '明天我生日,你准备送给我什么礼物呢?'], - [SCENE.TRANSLATE]: ['道可道,非常道;名可名,非常名。', 'Stay hungry, stay foolish.', '天生我材必有用,千金散尽还复来。'], - [SCENE.CHILDREN_ENCYCLOPEDIA]: ['天上有多少颗星星?', '太阳为什么总是从东边升起?', '苹果的英语怎么说?'], - [SCENE.CUSTOMER_SERVICE]: ['我上次来你们店里吃饭,等了三十分钟菜才上来。', '你们店里卫生间有点脏。', '你们空调开得太冷了。'], + [SCENE.INTELLIGENT_ASSISTANT]: [ + '最近有什么好看的电影推荐吗?', + '上海有什么好玩的地方吗?', + '能给我讲一个故事吗?', + ], + [SCENE.VIRTUAL_GIRL_FRIEND]: [ + '我今天有点累。', + '我们等会儿去看电影吧!', + '明天我生日,你准备送给我什么礼物呢?', + ], + [SCENE.TRANSLATE]: [ + '道可道,非常道;名可名,非常名。', + 'Stay hungry, stay foolish.', + '天生我材必有用,千金散尽还复来。', + ], + [SCENE.CHILDREN_ENCYCLOPEDIA]: [ + '天上有多少颗星星?', + '太阳为什么总是从东边升起?', + '苹果的英语怎么说?', + ], + [SCENE.CUSTOMER_SERVICE]: [ + '我上次来你们店里吃饭,等了三十分钟菜才上来。', + '你们店里卫生间有点脏。', + '你们空调开得太冷了。', + ], [SCENE.TEACHING_ASSISTANT]: ['这个单词是什么意思?', '这道题该怎么做?', '我的表情是什么样的?'], [SCENE.CUSTOM]: ['你能帮我解决什么问题?', '今天北京天气怎么样?', '你喜欢哪位流行歌手?'], }; +/** + * @brief 大模型 System 角色预设指令,可用于控制模型输出, 类似 Prompt 的概念。 + */ export const Prompt = { [SCENE.INTELLIGENT_ASSISTANT]: `##人设 你是一个全能智能体,拥有丰富的百科知识,可以为人们答疑解惑,解决问题。 diff --git a/src/config/config.ts b/src/config/config.ts index 7f3d4fe..278b93e 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -3,40 +3,94 @@ * SPDX-license-identifier: BSD-3-Clause */ -import { TTS_CLUSTER, ARK_V3_MODEL_ID, ModelSourceType, SCENE, Prompt, Welcome, Model, Voice, LLM_BOT_ID, AI_MODEL, AI_MODE_MAP, AI_MODEL_MODE } from '.'; +import { + TTS_CLUSTER, + ARK_V3_MODEL_ID, + ModelSourceType, + SCENE, + Prompt, + Welcome, + Model, + Voice, + LLM_BOT_ID, + AI_MODEL, + AI_MODE_MAP, + AI_MODEL_MODE, +} from '.'; export const CONVERSATION_SIGNATURE = 'conversation'; /** - * @brief RTC & AIGC 配置 - * @notes 更多参数请参考: https://api.volcengine.com/api-explorer?action=StartVoiceChat&groupName=%E6%99%BA%E8%83%BD%E4%BD%93&serviceCode=rtc&version=2024-12-01 + * @brief RTC & AIGC 配置。 + * @notes 更多参数请参考 + * https://www.volcengine.com/docs/6348/1404673 */ export class ConfigFactory { BaseConfig = { - AppId: 'Your AppId', /** - * @brief 非必填, 按需填充 + * @note 必填, RTC AppId 可于 https://console.volcengine.com/rtc/listRTC 中获取。 + */ + AppId: 'Your RTC AppId', + /** + * @brief 非必填, 按需填充。 */ BusinessId: undefined, + /** + * @brief 必填, 房间 ID, 自定义即可。 + */ RoomId: 'Your Room Id', + /** + * @brief 必填, 当前和 AI 对话的用户的 ID, 自定义即可。 + */ UserId: 'Your User Id', + /** + * @brief 必填, RTC Token, 由 AppId、RoomId、UserId、时间戳等等信息计算得出, 可于 https://console.volcengine.com/rtc/listRTC 列表中 + * 找到对应 AppId 行中 "操作" 列的 "临时Token" 按钮点击进行生成, 用于本地 RTC 通信进房鉴权校验。 + * @note 生成临时 Token 时, 页面上的 RoomId / UserId 填的与此处的 RoomId / UserId 保持一致。 + * 正式使用时可通参考 https://www.volcengine.com/docs/6348/70121 通过代码生成 Token。 + */ Token: 'Your Token', + /** + * @brief 必填, TTS(语音合成) AppId, 可于 https://console.volcengine.com/speech/app 中获取, 若无可先创建应用。 + * @note 创建应用时, 需要选择 "语音合成" 服务, 并选择对应的 App 进行绑定。 + */ TTSAppId: 'Your TTS AppId', + /** + * @brief 必填, ASR(语音识别) AppId, 可于 https://console.volcengine.com/speech/app 中获取, 若无可先创建应用。 + * @note 创建应用时, 需要按需根据语言选择 "流式语音识别" 服务, 并选择对应的 App 进行绑定。 + */ ASRAppId: 'Your ASR AppId', }; Model: AI_MODEL = Model[SCENE.INTELLIGENT_ASSISTANT]; + /** + * @note 必填, 音色 ID, 可具体看定义。 + * 音色 ID 获取方式可查看 VOICE_TYPE 定义 + * 此处已有默认值, 不影响跑通, 可按需修改。 + */ VoiceType = Voice[SCENE.INTELLIGENT_ASSISTANT]; + /** + * @note 大模型 System 角色预设指令, 可用于控制模型输出, 类似 Prompt 的概念。 + */ Prompt = Prompt[SCENE.INTELLIGENT_ASSISTANT]; + /** + * @note 智能体启动后的欢迎词。 + */ WelcomeSpeech = Welcome[SCENE.INTELLIGENT_ASSISTANT]; ModeSourceType = ModelSourceType.Available; + /** + * @note 非必填, 第三方模型才需要使用, 用火山方舟模型时无需关注。 + */ Url? = ''; + /** + * @note 非必填, 第三方模型才需要使用, 用火山方舟模型时无需关注。 + */ APIKey? = ''; /** diff --git a/src/lib/RtcClient.ts b/src/lib/RtcClient.ts index 8aa1f0e..7364a97 100644 --- a/src/lib/RtcClient.ts +++ b/src/lib/RtcClient.ts @@ -3,6 +3,7 @@ * SPDX-license-identifier: BSD-3-Clause */ + import VERTC, { MirrorType, StreamIndex, @@ -33,7 +34,11 @@ export interface IEventListener { handleUserJoin: (e: onUserJoinedEvent) => void; handleUserLeave: (e: onUserLeaveEvent) => void; handleUserPublishStream: (e: { userId: string; mediaType: MediaType }) => void; - handleUserUnpublishStream: (e: { userId: string; mediaType: MediaType; reason: StreamRemoveReason }) => void; + handleUserUnpublishStream: (e: { + userId: string; + mediaType: MediaType; + reason: StreamRemoveReason; + }) => void; handleRemoteStreamStats: (e: RemoteStreamStats) => void; handleLocalStreamStats: (e: LocalStreamStats) => void; handleLocalAudioPropertiesReport: (e: LocalAudioPropertiesInfo[]) => void; @@ -45,7 +50,10 @@ export interface IEventListener { handleUserStartAudioCapture: (e: { userId: string }) => void; handleUserStopAudioCapture: (e: { userId: string }) => void; handleRoomBinaryMessageReceived: (e: { userId: string; message: ArrayBuffer }) => void; - handleNetworkQuality: (uplinkNetworkQuality: NetworkQuality, downlinkNetworkQuality: NetworkQuality) => void; + handleNetworkQuality: ( + uplinkNetworkQuality: NetworkQuality, + downlinkNetworkQuality: NetworkQuality + ) => void; } interface EngineOptions { diff --git a/src/lib/listenerHooks.ts b/src/lib/listenerHooks.ts index 345daa6..ff2249c 100644 --- a/src/lib/listenerHooks.ts +++ b/src/lib/listenerHooks.ts @@ -86,7 +86,11 @@ const useRtcListeners = (): IEventListener => { dispatch(updateRemoteUser(payload)); }; - const handleUserUnpublishStream = (e: { userId: string; mediaType: MediaType; reason: StreamRemoveReason }) => { + const handleUserUnpublishStream = (e: { + userId: string; + mediaType: MediaType; + reason: StreamRemoveReason; + }) => { const { userId, mediaType } = e; const payload: IUser = { userId }; @@ -119,7 +123,9 @@ const useRtcListeners = (): IEventListener => { }; const handleLocalAudioPropertiesReport = (e: LocalAudioPropertiesInfo[]) => { - const localAudioInfo = e.find((audioInfo) => audioInfo.streamIndex === StreamIndex.STREAM_INDEX_MAIN); + const localAudioInfo = e.find( + (audioInfo) => audioInfo.streamIndex === StreamIndex.STREAM_INDEX_MAIN + ); if (localAudioInfo) { dispatch( updateLocalUser({ @@ -236,10 +242,15 @@ const useRtcListeners = (): IEventListener => { dispatch(updateAITalkState({ isAITalking: false })); }; - const handleNetworkQuality = (uplinkNetworkQuality: NetworkQuality, downlinkNetworkQuality: NetworkQuality) => { + const handleNetworkQuality = ( + uplinkNetworkQuality: NetworkQuality, + downlinkNetworkQuality: NetworkQuality + ) => { dispatch( updateNetworkQuality({ - networkQuality: Math.floor((uplinkNetworkQuality + downlinkNetworkQuality) / 2) as NetworkQuality, + networkQuality: Math.floor( + (uplinkNetworkQuality + downlinkNetworkQuality) / 2 + ) as NetworkQuality, }) ); }; diff --git a/src/lib/useCommon.ts b/src/lib/useCommon.ts index adbfec9..1a3a757 100644 --- a/src/lib/useCommon.ts +++ b/src/lib/useCommon.ts @@ -8,12 +8,23 @@ import { useSelector, useDispatch } from 'react-redux'; import { MediaType } from '@volcengine/rtc'; import Utils from '@/utils/utils'; import RtcClient from '@/lib/RtcClient'; -import { clearCurrentMsg, clearHistoryMsg, localJoinRoom, localLeaveRoom, updateAIGCState, updateLocalUser } from '@/store/slices/room'; +import { + clearCurrentMsg, + clearHistoryMsg, + localJoinRoom, + localLeaveRoom, + updateAIGCState, + updateLocalUser, +} from '@/store/slices/room'; import useRtcListeners from '@/lib/listenerHooks'; import { RootState } from '@/store'; -import { updateMediaInputs, updateSelectedDevice, setDevicePermissions } from '@/store/slices/device'; +import { + updateMediaInputs, + updateSelectedDevice, + setDevicePermissions, +} from '@/store/slices/device'; import logger from '@/utils/logger'; import aigcConfig, { AI_MODEL } from '@/config'; @@ -45,7 +56,10 @@ export const useGetDevicePermission = () => { return permission; }; -export const useJoin = (): [boolean, (formValues: FormProps, fromRefresh: boolean) => Promise] => { +export const useJoin = (): [ + boolean, + (formValues: FormProps, fromRefresh: boolean) => Promise +] => { const devicePermissions = useSelector((state: RootState) => state.device.devicePermissions); const room = useSelector((state: RootState) => state.room); @@ -198,7 +212,9 @@ export const useDeviceState = () => { const switchMic = (publish = true) => { if (publish) { - !isAudioPublished ? RtcClient.publishStream(MediaType.AUDIO) : RtcClient.unpublishStream(MediaType.AUDIO); + !isAudioPublished + ? RtcClient.publishStream(MediaType.AUDIO) + : RtcClient.unpublishStream(MediaType.AUDIO); } queryDevices(MediaType.AUDIO); !isAudioPublished ? RtcClient.startAudioCapture() : RtcClient.stopAudioCapture(); @@ -211,7 +227,9 @@ export const useDeviceState = () => { const switchCamera = (publish = true) => { if (publish) { - !isVideoPublished ? RtcClient.publishStream(MediaType.VIDEO) : RtcClient.unpublishStream(MediaType.VIDEO); + !isVideoPublished + ? RtcClient.publishStream(MediaType.VIDEO) + : RtcClient.unpublishStream(MediaType.VIDEO); } queryDevices(MediaType.VIDEO); !localUser.publishVideo ? RtcClient.startVideoCapture() : RtcClient.stopVideoCapture(); diff --git a/src/pages/MainPage/MainArea/Antechamber/InvokeButton/index.tsx b/src/pages/MainPage/MainArea/Antechamber/InvokeButton/index.tsx index 4299260..aa52e97 100644 --- a/src/pages/MainPage/MainArea/Antechamber/InvokeButton/index.tsx +++ b/src/pages/MainPage/MainArea/Antechamber/InvokeButton/index.tsx @@ -19,7 +19,11 @@ function InvokeButton(props: IInvokeButtonProps) {
    call - {loading ? : phone} + {loading ? ( + + ) : ( + phone + )}
    {loading ? '连接中' : '通话'}
    diff --git a/src/pages/MainPage/MainArea/Room/CameraArea.tsx b/src/pages/MainPage/MainArea/Room/CameraArea.tsx index 9a3f54a..f855dc8 100644 --- a/src/pages/MainPage/MainArea/Room/CameraArea.tsx +++ b/src/pages/MainPage/MainArea/Room/CameraArea.tsx @@ -26,7 +26,9 @@ function CameraArea(props: React.HTMLAttributes) { const handleOperateCamera = () => { !localUser.publishVideo ? RtcClient.startVideoCapture() : RtcClient.stopVideoCapture(); - !localUser.publishVideo ? RtcClient.publishStream(MediaType.VIDEO) : RtcClient.unpublishStream(MediaType.VIDEO); + !localUser.publishVideo + ? RtcClient.publishStream(MediaType.VIDEO) + : RtcClient.unpublishStream(MediaType.VIDEO); dispatch( updateLocalUser({ @@ -49,7 +51,11 @@ function CameraArea(props: React.HTMLAttributes) {
    ) : (
    - close + close
    diff --git a/src/pages/MainPage/MainArea/Room/Conversation.tsx b/src/pages/MainPage/MainArea/Room/Conversation.tsx index bf6635f..c4f50f3 100644 --- a/src/pages/MainPage/MainArea/Room/Conversation.tsx +++ b/src/pages/MainPage/MainArea/Room/Conversation.tsx @@ -46,11 +46,18 @@ function Conversation(props: React.HTMLAttributes) { return ''; } return ( -
    +
    {value}
    - {isAIReady && (isUserTalking || isAITalking) && index === msgHistory.length - 1 ? : ''} + {isAIReady && (isUserTalking || isAITalking) && index === msgHistory.length - 1 ? ( + + ) : ( + '' + )}
    {!isUserMsg && isInterrupted ? 已打断 : ''} diff --git a/src/pages/MainPage/MainArea/Room/ToolBar.tsx b/src/pages/MainPage/MainArea/Room/ToolBar.tsx index 8712827..3e0f66f 100644 --- a/src/pages/MainPage/MainArea/Room/ToolBar.tsx +++ b/src/pages/MainPage/MainArea/Room/ToolBar.tsx @@ -32,9 +32,25 @@ function ToolBar(props: React.HTMLAttributes) { }; return (
    - {utils.isMobile() ? setting : null} - switchMic(true)} className={style.btn} alt="mic" /> - {model === AI_MODEL.VISION ? switchCamera(true)} className={style.btn} alt="camera" /> : ''} + {utils.isMobile() ? ( + setting + ) : null} + switchMic(true)} + className={style.btn} + alt="mic" + /> + {model === AI_MODEL.VISION ? ( + switchCamera(true)} + className={style.btn} + alt="camera" + /> + ) : ( + '' + )} leave {utils.isMobile() ? ( state.device.devicePermissions); const devices = useSelector((state: RootState) => state.device); - const selectedDevice = type === MediaType.AUDIO ? devices.selectedMicrophone : devices.selectedCamera; + const selectedDevice = + type === MediaType.AUDIO ? devices.selectedMicrophone : devices.selectedCamera; const permission = devicePermissions?.[type === MediaType.AUDIO ? 'audio' : 'video']; const dispatch = useDispatch(); - const deviceList = useMemo(() => (type === MediaType.AUDIO ? devices.audioInputs : devices.videoInputs), [devices]); + const deviceList = useMemo( + () => (type === MediaType.AUDIO ? devices.audioInputs : devices.videoInputs), + [devices] + ); const handleDeviceChange = (value: string) => { RtcClient.switchDevice(type, value); @@ -66,7 +70,12 @@ function DeviceDrawerButton(props: IDeviceDrawerButtonProps) {
    {DEVICE_NAME[type]}
    - switcher(enable)} disabled={!permission} /> + switcher(enable)} + disabled={!permission} + />