Jelajahi Sumber

feat: 设置系统音色

王晓东 1 bulan lalu
induk
melakukan
fdcbaa149d
32 mengubah file dengan 612 tambahan dan 440 penghapusan
  1. 1 1
      src/app.config.ts
  2. 0 0
      src/components/AgentPage/components/AgentSwap/index.module.less
  3. 49 66
      src/components/AgentPage/components/AgentSwap/index.tsx
  4. 0 0
      src/components/AgentPage/components/SummaryBar/index.module.less
  5. 7 2
      src/components/AgentPage/components/SummaryBar/index.tsx
  6. 1 1
      src/components/AgentPage/components/agent-qrcode/index.module.less
  7. 66 0
      src/components/AgentPage/components/agent-qrcode/index.tsx
  8. 0 0
      src/components/AgentPage/index.module.less
  9. 63 0
      src/components/AgentPage/index.tsx
  10. 29 57
      src/components/voice-player-bar/index.tsx
  11. 69 0
      src/hooks/useAudioPlayer.ts
  12. 7 5
      src/hooks/useEditContactCard.ts
  13. 76 0
      src/pages/index/AgentNone.tsx
  14. 15 65
      src/pages/index/index.tsx
  15. 0 29
      src/pages/profile/components/agent-qrcode/index.tsx
  16. 0 10
      src/pages/profile/index.config.ts
  17. 0 93
      src/pages/profile/index.tsx
  18. 0 0
      src/pages/voice/components/MyVoiceList/components/index.module.less
  19. 0 0
      src/pages/voice/components/MyVoiceList/components/index.tsx
  20. 0 0
      src/pages/voice/components/MyVoiceList/components/popup-recorder/index.module.less
  21. 0 0
      src/pages/voice/components/MyVoiceList/components/popup-recorder/index.tsx
  22. 0 0
      src/pages/voice/components/MyVoiceList/components/popup-tryout/index.module.less
  23. 0 0
      src/pages/voice/components/MyVoiceList/components/popup-tryout/index.tsx
  24. 0 0
      src/pages/voice/components/MyVoiceList/index.module.less
  25. 20 33
      src/pages/voice/components/MyVoiceList/index.tsx
  26. 0 0
      src/pages/voice/components/VoiceList/index.module.less
  27. 9 27
      src/pages/voice/components/VoiceList/index.tsx
  28. 46 38
      src/pages/voice/index.tsx
  29. 28 8
      src/store/agentStore.ts
  30. 13 5
      src/types/agent.ts
  31. 65 0
      src/utils/audioPlayer.ts
  32. 48 0
      src/utils/upload.ts

+ 1 - 1
src/app.config.ts

@@ -1,6 +1,7 @@
 export default defineAppConfig({
   pages: [
     'pages/index/index',
+    // 'pages/profile/index',
     'pages/login/index',
     'pages/dashboard/index',
     'pages/contact/index',
@@ -8,7 +9,6 @@ export default defineAppConfig({
     'pages/knowledge-item/index',
     'pages/knowledge-item-editor/index',
     'pages/member/index',
-    'pages/profile/index',
     'pages/agent/index',
     'pages/editor-pages/editor-personality/index',
     'pages/editor-pages/editor-greeting/index',

+ 0 - 0
src/pages/profile/components/agent-swap/index.module.less → src/components/AgentPage/components/AgentSwap/index.module.less


+ 49 - 66
src/pages/profile/components/agent-swap/index.tsx → src/components/AgentPage/components/AgentSwap/index.tsx

@@ -11,11 +11,54 @@ import { useAppStore } from "@/store/appStore";
 import Popup from "@/components/popup/popup";
 import { useState } from "react";
 import WemetaTabs from "@/components/wemeta-tabs/index";
+import { useAgentStore } from "@/store/agentStore";
 interface IProps {
   show: boolean;
   setShow: (show: boolean) => void;
 }
 export default ({ show, setShow }: IProps) => {
+
+  const { agents } = useAgentStore()
+
+  const personalAgents = agents.filter( item => !item.isEnt)
+
+  const renderPersonalAgents = ()=> {
+    return personalAgents.map((item) => {
+      return <View className={item.isDefault ? style.cardActive : style.card}>
+        <View className="flex items-start mb-24">
+          <View className="flex flex-col flex-1">
+            <View className="flex items-end gap-8 text-gray-65">
+              <View className="text-20 font-medium leading-28 text-black">
+                {item.name}
+              </View>
+              <View className="text-12 leading-20">销售医师</View>
+            </View>
+            <View className="flex items-center gap-2">
+              <View className="text-12 leading-20 truncate max-w-[188px]">
+                北京茗视光眼科医院管理有限公司
+              </View>
+              <TagCertificated />
+            </View>
+          </View>
+        </View>
+        <View className={style.icons}>
+          <View className={style.icon}>
+            <IconPhoneGray />
+          </View>
+          <View className={style.icon}>
+            <IconMailGray />
+          </View>
+          <View className={style.icon}>
+            <IconLocationGray />
+          </View>
+          <View className={style.icon}>
+            <IconQRCodeGray />
+          </View>
+        </View>
+      </View>
+    })
+  }
+
   const tabList = [
     {
       key: "1",
@@ -24,7 +67,8 @@ export default ({ show, setShow }: IProps) => {
         <>
           <View className="pt-12">
             <View className={style.tabContainer}>
-              <View className={style.card}>
+              {renderPersonalAgents()}
+              {/* <View className={style.card}>
                 <View className="flex items-start mb-24">
                   <View className="flex flex-col flex-1">
                     <View className="flex items-end gap-8 text-gray-65">
@@ -55,71 +99,10 @@ export default ({ show, setShow }: IProps) => {
                     <IconQRCodeGray />
                   </View>
                 </View>
-              </View>
-              <View className={style.cardActive}>
-                <View className="flex items-start mb-24">
-                  <View className="flex flex-col flex-1">
-                    <View className="flex items-end gap-8 text-gray-65">
-                      <View className="text-20 font-medium leading-28 text-black">
-                        张三
-                      </View>
-                      <View className="text-12 leading-20">销售医师</View>
-                    </View>
-                    <View className="flex items-center gap-2">
-                      <View className="text-12 leading-20 truncate max-w-[188px]">
-                        北京茗视光眼科医院管理有限公司
-                      </View>
-                      <TagCertificated />
-                    </View>
-                  </View>
-                </View>
-                <View className={style.icons}>
-                  <View className={style.icon}>
-                    <IconPhoneGray />
-                  </View>
-                  <View className={style.icon}>
-                    <IconMailGray />
-                  </View>
-                  <View className={style.icon}>
-                    <IconLocationGray />
-                  </View>
-                  <View className={style.icon}>
-                    <IconQRCodeGray />
-                  </View>
-                </View>
-              </View>
-              <View className={style.card}>
-                <View className="flex items-start mb-24">
-                  <View className="flex flex-col flex-1">
-                    <View className="flex items-end gap-8 text-gray-65">
-                      <View className="text-20 font-medium leading-28 text-black">
-                        张三
-                      </View>
-                      <View className="text-12 leading-20">销售医师</View>
-                    </View>
-                    <View className="flex items-center gap-2">
-                      <View className="text-12 leading-20 truncate max-w-[188px]">
-                        北京茗视光眼科医院管理有限公司
-                      </View>
-                      <TagCertificated />
-                    </View>
-                  </View>
-                </View>
-                <View className={style.icons}>
-                  <View className={style.icon}>
-                    <IconPhoneGray />
-                  </View>
-                  <View className={style.icon}>
-                    <IconMailGray />
-                  </View>
-                  <View className={style.icon}>
-                    <IconLocationGray />
-                  </View>
-                  <View className={style.icon}>
-                    <IconQRCodeGray />
-                  </View>
-                </View>
-              </View>
+              </View> */}
+              
+
+              
               <View className="button-rounded button-primary-light gap-8 mb-15">
               <IconPlusBlue />
               <View>创建新的智能体</View>

+ 0 - 0
src/pages/profile/components/top-bar/index.module.less → src/components/AgentPage/components/SummaryBar/index.module.less


+ 7 - 2
src/pages/profile/components/top-bar/index.tsx → src/components/AgentPage/components/SummaryBar/index.tsx

@@ -13,7 +13,7 @@ import IconPhoneGray from "@/components/icon/icon-phone-gray";
 import IconMailGray from "@/components/icon/icon-mail-gray";
 import IconLocationGray from "@/components/icon/icon-location-gray";
 import { useAppStore } from "@/store/appStore";
-import AgentSwap from "../agent-swap/index";
+import AgentSwap from "../AgentSwap/index";
 import AgentQRCode from "../agent-qrcode/index";
 import SharePopup from "@/components/custom-share/share-popup";
 import Taro from "@tarojs/taro";
@@ -67,6 +67,7 @@ export default () => {
               </View>
             </View>
             <View className="flex items-center gap-8">
+              {/* 和TA聊聊 */}
               <View className="flex-1">
                 <View
                   className="button-rounded button-primary"
@@ -80,14 +81,16 @@ export default () => {
                   和TA聊聊
                 </View>
               </View>
+              {/* 二维码 */}
               <View
                 className={style.boxButton}
                 onClick={() => {
-                  setShowAgentSwap(true);
+                  setShowAgentQRcode(true);
                 }}
               >
                 <IconQRCodeBlack />
               </View>
+              {/* 智能体分享 */}
               <View
                 className={style.boxButton}
                 onClick={() => {
@@ -96,6 +99,7 @@ export default () => {
               >
                 <IconSendBlack />
               </View>
+              {/* 编辑智能体 */}
               <View
                 className={style.boxButton}
                 onClick={() => {
@@ -105,6 +109,7 @@ export default () => {
               >
                 <IconEditBlack />
               </View>
+              {/* 更多操作 */}
               <View className={style.boxButton} onClick={()=> {
                 setShowPopup(true)
               }}>

+ 1 - 1
src/pages/profile/components/agent-qrcode/index.module.less → src/components/AgentPage/components/agent-qrcode/index.module.less

@@ -9,7 +9,7 @@
   justify-content: center;
   margin-bottom: 12px;
   padding: 20px;
-  width: 100%;
+  width: 315px;
   height: 415px;
   border-radius: 16px;
   border: 1px solid rgba(#000, .05);

+ 66 - 0
src/components/AgentPage/components/agent-qrcode/index.tsx

@@ -0,0 +1,66 @@
+import { Image, View } from "@tarojs/components";
+import { useAppStore } from "@/store/appStore";
+import Popup from "@/components/popup/popup";
+import { useState } from "react";
+import style from "./index.module.less";
+import QrcodeUploadTips from "@/components/qrcode-upload-tips";
+import useEditContactCard from "@/hooks/useEditContactCard";
+import { useAgentStore } from "@/store/agentStore";
+import { pickAndUploadImage } from "@/utils/upload";
+import { EUploadFileScene } from "@/consts/enum";
+
+interface IProps {
+  show: boolean;
+  setShow: (show: boolean) => void;
+}
+export default ({ show, setShow }: IProps) => {
+  const agentContactCard = useAgentStore((state) => state.agentContactCard);
+  const { setValue, submit } = useEditContactCard(
+    "qrCodeUrl",
+    agentContactCard?.qrCodeUrl
+  );
+  const handleClick = async () => {
+    const result = await pickAndUploadImage(["album"], EUploadFileScene.OTHER);
+    if (!result?.url) {
+      return;
+    }
+    setValue(result.url);
+
+    submit(result.url);
+  };
+
+  const handleClear = () => {
+    setValue('');
+
+    submit('');
+  }
+
+  const renderQrcode = () => {
+    if (agentContactCard?.qrCodeUrl) {
+      return <Image src={agentContactCard?.qrCodeUrl} mode="widthFix"></Image>;
+    }
+    return (
+      <View onClick={handleClick}>
+        <QrcodeUploadTips />
+      </View>
+    );
+  };
+
+  return (
+    <View className="relative">
+      <Popup setShow={setShow} show={show} title="二维码">
+        <View className={style.container}>
+          <View className={style.card}>
+            {renderQrcode()}
+          </View>
+
+          <View className={style.bottomButtons}>
+            <View className="pr-16" onClick={handleClick}>替换</View>
+            <View className="text-gray-25">|</View>
+            <View className="pl-16" onClick={handleClear}>清空</View>
+          </View>
+        </View>
+      </Popup>
+    </View>
+  );
+};

+ 0 - 0
src/pages/profile/index.module.less → src/components/AgentPage/index.module.less


+ 63 - 0
src/components/AgentPage/index.tsx

@@ -0,0 +1,63 @@
+import NavBarNormal from "@/components/nav-bar-normal";
+
+import { Image, Text, View } from "@tarojs/components";
+import Logo from "@/components/logo";
+import SummaryBar from "./components/SummaryBar";
+import { useAgentStore } from "@/store/agentStore";
+import { useEffect } from "react";
+
+interface IProps {
+  agentId: string;
+}
+export default function Index({ agentId }: IProps) {
+  const { fetchAgent } = useAgentStore();
+
+  useEffect(() => {
+    if (agentId) {
+      fetchAgent(agentId);
+    }
+  }, [agentId]);
+  
+
+  return (
+    <View className="w-full">
+      <NavBarNormal scrollFadeIn showBgColor leftColumn={Logo}></NavBarNormal>
+      <SummaryBar></SummaryBar>
+      <View className="p-16">
+        <View className="rounded-12 bg-white p-24">
+          杭州茗视佳眼科,引入先进的精品专项医院运营模式,引进了阿玛仕1050rs、新版蔡司、生物力学分析仪、欧堡眼底照相等国际先进设备,汇聚浙一浙二的顶级近视手术专家手术。开展Smart全激光、半飞秒、全飞秒、ICL晶体植入以及多焦点人工晶体置换手术。
+        </View>
+        <View className="rounded-12 bg-white p-24">
+          杭州茗视佳眼科,引入先进的精品专项医院运营模式,引进了阿玛仕1050rs、新版蔡司、生物力学分析仪、欧堡眼底照相等国际先进设备,汇聚浙一浙二的顶级近视手术专家手术。开展Smart全激光、半飞秒、全飞秒、ICL晶体植入以及多焦点人工晶体置换手术。
+        </View>
+        <View className="rounded-12 bg-white p-24">
+          杭州茗视佳眼科,引入先进的精品专项医院运营模式,引进了阿玛仕1050rs、新版蔡司、生物力学分析仪、欧堡眼底照相等国际先进设备,汇聚浙一浙二的顶级近视手术专家手术。开展Smart全激光、半飞秒、全飞秒、ICL晶体植入以及多焦点人工晶体置换手术。
+        </View>
+        <View className="rounded-12 bg-white p-24">
+          杭州茗视佳眼科,引入先进的精品专项医院运营模式,引进了阿玛仕1050rs、新版蔡司、生物力学分析仪、欧堡眼底照相等国际先进设备,汇聚浙一浙二的顶级近视手术专家手术。开展Smart全激光、半飞秒、全飞秒、ICL晶体植入以及多焦点人工晶体置换手术。
+        </View>
+        <View className="rounded-12 bg-white p-24">
+          杭州茗视佳眼科,引入先进的精品专项医院运营模式,引进了阿玛仕1050rs、新版蔡司、生物力学分析仪、欧堡眼底照相等国际先进设备,汇聚浙一浙二的顶级近视手术专家手术。开展Smart全激光、半飞秒、全飞秒、ICL晶体植入以及多焦点人工晶体置换手术。
+        </View>
+        <View className="rounded-12 bg-white p-24">
+          杭州茗视佳眼科,引入先进的精品专项医院运营模式,引进了阿玛仕1050rs、新版蔡司、生物力学分析仪、欧堡眼底照相等国际先进设备,汇聚浙一浙二的顶级近视手术专家手术。开展Smart全激光、半飞秒、全飞秒、ICL晶体植入以及多焦点人工晶体置换手术。
+        </View>
+        <View className="rounded-12 bg-white p-24">
+          杭州茗视佳眼科,引入先进的精品专项医院运营模式,引进了阿玛仕1050rs、新版蔡司、生物力学分析仪、欧堡眼底照相等国际先进设备,汇聚浙一浙二的顶级近视手术专家手术。开展Smart全激光、半飞秒、全飞秒、ICL晶体植入以及多焦点人工晶体置换手术。
+        </View>
+        <View className="rounded-12 bg-white p-24">
+          杭州茗视佳眼科,引入先进的精品专项医院运营模式,引进了阿玛仕1050rs、新版蔡司、生物力学分析仪、欧堡眼底照相等国际先进设备,汇聚浙一浙二的顶级近视手术专家手术。开展Smart全激光、半飞秒、全飞秒、ICL晶体植入以及多焦点人工晶体置换手术。
+        </View>
+        <View className="rounded-12 bg-white p-24">
+          杭州茗视佳眼科,引入先进的精品专项医院运营模式,引进了阿玛仕1050rs、新版蔡司、生物力学分析仪、欧堡眼底照相等国际先进设备,汇聚浙一浙二的顶级近视手术专家手术。开展Smart全激光、半飞秒、全飞秒、ICL晶体植入以及多焦点人工晶体置换手术。
+        </View>
+        <View className="rounded-12 bg-white p-24">
+          杭州茗视佳眼科,引入先进的精品专项医院运营模式,引进了阿玛仕1050rs、新版蔡司、生物力学分析仪、欧堡眼底照相等国际先进设备,汇聚浙一浙二的顶级近视手术专家手术。开展Smart全激光、半飞秒、全飞秒、ICL晶体植入以及多焦点人工晶体置换手术。
+        </View>
+        <View className="rounded-12 bg-white p-24">
+          杭州茗视佳眼科,引入先进的精品专项医院运营模式,引进了阿玛仕1050rs、新版蔡司、生物力学分析仪、欧堡眼底照相等国际先进设备,汇聚浙一浙二的顶级近视手术专家手术。开展Smart全激光、半飞秒、全飞秒、ICL晶体植入以及多焦点人工晶体置换手术。
+        </View>
+      </View>
+    </View>
+  );
+}

+ 29 - 57
src/components/voice-player-bar/index.tsx

@@ -1,53 +1,45 @@
 import IconMutedVoice from "@/images/icon-muted-voice.png";
 import IconPlay from "@/images/icon-play.png";
 import IconVoiceWave from "@/images/icon-voice-wave.png";
-import { voiceTryout } from '@/service/audio';
-import { TServiceAudioModel } from "@/types/index";
-import { useBase64AudioPlayer } from '@/utils/audioBase64';
+
 import { Image, View } from "@tarojs/components";
-import { useUnload } from "@tarojs/taro";
+import Taro, { useUnload } from "@tarojs/taro";
 import { forwardRef, useImperativeHandle, useState } from "react";
-import { axios } from "taro-axios";
 import style from "./index.module.less";
+import { TVoiceItem } from "@/types/voice";
+
+
 
-type ExtendedTServiceAudioModel = TServiceAudioModel & { checked: boolean };
 
 interface Props {
-  voiceItem?: ExtendedTServiceAudioModel | null
-  voiceName?: string
-  voiceNameText?: string
+  voiceItem?: TVoiceItem | null
 }
 
 export interface IVoicePlayerBar {
   play: (voice: string) => Promise<void>;
+  stop: () => void;
 }
-let cancelTokenSource = axios.CancelToken.source();
-export default forwardRef<IVoicePlayerBar, Props>(({voiceItem, voiceName, voiceNameText = '还没有选择音色'}:Props, ref)=> {
+const audioInstance = Taro.createInnerAudioContext();
+  
+export default forwardRef<IVoicePlayerBar, Props>(({voiceItem}:Props, ref)=> {
   const [playing, setPlaying] = useState(false);
-  const { playAudio, stopAudio, onEnded } = useBase64AudioPlayer();
+  audioInstance.onEnded(()=> {
+    setPlaying(false)
+  })
+  
+  
+  const stopAudio = ()=> {
+    audioInstance.stop()
+    setPlaying(false)
+  }
 
-  let loading = false;
-  const playVoice = async (voice: string) => {
-    // 在发起请求前取消之前的请求
-    cancelTokenSource.cancel('Previous request canceled')
+  const playVoice = async (voiceUrl: string) => {
     stopAudio();
     try{
-      if(loading){
-        return
-      }
-      // 重新创建一个新的取消令牌源
-      cancelTokenSource = axios.CancelToken.source();
-      loading = true
-      const res = await voiceTryout(voice, {
-        cancelToken: cancelTokenSource.token
-      })
-      loading = false
-      if(res?.data?.audio){
-        playAudio(res.data.audio)
-        setPlaying(true)
-      }
+      audioInstance.src = voiceUrl
+      audioInstance.play()
+      setPlaying(true)
     }catch(e){
-      loading = false
       setPlaying(false)
     }
   }
@@ -60,39 +52,19 @@ export default forwardRef<IVoicePlayerBar, Props>(({voiceItem, voiceName, voiceN
   useImperativeHandle(ref, () => {
     return {
       play: playVoice,
+      stop: stopAudio,
     };
   });
-  onEnded(() => {
-    setPlaying(false)
-  })
+  
   useUnload(()=> {
     stopAudio();
-    cancelTokenSource.cancel('Previous request canceled')
+    
   })
   const renderPlayerBar = () => {
-    // 直接传入 voiceName 与 voiceName 播放自己录制的音色
-    // todo: 有时间做成不同的播放 bar
-    if(voiceName){
-      return(
-        <>
-          <View className={style.playStatus} onClick={()=> handlePlay(voiceName)}>
-            <Image
-              src={playing ? IconVoiceWave : IconPlay}
-              mode='widthFix'
-              className={style.icon}
-            ></Image>
-          </View>
-          <View className='text-14 leading-22 font-medium truncate'>
-            {voiceNameText}
-          </View>
-        </>
-      )
-    }
-    // 播放系统音色
     if (voiceItem) {
       return (
         <>
-          <View className={style.playStatus} onClick={()=> handlePlay(voiceItem.voice)}>
+          <View className={style.playStatus} onClick={()=> handlePlay(voiceItem.voiceUrl)}>
             <Image
               src={playing ? IconVoiceWave : IconPlay}
               mode='widthFix'
@@ -100,7 +72,7 @@ export default forwardRef<IVoicePlayerBar, Props>(({voiceItem, voiceName, voiceN
             ></Image>
           </View>
           <View className='text-14 leading-22 font-medium truncate'>
-            {voiceItem?.name} {voiceItem?.gender === 1 ? "男" : "女"} {voiceItem.style}
+            {voiceItem?.voiceName} {voiceItem?.gender === 'male' ? "男" : "女"}
           </View>
         </>
       );
@@ -116,7 +88,7 @@ export default forwardRef<IVoicePlayerBar, Props>(({voiceItem, voiceName, voiceN
           ></Image>
         </View>
         <View className='text-14 leading-22 font-medium text-gray-45'>
-          请选择声音{voiceName}
+          请选择声音
         </View>
       </>
     );

+ 69 - 0
src/hooks/useAudioPlayer.ts

@@ -0,0 +1,69 @@
+// src/hooks/useAudioPlayer.ts
+import { useState, useCallback, useEffect } from 'react';
+import Taro from '@tarojs/taro';
+
+type AudioPlayStatus = 'idle' | 'playing' | 'paused' | 'stopped' | 'error';
+
+export interface AudioInstance {
+  play: () => void;
+  pause: () => void;
+  stop: () => void;
+  destroy: () => void;
+  getStatus: () => AudioPlayStatus;
+}
+
+export default function useAudioPlayer(url: string) {
+  const [status, setStatus] = useState<AudioPlayStatus>('idle');
+  const [audio, setAudio] = useState<Taro.InnerAudioContext | null>(null);
+
+  // 初始化音频实例
+  useEffect(() => {
+    const audioInstance = Taro.createInnerAudioContext();
+    audioInstance.src = url;
+
+    // 事件监听
+    audioInstance.onPlay(() => setStatus('playing'));
+    audioInstance.onPause(() => setStatus('paused'));
+    audioInstance.onStop(() => setStatus('stopped'));
+    audioInstance.onEnded(() => setStatus('stopped'));
+    audioInstance.onError(() => {
+      setStatus('error');
+      audioInstance.destroy();
+    });
+
+    setAudio(audioInstance);
+
+    // 清理函数
+    return () => {
+      audioInstance.destroy();
+      setAudio(null);
+    };
+  }, [url]);
+
+  // 播放控制方法
+  const play = useCallback(() => {
+    if (audio && status !== 'playing') {
+      audio.play();
+    }
+  }, [audio, status]);
+
+  const pause = useCallback(() => {
+    if (audio && status === 'playing') {
+      audio.pause();
+    }
+  }, [audio, status]);
+
+  const stop = useCallback(() => {
+    if (audio && (status === 'playing' || status === 'paused')) {
+      audio.stop();
+    }
+  }, [audio, status]);
+
+  return {
+    status,
+    play,
+    pause,
+    stop,
+    audioInstance: audio, // 可选:暴露原始实例用于扩展
+  };
+}

+ 7 - 5
src/hooks/useEditContactCard.ts

@@ -8,29 +8,30 @@ const useEditContactCard = (
   initValue?: string,
   _agent?: TAgentDetail
 ) => {
-  const router = useRouter();
-  const { agentId } = router.params;
   const agent = useAgentStore((state) => _agent ?? state.agent);
   const agentContactCard = useAgentStore((state) => state.agentContactCard);
   const [value, setValue] = useState(initValue ?? "");
   const { editAgentCard,fetchAgent } = useAgentStore();
   const handleSubmit = async () => {
+    submit(value)
+  };
+  const submit = async (_value: string) => {
     if (!agent?.agentId) {
       return;
     }
-    if (value.length <= 0) {
+    if (_value === undefined) {
       return;
     }
 
     const result = await editAgentCard(agent.agentId, {
       ...agentContactCard,
-      [editKey]: value,
+      [editKey]: _value,
     });
     if(result){
       await fetchAgent(agent.agentId)
       Taro.navigateBack()
     }
-  };
+  }
   const onChange = (e: any) => {
     setValue(e);
   };
@@ -38,6 +39,7 @@ const useEditContactCard = (
     value,
     setValue,
     onChange,
+    submit,
     handleSubmit,
   };
 };

+ 76 - 0
src/pages/index/AgentNone.tsx

@@ -0,0 +1,76 @@
+import { useEffect, useState } from "react";
+import { View,Image } from "@tarojs/components";
+import Taro, { useDidShow, useReady } from "@tarojs/taro";
+import NavBarNormal from "@/components/nav-bar-normal/index";
+import LogoImage from '@/images/logo.png'
+import PageCustom from "@/components/page-custom/index";
+import { UserInfoResponse } from '@/xiaolanbenlib/api/auth'
+import { onLogout, useIsLogin } from '@/xiaolanbenlib/hooks/data/useAuth'
+import refreshUserId, { clearUserInfo, getOpenIdAsync } from '@/xiaolanbenlib/utils/auth'
+
+import WelcomeTips from './components/welcome/index'
+import { useAgentStore } from '@/store/agentStore'
+import { useSystemStore } from '@/store/systemStore'
+import { TAgent } from "@/types/agent";
+interface Iprops {
+  setDefault: (agent: TAgent) => void
+}
+export default function Index({setDefault}: Iprops) {
+
+  const [userInfo, setUserInfo] = useState<UserInfoResponse>()
+  const isLogin = useIsLogin()
+
+  const {fetchAgents} =  useAgentStore()
+  const { getSysCoreCnf } =  useSystemStore()
+
+  async function initUserInfo() {
+    await refreshUserId()
+      .then( async (value) => {
+        setUserInfo(value)
+        getOpenIdAsync().then((openId) => {
+          console.log('🚀 ~ getOpenIdAsync ~ value:', openId)
+        })
+      })
+      .catch((error) => {
+        if (error.message === 'unauthorized' || error.code === 401) {
+          Taro.showToast({
+            title: '未登录',
+            icon: 'none',
+            duration: 2000,
+          })
+        }
+      })
+    await getSysCoreCnf()
+
+    const agents = await fetchAgents()
+    const agent = agents.find( item=> item.isDefault)
+    if(agent){
+      setDefault(agent)
+    }
+  }
+
+  useEffect(() => {
+    if (isLogin) {
+      initUserInfo()
+    }
+  }, [isLogin])
+  
+
+  const renderLogo = ()=> {
+    return <View><Image className="w-68 h-24" src={LogoImage}></Image></View>
+  }
+
+  return (
+    <PageCustom>
+      <NavBarNormal leftColumn={renderLogo}></NavBarNormal>
+      
+      <WelcomeTips>
+        <View className="flex items-center">
+          <View>{userInfo?.logo && <Image className="w-24 h-24" src={userInfo?.logo}></Image>}</View>
+          <View>{isLogin ? `已登录「${userInfo?.nickName}」` : '未登录'}</View>
+        </View>      
+      </WelcomeTips>
+      
+      </PageCustom>
+  );
+}

+ 15 - 65
src/pages/index/index.tsx

@@ -1,77 +1,27 @@
-import { useEffect, useState } from "react";
-import { View,Image } from "@tarojs/components";
-import Taro, { useDidShow, useReady } from "@tarojs/taro";
-import style from "./index.module.less";
-import NavBarNormal from "@/components/nav-bar-normal/index";
-import LogoImage from '@/images/logo.png'
+
 import PageCustom from "@/components/page-custom/index";
-import { UserInfoResponse } from '@/xiaolanbenlib/api/auth'
-import { onLogout, useIsLogin } from '@/xiaolanbenlib/hooks/data/useAuth'
-import refreshUserId, { clearUserInfo, getOpenIdAsync } from '@/xiaolanbenlib/utils/auth'
 
-import WelcomeTips from './components/welcome/index'
-import { useAgentStore } from '@/store/agentStore'
-import { useSystemStore } from '@/store/systemStore'
-import { OSS_PUBLIC_BUCKET_NAME_KEY } from '@/xiaolanbenlib/constant'
+import { useEffect, useState } from "react";
+import DefaultAgent from '@/components/AgentPage'
+import AgentNone from './AgentNone'
+import { TAgent } from "@/types/agent";
 
 export default function Index() {
 
-  const [userInfo, setUserInfo] = useState<UserInfoResponse>()
-  const isLogin = useIsLogin()
-
-  const {fetchAgents} =  useAgentStore()
-  const { getSysCoreCnf } =  useSystemStore()
+  const [defaultAgent, setDefaultAgent] = useState<TAgent|null>(null)
 
-  async function initUserInfo() {
-    await refreshUserId()
-      .then( async (value) => {
-        setUserInfo(value)
-        getOpenIdAsync().then((openId) => {
-          console.log('🚀 ~ getOpenIdAsync ~ value:', openId)
-        })
-      })
-      .catch((error) => {
-        if (error.message === 'unauthorized' || error.code === 401) {
-          Taro.showToast({
-            title: '未登录',
-            icon: 'none',
-            duration: 2000,
-          })
-        }
-      })
-    await getSysCoreCnf()
 
-    const agents = await fetchAgents()
-    const agent = agents[0]
-    Taro.navigateTo({url: `/pages/profile/index?agentId=${agent.agentId}`})
-  }
-
-  useEffect(() => {
-    if (isLogin) {
-      initUserInfo()
-    }else{
-      // Taro.navigateTo({url: '/pages/login/index'})
-    }
-    // console.log(isLogin,1111)
-    // 
-  }, [isLogin])
+  useEffect(()=> {
+    
+  }, [])
+  
   
-
-  const renderLogo = ()=> {
-    return <View><Image className="w-68 h-24" src={LogoImage}></Image></View>
-  }
-
   return (
     <PageCustom>
-      <NavBarNormal leftColumn={renderLogo}></NavBarNormal>
-      
-      <WelcomeTips>
-        <View className="flex items-center">
-          <View>{userInfo?.logo && <Image className="w-24 h-24" src={userInfo?.logo}></Image>}</View>
-          <View>{isLogin ? `已登录「${userInfo?.nickName}」` : '未登录'}</View>
-        </View>      
-      </WelcomeTips>
-      
-      </PageCustom>
+      <>
+      {!defaultAgent && <AgentNone setDefault={setDefaultAgent}></AgentNone>}
+      {defaultAgent && <DefaultAgent agentId={defaultAgent.agentId}></DefaultAgent>}
+      </>
+    </PageCustom>
   );
 }

+ 0 - 29
src/pages/profile/components/agent-qrcode/index.tsx

@@ -1,29 +0,0 @@
-import { View } from "@tarojs/components";
-import { useAppStore } from "@/store/appStore";
-import Popup from "@/components/popup/popup";
-import { useState } from "react";
-import style from './index.module.less';
-import QrcodeUploadTips from "@/components/qrcode-upload-tips";
-interface IProps {
-  show: boolean;
-  setShow: (show: boolean) => void;
-}
-export default ({ show, setShow }: IProps) => {
-  
-  return (
-    <View className="relative">
-      <Popup setShow={setShow} show={show} title="二维码">
-        <View className={style.container}>
-          <View className={style.card}>
-            <QrcodeUploadTips />
-          </View>
-          <View className={style.bottomButtons}>
-            <View className="pr-16">替换</View>
-            <View className="text-gray-25">|</View>
-            <View className="pl-16">清空</View>
-          </View>
-        </View>
-      </Popup>
-    </View>
-  );
-};

+ 0 - 10
src/pages/profile/index.config.ts

@@ -1,10 +0,0 @@
-export default definePageConfig({
-  navigationBarTitleText: '个人主页',
-  navigationStyle: 'custom',
-  enableShareAppMessage: true,
-  enableShareTimeline: true,
-  enablePageMeta: true,
-  usingComponents: {
-    
-  }
-})

+ 0 - 93
src/pages/profile/index.tsx

@@ -1,93 +0,0 @@
-import NavBarNormal from "@/components/nav-bar-normal";
-import PageCustom from "@/components/page-custom/index";
-
-import { Image, Text, View } from "@tarojs/components";
-import Logo from '@/components/logo'
-import TopBar from './components/top-bar'
-import { useAgentStore } from '@/store/agentStore'
-import { useEffect } from "react";
-import Taro, { useRouter } from "@tarojs/taro";
-import { uploadFile } from "@/utils/http";
-import { EUploadFileScene } from "@/consts/enum";
-export default function Index() {
-
-  const router = useRouter();
-  const { agentId } = router.params;
- 
-  const {fetchAgent} = useAgentStore()
-
-
-  useEffect(()=> {
-    if(agentId){
-      fetchAgent(agentId)
-    }
-  }, [agentId])
-  // const renderFloatingTips = () => {
-  //   return isShared ? <FloatingTips></FloatingTips> : <></>;
-  // };
-
-  const FILE_LIMIT = 100 * 1024 * 1024 // 100MB
-  const handleClick = async () => {
-    // const res = await Taro.chooseMessageFile({
-    //   count: 1,
-    //   type: 'file',
-    // })
-
-    // const file = res.tempFiles[0]
-    // if (file.size >= FILE_LIMIT) {
-    //   Taro.showToast({
-    //     title: '文件大小超过限制',
-    //     icon: 'error',
-    //     duration: 1500,
-    //   })
-    //   throw new Error('File size exceeds limit')
-    // }
-    // const r = await uploadFile(file.path, EUploadFileScene.DOC)
-    // console.log(r, 4444)
-  }
-  
-  return (
-    <PageCustom>
-      <View className="w-full">
-        <NavBarNormal scrollFadeIn showBgColor leftColumn={Logo}></NavBarNormal>
-        <TopBar></TopBar>
-        <View onClick={handleClick}>helloworld</View>
-        <View className="p-16">
-        <View className="rounded-12 bg-white p-24">
-        杭州茗视佳眼科,引入先进的精品专项医院运营模式,引进了阿玛仕1050rs、新版蔡司、生物力学分析仪、欧堡眼底照相等国际先进设备,汇聚浙一浙二的顶级近视手术专家手术。开展Smart全激光、半飞秒、全飞秒、ICL晶体植入以及多焦点人工晶体置换手术。
-        </View>
-        <View className="rounded-12 bg-white p-24">
-        杭州茗视佳眼科,引入先进的精品专项医院运营模式,引进了阿玛仕1050rs、新版蔡司、生物力学分析仪、欧堡眼底照相等国际先进设备,汇聚浙一浙二的顶级近视手术专家手术。开展Smart全激光、半飞秒、全飞秒、ICL晶体植入以及多焦点人工晶体置换手术。
-        </View>
-        <View className="rounded-12 bg-white p-24">
-        杭州茗视佳眼科,引入先进的精品专项医院运营模式,引进了阿玛仕1050rs、新版蔡司、生物力学分析仪、欧堡眼底照相等国际先进设备,汇聚浙一浙二的顶级近视手术专家手术。开展Smart全激光、半飞秒、全飞秒、ICL晶体植入以及多焦点人工晶体置换手术。
-        </View>
-        <View className="rounded-12 bg-white p-24">
-        杭州茗视佳眼科,引入先进的精品专项医院运营模式,引进了阿玛仕1050rs、新版蔡司、生物力学分析仪、欧堡眼底照相等国际先进设备,汇聚浙一浙二的顶级近视手术专家手术。开展Smart全激光、半飞秒、全飞秒、ICL晶体植入以及多焦点人工晶体置换手术。
-        </View>
-        <View className="rounded-12 bg-white p-24">
-        杭州茗视佳眼科,引入先进的精品专项医院运营模式,引进了阿玛仕1050rs、新版蔡司、生物力学分析仪、欧堡眼底照相等国际先进设备,汇聚浙一浙二的顶级近视手术专家手术。开展Smart全激光、半飞秒、全飞秒、ICL晶体植入以及多焦点人工晶体置换手术。
-        </View>
-        <View className="rounded-12 bg-white p-24">
-        杭州茗视佳眼科,引入先进的精品专项医院运营模式,引进了阿玛仕1050rs、新版蔡司、生物力学分析仪、欧堡眼底照相等国际先进设备,汇聚浙一浙二的顶级近视手术专家手术。开展Smart全激光、半飞秒、全飞秒、ICL晶体植入以及多焦点人工晶体置换手术。
-        </View>
-        <View className="rounded-12 bg-white p-24">
-        杭州茗视佳眼科,引入先进的精品专项医院运营模式,引进了阿玛仕1050rs、新版蔡司、生物力学分析仪、欧堡眼底照相等国际先进设备,汇聚浙一浙二的顶级近视手术专家手术。开展Smart全激光、半飞秒、全飞秒、ICL晶体植入以及多焦点人工晶体置换手术。
-        </View>
-        <View className="rounded-12 bg-white p-24">
-        杭州茗视佳眼科,引入先进的精品专项医院运营模式,引进了阿玛仕1050rs、新版蔡司、生物力学分析仪、欧堡眼底照相等国际先进设备,汇聚浙一浙二的顶级近视手术专家手术。开展Smart全激光、半飞秒、全飞秒、ICL晶体植入以及多焦点人工晶体置换手术。
-        </View>
-        <View className="rounded-12 bg-white p-24">
-        杭州茗视佳眼科,引入先进的精品专项医院运营模式,引进了阿玛仕1050rs、新版蔡司、生物力学分析仪、欧堡眼底照相等国际先进设备,汇聚浙一浙二的顶级近视手术专家手术。开展Smart全激光、半飞秒、全飞秒、ICL晶体植入以及多焦点人工晶体置换手术。
-        </View>
-        <View className="rounded-12 bg-white p-24">
-        杭州茗视佳眼科,引入先进的精品专项医院运营模式,引进了阿玛仕1050rs、新版蔡司、生物力学分析仪、欧堡眼底照相等国际先进设备,汇聚浙一浙二的顶级近视手术专家手术。开展Smart全激光、半飞秒、全飞秒、ICL晶体植入以及多焦点人工晶体置换手术。
-        </View>
-        <View className="rounded-12 bg-white p-24">
-        杭州茗视佳眼科,引入先进的精品专项医院运营模式,引进了阿玛仕1050rs、新版蔡司、生物力学分析仪、欧堡眼底照相等国际先进设备,汇聚浙一浙二的顶级近视手术专家手术。开展Smart全激光、半飞秒、全飞秒、ICL晶体植入以及多焦点人工晶体置换手术。
-        </View>
-        </View>
-      </View>
-    </PageCustom>
-  );
-}

+ 0 - 0
src/pages/voice/components/my-voice/components/index.module.less → src/pages/voice/components/MyVoiceList/components/index.module.less


+ 0 - 0
src/pages/voice/components/my-voice/components/index.tsx → src/pages/voice/components/MyVoiceList/components/index.tsx


+ 0 - 0
src/pages/voice/components/my-voice/components/popup-recorder/index.module.less → src/pages/voice/components/MyVoiceList/components/popup-recorder/index.module.less


+ 0 - 0
src/pages/voice/components/my-voice/components/popup-recorder/index.tsx → src/pages/voice/components/MyVoiceList/components/popup-recorder/index.tsx


+ 0 - 0
src/pages/voice/components/my-voice/components/popup-tryout/index.module.less → src/pages/voice/components/MyVoiceList/components/popup-tryout/index.module.less


+ 0 - 0
src/pages/voice/components/my-voice/components/popup-tryout/index.tsx → src/pages/voice/components/MyVoiceList/components/popup-tryout/index.tsx


+ 0 - 0
src/pages/voice/components/my-voice/index.module.less → src/pages/voice/components/MyVoiceList/index.module.less


+ 20 - 33
src/pages/voice/components/my-voice/index.tsx → src/pages/voice/components/MyVoiceList/index.tsx

@@ -17,26 +17,22 @@ import PopupTryout from "./components/popup-tryout/index";
 import style from "./index.module.less";
 import { useVoiceStore } from "@/store/voiceStore";
 import { EVoiceStatus, TVoiceItem } from "@/types/voice";
+import { TAgentDetail } from "@/types/agent";
 
 interface Props {
+  agent: TAgentDetail|null
   value?: ICharacter;
   setValue?: (value: ICharacter) => void;
   onPlay?: (voice: any) => void;
 }
 
-export default ({ onPlay }: Props) => {
-  const playerRef = useRef<IVoicePlayerBar>(null);
+export default ({ onPlay, agent }: Props) => {
   const intervalRef = useRef<NodeJS.Timeout | null>(null);
   const [show, setShow] = useState(false);
   const [popupType, setPopupType] = useState<"clone" | "try" | "reclone">(
     "clone",
   );
-
-  const [voiceName, setVoiceName] = useState("");
-  const [voiceIndex, setVoiceIndex] = useState(-1);
-
-  // const { saveCharacter, fetchCharacter, fetchVoiceCloneHistory } =
-  //   useCharacterStore();
+  
   const {getVoices} = useVoiceStore()
   const voices = useVoiceStore(state => state.voices)
   const myVoices = useVoiceStore(state => state.myVoices)
@@ -45,26 +41,21 @@ export default ({ onPlay }: Props) => {
   
   
 
-  const handleSelect = (item: TVoiceItem, index: number) => {
-    setVoiceIndex(index);
-
-    if (item.status == CloneVoiceStatus.CloneVoiceStatusSuccess) {
+  const handleSelect = (item: TVoiceItem) => {
+    if (item.status == EVoiceStatus.DONE) {
       if (item.voiceName) {
-        setVoiceName(item.voiceName);
+        // setVoiceName(item.voiceName);
         onPlay &&
-          onPlay({
-            voiceName: item.voiceName,
-            voiceIndex: index,
-          });
+          onPlay(item);
       }
       // save
     }
 
-    if (item.status == CloneVoiceStatus.CloneVoiceStatusUnconfirmed) {
-      // 未确认,弹出试听框
-      setPopupType("try");
-      setShow(true);
-    }
+    // if (item.status == CloneVoiceStatus.CloneVoiceStatusUnconfirmed) {
+    //   // 未确认,弹出试听框
+    //   setPopupType("try");
+    //   setShow(true);
+    // }
   };
 
   // 克隆按钮状态
@@ -84,8 +75,8 @@ export default ({ onPlay }: Props) => {
 
   const handleSureAction = async () => {
     setShow(false);
-    await voiceCloneConfirm(myVoices[voiceIndex].voiceName!);
-    fetchVoiceList();
+    // await voiceCloneConfirm(myVoices[voiceIndex].voiceName!);
+    // fetchVoiceList();
   };
 
   const handleRecloneAction = () => {
@@ -136,7 +127,7 @@ export default ({ onPlay }: Props) => {
   // 克隆列表右侧操作栏
   const renderRightColumn = (item: TVoiceItem) => {
     if (item.status == EVoiceStatus.DONE) {
-      return <WemetaRadio></WemetaRadio>;
+      return <WemetaRadio checked={item.voiceId === agent?.voiceId}></WemetaRadio>;
     }
 
     return <></>;
@@ -147,7 +138,7 @@ export default ({ onPlay }: Props) => {
     if (item.status === EVoiceStatus.DONE) {
       return (
         <View className={`text-12 leading-20 text-gray-45`}>
-          {item.createTime && formatDateFull(new Date(item.createTime))}
+          {item.createTime && formatDateFull(new Date(item.createTime.replace(/-/g,'/')))}
         </View>
       );
     }
@@ -181,7 +172,7 @@ export default ({ onPlay }: Props) => {
               rightRenderer={() => {
                 return renderRightColumn(item);
               }}
-              onClick={() => handleSelect(item, _index)}
+              onClick={() => handleSelect(item)}
             >
               <View className="flex items-center gap-16">
                 <View className="flex flex-col gap-4">
@@ -218,20 +209,16 @@ export default ({ onPlay }: Props) => {
               show={show}
               setShow={setShow}
               setCloneStatus={(status) => handleCloneStatus(status)}
-              voiceName={
-                popupType == "reclone" ? myVoices[voiceIndex].voiceName! : ""
-              }
             ></PopupRecorder>
           )}
-          {popupType == "try" && (
+          {/* {popupType == "try" && (
             <PopupTryout
               show={show}
               onSure={handleSureAction}
               onReclone={handleRecloneAction}
-              voiceName={myVoices[voiceIndex].voiceName!}
               showName={getCloneVoiceIdentifier(voiceIndex + 1)}
             ></PopupTryout>
-          )}
+          )} */}
         </View>
       </Popup>
 

+ 0 - 0
src/pages/voice/components/all-voice/index.module.less → src/pages/voice/components/VoiceList/index.module.less


+ 9 - 27
src/pages/voice/components/all-voice/index.tsx → src/pages/voice/components/VoiceList/index.tsx

@@ -1,19 +1,18 @@
 import WemetaRadio from "@/components/wemeta-radio/index";
-import { getSysVoiceList } from "@/service/audio";
-import { useCharacterStore } from "@/store/characterStore";
-import { TServiceAudioModel } from "@/types/index";
 import { Image, View } from "@tarojs/components";
 import { useEffect, useState } from "react";
-import CardList from "@/components/list/card-list";
 import CardListItem from "@/components/list/card-list-item";
-import { TGetMyVoicesParams, TVoiceItem, TPageination } from '@/types/storage'
-type ExtendedTVoiceItem = TVoiceItem & { checked: boolean };
+import { TVoiceItem } from '@/types/voice'
+import { TAgentDetail } from "@/types/agent";
+
 export default function Index({
   onPlay,
   list,
+  agent,
 }: {
   onPlay: (voice: any) => void;
   list: TVoiceItem[]
+  agent: TAgentDetail|null
 }) {
   
   
@@ -37,24 +36,9 @@ export default function Index({
     // }
   };
 
-  const handleSelect = async (voiceItem: ExtendedTVoiceItem) => {
+  const handleSelect = async (voiceItem: TVoiceItem) => {
     console.log("system voice select");
 
-    // const _list = list.map((item) => {
-    //   return { ...item, checked: item.voice === voiceItem.voice };
-    // });
-
-    // setList(_list);
-
-    // setSysVoice(voiceItem);
-    // if (profileId) {
-    //   saveCharacter({
-    //     defaultSystemVoice: voiceItem.voice,
-    //     voice: voiceItem.voice,
-    //     profileId: profileId,
-    //   });
-    // }
-    // playerRef.current && playerRef.current.play(voiceItem.voice);
     onPlay && onPlay(voiceItem);
   };
 
@@ -67,18 +51,16 @@ export default function Index({
       <View className="flex flex-col w-full gap-28">
       <View>
           <View className="flex flex-col gap-12">
-        {list.map((item: ExtendedTVoiceItem, index) => {
+        {list.map((item: TVoiceItem, index) => {
           // 与克隆语音是否相同
-          // const isEqualToClonedVoice = item.voice === character?.voice;
-          const isEqualToClonedVoice = false;
+          const isEqualToClonedVoice = item.voiceId === agent?.voiceId;
           return (
             <CardListItem
             underline
             rightRenderer={()=> {
               return <View className="flex items-center">
                 <WemetaRadio
-                  // checked={item.checked && isEqualToClonedVoice}
-                  checked={ index === 0}
+                  checked={isEqualToClonedVoice}
                 ></WemetaRadio>
               </View>
             }}

+ 46 - 38
src/pages/voice/index.tsx

@@ -1,8 +1,7 @@
 import WemetaTabs from "@/components/wemeta-tabs/index";
 import VoicePlayerBar, { IVoicePlayerBar } from "@/components/voice-player-bar";
-import AllVoice from "./components/all-voice";
-import MyVoice from "./components/my-voice/index";
-import { TServiceAudioModel } from "@/types";
+import VoiceList from "./components/VoiceList";
+import MyVoiceList from "./components/MyVoiceList/index";
 import { View, ScrollView } from "@tarojs/components";
 import { useRouter } from "@tarojs/taro";
 import React, { useEffect, useRef, useState } from "react";
@@ -10,20 +9,18 @@ import NavBarNormal from "@/components/nav-bar-normal/index";
 import style from "./index.module.less";
 import PageCustom from "@/components/page-custom/index";
 import { useVoiceStore } from "@/store/voiceStore";
+import { TVoiceItem } from "@/types/voice";
+import { useAgentStore } from "@/store/agentStore";
 
 interface Props {}
-type ExtendedTServiceAudioModel = TServiceAudioModel & { checked: boolean };
+
 const VoiceTabs: React.FC<Props> = ({}) => {
   const playerRef = useRef<IVoicePlayerBar>(null);
-  const [sysVoice, setSysVoice] = useState<ExtendedTServiceAudioModel | null>(
+  const [sysVoice, setSysVoice] = useState<TVoiceItem | null>(
     null
   );
 
-  const [voiceName, setVoiceName] = useState("");
-  const [voiceAlias, setVoiceAlias] = useState("");
-  const [voiceIdx, setVoiceIdx] = useState(-1);
-  const router = useRouter();
-  const profileId = router.params.profileId || "";
+  const {agent, agentCharacter, editAgentCharacter} = useAgentStore()
 
   const {
     voices,
@@ -75,13 +72,38 @@ const VoiceTabs: React.FC<Props> = ({}) => {
     setMalePagination({ pageIndex: malePagination.pageIndex + 1 });
   };
 
+  // 保存默认 voiceId
+  const saveAgentVoiceId = async (voiceItem: TVoiceItem) => {
+    if(!agent?.agentId || !voiceItem?.voiceId || !agentCharacter){
+      return
+    }
+    const result = await editAgentCharacter(agent?.agentId, {
+      ...agentCharacter,
+      voiceId: voiceItem.voiceId
+    })
+    console.log(result)
+  }
+
+  // 设置当前播放器的播放音频信息
+  const setPlayerItem = async (voiceItem: TVoiceItem, type: "system" | "cloned") => {
+    playerRef.current && playerRef.current.stop();
+    setSysVoice(voiceItem);
+    saveAgentVoiceId(voiceItem)
+  };
+
   const tabList = [
     {
       label: "我的",
       key: "1",
       children: (
         <View className={style.tabContent}>
-          <MyVoice></MyVoice>
+          <MyVoiceList
+          agent={agent}
+          onPlay={(item) => {
+            console.log(item,'cloned')
+            setPlayerItem(item, "cloned");
+          }}
+          ></MyVoiceList>
         </View>
       ),
     },
@@ -100,12 +122,13 @@ const VoiceTabs: React.FC<Props> = ({}) => {
             }}
           >
             <View className="px-16 py-12">
-              <AllVoice
+              <VoiceList
+                agent={agent}
                 list={voices}
                 onPlay={(item) => {
-                  handlePlayAction(item, "system");
+                  setPlayerItem(item, "system");
                 }}
-              ></AllVoice>
+              ></VoiceList>
             </View>
           </ScrollView>
         </View>
@@ -126,12 +149,13 @@ const VoiceTabs: React.FC<Props> = ({}) => {
             }}
           >
             <View className="px-16 py-12">
-              <AllVoice
+              <VoiceList
+                agent={agent}
                 list={femaleVoices}
                 onPlay={(item) => {
-                  handlePlayAction(item, "system");
+                  setPlayerItem(item, "system");
                 }}
-              ></AllVoice>
+              ></VoiceList>
             </View>
           </ScrollView>
         </View>
@@ -152,12 +176,13 @@ const VoiceTabs: React.FC<Props> = ({}) => {
             }}
           >
             <View className="px-16 py-12">
-              <AllVoice
+              <VoiceList
+                agent={agent}
                 list={maleVoices}
                 onPlay={(item) => {
-                  handlePlayAction(item, "system");
+                  setPlayerItem(item, "system");
                 }}
-              ></AllVoice>
+              ></VoiceList>
             </View>
           </ScrollView>
         </View>
@@ -165,22 +190,7 @@ const VoiceTabs: React.FC<Props> = ({}) => {
     },
   ];
 
-  const handlePlayAction = (voiceItem: any, type: "system" | "cloned") => {
-    if (type == "system") {
-      // setVoiceName("");
-      // setVoiceAlias("");
-      // setVoiceIdx(-1);
-
-      // setSysVoice(voiceItem);
-      playerRef.current && playerRef.current.play(voiceItem.voice);
-    } else {
-      // setSysVoice(null);
-      // setVoiceName(voiceItem.voiceName);
-      // setVoiceAlias(voiceItem.voiceAlias);
-      // setVoiceIdx(voiceItem.voiceIndex);
-      playerRef.current && playerRef.current.play(voiceItem.voiceName);
-    }
-  };
+  
 
   return (
     <PageCustom>
@@ -190,8 +200,6 @@ const VoiceTabs: React.FC<Props> = ({}) => {
           <VoicePlayerBar
             ref={playerRef}
             voiceItem={sysVoice}
-            voiceName={voiceName}
-            voiceNameText={voiceIdx == -1 ? "" : voiceAlias}
           />
         </View>
         <View className={style.voiceTab}>

+ 28 - 8
src/store/agentStore.ts

@@ -4,6 +4,7 @@ import request from "@/xiaolanbenlib/module/axios.js";
 
 import {
   createAgent as _createAgent,
+  setDefaultAgent as _setDefaultAgent,
   deleteAgent as _deleteAgent,
   editAgentCard as _editAgentCard,
   editAgentCharacter as _editAgentCharacter,
@@ -24,6 +25,7 @@ export interface AgentStoreState {
   fetchAgents: () => Promise<TAgent[]>;
   fetchAgent: (agentId: string) => Promise<TAgentDetail | null>;
   createAgent: (data: TAgentDetail) => Promise<TAgentDetail | null>;
+  setDefaultAgent: (agentId: string) => Promise<TAgentDetail | null>;
   editAgentCharacter: (
     agentId: string,
     data: TEditAgentCharacter
@@ -55,7 +57,8 @@ export const useAgentStore = create<AgentStoreState>((set, get) => ({
     const response = await request.get<TAgentDetail>(
       `${bluebookAiAgent}api/v1/my/agent/${agentId}`
     );
-    if (response.data) {
+    const result = isSuccess(response.status)
+    if (result && response.data) {
       const agent = response.data;
       set({
         agent: response.data,
@@ -81,6 +84,14 @@ export const useAgentStore = create<AgentStoreState>((set, get) => ({
     }
     return null;
   },
+  setDefaultAgent: async (agentId: string) => {
+    const response = await _setDefaultAgent(agentId);
+    const result = isSuccess(response.status)
+    if(result){
+      await get().fetchAgent(agentId)
+    }
+    return null
+  },
   createAgent: async () => {
     const response = await _createAgent();
     if (response.data) {
@@ -103,18 +114,27 @@ export const useAgentStore = create<AgentStoreState>((set, get) => ({
     return null;
   },
   editAgentCharacter: async (agentId: string, data: TEditAgentCharacter) => {
-    const filteredObj = pickNonEmpty(data);
-    const response = await _editAgentCharacter(agentId, filteredObj);
+    // const filteredObj = pickNonEmpty(data);
+    const response = await _editAgentCharacter(agentId, data);
     console.log(response.data);
-    return isSuccess(response.status)
+    const result = isSuccess(response.status)
+    set((state) => {
+      return {
+        agent: {
+          ...state.agent,
+          voiceId: data.voiceId
+        },
+      };
+    });
+    return result
   },
   editAgentCard: async (agentId: string, data: TAgentContactCard) => {
     console.log(agentId, data);
-    const filteredObj = pickNonEmpty(data);
-    const response = await _editAgentCard(agentId, filteredObj);
+    const response = await _editAgentCard(agentId, data);
     console.log(response);
-
-    return isSuccess(response.status)
+    const result = isSuccess(response.status)
+    
+    return result
   },
   // deleteAgent: async (agentId: string)=> {
   //   const response = await _deleteAgent(agentId)

+ 13 - 5
src/types/agent.ts

@@ -1,10 +1,18 @@
 
 export type TAgent = {
-  "agentId": string,
-  "isDefault": boolean,
-  "isEnt": boolean,
-  "isNewEnt": boolean,
-  "name": string
+  address: string|null
+  agentId: string
+  avatarUrl: string|null
+  email: string
+  enabledChatBg: boolean
+  entName: string|null
+  isDefault: boolean
+  isEnt: boolean
+  isNewEnt: boolean
+  mobile: string
+  name: string
+  position: string|null
+  qrCodeUrl: string
 }
 export type TComponentItem = {
   "data"?: any,

+ 65 - 0
src/utils/audioPlayer.ts

@@ -0,0 +1,65 @@
+import Taro from "@tarojs/taro";
+// 音频播放状态类型
+type AudioPlayStatus = 'idle' | 'playing' | 'paused' | 'stopped' | 'error';
+
+// 封装后的音频实例类型
+interface AudioInstance {
+  play: () => void;
+  pause: () => void;
+  stop: () => void;
+  destroy: () => void;
+  onStatusChange: (callback: (status: AudioPlayStatus) => void) => void;
+  getStatus: () => AudioPlayStatus;
+}
+
+export function playWavAudio(url: string): AudioInstance {
+  const audio = Taro.createInnerAudioContext();
+  let currentStatus: AudioPlayStatus = 'idle';
+  let statusChangeCallback: ((status: AudioPlayStatus) => void) | null = null;
+
+  // 更新状态并触发回调
+  const updateStatus = (newStatus: AudioPlayStatus) => {
+    currentStatus = newStatus;
+    if (statusChangeCallback) {
+      statusChangeCallback(newStatus);
+    }
+  };
+
+  audio.src = url;
+
+  // 事件监听
+  audio.onPlay(() => updateStatus('playing'));
+  audio.onPause(() => updateStatus('paused'));
+  audio.onStop(() => updateStatus('stopped'));
+  audio.onEnded(() => updateStatus('stopped'));
+  audio.onError(() => {
+    updateStatus('error');
+    audio.destroy();
+  });
+
+  return {
+    play: () => {
+      if (currentStatus !== 'playing') {
+        audio.play();
+      }
+    },
+    pause: () => {
+      if (currentStatus === 'playing') {
+        audio.pause();
+      }
+    },
+    stop: () => {
+      if (currentStatus === 'playing' || currentStatus === 'paused') {
+        audio.stop();
+      }
+    },
+    destroy: () => {
+      audio.destroy();
+      updateStatus('idle');
+    },
+    onStatusChange: (callback) => {
+      statusChangeCallback = callback;
+    },
+    getStatus: () => currentStatus,
+  };
+}

+ 48 - 0
src/utils/upload.ts

@@ -0,0 +1,48 @@
+import Taro from '@tarojs/taro'
+import { uploadFile } from './http'
+import { EUploadFileScene } from "@/consts/enum";
+const IMAGE_LIMIT = 50 * 1024 * 1024 // 50MB = 52428800 bytes
+const VIDEO_LIMIT = 30 * 1024 * 1024 // 30MB
+const FILE_LIMIT = 100 * 1024 * 1024 // 100MB
+
+export async function pickAndUploadImage(
+  sourceType: Array<'album' | 'camera'> = ['album', 'camera'],
+  scene: EUploadFileScene
+): Promise<{ url: string; size: number }| null> {
+  try {
+    const res = await Taro.chooseImage({
+      count: 1,
+      sizeType: ['compressed', 'original'],
+      sourceType,
+    })
+
+    const file = res.tempFiles[0]
+    if (file.size >= IMAGE_LIMIT) {
+      Taro.showToast({
+        title: '图片大小超限',
+        icon: 'error',
+        duration: 1500,
+      })
+      throw new Error('Image size exceeds limit')
+    }
+    const uploadResult = await uploadFile(
+      file.path,
+      scene,
+    );
+
+    Taro.showToast({
+      title: '上传成功',
+      icon: 'success',
+      duration: 1500,
+    })
+    if(uploadResult?.publicUrl){
+      return {
+        url: uploadResult?.publicUrl,
+        size: file.size,
+      }
+    }
+    return null
+  } catch (error) {
+    throw new Error('Failed to pick or upload image')
+  }
+}