Prerequisites

Development environment

For more information, see Panel MiniApp > Set up environment.

Product name: AI Agent

Requirement prototype

Create an agent

After the Smart Life OEM app and (optional) smart products are ready, you need to create an agent on the AI Agent Platform.

  1. Click Create Agent, fill in the required information related to the project, and then click OK to create an agent.
  2. In the section of 01 Model Capability Configuration, customize the model, max context messages, plugin, knowledge base, and workflow of the agent as needed. Then, in the 02 Prompt Development, configure the prompt. Thus, your agent is created in the current data center.

Debug the agent

  1. After configuring the prompt, click Retrieve QR Code.

  1. Set up agents in different data centers as needed, click Release, and then configure the version number to release the agent to the online environment.

  1. Click Publish to make the agent available under the platform account, to be bound with products later.

  1. After completing the above steps, go back to the agent list page. You can see the custom agent you just created.

A product defines the data points (DPs) of the associated panel and device. Before you develop a panel, you must create an AI toy product, define and enable the required DPs, and then implement these DPs on the panel.

Register and log in to the Tuya Developer Platform and create a product.

  1. Click Create.
  2. Click the Standard Category tab and choose Audio Wearables > AI Toy.
  3. Follow the prompts to select the smart mode and solution, complete the product information, and then click Create.
  4. On the page of Add Standard Function, select functions that suit the hardware solution and click OK.
  5. You have created a product. In the Function Definition tab, find Product AI Capabilities.
  6. Add product AI capabilities. Click Add Product AI Capabilities to select the AI capabilities your product needs. And then, click OK.
  7. Select AI agents. Configure agents for your product (this template uses the agent on devices as an example). Click Select AI Agent to configure default agents.
  8. Select the desired agent from the list, click > to bind it with your device, and then click OK.
  9. After binding the agents, click Next Step: Device Interaction at the bottom of the page.
  10. On the Device Interaction page, select a panel template. For subsequent operations, refer to the Product Development Guide.

Create panel miniapp on Smart MiniApp Developer Platform

Register and log in to the Smart MiniApp Developer Platform. For more information, see Create panel miniapp.

Create a project based on a template

Open Tuya MiniApp IDE and create a panel miniapp project based on the AI agent template.

For more information, see Initialize project.

By now, you have completed the initialization of the development template of a panel miniapp. The following section shows the project directories.

├── src
│  	├── api // Aggregate file of all cloud API requests of the panel
│  	├── components
│  	│   ├── AICard // Agent card component
│  	│   ├── DialogConfirm // Confirmation dialog component
│  	│   ├── DialogInput // Text input dialog component
│  	│   ├── DialogPicker // DP selection dialog component
│  	│   ├── GridBattery // Battery level component
│  	│   ├── icon-font // SVG icon container component
│  	│   ├── Modal // General dialog component
│  	│   ├── NoData // Fallback component when there is no data
│  	│   ├── PickerItem // General selection button component
│  	│   ├── SearchBar // Search bar component
│  	│   ├── Tag // Category tag subcomponent
│  	│   ├── TagBar // Category tag bar component
│  	│   ├── Text // General text component
│  	│   ├── TopBar // General top bar component
│  	│   ├── TouchableOpacity // General button component
│  	├── constant
│  	│   ├── dpCodes.ts // dpCode constant
│  	│   ├── index.ts // Stores all constant configurations
│  	├── devices // Device model
│  	├── hooks // Hooks
│  	├── i18n // Multilingual settings
│  	├── pages
│   │   ├── AIDialogue // Agent list page
│   │   ├── AvatarSelect // Select an avatar
│   │   ├── CloneSetting // Select the timbre cloning method
│   │   ├── CloneVoice // Clone the timbres
│   │   ├── CustomAgentEdit // Add and edit the agent roles
│   │   ├── DialogHistory // Show chat history of one agent
│   │   ├── home // Homepage
│   │   ├── VoiceSetting // Edit a single timbre
│   │   ├── VoiceSquare // Voice square
│  	├── redux // Redux
│   ├── res // Resources, such as pictures and SVG
│   ├── styles // Global style
│   ├── types // Define global types
│   ├── utils // Common utility methods
│   ├── app.config.ts
│   ├── app.less
│   ├── app.tsx
│   ├── composeLayout.tsx // Handle and listen for the adding, unbinding, and DP changes of sub-devices
│   ├── global.config.ts
│   ├── mixins.less // Less mixins
│   ├── routes.config.ts // Configure routing
│   ├── variables.less // Less variables

Get the list of roles

Features

Call the getAIAgentRoles API method to get the list of agent roles. Up to 10 roles can be obtained.

Code snippet

// Request the role list
export const getAIAgentRoles = async () => {
  try {
    // getAgentRoles
    const response = await getAI2AgentRoles({
      devId: getDevInfo().devId,
      pageNo: 1,
      pageSize: 10,
      panelCode: 'ai_platform',
    });
    return response;
  } catch (err) {
    return Promise.reject(err);
  }
};

const getBoundAgentsFunc = async () => {
  getAIAgentRoles()
    .then((res: any) => {
      const { list: boundAgentList } = res;
      console.log('==55boundAgentList', boundAgentList);
      dispatch(updateRoleList([...boundAgentList]));
      setRefresherTriggeredState(false);
    })
    .catch(err => {
      setInitLoading(false);
      console.log('getBoundAgentsFunc::err::', err);
      setRefresherTriggeredState(false);

    });
};

Bind an agent with a device

Features

Tap Bind to device in the top-right corner of the agent chat card, and the agent role will be bound with the device. The default agent information display area at the top of the panel will automatically switch to the corresponding content of this agent role. The agent chat card will be automatically sorted to the first position. After binding, users will perceive the latest content about the agent's role when interacting with the device.

Code snippet

export const bindAIAgentRoles = async (roleId: string) => {
  try {
    // bindAgentRoles
    const response = await bindAI2AgentRoles({
      devId: getDevInfo().devId,
      roleId,
    });
    return response;
  } catch (err) {
    return Promise.reject(err);
  }
};

const bindAIRole = (roleId: string, inBind: boolean) => {
  if (inBind) return;
  showLoading({
    title: '',
  });
  bindAIAgentRoles(roleId)
    .then(() => {
      hideLoading();
      showToast({
        title: Strings.getLang('dsc_binding_success'),
        icon: 'success',
      });
      emitter.emit('refreshDialogData', '');
    })
    .catch(() => {
      hideLoading();
      showToast({
        title: Strings.getLang('dsc_binding_fail'),
        icon: 'error',
      });
    });
};

Code snippet

export const deleteAIAgentRoles = async (roleId: string) => {
  try {
    // deleteAgentRoles
    const response = await deleteAI2AgentRoles({
      devId: getDevInfo().devId,
      roleId,
    });
    return response;
  } catch (err) {
    return Promise.reject(err);
  }
};

const deleteAIRole = (roleId: string) => {
  showLoading({
    title: '',
  });
  deleteAIAgentRoles(roleId)
    .then(() => {
      hideLoading();
      showToast({
        title: Strings.getLang('dsc_delete_success'),
        icon: 'success',
      });
      emitter.emit('refreshDialogData', '');
    })
    .catch((err) => {
      hideLoading();
      console.log('deleteAIAgentRoles::err::', err);
      showToast({
        title: Strings.getLang('dsc_delete_fail'),
        icon: 'error',
      });
    });
  setUnbindId('-1');
  setIsShowUnbindConfirm(false);
};

Page description

This chat history page shows the chat history between users and agent roles. When no chat content has been generated between the users and the agent, the template will show a placeholder graphic with a prompt. Users can tap the card at the top of the agent chat history to navigate to the agent editing page.

Code snippet

// Get the role's session history
export const getDialogHistoryList = async (params: GetHistoryParams) => {
  try {
    // getAIAgentHistory
    const response = await getAI2AgentEndpointHistory({
      devId: getDevInfo().devId,
      roleId: params.roleId,
      pageNo: params.pageNo,
      pageSize: params.pageSize,
      startTime: dayjs().subtract(3, 'months').format('YYYY-MM-DD HH:mm:ss'),
      endTime: dayjs().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,
        roleId,
        startTime: params.startTime,
        endTime: params.endTime,
      })
        .then((res: DialogHistoryRes) => {
          const { totalPage, list } = res;
          resolve({
            total: totalPage,
            list,
          });
        })
        .catch(error => {
          reject(error);
        });
    });
  }, []);

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

Clear context

Features

Delete chat information for one or more groups of roles.

Code snippet

// Clear the context clearAgentHistoryMessage
export const clearAgentHistoryMessage = async (params: any) => {
  try {
    // deleteAIAgentHistoryMessage
    const response = await deleteAI2AgentEndpointHistory({
      devId: getDevInfo().devId,
      ...params,
    });
    return response;
  } catch (err) {
    return Promise.reject(err);
  }
};

const handleDeleteMessage = async () => {
  try {
    showLoading({ title: '' });
    await clearAgentHistoryMessage({
      roleId,
      requestIds: selectedItems?.join(','), // requestId is docId
    });

    const newList = historyList.filter(item => !selectedItems.includes(item.requestId));
    setHistoryList(newList);

    hideLoading();
    showToast({
      title: Strings.getLang('dsc_delete_chat_history_tip_new'),
      icon: 'success',
    });
  } catch (err) {
    hideLoading();
    showToast({
      title: Strings.getLang('dsc_delete_chat_history_failed'),
      icon: 'error',
    });
  }
};

Clear chat history

Features

This feature is used to clear the chat history between the user and the agent role and initialize the chat content history of this agent role.

Code snippet

// Clear chat history
export const clearingHistoryRecord = async (roleId: string) => {
  try {
    // deleteAIAgentHistory
    const response = await clearAI2AgentEndpointHistory({
      devId: getDevInfo().devId,
      roleId,
    });
    return response;
  } catch (err) {
    return Promise.reject(err);
  }
};

const handleClearingHistoryRecord = () => {
  showModal({
    title: '',
    content: Strings.getLang('dsc_delete_chat_history'),
    confirmText: Strings.getLang('delete'),
    cancelText: Strings.getLang('dsc_cancel'),
    success: ({ confirm }) => {
      if (confirm) {
        showLoading({
          title: '',
        });
        clearingHistoryRecord(roleId)
          .then(() => {
            hideLoading();
            showToast({
              title: Strings.getLang('dsc_delete_chat_history_tip'),
              icon: 'success',
            });
            navigateBack({});
            setTimeout(() => {
              emitter.emit('refreshHistoryData', '');
            }, 2000);
          })
          .catch(() => {
            hideLoading();
            showToast({
              title: Strings.getLang('dsc_delete_chat_history_failed'),
              icon: 'error',
            });
          });
      }
    },
  });
};

Page description

Modify the role information, including avatar, name, role introduction, and max context messages. Also, select the timbre, language, and large language model.

Get role template list/details

Features

You can provide pre-configured templates to users to streamline the operation process.

Code snippet

// Get a list of role templates
export const getAIAgentRolesTemplateList = async () => {
  try {
    // getAgentRolesTemplates
    const response = await getAI2AgentRolesTemplates({ devId: getDevInfo().devId, panelCode: 'ai_platform' });

    return response;
  } catch (err) {
    return Promise.reject(err);
  }
};

const getTemplateList = async () => {
  getAIAgentRolesTemplateList()
    .then((res: any) => {
      console.log('==getTemplateList', res);
      setTemplateList(res);
      if (res?.length > 0) {
        setTemplateRoleId(res[0].roleId);
      }
    })
    .catch(err => {
      console.log('getTemplateList::err::', err);
    });
};
// Get role template details
export const getAIAgentRolesTemplatesDetail = async (roleId: string) => {
  try {
    // getAgentRolesTemplatesDetail
    const response = await getAI2AgentRolesTemplatesDetail({ devId: getDevInfo().devId, roleId });

    return response;
  } catch (err) {
    return Promise.reject(err);
  }
};

const getTemplateListDetail = async (roleId: string) => {
  getAIAgentRolesTemplatesDetail(roleId)
    .then((res: any) => {
      console.log('==getTemplateListDetail', res);
      initRoleData(res);
      initRoleInfoData();
    })
    .catch(err => {
      console.log('getTemplateListDetail::err::', err, roleId);
    });
};

Get role details

Features

When editing a role, get the details of the specified role.

Code snippet

export const getAIAgentRoleDetail = async (roleId: string) => {
  try {
    // getAgentRolesDetail
    const response = await getAI2AgentRolesDetail({
      devId: getDevInfo().devId,
      roleId,
    });
    return response;
  } catch (err) {
    return Promise.reject(err);
  }
};

const getAgentRoleDetail = async (roleId: string) => {
  getAIAgentRoleDetail(roleId)
    .then((res: any) => {
      console.log('==getAIAgentRoleDetail', res);
      initRoleData(res);
      initRoleInfoData();
    })
    .catch(err => {
      console.log('getAIAgentRoleDetail::err::', err);
    });
};

Create/edit a role

Features

Create or edit the specified role.

Code snippet

// Create a role
export const createRole = async (params: any) => {
  try {
    // postCreateAgentRole
    const response = await addAI2AgentRoles({
      devId: getDevInfo().devId,
      ...params,
    });
    return response;
  } catch (err) {
    return Promise.reject(err);
  }
};
// Edit the role
export const updateRole = async (params: any) => {
  try {
    // postUpdateAgentRole
    const response = await updateAI2AgentRoles({
      devId: getDevInfo().devId,
      ...params,
    });
    return response;
  } catch (err) {
    return Promise.reject(err);
  }
};

const handleSave = async () => {
  if (!name || nameErrorMessage) {
    showToast({
      title: `${Strings.getLang('please_complete')}${Strings.getLang('role_name')}`,
      icon: 'error',
    });
    return;
  }
  if (!introduction) {
    showToast({
      title: `${Strings.getLang('please_complete')}${Strings.getLang('introduction')}`,
      icon: 'error',
    });
    return;
  }
  if (!roleInfo?.voiceId && !selectedVoiceId) {
    showToast({
      title: `${Strings.getLang('please_complete')}${Strings.getLang('role_voice')}`,
      icon: 'error',
    });
    return;
  }
  if (!selectedModel) {
    showToast({
      title: `${Strings.getLang('please_complete')}${Strings.getLang('model')}`,
      icon: 'error',
    });
    return;
  }

  const params = {
    roleName: name,
    roleIntroduce: introduction,
    roleImgUrl: roleInfo?.roleImgUrl || roleImgUrl,
    useLangCode: roleInfo?.useLangId || selectedLanguageCode,
    useTimbreId: roleInfo?.voiceId || selectedVoiceId,
    useLlmId: selectedModel,
    memoryInfo: memoryInfo,
  }

  try {
    if (roleId) {
      params.roleId = roleId
      await updateRole(params);
    } else {
      await createRole(params);
    }
    hideLoading();
    showToast({
      title: Strings.getLang('save_success'),
      icon: 'success',
    });

    emitter.emit('refreshDialogData', '');
    navigateBack({ delta: 9999 });
  } catch (error) {
    console.log('createRole/updateRole:::', error);
    hideLoading();
    showToast({
      title: Strings.getLang('save_failed'),
      icon: 'error',
    });
  }
};

Get role avatar

Features

You can provide pre-configured avatars for users to choose from.

Code snippet

// Get an avatar
export const getAIAvatars = async () => {
  try {
    // getAgentAvatars
    const response = await getAI2AgentAvatars({
      devId: getDevInfo().devId,
    });
    return response;
  } catch (err) {
    return Promise.reject(err);
  }
};

const getBoundAgentsFunc = async () => {
  getAIAvatars()
    .then(res => {
      console.log('getAIAvatars::', res);
      setAvatarList(res);
    })
    .catch(err => {
      console.log('getAIAvatars::err::', err);
    });
};

Get languages

Features

Get the supported languages.

Code snippet

// Get supported languages
export const getAgentLanguages = async () => {
  try {
    // getAgentLanguageConfig
    const response = await getAI2AgentConfigLangList({
      devId: getDevInfo().devId,
    });
    return response;
  } catch (err) {
    return Promise.reject(err);
  }
};

const useAgentLanguages = (id: number) => {
  const [langRangeList, setSupportLangs] = useState<Array<{ key: string; dataString: string }>>([]);

  useEffect(() => {
    const fetchSupportedLangs = async () => {
      try {
        const response = await getAgentLanguages();
        if (response) {
          setSupportLangs(
            response?.map(item => ({
              dataString: item?.langName,
              key: item?.langCode,
            }))
          );
        }
      } catch (error) {
        console.error('Failed to fetch supported languages:', error);
      }
    };

    fetchSupportedLangs();
  }, []);

  return { langRangeList };
};

import useAgentLanguages from '@/hooks/useAgentLanguages';
const { langRangeList } = useAgentLanguages(id);

Get model list

Features

Get the list of large language models.

Code snippet

// Get a list of large language models
export const getAgentModels = async () => {
  try {
    // getAgentModelConfig
    const response = await getAI2AgentConfigLlmList({
      devId: getDevInfo().devId,
    });
    return response;
  } catch (err) {
    return Promise.reject(err);
  }
};

const fetchSupportedModels = async () => {
  try {
    const response = await getAgentModels();
    console.log('getAgentModels:::', response);
    if (response) {
      const models = response?.map(model => ({
        key: model.llmId,
        dataString: model.llmName,
      }));
      setModelList(models);
    }
  } catch (error) {
    console.error('Failed to fetch supported models:', error);
  }
};

Page description

Switch the timbre information carried by the specified agent role. Manage user-cloned timbres and system default timbres.

Clone timbres

Features

Users can view their cloned timbres in the Mine category. If no cloned timbre is currently available, click Clone voice to navigate to the cloning page and begin creating your personalized timbre.

Code snippet

// Get the recording capability set
const RecorderManager = getRecorderManager({
  complete: () => {
    console.log('==complete');
  },
  success: (params: null) => {
    console.log('===success==getRecorderManager', params);
  },
  fail: (params: null) => {
    console.log('===fail==getRecorderManager', params);
  },
});

// Start recording
const startRecording = async () => {
  if (!RecorderManagerRef.current) {
    RecorderManager?.start?.({
      sampleRate: 16000,
      encodeBitRate: 96000,
      frameSize: 10,
      format: 'wav' as any,
      complete: () => {
        console.log('===startRecording==complete');
      },
      success: params => {
        console.log('===startRecording==success', params);
        RecorderManagerRef.current = true;
      },
      fail: params => {
        console.log('===startRecording==fail', params);
        resetAll();
      },
    });
  }
  try {
    ty.createAIAssistant({
      complete: () => {
        console.log('createAIAssistant==complete');
      },
      success: (params: null) => {
        console.log('createAIAssistant===success==getRecorderManager', params);
      },
      fail: (params: null) => {
        console.log('createAIAssistant===fail==getRecorderManager', params);
      },
    });
    const authorize = await Asr.authorize();
    if (!authorize) {
      resetAll();
      showToast({
        title: Strings.getLang('no_asr_permission'),
        icon: 'error',
      });
      return;
    }
    // if (!asrRef.current) {
    // Create ASR detector
    const asr = createAsrDetector();
    // Start ASR
    asrRef.current = asr;
    // }
    await asrRef.current?.start();
    // Initialization is successful, you can start recording
    changeAsrInted(true);
    timer.current = setInterval(() => {
      timerCheck();
    }, 1000);
  } catch (error) {
    console.error('Error starting ASR:', error);
    stopTouch();
    showToast({
      title: Strings.getLang('dsc_clone_fail'),
      icon: 'error',
    });
    clearAsr();
  }
};

// Stop recording
const stopRecording = async () => {
  RecorderManager?.stop?.({
    complete: () => {
      console.log('===stopRecording==complete');
      // setCloneState('start');
      RecorderManagerRef.current = false;
      clearAsr();
    },
    success: params => {
      console.log('params?.tempFilePath', params?.tempFilePath);
      try {
        uploadImage({
          filePath: params?.tempFilePath,
          bizType: 'voice_clone',
          contentType: 'audio/mpeg',
          success: params => {
            const { publicUrl } = JSON.parse(params?.result);
            console.log('publicUrl', publicUrl);
            submitVoice(publicUrl);
          },
          fail: params => {
            console.log('===uploadImage==fail', params);
            clearAsr();
            resetAll();
          },
        });
        console.log('uploadImage:::success:::params', params);
      } catch (err) {
        console.log('catch uploadImage err:', err);
      }
    },
    fail: params => {
      console.log('===stopRecording==fail', params);
      clearAsr();
      resetAll();
    },
  });
};

Clone by reciting text

Demonstration

Features

How it works:

  1. Users tap the panel to enter the voice cloning page and select Reciting text.
  2. Tap the record button and recite the specified text aloud. After the record button is tapped again, the cloning will begin.
  3. After completing the cloning process, users can view, switch, and edit the cloned timbre in the timbre list.
  4. After switching to the cloned timbre, hold down the device's voice button to wake up the device and start a voice chat. At this point, the device can use the cloned timbre to communicate with the user.

Clone audio recorded on a phone

Demonstration

Features

  1. Users tap the panel to enter the cloning page and select Directly record existing audio.
  2. Tap the record button and play the existing audio (up to 15 seconds). After the record button is tapped again, the cloning will begin.
  3. After completing the cloning process, users can view, switch, and edit the cloned timbre in the timbre list.
  4. After switching to the cloned timbre, hold down the device's voice button to wake up the device and start a voice chat. At this point, the device can use the cloned timbre to communicate with the user.

Show timbres on the square

Features

The list on the square shows the system's default timbres. Users can select suitable timbres from the square to empower the agent role. Users can filter the timbres by category or search for desired ones.

Code snippet

// Get a list of cloned timbres
export const getCloneVoiceList = async (params: any) => {
  try {
    // getCloneList
    const response = await getAI2TimbreCloneList({ devId: getDevInfo().devId, ...params });

    return response;
  } catch (err) {
    return Promise.reject(err);
  }
};

// Get a list of standard timbres
export const getStandardVoiceList = async (params: any) => {
  try {
    // getMarketList
    const response = await getAI2TimbreMarketList({
      devId: getDevInfo().devId,
      pageNo: params.pageNo,
      pageSize: params.pageSize,
      tag: '',
      keyWord: params.keyWord,
      lang: params.lang,
    });
    return response;
  } catch (err) {
    return Promise.reject(err);
  }
};

// Request a list of timbres (including cloned timbres)
const getVoiceListFunc = (params: GetListParams) => {
  return new Promise((resolve, reject) => {
    getStandardVoiceList({
      pageNo: params.current,
      pageSize: params.pageSize,
      tag,
      agentId,
      keyWord: params.searchText,
      lang: selectedLang,
    })
      .then((res: VoiceRes) => {
        const { totalPage, list = [] } = res;
        hideLoading();
        setLoading(false);
        resolve({
          total: totalPage,
          list,
        });
      })
      .catch(error => {
        hideLoading();
        setLoading(false);
        reject(error);
      });
  });
};
const initClone = () => {
  getCloneVoiceList({ lang: selectedLang })
    .then(res => {
      hideLoading();
      setResList(res);
      setLoading(false);
    })
    .catch(error => {
      hideLoading();
      setLoading(false);
    });
};

 // Manage timbre list requests
const { pagination, data, run } = usePagination(
  ({ current, pageSize, searchText }) =>
    getVoiceListFunc({ current, pageSize, searchText }) as Promise<GetStandardVoice>,
  {
    manual: true,
  }
);

// Response lazy loading
useEffect(() => {
  if (data?.list) {
    pagination.current > 1
      ? dispatch(updateVoiceList([...data?.list]))
      : dispatch(initVoiceList([...data?.list]));
  }
}, [data]);

Search for timbres

Code snippet

// The request process for searching for timbres is the same as that for querying the agent list. The difference lies in the [searchText] field. In order to pull the entire list, make the [searchText] field an empty string.
const { pagination, data, run } = usePagination(
  ({ current, pageSize, searchText }) =>
    getVoiceListFunc({ current, pageSize, searchText }) as Promise<GetStandardVoice>,
  {
    manual: true,
  }
);

Switch to another timbre

Code snippet

// Switch to another timbre (Edit agent details)
export const updateRole = async (params: any) => {
  try {
    // postUpdateAgentRole
    const response = await updateAI2AgentRoles({
      devId: getDevInfo().devId,
      ...params,
    });
    return response;
  } catch (err) {
    return Promise.reject(err);
  }
};

const handleItemChecked = async (idKey: string, item: any) => {
  try {
    if (tag === 'mine') {
      showLoading({ title: Strings.getLang('clone_voice_generating') });
      const cloneMp3 = await localGetCloneRadioSource({
        objectKey: item?.demoUrl || undefined,
        voiceId: idKey,
        lang: selectedLang,
        // lang: finalLanguage,
      }).catch(err => {
        hideLoading();
      });
      setTimeout(() => {
        hideLoading();
      }, 500);
      if (cloneMp3) {
        playVoiceAudio(cloneMp3);
      }
    } else {
      playVoiceAudio(item?.demoUrl);
    }
    if (squareEntry === 'role') {
      dispatch(
        updateRoleInfo({
          voiceId: idKey,
          voiceName: item?.voiceName,
          supportLangs: item?.supportLangs,
        })
      );

      return;
    }
    if (tag !== 'mine') {
      showLoading({
        title: '',
      });
    }
    const params = { ...roleInfo, useTimbreId: idKey, roleId };
    if (squareEntry === 'role') {
      delete params.roleId;
    }
    updateRole(params)
      .then(async res => {
        if (res) {
          dispatch(updateRoleInfo({ voiceId: idKey, voiceName: item?.voiceName }));

          setTimeout(async () => {
            const agentCloudInfo = (await getAgentInfo(roleId)) as AgentInfo;
            dispatch(updateAgentInfo({ ...agentCloudInfo, roleId }));
            hideLoading();
          }, 500);
        }
        hideLoading();
      })
      .catch(error => {
        if (platform === 'android' && error?.innerError?.errorCode === '13890100') {
          showToast({
            title: error?.innerError?.errorMsg,
            icon: 'error',
          });
        } else if (platform === 'ios') {
          showToast({
            title: iOSExtractErrorMessage(error?.innerError?.errorMsg),
            icon: 'error',
          });
        } else {
          showToast({
            title: Strings.getLang('dsc_choose_fail'),
            icon: 'error',
          });
        }
        hideLoading();
      });
  } catch (err) {
    console.log('handleItemChecked:::err', err);
    hideLoading();
  }
};

Edit timbres

Page description

On this page, you can edit specific details of a timbre. Regarding timbres on the square, their speed can be modified on this page. Regarding cloned ones, additional functionalities are supported, such as modifying speed, re-cloning, and deleting them.

Edit pitch and speed

Code snippet

// Edit agent details (including intonation and speaking speed)
export const updateRole = async (params: any) => {
  try {
    // postUpdateAgentRole
    const response = await updateAI2AgentRoles({
      devId: getDevInfo().devId,
      ...params,
    });
    return response;
  } catch (err) {
    return Promise.reject(err);
  }
};

const onChangeToneOrSpeed = (sv: number, tv: number) => {
  showLoading({
    title: '',
  });
  updateRole({ roleId, [speedKey]: `${sv}`, [toneKey]: `${tv}` })
    .then(async res => {
      sv >= 0 && setSpeedValue(sv);
      tv >= 0 && setToneValue(tv);
      hideLoading();
      showToast({
        title: Strings.getLang('dsc_edit_success'),
        icon: 'success',
      });
    })
    .catch(() => {
      hideLoading();
      showToast({
        title: Strings.getLang('dsc_edit_fail'),
        icon: 'error',
      });
    });
};

Restore the default pitch and speed

Code snippet

// Restore default settings (including intonation and speaking speed)
export const localResetToDefaultVoice = async (params: any) => {
  try {
    // resetToDefaultVoice
    const response = await restoreAI2AgentRolesSpeed({
      devId: getDevInfo().devId,
      ...params,
    });
    return response;
  } catch (err) {
    return Promise.reject(err);
  }
};

const onChangeToneOrSpeedReset = () => {
  showLoading({
    title: '',
  });
  localResetToDefaultVoice({ roleId })
    .then(async res => {
      const sv = res?.speed;
      const tv = res?.tone;
      sv >= 0 && setSpeedValue(sv);
      tv >= 0 && setToneValue(tv);
      hideLoading();
      showToast({
        title: Strings.getLang('dsc_edit_success'),
        icon: 'success',
      });
    })
    .catch(() => {
      hideLoading();
      showToast({
        title: Strings.getLang('dsc_edit_fail'),
        icon: 'error',
      });
    });
};

Rename a cloned timbre

Code snippet

// Edit the name of a cloned timbre
export const renameCloneVoice = async (voiceId: string, nameText: string) => {
  try {
    // renameAITimbreClone
    const response = await renameAI2TimbreClone({
      voiceId,
      name: nameText,
      devId: getDevInfo().devId,
    });
    return response;
  } catch (err) {
    return Promise.reject(err);
  }
};

const editCloneName = (text: string) => {
  showLoading({
    title: '',
  });
  renameCloneVoice(voiceId, text)
    .then(async res => {
      setTimeout(async () => {
        const agentCloudInfo = (await getAgentInfo(Number(roleId))) as AgentInfo;
        updateAgentInfo({ ...agentCloudInfo, roleId });
      }, 300);

      hideLoading();
      setNameText(text);
      showToast({
        title: Strings.getLang('dsc_edit_success'),
        icon: 'success',
      });
      emitter.emit('refreshVoiceData', '');
    })
    .catch(err => {
      hideLoading();
      showToast({
        title: Strings.getLang('dsc_edit_fail'),
        icon: 'error',
      });
    });
};

Delete a cloned timbre

Code snippet

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

const handleDeleteClone = () => {
  showLoading({
    title: '',
  });
  deleteCloneVoice(voiceId)
    .then(async () => {
      hideLoading();
      showToast({
        title: Strings.getLang('dsc_delete_success'),
        icon: 'success',
      });
      const agentCloudInfo = (await getAgentInfo(Number(roleId))) as AgentInfo;
      updateAgentInfo({ ...agentCloudInfo, roleId });
      emitter.emit('refreshVoiceData', '');
      setTimeout(() => {
        router.back();
      }, 300);
    })
    .catch(error => {
      hideLoading();
      if (platform === 'android' && error?.innerError?.errorCode === '138903140') {
        showToast({
          title: error?.innerError?.errorMsg,
          icon: 'error',
        });
      } else if (platform === 'ios') {
        showToast({
          title: iOSExtractErrorMessage(error?.innerError?.errorMsg),
          icon: 'error',
        });
      } else {
        showToast({
          title: Strings.getLang('dsc_delete_fail'),
          icon: 'error',
        });
      }
    });
};