前提条件

开发环境

详见 面板小程序 > 搭建环境

产品名称:AI 毛绒玩具

需求原型

创建智能体

准备好 智能生活 OEM App 和智能产品(可选)后,需要在 智能体平台 上创建智能体。

  1. 单击 创建 Agent 按钮,填入项目相关必选信息后,单击 确定 创建一个智能体。

  1. 依照您的需求,在 01 模型能力配置 下定制智能体的模型、记忆、工具、知识库及工作流等功能,再在 02 提示词开发 下配置提示词(Prompt),完成当前数据中心的智能体创建。

调试智能体

  1. 配置完成 Prompt 后,单击 获取调试二维码 按钮获取二维码。

  1. 调试完成后,按照您的需要分别配置不同数据中心的智能体,然后单击 发布,并配置版本号,将智能体发布至线上环境。

  1. 发布完成后,单击 上架 将智能体上架到平台账号下,用于后续产品的绑定。

  1. 完成上述步骤后返回智能体列表页面,可以看到您刚刚创建的专属智能体。

由于产品定义了面板和设备所拥有的功能点与智能体,所以在开发一个智能设备面板之前,首先需要创建一个 AI 毛绒玩具产品,定义产品有哪些功能点、携带哪些智能体,然后再在面板中一一实现这些功能点。

首先,注册登录 涂鸦开发者平台,并在平台创建产品:

  1. 在涂鸦开发者平台产品类目下,单击 创建产品

  1. 在标准类目下选择 影音穿戴 > AI 毛绒玩具

  1. 选择 智能化方式产品方案,完善产品信息,单击 创建产品

  1. 添加标准功能 页面,选择适合硬件方案维度的功能点,单击 确定

  1. 完成以上步骤后,您已经完成了产品的创建。接下来,在 01 功能定义 > 产品 AI 功能 下继续添加产品 AI 功能。

  1. 添加产品 AI 能力:单击 添加产品 AI 能力 来选择您的产品需要使用的 AI 能力,选择完成后单击 确定

  1. 选择产品 AI Agent:选择为产品配置智能体(本模板以设备端智能体为例),单击 选择智能体 进入添加智能体页面,进行设备默认智能体配置。

  1. 在智能体列表中选择要与设备绑定的默认智能体。

  1. 单个设备可在默认智能体下维护多个扩展智能体,以扩充设备的 AI 能力,单击 扩展智能体 进入扩展智能体管理页面。

  1. 扩展智能体管理 页面中,可以自定义设备的更多智能体能力。

  1. 完成智能体绑定配置后,单击 下一步 设备交互 进入交互设置等一系列操作,后续操作可参考 产品开发指南

开发者平台创建面板小程序

面板小程序的开发在 小程序开发者 平台上进行操作,首先请前往 小程序开发者平台 完成平台的注册登录。

详细操作步骤可以参考 创建面板小程序

IDE 基于模板创建项目工程

打开 IDE 创建一个基于 AI 毛绒玩具小程序模板 的面板小程序项目,需要在 Tuya MiniApp IDE 上操作。

详细操作步骤可以参考 初始化项目工程

完成以上步骤后,一个面板小程序的开发模板初始化完成。以下为工程目录的介绍:

├── src
│  	├── api // 面板所有云端 API 请求聚合文件
│  	├── components
│  	│   ├── AICard // 智能体卡片组件
│  	│   ├── DialogConfirm // 确认弹窗组件
│  	│   ├── DialogInput // 文本输入弹窗组件
│  	│   ├── DialogPicker // DP 选择弹窗组件
│  	│   ├── GridBattery // 电池电量组件
│  	│   ├── icon-font // svg 图标容器组件
│  	│   ├── Modal // 弹窗通用组件
│  	│   ├── NoData // 无数据兜底组件
│  	│   ├── PickerItem // 选择按钮通用组件
│  	│   ├── SearchBar // 搜索组件
│  	│   ├── Tag // 分类标签子组件
│  	│   ├── TagBar // 分类标签组件
│  	│   ├── Text // 通用文本组件
│  	│   ├── TopBar // 通用 TopBar 组件
│  	│   ├── TouchableOpacity // 通用按钮组件
│  	├── constant
│  	│   ├── dpCodes.ts // dpCode 常量
│  	│   ├── index.ts // 存放所有的常量配置
│  	├── devices // 设备模型
│  	├── hooks // hooks
│  	├── i18n // 多语言
│  	├── pages
│   │   ├── AIAgentEdit // 单个智能体编辑页面
│   │   ├── AIDialogue // 智能体会话+智能体广场页面
│   │   ├── CloneSetting // 音色克隆方式选择页面
│   │   ├── CloneVoice // 音色克隆操作页面
│   │   ├── DialogHistory // 单个智能体会话历史记录页面
│   │   ├── home // 首页
│   │   ├── VoiceSetting // 单个音色编辑页面
│   │   ├── VoiceSquare // 音色广场页面
│  	├── redux // redux
│   ├── res // 图片资源 & svg 相关
│   ├── styles // 全局样式
│   ├── types // 全局类型定义
│   ├── utils // 业务常用工具方法
│   ├── app.config.ts
│   ├── app.less
│   ├── app.tsx
│   ├── composeLayout.tsx // 处理监听子设备添加、解绑、DP 变化等
│   ├── global.config.ts
│   ├── mixins.less // less mixins
│   ├── routes.config.ts // 配置路由
│   ├── variables.less // less variables

获取智能体的分类标签列表

下文介绍如何获取涂鸦开发者平台配置智能体的分类标签列表。

功能展示

功能说明

上述标签列表是设备在涂鸦开发者平台智能体角色列表中各元素标签字段的聚合数据集。 当用户在平台新增、替换智能体角色标签后,通过以下方法可实时更新到标签聚合列表,然后通过标签字段便可对平台上所配置的智能体角色进行分类管理、展示。(通过标签列表获取到的智能体角色列表在后续的介绍中,本文中暂时统称为"智能体广场"。) 同时,在列表模版中加入了面板业务专属的会话标签。当 C 端用户将分类标签下的智能体角色添加到对话后,会话标签将展示小红点,提示用户有新的会话卡片产生,并且对应的智能体角色会话内容将自动划分到会话标签下,点击会话标签后便可查看相关信息。

相关代码段

// 获取涂鸦开发者平台标签列表
import {
  ...
  getAIAgentTagList,
} from '@ray-js/ray';

const [tags, setTags] = useState([]);

useEffect(() => {
    const tags = [{ text: Strings.getLang('dsc_tags_dialog'), key: 'dialog' }];
    getAIAgentTagList({ lang: 'zh' })
      .then(res => {
        // eslint-disable-next-line no-restricted-syntax
        for (const [key, text] of Object.entries(res)) {
          tags.push({ text, key });
        }
        setTags(tags);
      })
      .catch(err => {
        console.warn(`${JSON.stringify(err)}`);
        setTags([
          { text: Strings.getLang('dsc_tags_dialog'), key: 'dialog' },
          { text: Strings.getLang('dsc_tags_recommend'), key: 'recommend' },
          { text: Strings.getLang('dsc_tags_study'), key: 'study' },
          { text: Strings.getLang('dsc_tags_child'), key: 'child' },
          { text: Strings.getLang('dsc_tags_life'), key: 'life' },
          { text: Strings.getLang('dsc_tags_entertainment'), key: 'entertainment' },
        ]);
      });
  }, []);

获取智能体列表

下文介绍如何获取在涂鸦开发者平台配置的智能体的列表。

功能展示

功能说明

在获取到智能体角色标签列表后,便可将标签 key 作为参数,使用 getAIAgentMarketList API 方法获取单个标签下的智能体角色列表,该 API 已支持分页获取数据。

相关代码段

/**
 * 获取智能体市场分页列表
 */
export const getAIAgentList = (data: GetAIAgentListParams) => {
  return getAIAgentMarketList(data);
};

// 智能体列表
const [AIAgentPageList, setAIAgentPageList] = useState([] as Array<AgentListItem>);

// 智能体列表请求函数
const getAIAgentListFunc = useCallback((params: GetListParams) => {
    return new Promise((resolve, reject) => {
      getAIAgentList({
        devId: getDevId(),
        tag: params.tag,
        keyWord: '',
        pageNo: params.current,
        pageSize: params.pageSize,
      })
        .then((res: ListRes<AIAgent>) => {
          const { totalPage, list } = res;
          resolve({
            total: totalPage,
            list,
          });
        })
        .catch(error => {
          reject(error);
        });
    });
}, []);


// 智能体列表请求管理
  const {
    pagination: paginationSquare,
    data: dataSquare,
    run: runSquare,
  } = usePagination(
    ({ current, pageSize, tag }) =>
      getAIAgentListFunc({ current, pageSize, tag }) as Promise<GetAgentListRes>,
    {
      manual: true,
    }
  );

  // 响应智能体广场懒加载
  useEffect(() => {
    if (dataSquare?.list && paginationSquare.current > 1) {
      setAIAgentPageList([...AIAgentPageList, ...dataSquare?.list]);
    } else if (dataSquare?.list && paginationSquare.current <= 1) {
      setAIAgentPageList([...dataSquare?.list]);
    }
  }, [dataSquare, tag]);

添加智能体到对话

下文介绍如何将智能体添加到对话中。

功能展示

功能说明

智能体广场中的智能体角色卡片带有一个 添加到对话 的按钮,点击按钮后,智能体卡片将会复制为会话卡片,并出现在会话分类标签下(小红点中的数字代表已将相应数量的智能体角色新增到了会话标签下)。

相关代码段

/**
 * 添加端点智能体
 */
export const addAgentEndpoint = (data: {
  devId: string,
  agentId: number,
  wakeWord?: string,
}) => {
  return bindAIAgentEndpoint(data);
};

addAgentEndpoint({ devId: getDevId(), agentId: item.id })
  .then(() => {
    showToast({
      title: Strings.getLang(`AICardMenu_0_success`),
    });
    // 找到目标 Agent,修改 added 为 true
    const newAIAgentPageList = AIAgentPageList.map((element) => {
      if (item.id === element.id) {
        return {
          ...element,
          added: true,
        };
      }
      return element;
    });
    setAIAgentPageList(newAIAgentPageList);
    emitter.emit("refreshDialogData", "");
    setTimeout(() => {
      setIsBackToTop(true);
    }, 600);
  })
  .catch((err) => {
    console.warn(err);
  });

查询智能体会话列表

下文介绍如何查询智能体会话列表。

功能展示

功能说明

点击会话标签后便可查看所有会话卡片;点击会话卡片可跳转至会话记录页面查看历史对话记录;长按会话卡片便可删除会话卡片,删除会话卡片后,可在智能体广场中重新将智能体角色复制到会话标签下。

相关代码段

// 智能体会话 redux 数据
const { list, boundList } = useSelector(selectAgentList);

// 生成适配 usePagination的 接口请求函数,用于获取未绑定智能体会话列表
const getAgentListFunc = useCallback((params: GetListParams) => {
    return new Promise((resolve, reject) => {
      getUnboundAgent({ pageNo: params.current, pageSize: params.pageSize })
        .then((res: AgentListRes) => {
          const { totalPage, list } = res;
          console.log('==list', list);

          resolve({
            total: totalPage,
            list,
          });
        })
        .catch(error => {
          reject(error);
        });
    });
  }, []);

// 管理智能体会话请求
const { pagination, data, loading, run } = usePagination(
    ({ current, pageSize, tag }) =>
      getAgentListFunc({ current, pageSize, tag }) as Promise<GetAgentListRes>,
    {
      manual: true,
    }
);

// 响应会话懒加载
useEffect(() => {
    if (data?.list && pagination.current > 1) {
      dispatch(updateAgentList([...data?.list]));
    } else if (data?.list && pagination.current <= 1) {
      isRefresh
        ? dispatch(refreshAgentList([...data?.list]))
        : dispatch(initAgentList([...data?.list]));
    }
}, [data, tag]);

将智能体与设备进行绑定

下文介绍如何将智能体与设备进行绑定。

功能展示

功能说明

点击智能体会话卡片右上角的 绑定到设备 按钮后,智能体角色将和设备进行绑定,面板顶部的默认智能体信息展示区域将会自动切换为该智能体角色的对应内容,同时该智能体会话卡片将自动排序为第一个。完成上述绑定操作后,C 端用户在与设备交互时,将感知到最新的智能体角色相关内容。

相关代码段

const agentInfo = (await getAgentInfo(item.id)) as AgentInfo;
const {
  voiceId = '',
  speed = 1.2,
  tone = 1.1,
  lang = 'zh',
  keepChat = true,
} = agentInfo ?? {};

editAgentInfo({
  voiceId,
  speed,
  tone,
  lang,
  keepChat,
  isMain: !isBound,
  endpointAgentId: item.id,
}).then(async () => {
  setTimeout(async () => {
    dispatch(updateAgentInfo({ ...agentInfo, endpointAgentId: item.id }));
  }, 1000);
  emitter.emit('refreshDialogData', '');
  ty.hideLoading();
  ty.showToast({
    title: Strings.getLang('dsc_edit_success'),
    icon: 'success',
  });
}).catch(() => {
  ty.hideLoading();
  ty.showToast({
    title: Strings.getLang('dsc_edit_fail'),
    icon: 'error',
  });
});

页面介绍

智能体会话记录展示页主要用于展示 C 端用户与智能体角色的对话记录,当 C 端用户暂未与智能体产生对话内容时,模版将会展示空内容提示图文。用户点击智能体会话记录的顶部的卡片后,便可跳转至智能体编辑页面。

相关代码段

// 获取三个月内的智能体会话记录
export const getDialogHistoryList = async (params: GetHistoryParams) => {
  try {
    const response = await getAIAgentHistory({
      devId: getDevInfo().devId,
      endpointAgentId: params.endpointAgentId,
      pageNo: params.pageNo,
      pageSize: params.pageSize,
      startTime: moment().subtract(3, 'months').format('YYYY-MM-DD HH:mm:ss'),
      endTime: moment().format('YYYY-MM-DD HH:mm:ss'),
    });
    return response;
  } catch (err) {
    return Promise.reject(err);
  }
};


const [historyList, setHistoryList] = useState([]);

const getDialogHistoryListFunc = useCallback((params: GetListParams) => {
    return new Promise((resolve, reject) => {
      getDialogHistoryList({
        pageNo: params.current,
        pageSize: params.pageSize,
        endpointAgentId: tagKey === 'dialog' ? id : endpointAgentId,
      })
        .then((res: DialogHistoryRes) => {
          const { totalPage, list } = res;
          resolve({
            total: totalPage,
            list,
          });
        })
        .catch(error => {
          reject(error);
        });
    });
  }, []);

  const { pagination, data, loading, run } = usePagination(
    ({ current, pageSize }) =>
      getDialogHistoryListFunc({ current, pageSize }) as Promise<GetDialogHistory>,
    {
      manual: true,
    }
  );

  useEffect(() => {
    if (data?.list && data?.list?.length > 0) {
      const { list } = data;
      const newList = [...list];
      newList.reverse();
      newList.push({ emptyId: `empty_${historyList.length}` });
      setTimeout(() => {
        setEmptyIdState(`empty_${historyList.length}`);
      }, 300);
      const tempList = [...newList, ...historyList];
      setHistoryList(tempList);
    }
  }, [data]);

页面介绍

智能体角色编辑页面主要用于编辑智能体角色相关信息,目前支持编辑智能体角色的音色、清除智能体角色的上下文信息、删除当前 C 端用户与该智能体角色的全部聊天记录等。

相关代码段

// 获取三个月内的智能体会话记录
export const getDialogHistoryList = async (params: GetHistoryParams) => {
  try {
    const response = await getAIAgentHistory({
      devId: getDevInfo().devId,
      endpointAgentId: params.endpointAgentId,
      pageNo: params.pageNo,
      pageSize: params.pageSize,
      startTime: moment().subtract(3, 'months').format('YYYY-MM-DD HH:mm:ss'),
      endTime: moment().format('YYYY-MM-DD HH:mm:ss'),
    });
    return response;
  } catch (err) {
    return Promise.reject(err);
  }
};


const [historyList, setHistoryList] = useState([]);

const getDialogHistoryListFunc = useCallback((params: GetListParams) => {
    return new Promise((resolve, reject) => {
      getDialogHistoryList({
        pageNo: params.current,
        pageSize: params.pageSize,
        endpointAgentId: tagKey === 'dialog' ? id : endpointAgentId,
      })
        .then((res: DialogHistoryRes) => {
          const { totalPage, list } = res;
          resolve({
            total: totalPage,
            list,
          });
        })
        .catch(error => {
          reject(error);
        });
    });
  }, []);

  const { pagination, data, loading, run } = usePagination(
    ({ current, pageSize }) =>
      getDialogHistoryListFunc({ current, pageSize }) as Promise<GetDialogHistory>,
    {
      manual: true,
    }
  );

  useEffect(() => {
    if (data?.list && data?.list?.length > 0) {
      const { list } = data;
      const newList = [...list];
      newList.reverse();
      newList.push({ emptyId: `empty_${historyList.length}` });
      setTimeout(() => {
        setEmptyIdState(`empty_${historyList.length}`);
      }, 300);
      const tempList = [...newList, ...historyList];
      setHistoryList(tempList);
    }
  }, [data]);

清除上下文

功能说明

该功能主要用于清除智能体角色上下文信息,初始化该智能体角色的聊天背景。

相关代码段

// 清除上下文接口
export const clearingContext = async (endpointAgentId: number) => {
  try {
    const response = await deleteAIAgentContext({
      devId: getDevInfo().devId,
      endpointAgentId,
    });
    return response;
  } catch (err) {
    return Promise.reject(err);
  }
};

const handleClearingContext = () => {
  ty.showLoading({
    title: "",
  });
  clearingContext(id)
    .then(() => {
      ty.hideLoading();
      ty.showToast({
        title: Strings.getLang("dsc_clearing_success"),
        icon: "success",
      });
    })
    .catch((error) => {
      ty.hideLoading();
      ty.showToast({
        title: Strings.getLang("dsc_clearing_fail"),
        icon: "error",
      });
    });
};

清除历史聊天记录

功能说明

该功能主要用于清除 C 端用户与智能体角色的聊天记录,初始化该智能体角色的聊天内容记录。

相关代码段

// 清除历史聊天记录接口
export const clearingHistoryRecord = async (endpointAgentId: number) => {
  try {
    const response = await deleteAIAgentHistory({
      devId: getDevInfo().devId,
      endpointAgentId,
    });
    return response;
  } catch (err) {
    return Promise.reject(err);
  }
};

const handleClearingHistoryRecord = () => {
  ty.showLoading({
    title: "",
  });
  clearingHistoryRecord(id)
    .then(() => {
      ty.hideLoading();
      ty.showToast({
        title: Strings.getLang("dsc_clearing_success"),
        icon: "success",
      });
      emitter.emit("refreshHistoryData", "");
    })
    .catch(() => {
      ty.hideLoading();
      ty.showToast({
        title: Strings.getLang("dsc_clearing_fail"),
        icon: "error",
      });
    });
};

页面介绍

音色管理页面主要用于切换智能体角色所携带的音色信息,主要包含用户克隆音色管理、系统默认音色管理。

音色克隆

功能展示

功能说明

C 端用户可以在 我的 音色分类中查看自己的克隆音色,若暂无克隆音色,可通过点击 克隆音色 按钮跳转至克隆音色页面开始克隆自己的专属音色。 克隆音色的途径共分为两种:

相关代码段

// 获取录音能力集
const RecorderManager = ty.getRecorderManager({
  complete: () => {
    console.log("==complete");
  },
  success: (params: null) => {
    console.log("===success==getRecorderManager", params);
  },
  fail: (params: null) => {
    console.log("===fail==getRecorderManager", params);
  },
});

// 开始录音
const startRecording = () => {
  RecorderManager.start({
    sampleRate: 32000,
    complete: () => {
      console.log("===startRecording==complete");
    },
    success: (params) => {
      // setFileData(params?.tempFilePath);
      setCloneState("recording");
      console.log("===startRecording==success", params);
    },
    fail: (params) => {
      console.log("===startRecording==fail", params);
      const { errorMsg } = params;
      ty.showToast({
        title: errorMsg,
        icon: "error",
      });
    },
  });
};

// 完成录音
const stopRecording = () => {
  RecorderManager.stop({
    complete: () => {
      console.log("===stopRecording==complete");
    },
    success: (params) => {
      ty.uploadImage({
        filePath: params?.tempFilePath,
        bizType: "voice_clone",
        contentType: "audio/mpeg",
        success: (params) => {
          const { publicUrl } = JSON.parse(params?.result);
          console.log("===uploadImage==success", params, publicUrl);
          setCloneState("update");
          ty.showToast({
            title: Strings.getLang("dsc_cloning_tips"),
            icon: "none",
            duration: 2000,
          });
          if (voiceId) {
            resetCloneVoice(
              voiceId,
              lang,
              publicUrl,
              cloneWay === "reciting"
                ? Strings.getLang("dsc_recording_text_content")
                : ""
            )
              .then(() => {
                checkCloneState();
              })
              .catch((error) => {
                console.log(error);
                checkCloneState();
              });
          } else {
            createCloneVoice(
              lang,
              publicUrl,
              cloneWay === "reciting"
                ? Strings.getLang("dsc_recording_text_content")
                : ""
              // '大家好,今天阳光明媚,心情格外舒畅。'
            )
              .then(() => {
                checkCloneState();
              })
              .catch(() => {
                checkCloneState();
              });
          }
        },
        fail: (params) => {
          console.log("===uploadImage==fail", params);
        },
      });
      setCloneState("update");
      console.log("===stopRecording==success", params);
    },
    fail: (params) => {
      console.log("===stopRecording==fail", params);
    },
  });
};

人声朗诵克隆

功能展示

功能说明

克隆流程如下:

录制音频克隆

功能展示

功能说明

音色广场列表展示

功能说明

音色广场列表主要用于展示系统默认音色列表,C 端用户可在广场内挑选适合自己的音色内容赋能给智能体角色。在筛选音色方面,模版支持分类筛选、搜索筛选功能。

相关代码段

// 获取克隆音色列表
export const getCloneVoiceList = async () => {
  try {
    const response = await getAITimbreCloneList({});
    return response;
  } catch (err) {
    return Promise.reject(err);
  }
};

// 获取标准音色列表
export const getStandardVoiceList = async (params: GetStandardVoiceParams) => {
  try {
    const response = await getAITimbreMarketList({
      devId: getDevInfo().devId,
      pageNo: params.pageNo,
      pageSize: params.pageSize,
      agentId: params.agentId,
      tag: params.tag,
      keyWord: params.keyWord,
    });
    return response;
  } catch (err) {
    return Promise.reject(err);
  }
};

// 音色列表请求函数(包含克隆音色列表)
const getVoiceListFunc = useCallback(
    (params: GetListParams) => {
      if (tag === 'mine') {
        return new Promise((resolve, reject) => {
          getCloneVoiceList()
            .then((res: VoiceRes) => {
              resolve({
                total: res?.length,
                list: res,
              });
            })
            .catch(error => {
              reject(error);
            });
        });
      }
      return new Promise((resolve, reject) => {
        getStandardVoiceList({
          pageNo: params.current,
          pageSize: params.pageSize,
          tag,
          agentId,
          keyWord: params.searchText,
        })
          .then((res: VoiceRes) => {
            const { totalPage, list } = res;
            resolve({
              total: totalPage,
              list,
            });
          })
          .catch(error => {
            reject(error);
          });
      });
    },
    [tag, agentId]
  );

 // 音色列表请求管理
  const { pagination, data, loading, run } = usePagination(
    ({ current, pageSize, searchText }) =>
      getVoiceListFunc({ current, pageSize, searchText }) as Promise<GetStandardVoice>,
    {
      manual: true,
    }
  );

  // 响应懒加载
  useEffect(() => {
    if (data?.list) {
      console.log('==data?.list', data?.list);

      pagination.current > 1
        ? dispatch(updateVoiceList([...data?.list]))
        : dispatch(initVoiceList([...data?.list]));
    }
  }, [data]);

音色搜索

相关代码段

// 搜索与查询智能体列表接口请求流程相同,区别在于【searchText】字段的不同,拉取整体列表使【searchText】字段为空字符串
const { pagination, data, loading, run } = usePagination(
    ({ current, pageSize, searchText }) =>
      getVoiceListFunc({ current, pageSize, searchText }) as Promise<GetStandardVoice>,
    {
      manual: true,
    }
  );

音色切换

相关代码段

// 切换音色(编辑智能体详情)
export const editAgentInfo = async (params: any) => {
  try {
    const response = await updateAIAgent({
      devId: getDevInfo().devId,
      ...params,
    });
    return response;
  } catch (err) {
    return Promise.reject(err);
  }
};

const handleItemChecked = (idKey: string) => {
    ty.showLoading({
      title: '',
    });
    const { speed, tone, lang, keepChat, isMain } = agentInfo;
    editAgentInfo({ ...agentInfo, endpointAgentId, voiceId: idKey })
      .then(async res => {
        if (res) {
          setTimeout(async () => {
            const agentCloudInfo = (await getAgentInfo(Number(endpointAgentId))) as AgentInfo;
            dispatch(
              updateAgentInfo({ ...agentCloudInfo, endpointAgentId: Number(endpointAgentId) })
            );
            ty.hideLoading();
            ty.showToast({
              title: Strings.getLang('dsc_choose_success'),
              icon: 'success',
            });
          }, 500);
        }
        setTimeout(() => {
          ty.hideLoading();
        }, 3000);
      })
      .catch(() => {
        setTimeout(() => {
          ty.hideLoading();
          ty.showToast({
            title: Strings.getLang('dsc_choose_fail'),
            icon: 'error',
          });
        }, 1000);
      });
  };

编辑音色

页面说明

该页面用于编辑音色具体内容,音色广场中的音色在此页面支持修改音色的语调、语速,克隆音色在该页面支持修改音色的语调、语速,重置、删除克隆音色等功能。

编辑语调、语速

相关代码段

// 编辑智能体详情(语调、语速为智能体详情信息)
export const editAgentInfo = async (params: any) => {
  try {
    const response = await updateAIAgent({
      devId: getDevInfo().devId,
      ...params,
    });
    return response;
  } catch (err) {
    return Promise.reject(err);
  }
};

const onChangeToneOrSpeed = (typeKey: 'speed' | 'tone', value: number) => {
    ty.showLoading({
      title: '',
    });
    editAgentInfo({ ...agentInfo, [typeKey]: value, endpointAgentId })
      .then(async () => {
        setTimeout(async () => {
          const agentInfo = (await getAgentInfo(Number(endpointAgentId))) as AgentInfo;
          dispatch(updateAgentInfo({ ...agentInfo, endpointAgentId: Number(endpointAgentId) }));
        }, 300);
        ty.hideLoading();
        ty.showToast({
          title: Strings.getLang('dsc_edit_success'),
          icon: 'success',
        });
      })
      .catch(() => {
        ty.hideLoading();
        ty.showToast({
          title: Strings.getLang('dsc_edit_fail'),
          icon: 'error',
        });
      });
  };

删除克隆音色

相关代码段

// 删除克隆音色

import { deleteAITimbreClone } from "@ray-js/ray";

export const deleteCloneVoice = async (voiceId: string) => {
  try {
    const response = await deleteAITimbreClone({
      voiceId,
      devId: getDevInfo().devId,
    });
    return response;
  } catch (err) {
    return Promise.reject(err);
  }
};

deleteCloneVoice(voiceId)
  .then(() => {
    ty.hideLoading();
    ty.showToast({
      title: Strings.getLang("dsc_delete_success"),
      icon: "success",
    });
    emitter.emit("refreshVoiceData", "");
    setTimeout(() => {
      router.back();
    }, 300);
  })
  .catch(() => {
    ty.hideLoading();
    ty.showToast({
      title: Strings.getLang("dsc_delete_fail"),
      icon: "error",
    });
  });

重置克隆音色

相关代码段

// 重置克隆音色主要关注 resetCloneVoice 方法其他流程与新增克隆音色相同

import { updateAITimbreClone } from "@ray-js/ray";

// 重置克隆音色接口
export const resetCloneVoice = async (
  voiceId: string,
  lang: string,
  voiceUrl: string,
  text: string
) => {
  try {
    const response = await updateAITimbreClone({
      devId: getDevInfo().devId,
      voiceId,
      lang,
      voiceUrl,
      text,
    });
    return response;
  } catch (err) {
    return Promise.reject(err);
  }
};

const stopRecording = () => {
  RecorderManager.stop({
    complete: () => {
      console.log("===stopRecording==complete");
    },
    success: (params) => {
      ty.uploadImage({
        filePath: params?.tempFilePath,
        bizType: "voice_clone",
        contentType: "audio/mpeg",
        success: (params) => {
          const { publicUrl } = JSON.parse(params?.result);
          console.log("===uploadImage==success", params, publicUrl);
          setCloneState("update");
          ty.showToast({
            title: Strings.getLang("dsc_cloning_tips"),
            icon: "none",
            duration: 2000,
          });
          if (voiceId) {
            resetCloneVoice(
              voiceId,
              lang,
              publicUrl,
              cloneWay === "reciting"
                ? Strings.getLang("dsc_recording_text_content")
                : ""
            )
              .then(() => {
                checkCloneState();
              })
              .catch((error) => {
                console.log(error);
                checkCloneState();
              });
          } else {
            createCloneVoice(
              lang,
              publicUrl,
              cloneWay === "reciting"
                ? Strings.getLang("dsc_recording_text_content")
                : ""
              // '大家好,今天阳光明媚,心情格外舒畅。'
            )
              .then(() => {
                checkCloneState();
              })
              .catch(() => {
                checkCloneState();
              });
          }
        },
        fail: (params) => {
          console.log("===uploadImage==fail", params);
        },
      });
      setCloneState("update");
      console.log("===stopRecording==success", params);
    },
    fail: (params) => {
      console.log("===stopRecording==fail", params);
    },
  });
};