SDM(Smart Device Model)
开发,关于 SDM
相关可以 查看 SDM 文档。详见 面板小程序 > 搭建环境。
产品名称:AI 毛绒玩具
准备好 智能生活 OEM App 和智能产品(可选)后,需要在 智能体平台 上创建智能体。
由于产品定义了面板和设备所拥有的功能点与智能体,所以在开发一个智能设备面板之前,首先需要创建一个 AI 毛绒玩具产品,定义产品有哪些功能点、携带哪些智能体,然后再在面板中一一实现这些功能点。
首先,注册登录 涂鸦开发者平台,并在平台创建产品:
面板小程序的开发在 小程序开发者 平台上进行操作,首先请前往 小程序开发者平台 完成平台的注册登录。
详细操作步骤可以参考 创建面板小程序。
打开 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);
},
});
};