Преглед изворни кода

feat: 对接克隆声音,声音列表

王晓东 пре 1 месец
родитељ
комит
227dc0eee3

+ 55 - 0
src/hooks/useEditAgent.ts

@@ -0,0 +1,55 @@
+import Taro, { useRouter } from "@tarojs/taro";
+import { useState } from "react";
+import { useAgentStore } from "@/store/agentStore";
+import { TAgentDetail } from "@/types/agent";
+
+const useEditAgent = (
+  editKey: string,
+  initValue?: string,
+  _agent?: TAgentDetail
+) => {
+  const router = useRouter();
+  const { agentId } = router.params;
+  const agent = useAgentStore((state) => _agent ?? state.agent);
+  const agentCharacter = useAgentStore((state) => state.agentCharacter);
+  const [value, setValue] = useState(initValue ?? "");
+  const { editAgentCharacter,fetchAgent } = useAgentStore();
+  const handleSubmit = async () => {
+    if (!agent?.agentId) {
+      return;
+    }
+    if (value.length <= 0) {
+      return;
+    }
+    if(!agentCharacter){
+      return
+    }
+
+    // {
+    //   enabledPersonalKb: agent.enabledPersonalKb ?? false,
+    //   greeting: agent.greeting ?? '',
+    //   personality: value,
+    //   questionGuides: agent.questionGuides ?? [],
+    //   voiceId: agent.voiceId ?? '',
+    // }
+    const result = await editAgentCharacter(agent.agentId, {
+      ...agentCharacter,
+      [editKey]: value,
+    });
+    if(result){
+      await fetchAgent(agent.agentId)
+      Taro.navigateBack()
+    }
+  };
+  const onChange = (e: any) => {
+    setValue(e);
+  };
+  return {
+    value,
+    setValue,
+    onChange,
+    handleSubmit,
+  };
+};
+
+export default useEditAgent;

+ 7 - 3
src/hooks/useEditContactCard.ts

@@ -1,4 +1,4 @@
-import { useRouter } from "@tarojs/taro";
+import Taro, { useRouter } from "@tarojs/taro";
 import { useState } from "react";
 import { useAgentStore } from "@/store/agentStore";
 import { TAgentDetail } from "@/types/agent";
@@ -13,7 +13,7 @@ const useEditContactCard = (
   const agent = useAgentStore((state) => _agent ?? state.agent);
   const agentContactCard = useAgentStore((state) => state.agentContactCard);
   const [value, setValue] = useState(initValue ?? "");
-  const { editAgentCard } = useAgentStore();
+  const { editAgentCard,fetchAgent } = useAgentStore();
   const handleSubmit = async () => {
     if (!agent?.agentId) {
       return;
@@ -22,10 +22,14 @@ const useEditContactCard = (
       return;
     }
 
-    await editAgentCard(agent.agentId, {
+    const result = await editAgentCard(agent.agentId, {
       ...agentContactCard,
       [editKey]: value,
     });
+    if(result){
+      await fetchAgent(agent.agentId)
+      Taro.navigateBack()
+    }
   };
   const onChange = (e: any) => {
     setValue(e);

+ 43 - 34
src/pages/chat/components/input-bar/index.tsx

@@ -11,10 +11,11 @@ import { EChatRole, EContent } from "@/types/bot";
 interface Props {
   agent: TAgentDetail | null;
   aiModel: EAI_MODEL;
+  setShowWelcome: (b: boolean) => void;
 }
 
-let stopReceiveChunk: (()=> void)|undefined
-export default ({ agent, aiModel }: Props) => {
+let stopReceiveChunk: (() => void) | undefined;
+export default ({ agent, setShowWelcome }: Props) => {
   const [isVoice, setIsVoice] = useState(false);
   const [disabled, setDisabled] = useState(false);
   const {
@@ -27,7 +28,7 @@ export default ({ agent, aiModel }: Props) => {
     deleteMessage,
   } = useTextChat();
   // const currentRobotMessageId = useTextChat((state)=> state.currentRobotMessageId);
-  let myMessageId = '';
+  let myMessageId = "";
 
   const handleTextInputBarSwitch = () => {
     console.log("voice input on");
@@ -37,38 +38,43 @@ export default ({ agent, aiModel }: Props) => {
     console.log("voice input off");
     setIsVoice(false);
   };
-  
-  
-  
+
   const chatWithGpt = async (message: string) => {
-    
-  let currentRobotMessageId = ''
+    setShowWelcome(false)
+    let currentRobotMessageId = "";
     await delay(300);
     setDisabled(true);
-    if(!agent?.agentId){
-      return 
+    if (!agent?.agentId) {
+      return;
     }
-    const loginId = getLoginId()
-    if(!loginId){
-      return ;
+    const loginId = getLoginId();
+    if (!loginId) {
+      return;
     }
+
+    const greeting = "欢迎光临我的智能体,你想问什么?";
+    const reqMessages = [
+      {
+        content: greeting,
+        contentType: EContent.TextPlain,
+        role: EChatRole.System,
+      },
+      {
+        content: message,
+        contentType: EContent.TextPlain,
+        role: EChatRole.User,
+      },
+    ];
+
     // 发起文本聊天
     stopReceiveChunk = textChat({
       params: {
         agentId: agent.agentId,
-        isEnableOutputAudioStream: false, 
-        isEnableSearch:false, 
-        isEnableThinking:false, 
+        isEnableOutputAudioStream: false,
+        isEnableSearch: false,
+        isEnableThinking: false,
         loginId,
-        messages: [
-          {
-            content: message,
-            contentType: EContent.TextPlain,
-            role: EChatRole.User,
-
-          },
-
-        ],
+        messages: reqMessages,
         sessionId: generateRandomId(),
       },
       onStart: () => {
@@ -83,19 +89,22 @@ export default ({ agent, aiModel }: Props) => {
         });
       },
       onReceived: (m) => {
-        
+        console.log("received:", m);
         if (m.reasoningContent) {
-          updateRobotReasoningMessage(m.reasoningContent, currentRobotMessageId);
+          updateRobotReasoningMessage(
+            m.reasoningContent,
+            currentRobotMessageId
+          );
         } else {
           updateRobotMessage(m.content);
         }
       },
       onFinished: () => {
         console.log("回复完毕 ok");
-        const currentRobotMessage = getCurrentRobotMessage()
+        const currentRobotMessage = getCurrentRobotMessage();
         // 如果没有任何回答,则显示
-        if(!currentRobotMessage?.content?.length){
-          updateRobotMessage('服务器繁忙...')
+        if (!currentRobotMessage?.content?.length) {
+          updateRobotMessage("服务器繁忙...");
         }
         setDisabled(false);
       },
@@ -124,11 +133,11 @@ export default ({ agent, aiModel }: Props) => {
     deleteMessage(myMessageId);
   };
 
-  useUnload(()=> {
-    if(stopReceiveChunk){
-      stopReceiveChunk()
+  useUnload(() => {
+    if (stopReceiveChunk) {
+      stopReceiveChunk();
     }
-  })
+  });
 
   if (isVoice) {
     // 语音输入

+ 16 - 16
src/pages/chat/index.tsx

@@ -97,21 +97,21 @@ export default function Index() {
     setDeepThink(EAI_MODEL.DeepseekChat);
   };
 
-  useEffect(() => {
-    // 主动打招呼
-    if (agent) {
-      const greetingText = `Hi~我是${agent.name},和我聊聊吧~`;
-      pushRobotMessage({
-        content: greetingText,
-        reasoningContent: "",
-        robot: {
-          avatar: agent.avatarUrl ?? "",
-          name: agent.name ?? "",
-          agentId: agent.agentId ?? "",
-        },
-      });
-    }
-  }, []);
+  // useEffect(() => {
+  //   // 主动打招呼
+  //   if (agent) {
+  //     const greetingText = `Hi~我是${agent.name},和我聊聊吧~`;
+  //     pushRobotMessage({
+  //       content: greetingText,
+  //       reasoningContent: "",
+  //       robot: {
+  //         avatar: agent.avatarUrl ?? "",
+  //         name: agent.name ?? "",
+  //         agentId: agent.agentId ?? "",
+  //       },
+  //     });
+  //   }
+  // }, []);
 
   
   const renderNavLeft = ()=> {
@@ -172,7 +172,7 @@ export default function Index() {
           >
             深度思考(R1)
           </View>
-          <InputBar aiModel={deepThink} agent={agent}></InputBar>
+          <InputBar aiModel={deepThink} agent={agent}  setShowWelcome={setShowWelcome}></InputBar>
         </View>
       </View>
     </PageCustom>

+ 63 - 84
src/pages/editor-pages/editor-greeting-questions/index.tsx

@@ -4,107 +4,86 @@ import { View } from "@tarojs/components";
 import PageCustom from "@/components/page-custom/index";
 import NavBarNormal from "@/components/nav-bar-normal/index";
 import WemetaInput from "@/components/wemeta-input/index";
-import editorStyle from "../editor.module.less";
 import Taro, { useUnload } from "@tarojs/taro";
-import { useCharacterStore } from "@/store/characterStore";
-import { useComponentStore } from "@/store/componentStore";
+import { useAgentStore } from "@/store/agentStore";
+
+type InputField = {
+  id: string; // 唯一标识(用于 key)
+  value: string;
+  title: string;
+};
 
 export default function Index() {
-  const $instance = Taro.getCurrentInstance();
-  const id = $instance.router?.params.id;
-  const deltaNum = id ? 1 : 2;
-  let currentComponent = useComponentStore((state) => state.currentComponent);
-  const character = useCharacterStore((state) => state.character);
-  const { saveComponent } = useComponentStore();
-  const loading = useComponentStore((state) => state.loading);
-  const { job, department, company } = currentComponent?.data ?? {};
-  const [value, setValue] = useState({
-    job: job ?? "",
-    department: department ?? "",
-    company: company ?? "",
-  });
+  const { agent, agentCharacter, editAgentCharacter, fetchAgent } =
+    useAgentStore();
+  const questionGuides = agentCharacter?.questionGuides ?? [];
+  const initialInputs: InputField[] = [
+    { id: "1", value: "", title: "问题一" },
+    { id: "2", value: "", title: "问题二" },
+    { id: "3", value: "", title: "问题三" },
+  ];
 
-  const onChange = (key: string, v: string) => {
-    value[key] = v;
-    setValue({
-      ...value,
-    });
+  questionGuides.map((q, index) => {
+    initialInputs[index].value = q;
+  });
+  const [inputs, setInputs] = useState<InputField[]>(initialInputs);
+  const handleChange = (id: string, newValue: string) => {
+    setInputs((prev) =>
+      prev.map((input) =>
+        input.id === id ? { ...input, value: newValue } : input
+      )
+    );
   };
-
-  const handleSave = async () => {
-    if (
-      !value.job?.length &&
-      !value.department?.length &&
-      !value.company?.length
-    ) {
+  let loading = false
+  const handleSubmit = async () => {
+    if (!agent?.agentId) {
       return;
     }
-    if (loading) {
+    if (!agentCharacter) {
       return;
     }
-    // const c = {
-    //   data: {
-    //     job: value.job,
-    //     department: value.department,
-    //     company: value.company,
-    //   },
-    //   enabled: currentComponent?.enabled ?? true,
-    //   id: currentComponent?.id,
-    //   name: currentComponent?.name ?? "商务名片",
-    //   characterProfileId:
-    //     currentComponent?.characterProfileId ?? character?.profileId,
-    //   type: "businessCard",
-    // };
-
-    // await saveComponent(c);
-  };
-
-  const handleNavBack = async () => {
-    await handleSave();
+    if(loading){
+      return;
+    }
+    loading = true
+    const result = await editAgentCharacter(agent.agentId, {
+      ...agentCharacter,
+      questionGuides: inputs.map((item) => item.value),
+    });
+    loading = false
+    if (result) {
+      await fetchAgent(agent.agentId);
+      Taro.navigateBack();
+    }
   };
 
   return (
     <PageCustom>
-      <NavBarNormal
-        backText="开场提问引导"
-        navDelta={deltaNum}
-        onNavBack={handleNavBack}
-      ></NavBarNormal>
+      <NavBarNormal backText="开场提问引导"></NavBarNormal>
       <View className="flex flex-col items-center w-full gap-16 p-16">
-        <View className="flex flex-col gap-6 w-full">
-          <View className="text-14 font-medium text-black leading-28">
-            问题一
-          </View>
-          <WemetaInput
-            value={value.job}
-            onInput={(value: string) => onChange("job", value)}
-            placeholder="请输入"
-          />
-        </View>
-        <View className="flex flex-col gap-6  w-full">
-          <View className="text-14 font-medium text-black leading-28">
-            问题一
-          </View>
-          <WemetaInput
-            value={value.job}
-            onInput={(value: string) => onChange("job", value)}
-            placeholder="请输入"
-          />
-        </View>
-        <View className="flex flex-col gap-6  w-full">
-          <View className="text-14 font-medium text-black leading-28">
-            问题一
-          </View>
-          <WemetaInput
-            value={value.job}
-            onInput={(value: string) => onChange("job", value)}
-            placeholder="请输入"
-          />
-        </View>
+        {inputs.map((item) => {
+          return (
+            <View className="flex flex-col gap-6 w-full">
+              <View className="text-14 font-medium text-black leading-28">
+                {item.title}
+              </View>
+              <WemetaInput
+                value={item.value}
+                onInput={(value: string) => handleChange(item.id, value)}
+                placeholder="请输入"
+              />
+            </View>
+          );
+        })}
       </View>
       <View className="bottom-bar">
         <View className="pt-12 px-20">
-          <View className={`button-rounded button-primary`}>保存</View>
+          <View
+            className={`button-rounded button-primary`}
+            onClick={handleSubmit}
+          >
+            保存
+          </View>
         </View>
       </View>
     </PageCustom>

+ 3 - 27
src/pages/editor-pages/editor-personality/index.tsx

@@ -8,36 +8,12 @@ import BottomBar from "@/components/BottomBar";
 import WemetaTextarea from "@/components/wemeta-textarea/index";
 import Taro, {useRouter, useUnload} from "@tarojs/taro";
 import { useAgentStore } from "@/store/agentStore";
+import useEditAgent from "@/hooks/useEditAgent";
+
 
 export default function Index() {
-  const router = useRouter();
-  const { agentId } = router.params;
   const agent = useAgentStore((state)=> state.agent)  
-  const {editAgentCharacter} = useAgentStore()  
-  const [value, setValue] = useState(agent?.personality ?? '');
-  
-  const handleSubmit = async () => {
-    if(!agent?.agentId){
-      return
-    }
-    if(value.length <= 0){
-      return;
-    }
-
-    await editAgentCharacter(agent.agentId, {
-      enabledPersonalKb: agent.enabledPersonalKb ?? false,
-      greeting: agent.greeting ?? '',
-      personality: value,
-      questionGuides: agent.questionGuides ?? [],
-      voiceId: agent.voiceId ?? '',
-    })
-    
-  };
-
-  const onChange = (e: any) => {
-    setValue(e);
-  };
-  
+  const {handleSubmit, onChange, value} = useEditAgent('personality', agent?.personality ?? '')
   
   
 

+ 8 - 4
src/pages/index/components/welcome/index.tsx

@@ -4,12 +4,12 @@ import Taro from "@tarojs/taro";
 
 import { getAgents, getAgent, createAgent, } from "@/service/agent";
 
-import { TAgentDetail } from '@/types/agent'
+import { isSuccess } from "@/utils";
 
 export default function Index({children}) {
   
-  const saveAgent = async (agent: TAgentItem) => {
-    const res = await createAgent(agent)
+  const saveAgent = async () => {
+    const res = await createAgent()
     console.log(res)
     Taro.navigateTo({url: '/pages/agent-gen/index'})
   }
@@ -18,8 +18,12 @@ export default function Index({children}) {
   }
   const go = async () => {
     const response = await getAgents()
+    const res = await createAgent()
+    
+    if(isSuccess(res.status)){
+      console.log(response, 'gogogo')
+    }
     
-    console.log(response, 'gogogo')
     // return 
     // saveAgent({
     //   address: '浙江杭州余杭文一西路1700号',

+ 2 - 1
src/pages/index/index.tsx

@@ -12,6 +12,7 @@ import refreshUserId, { clearUserInfo, getOpenIdAsync } from '@/xiaolanbenlib/ut
 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'
 
 export default function Index() {
 
@@ -19,7 +20,7 @@ export default function Index() {
   const isLogin = useIsLogin()
 
   const {fetchAgents} =  useAgentStore()
-  const {getSysCoreCnf} =  useSystemStore()
+  const { getSysCoreCnf } =  useSystemStore()
 
   async function initUserInfo() {
     await refreshUserId()

+ 3 - 3
src/pages/profile/components/top-bar/index.tsx

@@ -47,12 +47,12 @@ export default () => {
             <View className="flex items-start mb-24">
               <View className="flex flex-col flex-1">
                 <View className="flex items-end gap-8">
-                  <View className="text-24 font-medium leading-32">张三</View>
-                  <View className="text-12 leading-20">销售医师</View>
+                  <View className="text-24 font-medium leading-32">{agent?.name}</View>
+                  <View className="text-12 leading-20">{agent?.position ?? '--'}</View>
                 </View>
                 <View className="flex items-center gap-2">
                   <View className="text-12 leading-20 truncate max-w-[188px]">
-                    北京茗视光眼科医院管理有限公司
+                    {agent?.entName ?? '--'}
                   </View>
                   <TagCertificated />
                 </View>

+ 23 - 3
src/pages/profile/index.tsx

@@ -6,7 +6,9 @@ import Logo from '@/components/logo'
 import TopBar from './components/top-bar'
 import { useAgentStore } from '@/store/agentStore'
 import { useEffect } from "react";
-import { useRouter } from "@tarojs/taro";
+import Taro, { useRouter } from "@tarojs/taro";
+import { uploadFile } from "@/utils/http";
+import { EUploadFileScene } from "@/consts/enum";
 export default function Index() {
 
   const router = useRouter();
@@ -24,14 +26,32 @@ export default function Index() {
   //   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晶体植入以及多焦点人工晶体置换手术。

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


+ 44 - 53
src/pages/voice/components/system-voice/index.tsx → src/pages/voice/components/all-voice/index.tsx

@@ -6,73 +6,71 @@ 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";
-
-type ExtendedTServiceAudioModel = TServiceAudioModel & { checked: boolean };
+import { TGetMyVoicesParams, TVoiceItem, TPageination } from '@/types/storage'
+type ExtendedTVoiceItem = TVoiceItem & { checked: boolean };
 export default function Index({
-  profileId,
   onPlay,
+  list,
 }: {
-  profileId: string;
   onPlay: (voice: any) => void;
+  list: TVoiceItem[]
 }) {
-  const { saveCharacter } = useCharacterStore();
-  const character = useCharacterStore((state) => state.character);
-  const [list, setList] = useState<ExtendedTServiceAudioModel[]>([]);
-  const [sysVoice, setSysVoice] = useState<ExtendedTServiceAudioModel | null>(
-    null
-  );
+  
+  
+  
+  
 
   const initPage = async () => {
-    console.log("profileId: ", profileId);
-    const res = await getSysVoiceList();
+    // console.log("profileId: ", profileId);
+    // const res = await getSysVoiceList();
 
-    if (res.code === 0 && res.data) {
-      const r = res.data.map((item) => {
-        item.checked = character?.defaultSystemVoice === item.voice;
-        return item;
-      }) as ExtendedTServiceAudioModel[];
-      const checkedVoice = r.find((item) => !!item.checked);
-      if (checkedVoice) {
-        setSysVoice(checkedVoice);
-      }
-      setList(r);
-    }
+    // if (res.code === 0 && res.data) {
+    //   const r = res.data.map((item) => {
+    //     item.checked = character?.defaultSystemVoice === item.voice;
+    //     return item;
+    //   }) as ExtendedTVoiceItem[];
+    //   const checkedVoice = r.find((item) => !!item.checked);
+    //   if (checkedVoice) {
+    //     setSysVoice(checkedVoice);
+    //   }
+    //   setList(r);
+    // }
   };
 
-  const handleSelect = async (voiceItem: ExtendedTServiceAudioModel) => {
+  const handleSelect = async (voiceItem: ExtendedTVoiceItem) => {
     console.log("system voice select");
 
-    const _list = list.map((item) => {
-      return { ...item, checked: item.voice === voiceItem.voice };
-    });
+    // const _list = list.map((item) => {
+    //   return { ...item, checked: item.voice === voiceItem.voice };
+    // });
 
-    setList(_list);
+    // setList(_list);
 
-    setSysVoice(voiceItem);
-    if (profileId) {
-      saveCharacter({
-        defaultSystemVoice: voiceItem.voice,
-        voice: voiceItem.voice,
-        profileId: profileId,
-      });
-    }
+    // setSysVoice(voiceItem);
+    // if (profileId) {
+    //   saveCharacter({
+    //     defaultSystemVoice: voiceItem.voice,
+    //     voice: voiceItem.voice,
+    //     profileId: profileId,
+    //   });
+    // }
     // playerRef.current && playerRef.current.play(voiceItem.voice);
     onPlay && onPlay(voiceItem);
   };
 
   useEffect(() => {
-    console.log("系统声音页面显示");
     initPage();
-  }, [profileId]);
+  }, []);
 
   return (
     <View className="w-full">
       <View className="flex flex-col w-full gap-28">
-      <CardList>
+      <View>
           <View className="flex flex-col gap-12">
-        {list.map((item: ExtendedTServiceAudioModel, index) => {
+        {list.map((item: ExtendedTVoiceItem, index) => {
           // 与克隆语音是否相同
-          const isEqualToClonedVoice = item.voice === character?.voice;
+          // const isEqualToClonedVoice = item.voice === character?.voice;
+          const isEqualToClonedVoice = false;
           return (
             <CardListItem
             underline
@@ -84,17 +82,10 @@ export default function Index({
                 ></WemetaRadio>
               </View>
             }}
-            leftRenderer={()=> {
-              return <Image
-                src={item.avatar}
-                mode="widthFix"
-                className="w-56 h-56 rounded-12 shrink-0"
-              ></Image>
-            }}
           >
             <View
-              className="flex gap-16 w-full"
-              key={item.id}
+              className="flex gap-16 w-full py-16"
+              key={item.voiceId}
               onClick={() => {
                 handleSelect(item);
               }}
@@ -102,10 +93,10 @@ export default function Index({
               
               <View className="flex flex-1 flex-col gap-4 overflow-hidden">
                 <View className="text-14 leading-22 font-medium text-black">
-                  {item.name}
+                  {item.voiceName}
                 </View>
                 <View className="flex-1 text-12 leading-20 font-medium text-gray-45 truncate">
-                  {item.gender === 1 ? "男" : "女"} {item.style}
+                  {item.gender === 'male' ? "男" : "女"}
                 </View>
               </View>
             </View>
@@ -113,7 +104,7 @@ export default function Index({
           );
         })}
         </View>
-        </CardList>
+        </View>
       </View>
     </View>
   );

+ 17 - 21
src/pages/voice/components/my-voice/components/popup-recorder/index.tsx

@@ -3,9 +3,13 @@ import { Image, View } from "@tarojs/components";
 import Taro, { useReady } from "@tarojs/taro";
 import { useEffect, useState } from "react";
 import popupStyle from "./index.module.less";
-
-import { useCharacterStore } from "@/store/characterStore";
+import {
+  cloneVoice
+  
+} from "@/service/voice";
 import { EUploadFileScene } from "@/consts/enum";
+import { useVoiceStore } from "@/store/voiceStore";
+import { isSuccess } from "@/utils";
 
 export enum ECloneStatus {
   STOP = 0,
@@ -44,15 +48,10 @@ export default ({
   show,
 }: Props) => {
   const [recording, setRecording] = useState(false);
-  const { cloneVoice } = useCharacterStore();
+  
   const recorderManager = Taro.getRecorderManager();
   const readText = text;
-  // let audioCtx:Taro.WebAudioContext | AudioContext;
-  // if (process.env.TARO_ENV === 'h5') {
-  //   audioCtx = new AudioContext()
-  // }else {
-  //   audioCtx = Taro.createWebAudioContext()
-  // }
+  
   recorderManager.onStart(() => {
     console.log("recorder start");
   });
@@ -94,17 +93,16 @@ export default ({
     //   title: '发送成功',
     //   icon: 'success'
     // })
-    console.log(response);
-    if (response && response.code === 0 && response.data) {
+    console.log(response?.publicUrl, 'publicUrl');
+    if (response && response.publicUrl) {
       Taro.showLoading();
-      const res = await cloneVoice(response.data, voiceName);
-      console.log(res);
+      const res = await cloneVoice({sourceUrl: response.publicUrl, voiceText: readText});
+      console.log();
       Taro.hideLoading();
-      if (res && res.code === 0) {
-        onRecordEnd(res.data, voiceName);
-      } else {
-        Taro.showToast({ title: "录制失败", icon: "error" });
-        onRecordEnd("", voiceName);
+      if(isSuccess(res.status)){
+        onRecordEnd(res.data.taskId, voiceName);
+      }else{
+        onRecordEnd('', voiceName);
       }
     }
   });
@@ -124,9 +122,7 @@ export default ({
     setCloneStatus(ECloneStatus.STOP);
     setRecording(false);
   };
-  const handlePopupLeave = () => {
-    setShow(false);
-  };
+  
   const onLongPress = (event: any) => {
     recorderManager.start({
       duration: 1000 * 60 * 20,

+ 93 - 110
src/pages/voice/components/my-voice/index.tsx

@@ -16,6 +16,8 @@ import Popup from "@/components/popup/popup";
 import PopupRecorder, { ECloneStatus } from "./components/popup-recorder/index";
 import PopupTryout from "./components/popup-tryout/index";
 import style from "./index.module.less";
+import { useVoiceStore } from "@/store/voiceStore";
+import { EVoiceStatus, TVoiceItem } from "@/types/voice";
 
 interface Props {
   value?: ICharacter;
@@ -35,95 +37,16 @@ export default ({ profileId, onPlay }: Props) => {
   const [voiceName, setVoiceName] = useState("");
   const [voiceIndex, setVoiceIndex] = useState(-1);
 
-  const { saveCharacter, fetchCharacter, fetchVoiceCloneHistory } =
-    useCharacterStore();
+  // const { saveCharacter, fetchCharacter, fetchVoiceCloneHistory } =
+  //   useCharacterStore();
+  const {getVoices} = useVoiceStore()
+  const voices = useVoiceStore(state => state.voices)
   const character = useCharacterStore((state) => state.character);
-  const voiceList = useCharacterStore((state) => state.voiceList);
+  const myVoices = useVoiceStore(state => state.myVoices)
   const appConfig = useAppStore((state) => state.appConfig);
 
-  const createVoiceNameText = (voiceName: string) => {
-    const i = voiceList.findIndex((item) => item.voiceName === voiceName);
-    console.log(voiceName, i);
-    if (i === -1) return "";
-    return `克隆声音 0${i + 1}`;
-  };
-  // 克隆列表操作
-  const renderRightColumn = (item?: TEntityVoiceCloneRecord) => {
-    if (item?.status == CloneVoiceStatus.CloneVoiceStatusExpired) {
-      return <WemetaRadio disabled></WemetaRadio>;
-    }
-
-    if (item?.status == CloneVoiceStatus.CloneVoiceStatusSuccess) {
-      const notSystemVoice = (character?.voice !== character?.defaultSystemVoice)
-      const isSameVoice = (item.voiceName === character?.voice)
-      return (
-        <WemetaRadio
-          checked={ isSameVoice && notSystemVoice}
-        ></WemetaRadio>
-      );
-    }
-
-    if (item?.status == CloneVoiceStatus.CloneVoiceStatusUnconfirmed) {
-      return (
-        <View className="text-14 bg-primary text-black leading-22 px-12 py-6 rounded-28 active:pressed-button">
-          试听
-        </View>
-      );
-    }
-    return <></>;
-  };
-
-  // 克隆状态
-  const renderCloneStatus = (item: TEntityVoiceCloneRecord) => {
-    if (item.status === CloneVoiceStatus.CloneVoiceStatusSuccess) {
-      return (
-        <View className={`text-12 leading-20 text-gray-45`}>
-          {item.createdAt && formatDateFull(new Date(item.createdAt))}
-        </View>
-      );
-    }
-    if (item.status === CloneVoiceStatus.CloneVoiceStatusUnconfirmed) {
-      return <View className={`text-12 leading-20 text-orange`}>试听确认</View>;
-    }
-
-    if (item.status == CloneVoiceStatus.CloneVoiceStatusExpired) {
-      return <View className={`text-12 leading-20 text-orange`}>已过期</View>;
-    }
-    return <View>当前时间</View>;
-  };
-
-  const renderCloneList = () => {
-    if (!voiceList.length) {
-      return <EmptyData></EmptyData>;
-    }
-    return (
-      <CardList>
-        {voiceList.map((item, _index) => {
-          return (
-            <CardListItem
-              rightRenderer={() => {
-                return renderRightColumn(item);
-              }}
-              onClick={() => handleSelect(item, _index)}
-            >
-              <View className="flex items-center gap-16">
-                <View className={style.listIcon}>
-                  <Image src={IconWave} className={style.iconImage}></Image>
-                </View>
-                <View className="flex flex-col gap-4">
-                  <View className={style.ListItemText}>
-                    {/* deprecated getCloneVoiceIdentifier */}
-                    {item.voiceAlias ?? getCloneVoiceIdentifier(_index + 1)}
-                  </View>
-                  {renderCloneStatus(item)}
-                </View>
-              </View>
-            </CardListItem>
-          );
-        })}
-      </CardList>
-    );
-  };
+  
+  
 
   const handleSelect = (item: TEntityVoiceCloneRecord, index: number) => {
     setVoiceIndex(index);
@@ -138,10 +61,7 @@ export default ({ profileId, onPlay }: Props) => {
             voiceIndex: index,
           });
       }
-      saveCharacter({
-        profileId: profileId,
-        voice: item.voiceName,
-      });
+      // save
     }
 
     if (item.status == CloneVoiceStatus.CloneVoiceStatusUnconfirmed) {
@@ -158,8 +78,8 @@ export default ({ profileId, onPlay }: Props) => {
 
   const fetchVoiceList = async () => {
     if (profileId) {
-      const r = await fetchVoiceCloneHistory(profileId);
-      const result = r.find((item) => item.status === "pending");
+      await getVoices();
+      const result = voices.find((item) => !item.isSystem && item.status === 'processing');
       if (result) {
         intervalRef.current = setTimeout(() => fetchVoiceList(), 3000);
       } else {
@@ -170,7 +90,7 @@ export default ({ profileId, onPlay }: Props) => {
 
   const handleSureAction = async () => {
     setShow(false);
-    await voiceCloneConfirm(voiceList[voiceIndex].voiceName!);
+    await voiceCloneConfirm(myVoices[voiceIndex].voiceName!);
     fetchVoiceList();
   };
 
@@ -194,26 +114,22 @@ export default ({ profileId, onPlay }: Props) => {
 
   const calcRemainCloneNum = () => {
     if (appConfig?.maxCloneNum) {
-      const clonedNum = voiceList.filter(
-        (item) =>
-          item.status === CloneVoiceStatus.CloneVoiceStatusSuccess ||
-          item.status === CloneVoiceStatus.CloneVoiceStatusUnconfirmed,
-      ).length;
-      const remainNum = appConfig.maxCloneNum - clonedNum;
-      return remainNum < 0 ? 0 : remainNum;
+      // const clonedNum = myVoices.filter(
+      //   (item) =>
+      //     item.status === CloneVoiceStatus.CloneVoiceStatusSuccess ||
+      //     item.status === CloneVoiceStatus.CloneVoiceStatusUnconfirmed,
+      // ).length;
+      // const remainNum = appConfig.maxCloneNum - clonedNum;
+      // return remainNum < 0 ? 0 : remainNum;
     }
-    return voiceList.length;
+    return myVoices.length;
   };
 
-  const initPage = async (profileId: string) => {
-    await fetchCharacter(profileId);
-    fetchVoiceCloneHistory(profileId);
-    character?.voice && setVoiceName(character.voice);
+  const initPage = async () => {
+    
   };
 
-  useEffect(() => {
-    profileId && initPage(profileId);
-  }, [profileId]);
+  
 
   // 清除定时器
   useEffect(() => {
@@ -221,6 +137,73 @@ export default ({ profileId, onPlay }: Props) => {
       stopTimer();
     };
   }, []);
+
+
+  // 克隆列表右侧操作栏
+  const renderRightColumn = (item: TVoiceItem) => {
+    if (item.status == EVoiceStatus.DONE) {
+      return <WemetaRadio></WemetaRadio>;
+    }
+
+    return <></>;
+  };
+
+  // 克隆状态栏
+  const renderCloneStatus = (item: TVoiceItem) => {
+    if (item.status === EVoiceStatus.DONE) {
+      return (
+        <View className={`text-12 leading-20 text-gray-45`}>
+          {item.createTime && formatDateFull(new Date(item.createTime))}
+        </View>
+      );
+    }
+    if (item.status === EVoiceStatus.DOING) {
+      return <View className={`text-12 leading-20 text-orange`}>生成中...</View>;
+    }
+
+    if (item.status == EVoiceStatus.FAILED) {
+      return <View className={`text-12 leading-20 text-orange`}>生成错误</View>;
+    }
+    return <View>...</View>;
+  };
+
+  // 渲染克隆列表
+  const renderCloneList = () => {
+    if (!myVoices.length) {
+      return <EmptyData></EmptyData>;
+    }
+    return (
+      <View className="py-12 px-16 gap-">
+        {myVoices.map((item, _index) => {
+          return (
+            <CardListItem
+             className="py-16"
+              underline
+              leftRenderer={()=> {
+                return <View className={style.listIcon}>
+                  <Image src={IconWave} className={style.iconImage}></Image>
+                </View>
+              }}
+              rightRenderer={() => {
+                return renderRightColumn(item);
+              }}
+              onClick={() => handleSelect(item, _index)}
+            >
+              <View className="flex items-center gap-16">
+                <View className="flex flex-col gap-4">
+                  <View className={style.ListItemText}>
+                    {item.voiceName}
+                  </View>
+                  {renderCloneStatus(item)}
+                </View>
+              </View>
+            </CardListItem>
+          );
+        })}
+      </View>
+    );
+  };
+
   return (
     <View className={`flex flex-col`}>
       <View className="flex-1">
@@ -242,7 +225,7 @@ export default ({ profileId, onPlay }: Props) => {
               setShow={setShow}
               setCloneStatus={(status) => handleCloneStatus(status)}
               voiceName={
-                popupType == "reclone" ? voiceList[voiceIndex].voiceName! : ""
+                popupType == "reclone" ? myVoices[voiceIndex].voiceName! : ""
               }
             ></PopupRecorder>
           )}
@@ -251,7 +234,7 @@ export default ({ profileId, onPlay }: Props) => {
               show={show}
               onSure={handleSureAction}
               onReclone={handleRecloneAction}
-              voiceName={voiceList[voiceIndex].voiceName!}
+              voiceName={myVoices[voiceIndex].voiceName!}
               showName={getCloneVoiceIdentifier(voiceIndex + 1)}
             ></PopupTryout>
           )}

+ 31 - 16
src/pages/voice/index.tsx

@@ -1,14 +1,15 @@
 import WemetaTabs from "@/components/wemeta-tabs/index";
 import VoicePlayerBar, { IVoicePlayerBar } from "@/components/voice-player-bar";
-import SystemVoice from "./components/system-voice";
+import AllVoice from "./components/all-voice";
 import MyVoice from "./components/my-voice/index";
 import { TServiceAudioModel } from "@/types";
 import { View, ScrollView } from "@tarojs/components";
 import { useRouter } from "@tarojs/taro";
-import React, { useRef, useState } from "react";
+import React, { useEffect, useRef, useState } from "react";
 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";
 
 interface Props {}
 type ExtendedTServiceAudioModel = TServiceAudioModel & { checked: boolean };
@@ -24,6 +25,19 @@ const [sysVoice, setSysVoice] = useState<ExtendedTServiceAudioModel | null>(
   const router = useRouter();
   const profileId = router.params.profileId || "";
 
+  const {voices, isLoading, pagination, getVoices, setPagination} = useVoiceStore()
+
+  const handleScrollEnd = () => {
+    if(isLoading){
+      return;
+    }
+    setPagination({ pageIndex: pagination.pageIndex + 1 });
+  }
+
+  useEffect(()=> {
+    getVoices()
+  }, [])
+
   const tabList = [
     {
       label: "我的",
@@ -42,18 +56,19 @@ const [sysVoice, setSysVoice] = useState<ExtendedTServiceAudioModel | null>(
           <ScrollView
             scrollY
             id="scrollView"
+            onScrollEnd={handleScrollEnd}
             style={{
               flex: 1,
               height: "100%", // 高度自适应
             }}
           >
             <View className="px-16 py-12">
-              <SystemVoice
-                profileId={profileId}
+              <AllVoice
+                list={voices}
                 onPlay={(item) => {
-                  handlePlayAction(item, "system");
+                  handlePlayAction(item, 'system');
                 }}
-              ></SystemVoice>
+              ></AllVoice>
             </View>
           </ScrollView>
         </View>
@@ -64,7 +79,7 @@ const [sysVoice, setSysVoice] = useState<ExtendedTServiceAudioModel | null>(
       key: "3",
       children: (
         <View className={style.tabContent}>
-          <View>我的</View>
+          <View>female</View>
         </View>
       ),
     },
@@ -73,7 +88,7 @@ const [sysVoice, setSysVoice] = useState<ExtendedTServiceAudioModel | null>(
       key: "4",
       children: (
         <View className={style.tabContent}>
-          <View>我的</View>
+          <View>male</View>
         </View>
       ),
     },
@@ -81,17 +96,17 @@ const [sysVoice, setSysVoice] = useState<ExtendedTServiceAudioModel | null>(
 
   const handlePlayAction = (voiceItem: any, type: "system" | "cloned") => {
     if (type == "system") {
-      setVoiceName("");
-      setVoiceAlias("");
-      setVoiceIdx(-1);
+      // setVoiceName("");
+      // setVoiceAlias("");
+      // setVoiceIdx(-1);
 
-      setSysVoice(voiceItem);
+      // setSysVoice(voiceItem);
       playerRef.current && playerRef.current.play(voiceItem.voice);
     } else {
-      setSysVoice(null);
-      setVoiceName(voiceItem.voiceName);
-      setVoiceAlias(voiceItem.voiceAlias);
-      setVoiceIdx(voiceItem.voiceIndex);
+      // setSysVoice(null);
+      // setVoiceName(voiceItem.voiceName);
+      // setVoiceAlias(voiceItem.voiceAlias);
+      // setVoiceIdx(voiceItem.voiceIndex);
       playerRef.current && playerRef.current.play(voiceItem.voiceName);
     }
   };

+ 2 - 2
src/service/bot.ts

@@ -13,7 +13,7 @@ export type TMessageHistories = {
 };
 
 // 获取与指定智能体的历史会话记录--按智能体维度倒序返回
-type TGetMessageHistoriesParams = {
+export type TGetMessageHistoriesParams = {
   agentId: string;
   startId?: string; // 起始ID, 如果未传入则获取最新的N条
   pageSize: number;
@@ -98,7 +98,7 @@ export const textChat = ({
     const uint8Array = new Uint8Array(chunk.data);
     console.log('uint8Array: ', uint8Array);
     var string = new TextDecoder("utf-8").decode(uint8Array);
-    console.log('decode', string);
+    console.log(string);
     jsonParser.parseChunk(string, (m) => {
       console.log('parseChunk', m);
       onReceived(m);

+ 3 - 30
src/service/storage.ts

@@ -7,6 +7,9 @@ import Taro from '@tarojs/taro'
 
 import {FileTypes} from '@/consts'
 
+import { EVoiceType, TGetMyVoicesParams, TPaginatedVoiceResponse, TVoiceItem } from '@/types/storage'
+import { EGender } from '@/types'
+
 export type TAvatarItem = {
   "avatarId": number,
   "avatarUrl": string,
@@ -87,36 +90,6 @@ export const getUploadSecurityToken = () => {
   return request.get<TUploadSecurityToken>(`${bluebookAiAgent}api/v1/my/upload/securityToken`)
 }
 
-// 克隆一个新的音色
-export type TVoiceItem = {
-  "createTime": string,
-  "failedReason": string,
-  "platform": string,
-  "sourceUrl": string,
-  "status": string,
-  "voiceId": string,
-  "voiceName": string
-}
-export const cloneVoice = (data: {
-  "sourceUrl": string,
-  "voiceText": string
-}) => {
-  return request.post<TVoiceItem>(`${bluebookAiAgent}api/v1/voice/clone`, data)
-}
 
-// 删除指定克隆的音色
-export const deleteVoice = (voiceId: string)=> {
-  return request.delete(`${bluebookAiAgent}api/v1/voice/clone/${voiceId}`)
-}
-
-// 获取指定克隆的音色记录--用于状态轮询
-export const getVoice = (voiceId: string) => {
-  return request.get<TVoiceItem>(`${bluebookAiAgent}api/v1/my/voice/${voiceId}`)
-}
-
-// 获取个人录音音色库
-export const getVoices = () => {
-  return request.get<TVoiceItem[]>(`${bluebookAiAgent}api/v1/my/voices/`)
-}
 
 

+ 0 - 1
src/service/visitor.ts

@@ -166,7 +166,6 @@ export type TVisitorMessagesResponse = {
 export const getVisitorMessages = (data: {
   "visitorId": string,
   "startId": string,
-  "startId": string,
   "pageSize": string,
 }) => {
   return request.get<TVisitorListResponse>(`${bluebookAiAgent}api/v1/my/visitor/messages`, {params: data})

+ 33 - 0
src/service/voice.ts

@@ -0,0 +1,33 @@
+// 我的音色库&形象库
+import {
+  bluebookAiAgent,
+} from '@/xiaolanbenlib/api/index'
+import request from '@/xiaolanbenlib/module/axios.js'
+import Taro from '@tarojs/taro'
+
+import { TCloneVoiceResponse, TGetMyVoicesParams, TPaginatedVoiceResponse, TVoiceItem } from '@/types/storage'
+
+// 克隆一个新的音色
+export const cloneVoice = (data: {
+  sourceUrl: string // 源语音地址 ,
+  voiceText?: string // 录音文案
+}) => {
+  return request.post<TCloneVoiceResponse>(`${bluebookAiAgent}api/v1/voice/clone`, data)
+}
+
+// 删除指定克隆的音色
+export const deleteVoice = (voiceId: string)=> {
+  return request.delete(`${bluebookAiAgent}api/v1/voice/clone/${voiceId}`)
+}
+
+// 获取指定克隆的音色记录--用于状态轮询
+export const getVoiceStatus = (taskId: string) => {
+  return request.get<TCloneVoiceResponse>(`${bluebookAiAgent}api/v1/my/voice/${taskId}/status`)
+}
+
+// 获取个人录音音色库
+export const getVoices = (data:TGetMyVoicesParams) => {
+  return request.get<TPaginatedVoiceResponse>(`${bluebookAiAgent}api/v1/my/voices/`, {
+    params: data
+  })
+}

+ 28 - 24
src/store/agentStore.ts

@@ -8,21 +8,30 @@ import {
   editAgentCard as _editAgentCard,
   editAgentCharacter as _editAgentCharacter,
 } from "@/service/agent";
-import { TAgentDetail, TAgent, TAgentContactCard, TEditAgentCharacter } from "@/types/agent";
-import { pickNonEmpty } from "@/utils";
+import {
+  TAgentDetail,
+  TAgent,
+  TAgentContactCard,
+  TEditAgentCharacter,
+} from "@/types/agent";
+import { isSuccess, pickNonEmpty } from "@/utils";
 
 export interface AgentStoreState {
   agents: TAgent[];
   agent: TAgentDetail | null;
   agentContactCard: TAgentContactCard | null;
+  agentCharacter: TEditAgentCharacter | null;
   fetchAgents: () => Promise<TAgent[]>;
   fetchAgent: (agentId: string) => Promise<TAgentDetail | null>;
   createAgent: (data: TAgentDetail) => Promise<TAgentDetail | null>;
-  editAgentCharacter: (agentId: string, data: TEditAgentCharacter) => Promise<void>;
+  editAgentCharacter: (
+    agentId: string,
+    data: TEditAgentCharacter
+  ) => Promise<boolean>;
   editAgentCard: (
     agentId: string,
     data: TAgentContactCard
-  ) => Promise<TAgentContactCard | null>;
+  ) => Promise<boolean>;
   // deleteAgent: (agentId: string) => Promise<void>
 }
 
@@ -30,6 +39,7 @@ export const useAgentStore = create<AgentStoreState>((set, get) => ({
   agents: [],
   agent: null,
   agentContactCard: null,
+  agentCharacter: null,
   fetchAgents: async () => {
     const response = await request.get<TAgent[]>(
       `${bluebookAiAgent}api/v1/my/agents`
@@ -58,6 +68,13 @@ export const useAgentStore = create<AgentStoreState>((set, get) => ({
           position: agent.position ?? "",
           qrCodeUrl: agent.qrCodeUrl ?? "",
         },
+        agentCharacter: {
+          enabledPersonalKb: agent.enabledPersonalKb ?? false,
+          greeting: agent.greeting ?? `你好,我是${agent.name}`,
+          personality: agent.personality ?? "",
+          questionGuides: agent.questionGuides ?? [],
+          voiceId: agent.voiceId ?? "",
+        },
       });
 
       return response.data;
@@ -86,31 +103,18 @@ export const useAgentStore = create<AgentStoreState>((set, get) => ({
     return null;
   },
   editAgentCharacter: async (agentId: string, data: TEditAgentCharacter) => {
-    const response = await _editAgentCharacter(agentId, data);
+    const filteredObj = pickNonEmpty(data);
+    const response = await _editAgentCharacter(agentId, filteredObj);
     console.log(response.data);
-    
+    return isSuccess(response.status)
   },
   editAgentCard: async (agentId: string, data: TAgentContactCard) => {
-    console.log(agentId, data)
-    const filteredObj = pickNonEmpty(data)
+    console.log(agentId, data);
+    const filteredObj = pickNonEmpty(data);
     const response = await _editAgentCard(agentId, filteredObj);
     console.log(response);
-    if (response) {
-      // set((state) => {
-      //   return {
-      //     agents: [...state.agents, {
-      //       "agentId": newAgent.agentId ?? '',
-      //       "isDefault": newAgent.isDefault ?? false,
-      //       "isEnt": newAgent.isEnt ?? false,
-      //       "isNewEnt": newAgent.isNewEnt ?? false,
-      //       "name": newAgent.name ?? ''
-      //     }]
-      //   }
-      // })
-      // return newAgent
-      return null;
-    }
-    return null;
+
+    return isSuccess(response.status)
   },
   // deleteAgent: async (agentId: string)=> {
   //   const response = await _deleteAgent(agentId)

+ 5 - 2
src/store/systemStore.ts

@@ -6,18 +6,21 @@ import {
   getSysCoreCnf as _getSysCoreCnf,
   
 } from "@/service/system";
+import Taro from "@tarojs/taro";
+import { OSS_PUBLIC_BUCKET_NAME_KEY } from "@/xiaolanbenlib/constant";
 
 
-export interface AgentStoreState {
+export interface SystemStoreState {
   sysCoreCnf: TSystemCoreCnf | null;
   getSysCoreCnf: () => Promise<TSystemCoreCnf|null>;
 }
 
-export const useSystemStore = create<AgentStoreState>((set, get) => ({
+export const useSystemStore = create<SystemStoreState>((set, get) => ({
   sysCoreCnf: null,
   getSysCoreCnf: async () => {
     const response = await _getSysCoreCnf()
     if (response && response?.data) {
+      Taro.setStorageSync(OSS_PUBLIC_BUCKET_NAME_KEY, response.data?.ossPublicBucketName)
       set({ sysCoreCnf: response.data });
       return response.data;
     }

+ 10 - 0
src/store/textChat.ts

@@ -5,6 +5,8 @@
 import { create } from "zustand";
 import { generateRandomId } from '@/utils/index'
 
+import { getMessageHistories as _getMessageHistories, type TGetMessageHistoriesParams,  } from '@/service/bot'
+
 export type TMessage = {
   messageId: string;
   content: string;
@@ -44,6 +46,7 @@ export interface TextChat {
   deleteMessage: (messageId: string) => void;
   // 清空
   destroy: () => void;
+  getMessageHistories: (data: TGetMessageHistoriesParams) => void
 }
 
 // 新messageId 为 index 加 1
@@ -152,5 +155,12 @@ export const useTextChat = create<TextChat>((set, get) => ({
   // 停止当前机器人说话输出框输出
   stopCurrentRobotMessaging: ()=> {
     set({currentRobotMessageId: INIT_CURRENT_ROBOT_MESSAGE_ID})
+  },
+  getMessageHistories: async (data: TGetMessageHistoriesParams) => {
+    const response = await _getMessageHistories(data)
+    console.log(response)
+    if(response.status){
+      
+    }
   }
 }));

+ 71 - 0
src/store/voiceStore.ts

@@ -0,0 +1,71 @@
+import { create } from "zustand";
+
+import { TGetMyVoicesParams, TVoiceItem, TPageination } from '@/types/storage'
+
+import {
+  getVoices as _getVoices,
+  cloneVoice as _cloneVoice,
+  
+} from "@/service/voice";
+import { isSuccess } from "@/utils";
+
+
+export interface StorageStoreState {
+  // 初始状态
+  voices: TVoiceItem[];
+  myVoices: TVoiceItem[];
+  pagination: TPageination
+  isLoading: boolean,
+  getVoices: (params?: TGetMyVoicesParams) => Promise<boolean>;
+  setPagination: (params:any) => Promise<void>;
+  cloneVoice: (params: {
+    sourceUrl: string // 源语音地址 ,
+    voiceText?: string // 录音文案
+  }) => Promise<void>;
+}
+
+export const useVoiceStore = create<StorageStoreState>((set, get) => ({
+  voices: [],
+  myVoices: [],
+  pagination: {
+    pageIndex: 1,
+    pageSize: 10,
+    totalCount: 0,
+    searchKey: '',
+    extData: null,
+  },
+  
+  isLoading: false,
+  getVoices: async (params = {}) => {
+    set({ isLoading: true });
+    // 合并当前分页参数和传入参数
+    const currentPagination = get().pagination;
+    const requestParams = { ...currentPagination, ...params };
+
+    const response = await _getVoices(requestParams)
+
+    // 更新状态
+    set({
+      voices: response.data.data,
+      myVoices: response.data.data.filter(item => !item.isSystem),
+      pagination: {
+        ...requestParams,
+        totalCount: response.data.totalCount,
+      },
+      isLoading: false
+    });
+    return isSuccess(response.status)
+  },
+  // 更新分页参数
+  setPagination: async (params:any) => {
+    set((state) => ({
+      pagination: { ...state.pagination, ...params },
+    }));
+    // 可选:自动触发请求
+    get().getVoices();
+  },
+  cloneVoice: async (params) => {
+    const response = await _cloneVoice(params)
+    console.log(response,444444) 
+  }
+}));

+ 17 - 1
src/types/index.ts

@@ -1,3 +1,13 @@
+export enum EGender {
+  MALE = 'male',
+  FEMALE = 'femail'
+}
+
+
+
+
+
+
 export interface IPageParams {
   /**
    * Page number
@@ -516,4 +526,10 @@ export type ModelColor = {
   g?: string;
   r?: string;
   [property: string]: any;
-}
+}
+
+
+
+
+
+

+ 0 - 0
src/types/storage.ts


+ 55 - 0
src/types/voice.ts

@@ -0,0 +1,55 @@
+
+export type TGender = "male" | "female" | "unknown";
+
+export enum EVoiceStatus {
+  UNKNOWN = 'unknown',
+  DOING = 'doing',
+  DONE = 'done',
+  FAILED = 'failed'
+}
+export type TVoiceStatus = EVoiceStatus[keyof EVoiceStatus];
+
+export type TVoiceItem = {
+  createTime: string;
+  duration: number;
+  failedMsg: string;
+  gender: TGender;
+  genderDesc: string;
+  isSystem: boolean;
+  sourceName: string;
+  status: TVoiceStatus;
+  voiceId: string;
+  voiceName: string;
+  voiceUrl: string;
+};
+
+// 分页获取音色列表
+export type TPaginatedVoiceResponse<T = TVoiceItem> = {
+  data: T[];
+  extData: Record<string, unknown> | null; // 明确可能为 null
+  pageIndex: number;
+  pageSize: number;
+  searchKey?: string; // 改为可选
+  totalCount: number;
+};
+
+export type TPageination = {
+  extData: Record<string, unknown> | null; // 明确可能为 null
+  pageIndex: number;
+  pageSize: number;
+  searchKey?: string; // 改为可选
+  totalCount: number;
+}
+
+export enum EVoiceType {
+  MINE = 'mine',
+  SYSTEM = 'system'
+}
+
+export type TGetMyVoicesParams = {
+  pageIndex: number,
+  pageSize: number,
+  type: EVoiceType,
+  gender: TGender,
+}
+

+ 11 - 146
src/utils/http.ts

@@ -1,170 +1,39 @@
 import Taro from "@tarojs/taro";
 // 多端上传文件,我们得引入 PostData 和 FileData 两个类
-import { axios, PostData, FileData, AxiosRequestConfig } from "taro-axios";
-import { BASE_URL } from "@/config/index";
 import { getToken, removeToken } from "./index";
-import { authLogin } from "./auth";
+import { bluebookAiAgent } from "@/xiaolanbenlib/api/index";
 import { APP_VERSION, TARO_APP_ID } from "@/config/index";
 // import { TaroAdapter } from "axios-taro-adapter";
 import { useAppStore } from '@/store/appStore'
 import { EUploadFileScene, EUploadFileBucketName } from '@/consts/enum'
+import { getSimpleHeader } from "@/xiaolanbenlib/module/axios.js";
+import {OSS_PUBLIC_BUCKET_NAME_KEY} from '@/xiaolanbenlib/constant'
 
-// type TResponseData = Promise<{
-//   "code": number,
-//   "data": string,
-//   "msg": string
-// }>
-
-
-
-export const getHeaders = () => {
-  const token = getToken();
-  const systemInfo = useAppStore.getState().systemInfo;
-  return {
-    token,
-    'x-app-version': APP_VERSION,
-    'x-app-language': systemInfo?.language ?? 'en',
-    'x-mp-app-id': TARO_APP_ID,
-  };
-};
-
-// 创建请求实例
-const httpConfig = {
-  baseURL: BASE_URL,
-};
-const instance = axios.create(httpConfig);
-// 添加请求拦截器
-instance.interceptors.request.use(
-  function (config: any) {
-    const headers = getHeaders()
-    config.headers = headers
-    return config;
-  },
-  function (error: any) {
-    return Promise.reject(error);
-  }
-);
-
-// 添加响应拦截器
-instance.interceptors.response.use(
-  async function (response: any) {
-    const code = response?.data?.code;
-    const currentPages = Taro.getCurrentPages()
-    const currentPage = currentPages[currentPages.length - 1]
-    // 此处全局提示处理
-    if (code !== undefined) {
-      Taro.hideLoading();
-      if (code === 20001) {
-        // Taro.showToast({
-        //   title: "登录过期",
-        //   duration: 2000,
-        // });
-        removeToken();
-        const res = await authLogin();
-        if(res){
-          Taro.reLaunch({
-            url: `/${currentPage.$taroPath}`
-          })
-          console.log(currentPage)
-        }
-      } else if(code === 20003){
-        // 资源未找到
-        return Promise.resolve(response.data);
-      }else if(code === 30004){
-        useAppStore.getState().setVipPopupVisible(true);
-        return Promise.resolve(response.data);
-      }else if (code === 0) {
-        // 正常业务逻辑
-        return {
-          ...response.data,
-        };
-      } else {
-        const error = response.data?.msg || "网络错误";
-        Taro.showModal({
-          content: error,
-          showCancel: false,
-        });
-      }
-    }
-    return Promise.resolve(response.data);
-  },
-  function (_error: any) {
-    const error = _error ?? "未知错误";
-    console.error(error);
-    Taro.hideLoading();
-    return Promise.reject(error);
-  }
-);
-
-export const service = {
-  get: async <T>(url: string, config?: AxiosRequestConfig) => {
-    const res = instance.get(url, config) as Promise<{
-      code: number;
-      data: T;
-      msg: string;
-    }>;
-    return res;
-  },
-  post: async <T, R = unknown>(
-    url: string,
-    data?: T,
-    _config?: AxiosRequestConfig
-  ) => {
-    const res = instance.post(url, data, _config) as Promise<{
-      code: number;
-      data: R;
-      msg: string;
-    }>;
-    return res;
-  },
-  put: async <T, R = unknown>(
-    url: string,
-    data?: T,
-    _config?: AxiosRequestConfig
-  ) => {
-    const res = instance.put(url, data, _config) as Promise<{
-      code: number;
-      data: R;
-      msg: string;
-    }>;
-    return res;
-  },
-  delete: async <T>(url: string, _config?: AxiosRequestConfig) => {
-    const res = instance.delete(url, _config) as Promise<{
-      code: number;
-      data: T;
-      msg: string;
-    }>;
-    return res;
-  },
-};
 
 const _uploadFile = (
   tmpPath: string,
   fileName: string = "file",
-  bucketName= EUploadFileBucketName.OTHER, 
   scene=EUploadFileScene.OTHER, 
   callback: (
     data: {
-      code: number;
-      data: string;
-      msg: string;
+      publicUrl: string;
+      fileId: string;
     } | null
   ) => void
 ) => {
+  const bucketName = Taro.getStorageSync(OSS_PUBLIC_BUCKET_NAME_KEY)
   return Taro.uploadFile({
-    url: `${BASE_URL}v1/base/upload`,
+    url: `${bluebookAiAgent}api/v1/my/upload?bucketName=${bucketName}&scene=${scene}`,
     filePath: tmpPath,
     name: fileName,
     formData: {
       file: fileName,
-      'scene': scene,
     },
     header: {
-      ...getHeaders(),
+      ...getSimpleHeader(),
     },
     success(res) {
-      // console.log(res)
+      console.log(res)
       try {
         const r = JSON.parse(res.data);
         callback && callback(r);
@@ -195,9 +64,8 @@ export async function uploadFile(
   tmpPath: string,
   scene: EUploadFileScene = EUploadFileScene.OTHER
 ): Promise<{
-  code: number;
-  data: string;
-  msg: string;
+  publicUrl: string;
+  fileId: string;
 } | null> {
   return new Promise((resolve) => {
     _uploadFile(tmpPath, "file", scene, (data) => {
@@ -205,6 +73,3 @@ export async function uploadFile(
     });
   });
 }
-
-
-export default instance; // 导出创建后的实例

+ 4 - 0
src/utils/index.ts

@@ -408,4 +408,8 @@ export const pickNonEmpty = <T extends object>(obj: T): Partial<T> => {
 
 export const getLoginId = ()=> {
   return Taro.getStorageSync(LOGIN_ID_STORAGE_KEY) // 打开小程序时创建 login_uuid 
+}
+
+export const isSuccess = (status: number)=> {
+  return status >= 200 && status < 300
 }

+ 4 - 3
src/utils/jsonChunkParser.ts

@@ -48,13 +48,14 @@ export default class JsonChunkParser {
     const lines = this.buffer.split("\n");
     let combinedContent: string[] = []; // 用于合并 content 字段
     let combinedReasoningContent: string[] = []; // 用于合并 reasoner 字段
+    
     // 遍历每一行
     for (let i = 0; i < lines.length; i++) {
       const line = lines[i].trim(); // 去除前后空格
 
       if (line) {
         // 如果行不为空
-        if (line === "data: [DONE]") {
+        if (line === "data:[DONE]") {
           // 如果遇到 DONE,合并并调用 onParsed
           if (combinedContent.length > 0) {
             onParsed({ content: combinedContent.join("") }); // 合并 content
@@ -66,8 +67,8 @@ export default class JsonChunkParser {
 
         try {
           // 处理 data: data: 前缀
-          if (line.startsWith("data: data:")) {
-            const jsonStr = line.substring(11); // 移除 "data: data:" 前缀
+          if (line.startsWith("data:")) {
+            const jsonStr = line.substring(5); // 移除 "data:" 前缀
             const json = JSON.parse(jsonStr);
             if (json.content) {
               combinedContent.push(json.content); // 收集 content 字段

+ 2 - 1
src/xiaolanbenlib/constant.ts

@@ -12,4 +12,5 @@ export const OPEN_ID_STORAGE_KEY = 'u51OpenId'
 
 export const SHARE_CHANNEL = 'xiaolanben-ai-agent'
 
-export const LOGIN_ID_STORAGE_KEY = 'xiaolanben_login_uuid'
+export const LOGIN_ID_STORAGE_KEY = 'xiaolanben_login_uuid'
+export const OSS_PUBLIC_BUCKET_NAME_KEY = 'oss_public_bucket_name_key'

+ 5 - 0
src/xiaolanbenlib/hooks/data/useAuth.tsx

@@ -132,6 +132,11 @@ export function useIsLogin() {
     if (newState !== isLogin) {
       setIsLogin(newState)
     }
+    console.log(newState,222)
+    if(!newState){
+      
+      Taro.navigateTo({url: '/pages/login/index'})
+    }
   }, [isLogin])
 
   useEffect(() => {