diff --git a/.eslintrc b/.eslintrc
index f13c040..80b12c9 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -41,7 +41,7 @@
}
],
"rules": {
- "prettier/prettier": ["warn", { "trailingComma": "es5", "printWidth": 200 }],
+ "prettier/prettier": ["warn", { "trailingComma": "es5", "printWidth": 100 }],
"linebreak-style": "off",
"no-console": ["warn", { "allow": ["warn", "error", "log"] }],
"no-case-declarations": 0,
diff --git a/README.md b/README.md
index 0e89c2d..f5913d5 100644
--- a/README.md
+++ b/README.md
@@ -8,11 +8,11 @@
## 【必看】环境准备
- **Node 版本: 16.0+**
1. 需要准备两个 Terminal,分别启动服务端、前端页面。
-2. 开通 ASR、TTS、LLM、RTC 等服务,可通过 [无代码跑通实时对话式](https://console.volcengine.com/rtc/guide) 快速开通服务, 点击 **快速开始** 中的 **跑通 Demo** 进行服务开通。
+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)、[SessionToken](https://www.volcengine.com/docs/6348/1315561#sub?s=g)(临时token, 子账号才需要), 修改 `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`, 否则无法正常启动智能体。
+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`。
## 快速开始
@@ -43,12 +43,12 @@ yarn dev
### 常见问题
| 问题 | 解决方案 |
| :-- | :-- |
-| **启动智能体之后, 对话无反馈,或者一直停留在 "AI 准备中, 请稍侯"** |
可能因为控制台中相关权限没有正常授予,请参考[流程](https://www.volcengine.com/docs/6348/1315561?s=g)再次确认下是否完成相关操作。此问题的可能性较大,建议仔细对照是否已经将相应的权限开通。参数传递可能有问题, 例如参数大小写、类型等问题,请再次确认下这类型问题是否存在。相关资源可能未开通或者用量不足,请再次确认。**请检查当前使用的模型 ID 等内容都是正确且可用的。** |
-| `Server/app.js` 中的 `sessionToken` 是什么,该怎么填,为什么要填 | `sessionToken` 是火山引擎子账号发起 OpenAPI 请求时所必须携带的临时 Token,获取方式可参考 [此文章末尾](https://www.volcengine.com/docs/6348/1315561?s=g)。 |
-| **浏览器报了 `Uncaught (in promise) r: token_error` 错误** | 请检查您填在项目中的 RTC Token 是否合法,检测用于生成 Token 的 UserId、RoomId 是否与项目中填写的一致;或者 Token 可能过期, 可尝试重新生成下。 |
+| 如何使用第三方模型、Coze Bot | 点击页面上的 "修改 AI 设定" 进入配置页,可切换 官方模型/Coze/第三方模型,填写对应参数即可,相关代码对应 `src/components/AISettings/index.tsx` 文件。 |
+| **启动智能体之后, 对话无反馈,或者一直停留在 "AI 准备中, 请稍侯"** | 可能因为控制台中相关权限没有正常授予,请参考[流程](https://www.volcengine.com/docs/6348/1315561?s=g)再次确认下是否完成相关操作。此问题的可能性较大,建议仔细对照是否已经将相应的权限开通。参数传递可能有问题, 例如参数大小写、类型等问题,请再次确认下这类型问题是否存在。相关资源可能未开通或者用量不足/欠费,请再次确认。**请检查当前使用的模型 ID 等内容都是正确且可用的。** |
+| **浏览器报了 `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 即可。 |
| 为什么我的麦克风正常、摄像头也正常,但是设备没有正常工作? | 可能是设备权限未授予,详情可参考 [Web 排查设备权限获取失败问题](https://www.volcengine.com/docs/6348/1356355?s=g)。 |
-| 接口调用时, 返回 "Invalid 'Authorization' header, Pls check your authorization header" 错误 | `Server/app.js` 中的 AK/SK/SessionToken 不正确 |
+| 接口调用时, 返回 "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) 。|
@@ -61,7 +61,21 @@ yarn dev
## 更新日志
-### [1.5.0] - [2025-03-31]
+### OpenAPI 更新
+参考 [OpenAPI 更新](https://www.volcengine.com/docs/6348/116363?s=g) 中与 实时对话式 AI 相关的更新内容。
+
+### Demo 更新
+#### [1.6.0] - [2025-04-16]
+- 支持 Coze Bot
+- 更新部分注释和文档内容
+- 删除子账号的 SessionToken 配置, 子账号调用无须 SessionToken
+- 修复通话前修改内容,在通话后配置消失的问题
+
+#### [1.5.1] - [2025-04-11]
+- 移除无用代码和依赖
+- 修复字幕逻辑
+
+#### [1.5.0] - [2025-03-31]
- 修复部分 UI 问题
- 追加屏幕共享能力 (视觉模型可用,**读屏助手** 人设下可使用)
- 修改字幕逻辑,避免字幕回调中标点符号、大小写不一致引起的字幕重复问题
diff --git a/Server/app.js b/Server/app.js
index edc0939..ec9df62 100644
--- a/Server/app.js
+++ b/Server/app.js
@@ -27,11 +27,6 @@ const ACCOUNT_INFO = {
* @notes 必填, 在 https://console.volcengine.com/iam/keymanage/ 获取
*/
secretKey: 'Your SK',
- /**
- * @notes 非必填, 主账号无须传入, 子账号须传, 获取方式可参考
- * https://www.volcengine.com/docs/6348/1315561 中的 步骤 4-使用子账号调用智能体接口 一节
- */
- // sessionToken: 'Your SessionToken',
}
app.use(bodyParser());
@@ -67,9 +62,7 @@ app.use(async ctx => {
/** 参考 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,
- },
+ headers: openApiRequestData.headers,
body: JSON.stringify(body),
});
const volcResponse = await result.json();
diff --git a/package.json b/package.json
index 247ece8..5bb6654 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "aigc",
- "version": "1.5.0",
+ "version": "1.6.0",
"license": "BSD-3-Clause",
"private": true,
"dependencies": {
diff --git a/src/components/AISettings/index.module.less b/src/components/AISettings/index.module.less
index 61d7ce5..a440e79 100644
--- a/src/components/AISettings/index.module.less
+++ b/src/components/AISettings/index.module.less
@@ -91,6 +91,12 @@
flex-direction: column;
gap: 36px;
+ .ai-settings-radio {
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-end;
+ }
+
.anchor {
position: absolute;
border-bottom: 12px solid white;
@@ -104,6 +110,7 @@
width: 100%;
display: flex;
flex-direction: row;
+ margin-top: -16px;
gap: 24px;
.ai-settings-wrapper {
diff --git a/src/components/AISettings/index.tsx b/src/components/AISettings/index.tsx
index 1cbbf27..4babc80 100644
--- a/src/components/AISettings/index.tsx
+++ b/src/components/AISettings/index.tsx
@@ -3,10 +3,10 @@
* SPDX-license-identifier: BSD-3-Clause
*/
-import { Button, Drawer, Input, Message } from '@arco-design/web-react';
+import { Button, Drawer, Input, Message, Radio, Tooltip } from '@arco-design/web-react';
import { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
-import { IconSwap } from '@arco-design/web-react/icon';
+import { IconExclamationCircle } from '@arco-design/web-react/icon';
import { StreamIndex } from '@volcengine/rtc';
import CheckIcon from '../CheckIcon';
import Config, {
@@ -18,7 +18,7 @@ import Config, {
Voice,
Model,
AI_MODEL,
- ModelSourceType,
+ MODEL_MODE,
VOICE_INFO_MAP,
VOICE_TYPE,
isVisionMode,
@@ -26,7 +26,7 @@ import Config, {
import TitleCard from '../TitleCard';
import CheckBoxSelector from '@/components/CheckBoxSelector';
import RtcClient from '@/lib/RtcClient';
-import { clearHistoryMsg, updateAIConfig, updateScene } from '@/store/slices/room';
+import { clearHistoryMsg, updateAIConfig, updateModelMode, updateScene } from '@/store/slices/room';
import { RootState } from '@/store';
import utils from '@/utils/utils';
import { useDeviceState } from '@/lib/useCommon';
@@ -42,6 +42,8 @@ export interface IAISettingsProps {
onCancel?: () => void;
}
+const RadioGroup = Radio.Group;
+
const SCENES = [
SCENE.INTELLIGENT_ASSISTANT,
SCENE.SCREEN_READER,
@@ -59,17 +61,19 @@ function AISettings({ open, onCancel, onOk }: IAISettingsProps) {
useDeviceState();
const room = useSelector((state: RootState) => state.room);
const [loading, setLoading] = useState(false);
- const [use3Part, setUse3Part] = useState(false);
+ const [modelMode, setModelMode] = useState(room.modelMode);
const [scene, setScene] = useState(room.scene);
const [data, setData] = useState({
- prompt: Prompt[scene],
- welcome: Welcome[scene],
- voice: Voice[scene],
- model: Model[scene],
+ prompt: Config.Prompt || Prompt[scene],
+ welcome: Config.WelcomeSpeech || Welcome[scene],
+ voice: Config.VoiceType || Voice[scene],
+ model: Config.Model || Model[scene],
- Url: '',
- APIKey: '',
- customModelName: '',
+ Url: Config.Url || '',
+ APIKey: Config.APIKey || '',
+ customModelName: (Config.Model || '') as string,
+
+ BotID: Config.BotID || '',
});
const handleVoiceTypeChanged = (key: string) => {
@@ -90,35 +94,55 @@ function AISettings({ open, onCancel, onOk }: IAISettingsProps) {
}));
};
- const handleUseThirdPart = () => {
- setUse3Part(!use3Part);
- Config.ModeSourceType = use3Part ? ModelSourceType.Custom : ModelSourceType.Available;
+ const handleUseThirdPart = (val: MODEL_MODE) => {
+ setModelMode(val);
+ Config.ModeSourceType = val;
};
const handleUpdateConfig = async () => {
dispatch(updateScene({ scene }));
- if (use3Part) {
- if (!data.Url) {
- Message.error('请输入正确的第三方模型地址');
- return;
- }
- if (!data.Url.startsWith('http://') && !data.Url.startsWith('https://')) {
- Message.error('第三方模型请求地址格式不正确, 请以 http:// 或 https:// 为开头');
- return;
- }
- Config.Url = data.Url;
- Config.APIKey = data.APIKey;
- Config.ModeSourceType = ModelSourceType.Custom;
- } else {
- Config.Url = undefined;
- Config.APIKey = undefined;
- Config.ModeSourceType = ModelSourceType.Available;
+ 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;
}
setLoading(true);
- Config.Model = use3Part ? (data.customModelName as AI_MODEL) : (data.model as AI_MODEL);
+ 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)) {
@@ -218,32 +242,85 @@ function AISettings({ open, onCancel, onOk }: IAISettingsProps) {
}}
/>
)}
-
- {
- setData((prev) => ({
- ...prev,
- prompt: val,
- }));
- }}
- placeholder="请输入你需要的 Prompt 设定"
- />
-
-
- {
- setData((prev) => ({
- ...prev,
- welcome: val,
- }));
- }}
- placeholder="请输入欢迎语"
- />
-
+
+ Coze
+
+ 访问令牌可参考{' '}
+
+ 添加个人访问令牌
+ {' '}
+ 获取。
+
+ 智能体 ID 可参考{' '}
+
+ 发送请求
+ {' '}
+ 获取。
+
+ 请注意智能体发布时须勾选 API 调用能力,否则无法成功对话。
+
+ }
+ >
+
+
+
+ ),
+ },
+ {
+ value: MODEL_MODE.VENDOR,
+ label: (
+
+ }
+ >
+
+
+
+ ),
+ },
+ ]}
+ value={modelMode}
+ size="mini"
+ type="button"
+ defaultValue="Beijing"
+ className={styles['ai-settings-radio']}
+ onChange={handleUseThirdPart}
+ />
- {use3Part ? (
+ {modelMode === MODEL_MODE.ORIGINAL && (
+
+ ({
+ 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}
+ />
+
+ )}
+ {modelMode === MODEL_MODE.VENDOR && (
<>
>
- ) : (
-
- ({
- 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}
- />
-
)}
-
-
+ {modelMode === MODEL_MODE.COZE && (
+ <>
+
+
+
+
+ {
+ setData((prev) => ({
+ ...prev,
+ APIKey: val,
+ }));
+ }}
+ placeholder="请输入访问令牌"
+ />
+
+
+ {
+ setData((prev) => ({
+ ...prev,
+ BotID: val,
+ }));
+ }}
+ placeholder="请输入智能体 ID"
+ />
+
+ >
+ )}
+
+ {
+ setData((prev) => ({
+ ...prev,
+ prompt: val,
+ }));
+ }}
+ placeholder="请输入你需要的 Prompt 设定"
+ />
+
+
+ {
+ setData((prev) => ({
+ ...prev,
+ welcome: val,
+ }));
+ }}
+ placeholder="请输入欢迎语"
+ />
+
);
diff --git a/src/components/AvatarCard/index.tsx b/src/components/AvatarCard/index.tsx
index f30772a..f1b6f21 100644
--- a/src/components/AvatarCard/index.tsx
+++ b/src/components/AvatarCard/index.tsx
@@ -10,7 +10,7 @@ import AISettings from '../AISettings';
import style from './index.module.less';
import DouBaoAvatar from '@/assets/img/DoubaoAvatarGIF.webp';
import { RootState } from '@/store';
-import { Name, VOICE_TYPE } from '@/config';
+import { MODEL_MODE, Name, VOICE_TYPE } from '@/config';
interface IAvatarCardProps extends React.HTMLAttributes {
avatar?: string;
@@ -24,11 +24,16 @@ const ReversedVoiceType = Object.entries(VOICE_TYPE).reduce state.room);
+ const { scene, aiConfig, modelMode } = room;
const [open, setOpen] = useState(false);
- const scene = room.scene;
- const { LLMConfig, TTSConfig } = room.aiConfig.Config || {};
+ const { LLMConfig, TTSConfig } = aiConfig.Config || {};
const { avatar, className, ...rest } = props;
const voice = TTSConfig.ProviderParams.audio.voice_type;
@@ -51,7 +56,11 @@ function AvatarCard(props: IAvatarCardProps) {
{Name[scene]}
声源来自 {ReversedVoiceType[voice || '']}
-
模型 {LLMConfig.ModelName}
+
+ {modelMode === MODEL_MODE.ORIGINAL
+ ? `模型 ${LLMConfig.ModelName}`
+ : `模型来源 ${SourceName[modelMode]}`}
+