瀏覽代碼

fix: 修复没有回复的对话,展示无响应

王晓东 4 周之前
父節點
當前提交
f68ef00d46

+ 1 - 4
.env.development

@@ -1,8 +1,5 @@
 # 配置文档参考 https://taro-docs.jd.com/docs/next/env-mode-config
 # 配置文档参考 https://taro-docs.jd.com/docs/next/env-mode-config
 TARO_APP_ID="wxede8ad263e6161b6"
 TARO_APP_ID="wxede8ad263e6161b6"
-TARO_APP_BASE_URL="https://dev-api.xiaolvye.cn/"
 TARO_APP_NAME_TEXT="小蓝本"
 TARO_APP_NAME_TEXT="小蓝本"
 TARO_APP_NAME="xiaolanben"
 TARO_APP_NAME="xiaolanben"
-# 产品故事
-TARO_APP_STORY_URL="https://mp.weixin.qq.com/s?__biz=MzkzMDc0OTU0Nw==&mid=2247483671&idx=1&sn=27749d28c6e2e96e69735df477dd1597&chksm=c274cc13f50345059f716fc795d66fbafbfdc195334b8ea68f240bc8e06b1be37b81ffd3e580"
-TARO_APP_AGREEMENT_URL="https://dev-h5.xiaolvye.cn/agreement"
+TARO_APP_PRE_RELEASE=false

+ 1 - 4
.env.production

@@ -1,7 +1,4 @@
 TARO_APP_ID="wxede8ad263e6161b6"
 TARO_APP_ID="wxede8ad263e6161b6"
-TARO_APP_BASE_URL="https://dev-api.xiaolvye.cn/"
 TARO_APP_NAME_TEXT="小蓝本"
 TARO_APP_NAME_TEXT="小蓝本"
 TARO_APP_NAME="xiaolanben"
 TARO_APP_NAME="xiaolanben"
-# 产品故事
-TARO_APP_STORY_URL="https://mp.weixin.qq.com/s?__biz=MzkzMDc0OTU0Nw==&mid=2247483671&idx=1&sn=27749d28c6e2e96e69735df477dd1597&chksm=c274cc13f50345059f716fc795d66fbafbfdc195334b8ea68f240bc8e06b1be37b81ffd3e580"
-TARO_APP_AGREEMENT_URL="https://dev-h5.xiaolvye.cn/agreement"
+TARO_APP_PRE_RELEASE=false

+ 4 - 0
.env.release

@@ -0,0 +1,4 @@
+TARO_APP_ID="wxede8ad263e6161b6"
+TARO_APP_NAME_TEXT="小蓝本"
+TARO_APP_NAME="xiaolanben"
+TARO_APP_PRE_RELEASE=true

+ 1 - 1
package.json

@@ -13,7 +13,7 @@
     "devbuild:weapp": "taro build --type weapp --watch --env production",
     "devbuild:weapp": "taro build --type weapp --watch --env production",
     "dev:weapp": "npm run build:weapp -- --watch",
     "dev:weapp": "npm run build:weapp -- --watch",
     "build:weapp": "taro build --type weapp",
     "build:weapp": "taro build --type weapp",
-    "release:weapp": "taro build --type weapp --mode release",
+    "release:weapp": "taro build --type weapp --mode release --env release",
     "build:swan": "taro build --type swan",
     "build:swan": "taro build --type swan",
     "build:alipay": "taro build --type alipay",
     "build:alipay": "taro build --type alipay",
     "build:tt": "taro build --type tt",
     "build:tt": "taro build --type tt",

+ 2 - 1
src/components/AgentPage/components/AgentQRcode/index.tsx

@@ -30,13 +30,14 @@ export default ({ show, setShow, agent, isVisitor }: IProps) => {
         maxWidth: 1024,
         maxWidth: 1024,
         maxHeight: 1024,
         maxHeight: 1024,
       });
       });
+      Taro.hideLoading()
       if (!result?.url) {
       if (!result?.url) {
         return;
         return;
       }
       }
       setValue(result.url);
       setValue(result.url);
 
 
       submit(result.url);
       submit(result.url);
-      Taro.hideLoading()
+      
     }catch(e:any){
     }catch(e:any){
       Taro.hideLoading()
       Taro.hideLoading()
     }
     }

+ 11 - 14
src/components/AgentPage/components/AgentSwap/index.tsx

@@ -10,7 +10,7 @@ import { TAgent } from "@/types/agent";
 import Taro from "@tarojs/taro";
 import Taro from "@tarojs/taro";
 import ContactIcon from "@/components/ContactIcon";
 import ContactIcon from "@/components/ContactIcon";
 import { useEffect } from "react";
 import { useEffect } from "react";
-import EmptyData from "@/components/EmptyData";
+import EmptyData, { EmptyDataSubInfo, EmptyDataTitle } from "@/components/EmptyData";
 interface IProps {
 interface IProps {
   show: boolean;
   show: boolean;
   setShow: (show: boolean) => void;
   setShow: (show: boolean) => void;
@@ -115,20 +115,17 @@ export default ({ show, setShow }: IProps) => {
       });
       });
     }
     }
     return (
     return (
-      <View className={style.card}>
-        <View className="flex flex-col items-center">
-          <View className="text-16 leading-24 font-medium mb-4">
-            你还没有企业配置的名片
+      <View className={`${style.card} h-full`}>
+        <EmptyData type='box'>
+          <EmptyDataTitle>你还没有加入任何企业</EmptyDataTitle>
+          <EmptyDataSubInfo>访问公司统一知识内容,提升回复效率与专业度</EmptyDataSubInfo>
+          <View
+              className="button-rounded button-primary-light button-border-primary button-inline-flex mt-20"
+              onClick={() => Taro.navigateTo({ url: "/pages/contact-us/index" })}
+            >
+              联系我们
           </View>
           </View>
-          <View className="text-12 text-gray-45 leading-20 mb-24">
-            企业小蓝本由公司创建并授权,可引用企业知识内容
-          </View>
-          <View className="text-primary font-medium leading-22" onClick={() => {
-            Taro.navigateTo({
-              url: "/pages/contact-us/index",
-            });
-          }}>联系我们</View>
-        </View>
+        </EmptyData>
       </View>
       </View>
     );
     );
   };
   };

+ 1 - 1
src/components/AvatarMedia/index.tsx

@@ -57,7 +57,7 @@ export const AvatarMedia = ({ source, className, roundedFull = true, mode = 'wid
     );  
     );  
   }
   }
 
 
-  const _source = source?.length ? source : DEFAULT_AVATAR
+  const _source = source?.length ? `${source}?x-oss-process=image/quality,Q_60/format,jpg` : DEFAULT_AVATAR
   return (
   return (
     <View className={`overflow-hidden shrink-0 ${roundedFull ? 'rounded-full': ''} ${className}`}>
     <View className={`overflow-hidden shrink-0 ${roundedFull ? 'rounded-full': ''} ${className}`}>
       <Image
       <Image

+ 1 - 1
src/components/SliderAction/index.tsx

@@ -17,7 +17,7 @@ interface SliderDeleteActionProps {
   scrolling?: boolean;
   scrolling?: boolean;
 }
 }
 
 
-const THRESHOLD = 120; // 滑动阈值
+const THRESHOLD = 60; // 滑动阈值
 const DEFAULT_BUTTON_WIDTH = 80; // 默认每个按钮80px
 const DEFAULT_BUTTON_WIDTH = 80; // 默认每个按钮80px
 
 
 const SliderDeleteAction: React.FC<SliderDeleteActionProps> = ({
 const SliderDeleteAction: React.FC<SliderDeleteActionProps> = ({

+ 1 - 13
src/config/index.ts

@@ -37,8 +37,6 @@ const APP_ENV_VERSION =  envVersion
 const HOME_URL = homeUrl
 const HOME_URL = homeUrl
 
 
 const APP_NAME_TEXT = process.env.TARO_APP_NAME_TEXT ?? ''
 const APP_NAME_TEXT = process.env.TARO_APP_NAME_TEXT ?? ''
-const APP_STORY_URL = process.env.TARO_APP_STORY_URL ?? ''
-const APP_AGREEMENT_URL = process.env.TARO_APP_AGREEMENT_URL ?? ''
 const TARO_APP_ID = process.env.TARO_APP_ID ?? ''
 const TARO_APP_ID = process.env.TARO_APP_ID ?? ''
 
 
 // 是否是小绿页
 // 是否是小绿页
@@ -57,8 +55,6 @@ export {
 	APP_VERSION,
 	APP_VERSION,
 	APP_NAME,
 	APP_NAME,
 	APP_NAME_TEXT,
 	APP_NAME_TEXT,
-	APP_STORY_URL,
-	APP_AGREEMENT_URL,
 	BASE_URL,
 	BASE_URL,
 	APP_ENV_VERSION,
 	APP_ENV_VERSION,
 	HOME_URL,
 	HOME_URL,
@@ -69,15 +65,7 @@ export {
 	DEFAULT_AGENT,
 	DEFAULT_AGENT,
 	DEFAULT_AVATAR_BG,
 	DEFAULT_AVATAR_BG,
 }
 }
-
-
-
-
-export enum VIP_LEVEL {
-	FREE = 'vip_free',
-	PLUS = 'vip_plus',
-	SUPER = 'vip_super',
-} 
+ 
 
 
 // 合作方域名链接,用于 webview
 // 合作方域名链接,用于 webview
 export const CORP_DOMAINS = [
 export const CORP_DOMAINS = [

+ 1 - 1
src/pages/agent-avatars/index.tsx

@@ -99,7 +99,7 @@ export default function Index() {
         </View>
         </View>
       </>
       </>
     }
     }
-    return <Image src={avatar.avatarUrl} mode="widthFix" className="w-full" />;
+    return <Image src={`${avatar.avatarUrl}?x-oss-process=image/quality,Q_60/format,jpg`} mode="widthFix" className="w-full" />;
   };
   };
 
 
   const renderList = () => {
   const renderList = () => {

+ 32 - 14
src/pages/chat-session-messages/index.tsx

@@ -6,7 +6,7 @@ import { useEffect, useState } from "react";
 import { isSuccess } from "@/utils";
 import { isSuccess } from "@/utils";
 import { getVisitorInfo } from "@/service/visitor";
 import { getVisitorInfo } from "@/service/visitor";
 import VisitorSummary from "./components/VisitorSummary";
 import VisitorSummary from "./components/VisitorSummary";
-
+import EmptyData, { EmptyDataSubInfo } from "@/components/EmptyData";
 import { getVisitorMessages } from "@/service/visitor";
 import { getVisitorMessages } from "@/service/visitor";
 import { TVisitorAgent, TVisitorChat } from "@/types/visitor";
 import { TVisitorAgent, TVisitorChat } from "@/types/visitor";
 import { createKey, useLoadMoreInfinite } from "@/utils/loadMoreInfinite";
 import { createKey, useLoadMoreInfinite } from "@/utils/loadMoreInfinite";
@@ -63,9 +63,9 @@ export default () => {
   //     }
   //     }
   //     return item
   //     return item
   //   })
   //   })
-    const qaList: { user?: TVisitorChat; assistant?: TVisitorChat }[] = [];
+    const qaList: { user?: TVisitorChat; assistant?: TVisitorChat|null }[] = [];
     for (let i = 0; i < list.length; i++) {
     for (let i = 0; i < list.length; i++) {
-      const item: { user?: TVisitorChat; assistant?: TVisitorChat } = {};
+      const item: { user?: TVisitorChat; assistant?: TVisitorChat|null } = {};
       if (list[i].role === "user") {
       if (list[i].role === "user") {
         item.user = list[i];
         item.user = list[i];
         if (list[i + 1]?.role === "assistant") {
         if (list[i + 1]?.role === "assistant") {
@@ -81,14 +81,18 @@ export default () => {
             }
             }
           }
           }
           qaList.push(item);
           qaList.push(item);
-          i++; // 跳过下一个元素,因为它已经被配对了
+          
         }else{
         }else{
-          continue; // 如果下一个不是 assistant,则跳过当前 user
+          item.assistant = null
+          qaList.push(item);
+          // continue; // 如果下一个不是 assistant,则跳过当前 user
         }
         }
+        i++; // 跳过下一个元素,因为它已经被配对了
       }
       }
     }
     }
+    
 
 
-    const handleEdit = (item: TVisitorChat)=> {
+    const handleEdit = (item: TVisitorChat|null)=> {
       if(!item){
       if(!item){
         return;
         return;
       }
       }
@@ -105,7 +109,12 @@ export default () => {
     fetchData()
     fetchData()
   }, [visitorId])
   }, [visitorId])
 
 
-  const renderAsistentContent = (item: TVisitorChat) => {
+  const renderAsistentContent = (item: TVisitorChat|null) => {
+    if(item === null){
+      return <View>
+        无响应
+      </View>
+    }
     if(item.contentType === EContentType.AiseekQA){
     if(item.contentType === EContentType.AiseekQA){
       const body = item.body
       const body = item.body
       const payload = body?.content?.answer?.payload;
       const payload = body?.content?.answer?.payload;
@@ -134,7 +143,7 @@ export default () => {
     return <View>{item.content}</View>
     return <View>{item.content}</View>
   }
   }
 
 
-  const renderContent = (item: TVisitorChat) => {
+  const renderContent = (item: TVisitorChat|null) => {
 
 
     return (
     return (
       <View
       <View
@@ -143,27 +152,36 @@ export default () => {
       >
       >
         <View>{renderAsistentContent(item)}</View>
         <View>{renderAsistentContent(item)}</View>
         <View className="absolute top-0 right-0 z-10">
         <View className="absolute top-0 right-0 z-10">
-          {item.isDislike ? <MarkDislike/>:<></>}
-          {item.isLike ? <MarkLike />:<></>}
+          {item?.isDislike ? <MarkDislike/>:<></>}
+          {item?.isLike ? <MarkLike />:<></>}
         </View>
         </View>
-        <View className={style.editButton} onClick={()=> handleEdit(item)}>
+        {!!item && <View className={style.editButton} onClick={()=> handleEdit(item)}>
           <Image src={IconEditButtonBlue} className="w-14 h-14" />
           <Image src={IconEditButtonBlue} className="w-14 h-14" />
-        </View>
+        </View>}
       </View>
       </View>
     );
     );
   };
   };
-
+  
   return (
   return (
     <PageCustom>
     <PageCustom>
       <NavBarNormal backText="对话记录" scrollFadeIn></NavBarNormal>
       <NavBarNormal backText="对话记录" scrollFadeIn></NavBarNormal>
       {visitor ? <VisitorSummary data={visitor} /> : <></>}
       {visitor ? <VisitorSummary data={visitor} /> : <></>}
       <View className="flex-1 overflow-hidden w-full mb-100">
       <View className="flex-1 overflow-hidden w-full mb-100">
         <View className="flex flex-col gap-16 px-16 w-full">
         <View className="flex flex-col gap-16 px-16 w-full">
+        {qaList.length <= 0 && (
+            <EmptyData type="chat">
+              <EmptyDataSubInfo>
+                <View>当前暂无用户访问记录</View>
+                <View>你的智能体正在等待首次互动</View>
+              </EmptyDataSubInfo>
+            </EmptyData>
+          )}
+
           {qaList.map((item) => {
           {qaList.map((item) => {
             return (
             return (
               <View className={` bg-white rounded-12 overflow-hidden`}>
               <View className={` bg-white rounded-12 overflow-hidden`}>
                 <View className="text-14 text-title mb-8 pt-16 px-16 font-pingfangSCMedium leading-22">{item.user?.content}</View>
                 <View className="text-14 text-title mb-8 pt-16 px-16 font-pingfangSCMedium leading-22">{item.user?.content}</View>
-                {item.assistant && <View className="px-16 pb-16">{renderContent(item.assistant)}</View>}
+                <View className="px-16 pb-16">{renderContent(item.assistant)}</View>
               </View>
               </View>
             )
             )
           })}
           })}

+ 0 - 36
src/pages/chat-session-messages/visitedDetail.ts

@@ -1,36 +0,0 @@
-import { getVisitorMessages, getVisitorSessions } from "@/service/visitor";
-import { TSessionItem, TVisitorMessage } from "@/types/visitor";
-import { useLoadMore } from "@/utils/loadMore";
-
-export const useVisitorMessages = (visitorId: string|number, sessionId: string) => {
-  const fetcher = async ([_url, nextId, page, pageSize, sessionId]) => {
-    const res = await getVisitorMessages({ startId: nextId, pageSize, visitorId, sessionId });
-    return res.data;
-  };
-
-  const { data, loadMore, refetch } = useLoadMore<{
-    data?: TVisitorMessage[]
-    nextId?: string,
-    totalCount?: number
-  }>({
-    url: `api/v1/my/visitor/messages/${visitorId}`,
-    fetcher,
-    params: [sessionId]
-  });
-
-  return {
-    data,
-    loadMore,
-    refetch
-  }
-}
-
-export const useVisitorSessions = (visitorId: string|number) => {
-  
-
-  return {
-    data,
-    loadMore,
-    refetch
-  }
-}

+ 9 - 2
src/pages/dashboard/components/AgentList/index.tsx

@@ -7,6 +7,7 @@ import { useAgentStore } from "@/store/agentStore";
 import WemetaRadio from "@/components/WemetaRadio/index";
 import WemetaRadio from "@/components/WemetaRadio/index";
 import { TAgent } from "@/types/agent";
 import { TAgent } from "@/types/agent";
 import AgentCard from "./components/AgentCard";
 import AgentCard from "./components/AgentCard";
+import EmptyData from "@/components/EmptyData";
 
 
 interface IProps {
 interface IProps {
   currentAgent: TAgent|null
   currentAgent: TAgent|null
@@ -38,10 +39,16 @@ export default ({currentAgent,  setCurrentAgent, show, setShow }: IProps) => {
   return (
   return (
     <Popup title="选择智能体" show={show} setShow={setShow}>
     <Popup title="选择智能体" show={show} setShow={setShow}>
       <View className="flex flex-col gap-12 w-full overflow-y-auto max-h-[440px]">
       <View className="flex flex-col gap-12 w-full overflow-y-auto max-h-[440px]">
-        {agents.map((item) => {
+        {!agents.length && 
+          <View className="mb-88">
+            <EmptyData type='search'></EmptyData>
+          </View>
+          
+        }
+        {agents.map((item, index, array) => {
           return (
           return (
             <AgentCard
             <AgentCard
-              className="border-bottom1-gray py-16 rounded-none"
+              className={`${index !== array.length -1 ? 'border-bottom1-gray': ''} py-16 rounded-none`}
               key={item.agentId}
               key={item.agentId}
               data={item}
               data={item}
               onClick={()=> handleClick(item)}
               onClick={()=> handleClick(item)}

+ 1 - 0
src/pages/editor-pages/editor-qrcode/index.tsx

@@ -22,6 +22,7 @@ export default function Index() {
     Taro.showLoading();
     Taro.showLoading();
     try{
     try{
       const result = await pickAndUploadImage(["album"], EUploadFileScene.OTHER);
       const result = await pickAndUploadImage(["album"], EUploadFileScene.OTHER);
+      
       Taro.hideLoading();
       Taro.hideLoading();
       if (!result?.url) {
       if (!result?.url) {
         return;
         return;

+ 9 - 9
src/pages/voice/components/MyVoiceList/index.tsx

@@ -243,8 +243,8 @@ export default ({ onPlay, onSelect, agent }: Props) => {
     return <></>;
     return <></>;
   };
   };
 
 
-  const renderItem = (item) => {
-    if (item.taskId) {
+  const renderItem = (_item: TVoiceItem|TTask, isLast: boolean) => {
+    if (('taskId' in _item) && _item.taskId) {
       return (
       return (
         <CardListItem
         <CardListItem
           underline
           underline
@@ -258,16 +258,16 @@ export default ({ onPlay, onSelect, agent }: Props) => {
           }}
           }}
         >
         >
           <View className="flex items-center h-full py-16">
           <View className="flex items-center h-full py-16">
-            {renderCloneStatus(item)}
+            {renderCloneStatus(_item as TTask)}
           </View>
           </View>
         </CardListItem>
         </CardListItem>
       );
       );
     }
     }
-
+    const item = _item as TVoiceItem
     return (
     return (
       <SliderAction actions={createSliderButtons(item)}>
       <SliderAction actions={createSliderButtons(item)}>
         <CardListItem
         <CardListItem
-          underline
+          underline={!isLast}
           className="px-16"
           className="px-16"
           leftRenderer={() => {
           leftRenderer={() => {
             return (
             return (
@@ -295,7 +295,7 @@ export default ({ onPlay, onSelect, agent }: Props) => {
   };
   };
 
 
   // 渲染克隆列表
   // 渲染克隆列表
-  const renderCloneList = () => {
+  const renderClonedList = () => {
     //
     //
     const voices = cloning ? [cloning, ...list] : list;
     const voices = cloning ? [cloning, ...list] : list;
     if (!voices.length) {
     if (!voices.length) {
@@ -303,8 +303,8 @@ export default ({ onPlay, onSelect, agent }: Props) => {
     }
     }
     return (
     return (
       <View className="flex flex-col w-full">
       <View className="flex flex-col w-full">
-        {voices.map((item, _index) => {
-          return renderItem(item);
+        {voices.map((item, index, array) => {
+          return renderItem(item, (index === array.length -1));
         })}
         })}
       </View>
       </View>
     );
     );
@@ -322,7 +322,7 @@ export default ({ onPlay, onSelect, agent }: Props) => {
             height: "100%", // 高度自适应
             height: "100%", // 高度自适应
           }}
           }}
         >
         >
-          {renderCloneList()}
+          {renderClonedList()}
         </ScrollView>
         </ScrollView>
       </View>
       </View>
       <View
       <View

+ 3 - 2
src/pages/voice/components/VoiceList/index.tsx

@@ -36,12 +36,13 @@ export default function Index({
         <View>
         <View>
           <View className="flex flex-col">
           <View className="flex flex-col">
             {!list.length && <EmptyData type={'search'}></EmptyData>}
             {!list.length && <EmptyData type={'search'}></EmptyData>}
-            {list.map((item: TVoiceItem, index) => {
+            {list.map((item: TVoiceItem, index, array) => {
               // 与克隆语音是否相同
               // 与克隆语音是否相同
               const isEqualToClonedVoice = item.voiceId === agent?.voiceId;
               const isEqualToClonedVoice = item.voiceId === agent?.voiceId;
+              const isLast = index === array.length - 1
               return (
               return (
                 <CardListItem
                 <CardListItem
-                  underline
+                  underline={!isLast}
                   rightRenderer={() => {
                   rightRenderer={() => {
                     return (
                     return (
                       <View className="flex items-center" onClick={() => {
                       <View className="flex items-center" onClick={() => {

+ 86 - 76
src/service/chat.ts

@@ -71,22 +71,20 @@ export const speechToText = async (agentId: string, tempFilePath: string) => {
 }
 }
 
 
 
 
-export type TTextToSpeechParams = {
-  params: {
-    agentId: string,
-    loginId: string,
-    msgUk: string,
-    text: string
-  };
+// 流式请求
+export type TRequestStream<T> = {
+  url: string,
+  params: T;
   onStart: () => void;
   onStart: () => void;
   onReceived: (m: ICompleteCallback) => void;
   onReceived: (m: ICompleteCallback) => void;
-  onAudioParsed: (m: {audio: string}) => void;
+  onAudioParsed:  (m: ICompleteCallback) => void;
   onFinished: (m: ICompleteCallback) => void;
   onFinished: (m: ICompleteCallback) => void;
   onComplete?: () => void; // 无论失败或成功都会执行
   onComplete?: () => void; // 无论失败或成功都会执行
   onError: () => void;
   onError: () => void;
 };
 };
 
 
-export const requestTextToSpeech = ({
+export const requestStream = <T>({
+  url,
   params,
   params,
   onStart,
   onStart,
   onReceived,
   onReceived,
@@ -94,24 +92,29 @@ export const requestTextToSpeech = ({
   onFinished,
   onFinished,
   onComplete,
   onComplete,
   onError,
   onError,
-}: TTextToSpeechParams) => {
+}: TRequestStream<T>) => {
   onStart();
   onStart();
 
 
-  let reqTask: Taro.RequestTask<any>|undefined = undefined;
+  let reqTask: Taro.RequestTask<any>|undefined|null = undefined;
   const jsonParser = new JsonChunkParser();
   const jsonParser = new JsonChunkParser();
   jsonParser.onParseComplete((m) => {
   jsonParser.onParseComplete((m) => {
     onFinished(m);
     onFinished(m);
   });
   });
   
   
   const onChunkReceived = (chunk: any) => {
   const onChunkReceived = (chunk: any) => {
+    // console.log('chunkReceived: ', chunk);
     const uint8Array = new Uint8Array(chunk.data);
     const uint8Array = new Uint8Array(chunk.data);
+    // console.log('uint8Array: ', uint8Array);
     var string = new TextDecoder("utf-8").decode(uint8Array);
     var string = new TextDecoder("utf-8").decode(uint8Array);
-    jsonParser.parseChunk(string, onReceived, onAudioParsed);
+    // console.log('chunked:', string);
+    jsonParser.parseChunk(string, (m) => {
+      // console.log('parseChunk', m);
+      onReceived(m);
+    }, onAudioParsed);
   };
   };
   const header = getSimpleHeader()
   const header = getSimpleHeader()
   
   
   try {
   try {
-    const url = `${bluebookAiAgent}api/v1/chat/text/speech`;
     reqTask = Taro.request({
     reqTask = Taro.request({
       url: url,
       url: url,
       data: params,
       data: params,
@@ -122,12 +125,19 @@ export const requestTextToSpeech = ({
       },
       },
       responseType: "arraybuffer",
       responseType: "arraybuffer",
       success: function (res) {
       success: function (res) {
-        // console.log("text to speech 服务端响应 >>", res);
-        // onComplete?.()
+        console.log("服务端响应 >>", res);
       },
       },
       complete: function(res) {
       complete: function(res) {
-        console.log("text to speech 服务端响应 complete", res);
+        console.log(reqTask)
+        console.log("reqTask >>", reqTask, res);
+       if(reqTask) {
         onComplete?.()
         onComplete?.()
+       }
+      },
+      fail: function (){
+        if(reqTask) {
+          onComplete?.()
+         }
       }
       }
     });
     });
 
 
@@ -140,6 +150,9 @@ export const requestTextToSpeech = ({
 
 
   const stopChunk = () => {
   const stopChunk = () => {
     reqTask?.offChunkReceived(onChunkReceived);
     reqTask?.offChunkReceived(onChunkReceived);
+    reqTask?.abort();
+    reqTask = null
+    console.log('stop reqTask: v1/chat/completions')
   };
   };
 
 
   return {reqTask: reqTask, stopChunk};
   return {reqTask: reqTask, stopChunk};
@@ -147,8 +160,53 @@ export const requestTextToSpeech = ({
 
 
 
 
 
 
-// 文本聊天,流式返回消息
+// 文本转语音
+export type TTextToSpeechParams = {
+  params: {
+    agentId: string,
+    loginId: string,
+    msgUk: string,
+    text: string
+  };
+  onStart: () => void;
+  onReceived: (m: ICompleteCallback) => void;
+  onAudioParsed: (m: ICompleteCallback) => void;
+  onFinished: (m: ICompleteCallback) => void;
+  onComplete?: () => void; // 无论失败或成功都会执行
+  onError: () => void;
+};
+
+export const requestTextToSpeech = ({
+  params,
+  onStart,
+  onReceived,
+  onAudioParsed,
+  onFinished,
+  onComplete,
+  onError,
+}: TTextToSpeechParams) => {
+
+  const url = `${bluebookAiAgent}api/v1/chat/text/speech`;
+  return requestStream<{
+    agentId: string,
+    loginId: string,
+    msgUk: string,
+    text: string
+  }>({
+    url,
+    params,
+    onStart,
+    onReceived,
+    onAudioParsed,
+    onFinished,
+    onComplete,
+    onError,
+  })
+};
+
+
 
 
+// 聊天,流式返回消息
 export type TTextChatParams = {
 export type TTextChatParams = {
   params: TRequestBody;
   params: TRequestBody;
   onStart: () => void;
   onStart: () => void;
@@ -168,63 +226,15 @@ export const requestTextToChat = ({
   onComplete,
   onComplete,
   onError,
   onError,
 }: TTextChatParams) => {
 }: TTextChatParams) => {
-  onStart();
-
-  let reqTask: Taro.RequestTask<any>|undefined = undefined;
-  const jsonParser = new JsonChunkParser();
-  jsonParser.onParseComplete((m) => {
-    onFinished(m);
-  });
-  
-  const onChunkReceived = (chunk: any) => {
-    // console.log('chunkReceived: ', chunk);
-    const uint8Array = new Uint8Array(chunk.data);
-    // console.log('uint8Array: ', uint8Array);
-    var string = new TextDecoder("utf-8").decode(uint8Array);
-    // console.log('chunked:', string);
-    jsonParser.parseChunk(string, (m) => {
-      // console.log('parseChunk', m);
-      onReceived(m);
-    }, onAudioParsed);
-  };
-  const header = getSimpleHeader()
-  
-  try {
-    const url = `${bluebookAiAgent}api/v1/chat/completions`;
-    reqTask = Taro.request({
-      url: url,
-      data: params,
-      enableChunked: true,
-      method: "POST",
-      header: {
-        ...header
-      },
-      responseType: "arraybuffer",
-      success: function (res) {
-        console.log("服务端响应 >>", res);
-      },
-      complete: function(res) {
-        console.log(reqTask)
-        console.log("reqTask >>", reqTask, res);
-       if(reqTask) {
-        onComplete?.()
-       }
-      }
-    });
-
-    // reqTask.
-    reqTask.onChunkReceived(onChunkReceived);
-  } catch (e) {
-    onComplete?.()
-    onError();
-  }
-
-  const stopChunk = () => {
-    reqTask?.offChunkReceived(onChunkReceived);
-    reqTask?.abort();
-    reqTask = null
-    console.log('stop reqTask: v1/chat/completions')
-  };
-
-  return {reqTask: reqTask, stopChunk};
+  const url = `${bluebookAiAgent}api/v1/chat/completions`;
+  return requestStream<TRequestBody>({
+    url,
+    params,
+    onStart,
+    onReceived,
+    onAudioParsed,
+    onFinished,
+    onComplete,
+    onError,
+  })
 };
 };

+ 10 - 16
src/utils/http.ts

@@ -5,16 +5,17 @@ import { EUploadFileScene } from '@/consts/enum'
 import { getSimpleHeader } from "@/xiaolanbenlib/module/axios.js";
 import { getSimpleHeader } from "@/xiaolanbenlib/module/axios.js";
 import {OSS_PUBLIC_BUCKET_NAME_KEY} from '@/xiaolanbenlib/constant'
 import {OSS_PUBLIC_BUCKET_NAME_KEY} from '@/xiaolanbenlib/constant'
 
 
+export type TUploadResult = {
+  publicUrl: string;
+  file: string;
+} |{trackingId:string, errors: {code:number, message: string}[]} | null
 
 
 const _uploadFile = (
 const _uploadFile = (
   tmpPath: string,
   tmpPath: string,
   fileName: string = "file",
   fileName: string = "file",
   scene=EUploadFileScene.OTHER, 
   scene=EUploadFileScene.OTHER, 
   callback: (
   callback: (
-    data: {
-      publicUrl: string;
-      file: string;
-    } | null
+    data: TUploadResult
   ) => void
   ) => void
 ) => {
 ) => {
   const bucketName = Taro.getStorageSync(OSS_PUBLIC_BUCKET_NAME_KEY)
   const bucketName = Taro.getStorageSync(OSS_PUBLIC_BUCKET_NAME_KEY)
@@ -34,22 +35,18 @@ const _uploadFile = (
         const r = JSON.parse(res.data);
         const r = JSON.parse(res.data);
         callback && callback(r);
         callback && callback(r);
       } catch (e) {
       } catch (e) {
-        console.log(e);
+        console.error(e);
         callback(null);
         callback(null);
       }
       }
     },
     },
     fail(err) {
     fail(err) {
-      console.log(err);
+      console.error(err);
       callback(null);
       callback(null);
     },
     },
   });
   });
 };
 };
 
 
-export async function uploadImage(tmpPath: string, scene: EUploadFileScene = EUploadFileScene.OTHER): Promise<{
-  publicUrl: string;
-  file: string;
-    
-} | null> {
+export async function uploadImage(tmpPath: string, scene: EUploadFileScene = EUploadFileScene.OTHER): Promise<TUploadResult> {
   return new Promise((resolve) => {
   return new Promise((resolve) => {
     _uploadFile(tmpPath, "file", scene, (r) => {
     _uploadFile(tmpPath, "file", scene, (r) => {
       resolve(r);
       resolve(r);
@@ -59,11 +56,8 @@ export async function uploadImage(tmpPath: string, scene: EUploadFileScene = EUp
 export async function uploadFile(
 export async function uploadFile(
   tmpPath: string,
   tmpPath: string,
   scene: EUploadFileScene = EUploadFileScene.OTHER
   scene: EUploadFileScene = EUploadFileScene.OTHER
-): Promise<{
-  publicUrl: string;
-  file: string;
-} | null> {
-  return new Promise((resolve) => {
+): Promise<TUploadResult> {
+  return new Promise((resolve, reject) => {
     _uploadFile(tmpPath, "file", scene, (data) => {
     _uploadFile(tmpPath, "file", scene, (data) => {
       resolve(data);
       resolve(data);
     });
     });

+ 2 - 5
src/utils/jsonChunkParser.ts

@@ -36,13 +36,10 @@ data: [DONE]
 
 
 */
 */
 export interface ICompleteCallback {
 export interface ICompleteCallback {
-  content: string;
+  content?: string;
   reasoningContent?: string;
   reasoningContent?: string;
-  body: null| (TJsonMessage & Record<string,any>)
-}
-export interface IAudioPared {
   body?: null| (TJsonMessage & Record<string,any>)
   body?: null| (TJsonMessage & Record<string,any>)
-  audio?:string
+  audio?: string
 }
 }
 
 
 type TContentType = "text/plain" | "aiseek/qa" | 'application/json' | 'aiseek/audio_chunk' | 'aiseek/thinking'  |  'aiseek/function_call' | 'aiseek/multimodal'
 type TContentType = "text/plain" | "aiseek/qa" | 'application/json' | 'aiseek/audio_chunk' | 'aiseek/thinking'  |  'aiseek/function_call' | 'aiseek/multimodal'

+ 6 - 9
src/utils/upload.ts

@@ -18,14 +18,14 @@ export async function pickAndUploadImage(
     })
     })
 
 
     const file = res.tempFiles[0]
     const file = res.tempFiles[0]
-    console.log(file,111)
+    
     if (file.size >= IMAGE_LIMIT) {
     if (file.size >= IMAGE_LIMIT) {
       Taro.showToast({
       Taro.showToast({
         title: '图片大小超限',
         title: '图片大小超限',
         icon: 'error',
         icon: 'error',
         duration: 1500,
         duration: 1500,
       })
       })
-      throw new Error('Image size exceeds limit')
+      return null
     }
     }
 
 
     if(maxSize){
     if(maxSize){
@@ -46,26 +46,23 @@ export async function pickAndUploadImage(
           icon: 'error',
           icon: 'error',
           duration: 1500,
           duration: 1500,
         })
         })
-        throw new Error('Image size exceeds limit')
+        return null
       }
       }
     }
     }
     
     
-
-
-    const uploadResult = await uploadFile(
+    const result = await uploadFile(
       file.path,
       file.path,
       scene,
       scene,
     );
     );
-
     
     
-    if(uploadResult?.publicUrl){
+    if(result && ('publicUrl' in result)){
       Taro.showToast({
       Taro.showToast({
         title: '上传成功',
         title: '上传成功',
         icon: 'success',
         icon: 'success',
         duration: 1500,
         duration: 1500,
       })
       })
       return {
       return {
-        url: uploadResult?.publicUrl,
+        url: result.publicUrl,
         size: file.size,
         size: file.size,
       }
       }
     }
     }

+ 5 - 3
src/xiaolanbenlib/api/index.ts

@@ -2,6 +2,7 @@ import Taro from '@tarojs/taro'
 import { SHARE_CHANNEL } from '../constant'
 import { SHARE_CHANNEL } from '../constant'
 // Determine if the environment is `weapp` and get the `envVersion`
 // Determine if the environment is `weapp` and get the `envVersion`
 const isWeApp = process.env.TARO_ENV === 'weapp'
 const isWeApp = process.env.TARO_ENV === 'weapp'
+const isPreRelease = process.env.TARO_APP_PRE_RELEASE === 'true'
 const accountInfo = isWeApp ? Taro.getAccountInfoSync() : null
 const accountInfo = isWeApp ? Taro.getAccountInfoSync() : null
 const envVersion = isWeApp
 const envVersion = isWeApp
   ? accountInfo?.miniProgram.envVersion || 'release'
   ? accountInfo?.miniProgram.envVersion || 'release'
@@ -9,14 +10,15 @@ const envVersion = isWeApp
 
 
 // Helper function to determine base URL based on environment
 // Helper function to determine base URL based on environment
 const getBaseUrl = (releaseUrl: string, preUrl: string) => {
 const getBaseUrl = (releaseUrl: string, preUrl: string) => {
+  if(isPreRelease){
+    return releaseUrl
+  }
   if (isWeApp) {
   if (isWeApp) {
     return envVersion === 'release' ? releaseUrl : preUrl
     return envVersion === 'release' ? releaseUrl : preUrl
-  } else {
-    return process.env.XLB_ENV !== 'pre_env' ? releaseUrl : preUrl
   }
   }
+  return process.env.XLB_ENV !== 'pre_env' ? releaseUrl : preUrl
 }
 }
 
 
-
 // Define base URLs
 // Define base URLs
 export const bluebookAiAgent = getBaseUrl(
 export const bluebookAiAgent = getBaseUrl(
   'https://api.xiaolanben.com/blue-aiagent/',
   'https://api.xiaolanben.com/blue-aiagent/',