Переглянути джерело

feat: 形象删除,错别与它人聊天

王晓东 1 місяць тому
батько
коміт
ccc0106e76

+ 1 - 1
src/components/AgentPage/components/SummaryBar/index.tsx

@@ -37,7 +37,7 @@ export default ({agent, isVisitor}: IProps) => {
 
   const handleClick = ()=> {
     Taro.navigateTo({
-      url: `/pages/chat/index?agentId=${agent.agentId}`,
+      url: `/pages/chat/index?agentId=${agent.agentId}&isVisitor=${isVisitor}`,
     });
   }
 

+ 1 - 1
src/components/chat-message/MessageRobot.tsx

@@ -14,7 +14,7 @@ import { TMessage } from "@/types/bot";
 import { getLoginId, isSuccess } from "@/utils";
 import { useState } from "react";
 interface Props {
-  agent?: TAgentDetail,
+  agent?: TAgentDetail | null,
   text: string,
   textReasoning: string,
   message: TMessage

+ 1 - 1
src/components/chat-message/index.tsx

@@ -4,7 +4,7 @@ import Message from "./Message";
 import MessageRobot from "./MessageRobot";
 import { TAgentDetail } from "@/types/agent";
 interface Props {
-  agent?: TAgentDetail,
+  agent?: TAgentDetail|null,
   role: TChatRole,
   text: string
   textReasoning?: string

BIN
src/images/icon-renshengkelong-18.png


BIN
src/images/icon-style.png


BIN
src/images/transparent-avatar-bg.png


BIN
src/images/voice-wave.png


+ 17 - 1
src/pages/agent-avatars/index.module.less

@@ -6,6 +6,7 @@
 }
 
 .gridItem {
+  position: relative;
   display: flex;
   flex-direction: column;
   align-items: center;
@@ -15,7 +16,7 @@
   border-radius: 12px;
   overflow: hidden;
   background-color: white;
-  border: 1px solid white;
+  border: 1px solid transparent;
 }
 .gridItemActived {
   .gridItem();
@@ -36,4 +37,19 @@
   overflow: hidden;
   width: 100%;
   height: calc(100vh - 120px);
+}
+.deleted{
+  display: none;
+}
+.deleteButton{
+  position: absolute;
+  top: 0;
+  right: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 28px;
+  height: 28px;
+  background-color: rgba(#FFF, .6);
+  border-bottom-left-radius: 12px;
 }

+ 97 - 57
src/pages/agent-avatars/index.tsx

@@ -1,67 +1,60 @@
-import { useEffect, useState } from "react";
+import { useEffect, useRef, useState } from "react";
 import { Image, View, ScrollView } from "@tarojs/components";
 
 import PageCustom from "@/components/page-custom/index";
 import NavBarNormal from "@/components/nav-bar-normal/index";
 
 import { uploadAndNavToGenNewAvatar } from "@/utils/avatar";
-import { useAvatars } from "./useAvatars";
 import IconPlusBig from "@/components/icon/icon-plus-big";
 
 import { editAgentAvatar } from "@/service/agent";
-import { deleteAvatar } from "@/service/storage";
+import { deleteAvatar, fetchMyAvatars } from "@/service/storage";
 
 import style from "./index.module.less";
 import { TAvatarItem } from "@/service/storage";
 import { useAgentStore } from "@/store/agentStore";
-import useSWRMutation from "swr/mutation";
+
 import Taro from "@tarojs/taro";
 import { isSuccess } from "@/utils";
+import IconDeleteGray16 from "@/components/icon/IconDeleteGray16";
+import { useLoadMore } from "@/utils/loadMore";
+import EmptyData from "@/components/empty-data";
+import { useModalStore } from "@/store/modalStore";
+
 
 export default function Index() {
   const { agent, fetchAgent } = useAgentStore();
-  const { avatars, initLoad, loadAvatars } = useAvatars();
+  const [list, setList] = useState<(TAvatarItem | TAvatarItem & { deletedTmp: boolean })[]>([]);
+  const [scrollTop, setScrollTop] = useState(0);
+  const scrollPositionRef = useRef(0);
+
+  const { showModal } = useModalStore()
+
+  const fetcher = async ([_url, _nextId, page, pageSize]) => {
+    const res = await fetchMyAvatars({ pageIndex: page, pageSize });
+    return res.data;
+  };
+
+  const { data, loadMore, refetch } = useLoadMore<TAvatarItem[]>({
+    url: 'fetchMyAvatars',
+    fetcher,
+  });
+
   const [current, setCurrent] = useState<TAvatarItem | null>(null);
 
-  // 用 useSWRMutation 包装 editAgentAvatar
-  const { trigger: editAvatar, isMutating } = useSWRMutation(
-    ["editAgentAvatar"],
-    async (
-      _key,
-      {
-        arg,
-      }: {
-        arg: {
-          agentId: string;
-          avatarId: string | number;
-          enabledChatBg: boolean;
-        };
-      }
-    ) => {
-      return await editAgentAvatar(
-        arg.agentId,
-        arg.avatarId,
-        arg.enabledChatBg
-      );
-    }
-  );
 
   const handleClick = async (item: TAvatarItem) => {
     console.log(item);
     if (!agent?.agentId) {
       return;
     }
-    // if (item.avatarId === current?.avatarId) {
-    //   setCurrent(null);
-    //   return;
-    // }
 
     Taro.showLoading();
-    const result = await editAvatar({
-      agentId: agent.agentId,
-      avatarId: item.avatarId,
-      enabledChatBg: false,
-    });
+    const result = await editAgentAvatar(
+      agent.agentId,
+      item.avatarId,
+      false,
+    );
     await fetchAgent(agent.agentId)
     Taro.hideLoading();
     setCurrent(item);
@@ -70,15 +63,75 @@ export default function Index() {
     }
   };
   const onScrollToLower = () => {
-    loadAvatars();
+    loadMore();
+    console.log('lower')
   };
   const handleCreate = () => {
     uploadAndNavToGenNewAvatar();
   };
 
+  const handleDelete = async (e:any, avatar: TAvatarItem) => {
+    e.stopPropagation()
+    showModal({
+      content: <>确认删除该形象?</>,
+      async onConfirm() {
+        const response = await deleteAvatar(avatar.avatarId)
+        if(isSuccess(response.status)){
+
+        // todo: 如何解决闪动问题? 直接操作 DOM 隐藏或删除?
+        setList(prevList => {
+          return prevList.map(item => 
+            item.avatarId === avatar.avatarId ? { ...item, deletedTmp: true } : item
+          );
+        })
+        setTimeout(()=> {
+          setScrollTop(scrollPositionRef.current)
+        }, 100)
+          
+        }
+      }
+    })
+    
+  }
+
   useEffect(() => {
-    initLoad();
-  }, []);
+    if (data?.data) {
+      setList([...list, ...data.data]);
+    }
+  }, [data]);
+
+  const renderList = ()=> {
+    if(!list.length){
+      return <>
+        <EmptyData></EmptyData>
+      </>
+    }
+    
+    return list.map((avatar) => {
+      return (
+        <View
+          className={
+            `${current?.avatarUrl === avatar.avatarUrl
+              ? style.gridItemActived
+              : style.gridItem} ${avatar?.deletedTmp ? style.deleted:''}`
+            
+          }
+          onClick={() => handleClick(avatar)}
+        >
+          <View className={style.deleteButton} onClick={(e)=> handleDelete(e, avatar)}>
+            <IconDeleteGray16/>
+          </View>
+          <Image
+            src={avatar.avatarUrl}
+            mode="widthFix"
+            className="w-full"
+          />
+        </View>
+      );
+    })
+  }
+
+  
 
   return (
     <PageCustom>
@@ -87,6 +140,10 @@ export default function Index() {
       <ScrollView
         scrollY
         onScrollToLower={onScrollToLower}
+        scrollTop={scrollTop}
+        onScroll={(e)=> {
+          scrollPositionRef.current = e.detail.scrollTop
+        }}
         style={{
           flex: 1,
           height: "100%", // 高度自适应
@@ -102,24 +159,7 @@ export default function Index() {
                 创建新形象
               </View>
             </View>
-            {avatars.map((avatar) => {
-              return (
-                <View
-                  className={
-                    current?.avatarId === avatar.avatarId
-                      ? style.gridItemActived
-                      : style.gridItem
-                  }
-                  onClick={() => handleClick(avatar)}
-                >
-                  <Image
-                    src={avatar.avatarUrl}
-                    mode="widthFix"
-                    className="w-full"
-                  />
-                </View>
-              );
-            })}
+            {renderList()}
           </View>
         </View>
       </ScrollView>

+ 6 - 4
src/pages/chat/components/chat-welcome/index.tsx

@@ -3,9 +3,11 @@ import { View } from '@tarojs/components'
 import style from './index.module.less'
 import PersonalCard from '../personal-card'
 import MessageRobotPlain from '@/components/chat-message/MessageRobotPlain'
-import { useAgentStore } from '@/store/agentStore'
-export default ()=> {
-  const {agent} = useAgentStore()
+import { TAgentDetail } from '@/types/agent';
+interface IProps {
+  agent: TAgentDetail | null;
+}
+export default ({agent}: IProps)=> {
 
   if(!agent){
     return <></>
@@ -27,7 +29,7 @@ export default ()=> {
     <View className='pt-118'>
     <View className={style.container}>
         <View className='mb-24'>
-          <PersonalCard />
+          <PersonalCard agent={agent} />
         </View>
         <View className='flex flex-col gap-8'>
           <MessageRobotPlain text={greeting}/>

+ 3 - 4
src/pages/chat/components/personal-card/index.tsx

@@ -3,13 +3,12 @@ import { Image, View } from '@tarojs/components'
 
 import TagCertificated from "@/components/tag-certificated";
 import IconCertificateColor from "@/components/icon/icon-certificate-color";
-import { useAgentStore } from '@/store/agentStore';
+import { TAgentDetail } from '@/types/agent';
 interface IProps {
   size?: 'mini'|'large'
+  agent: TAgentDetail|null
 }
-export default ({size='large'}:IProps) => {
-  
-  const {agent} = useAgentStore()
+export default ({size='large', agent}:IProps) => {
 
   const renderContent = () => {
     if(size === 'mini'){

+ 25 - 18
src/pages/chat/index.tsx

@@ -4,7 +4,6 @@ import PageCustom from "@/components/page-custom/index";
 import Taro, { useRouter, useUnload } from "@tarojs/taro";
 import ChatMessage from "@/components/chat-message";
 import style from "./index.module.less";
-// import { useAppStore } from "@/store/appStore";
 import InputBar from "./components/input-bar";
 import { useEffect, useState, useRef } from "react";
 import { useTextChat } from "@/store/textChat";
@@ -29,14 +28,18 @@ function isRobotMessage(
 
 export default function Index() {
   const router = useRouter();
-  const { agentId } = router.params;
-
+  const { agentId, isVisitor } = router.params;
   if (!agentId) {
     return <View>没有相应的智能体</View>;
   }
   const headerHeight = useAppStore((state) => state.headerHeight);
-  const { fetchAgent } = useAgentStore();
-  const agent = useAgentStore((state) => state.agent);
+  const { fetchAgent, fetchAgentProfile, } = useAgentStore();
+  const agent = useAgentStore((state) => {
+    if(isVisitor === 'true'){
+      return state.agentProfile
+    }
+    return state.agent
+  });
 
   const [deepThink, setDeepThink] = useState(EAI_MODEL.DeepseekChat);
 
@@ -64,12 +67,8 @@ export default function Index() {
     });
     return res.data;
   };
-  const { data, loadMore, page } = useLoadMore<{
-    data?: (TMessage | TRobotMessage)[];
-    nextId?: string;
-    totalCount?: number;
-  }>({
-    url: "/blue-aiagent/api/v1/my/messeagehistories",
+  const { data, loadMore, page } = useLoadMore<(TMessage | TRobotMessage)[]>({
+    url: `messeagehistories${isVisitor}${agentId}`,
     fetcher,
   });
 
@@ -82,8 +81,14 @@ export default function Index() {
   };
 
   useEffect(() => {
-    agentId && fetchAgent(agentId);
-  }, [agentId]);
+    if(agentId){
+      if(isVisitor){
+        fetchAgentProfile(agentId)
+      }else{
+        fetchAgent(agentId);
+      }
+    }
+  }, [agentId, isVisitor]);
 
   useEffect(() => {
     setShowWelcome(!histories.length);
@@ -91,14 +96,15 @@ export default function Index() {
 
   useEffect(() => {
     if (data?.data) {
-      setHistories([...data.data.reverse(), ...histories]);
+      const combine = [...histories, ...data.data]
+      setHistories(combine);
       if (page === 1) {
         setTimeout(() => {
           setScrollTop();
         }, 300);
       }
     }
-  }, [data, page]);
+  }, [data, page,]);
 
   // 计算 marginTopOffset 偏移的距离
   const marginTopOffset = (() => {
@@ -179,7 +185,7 @@ export default function Index() {
       >
         <IconArrowLeft />
         <View className={showWelcome ? "hidden" : "block"}>
-          <PersonalCard size="mini" />
+          <PersonalCard agent={agent} size="mini" />
         </View>
       </View>
     );
@@ -210,9 +216,10 @@ export default function Index() {
           scrollWithAnimation
           onScrollToUpper={onScrollToUpper}
         >
-          {showWelcome && <ChatWelcome />}
+          {showWelcome && <ChatWelcome agent={agent} />}
           <View id="messageList" className="flex flex-col gap-8 px-18 pb-140">
-            {[...histories, ...messageList].map((message) => {
+            {/* 复制 histories 再 reverse 否则会影响 state */}
+            {[...[...histories].reverse(), ...messageList].map((message) => {
               const robotMessage = isRobotMessage(message) ? message : null;
               return (
                 <ChatMessage

+ 1 - 5
src/pages/contact/index.tsx

@@ -21,11 +21,7 @@ export default function Index() {
     const res = await getContactList({ startId: nextId, pageSize, keyword });
     return res.data;
   };
-  const { data, loadMore, refetch } = useLoadMore<{
-    data?: TContactItem[]
-    nextId?: string,
-    totalCount?: number
-  }>({
+  const { data, loadMore, refetch } = useLoadMore<TContactItem[]>({
     url: '/blue-aiagent/api/v1/my/contacts',
     fetcher,
     params: [searchValue]

+ 1 - 1
src/service/storage.ts

@@ -50,7 +50,7 @@ export const fetchMyAvatars = ({pageSize = 20, pageIndex = 1}:{pageSize?: number
 
 
 // 删除指定的形象记录
-export const deleteAvatar = (avatarId: string) => {
+export const deleteAvatar = (avatarId: string|number) => {
   return request.delete(`${bluebookAiAgent}api/v1/my/avatar/${avatarId}`)
 }
 

+ 44 - 8
src/utils/loadMore.ts

@@ -1,27 +1,63 @@
-import { useEffect, useState } from "react";
+import { useState } from "react";
 import useSWR from "swr";
-export const useLoadMore = <T>({url, fetcher, startId,  pageSize = 10, params = []} : {
+
+type TResponseData<T> = {
+  data:T
+  nextId?: string
+  totalCount?: null|number
+  pageIndex?: number
+  pageSize?: number
+}
+
+export const useLoadMore = <T>({url, fetcher, startId, pageIndex=1,  pageSize = 10, params = [], } : {
   url: string,
   fetcher: any,
-  startId?: number,
+  startId?: number, // nextId
+  pageIndex?: number,
   pageSize?: number,
   params?: any[]
 
 }) => {
-  const [page, setPage]  = useState(1)
+  const [page, setPage]  = useState(pageIndex)
   const [nextId, setNextId]  = useState(startId)
   console.log(url, nextId, page, pageSize)
   // 使用 useSWR
-  const { data, error, isLoading, mutate } = useSWR<T>(
+  const { data, error, isLoading, mutate } = useSWR<TResponseData<T>>(
     // key 数组,当这些值变化时会重新请求
     [url, nextId, page, pageSize, ...params],
-    fetcher
+    fetcher,
   )
+
+  
+  // 多页加载 可能返回的数据结构
+  // nextId?: string
+  // totalCount?: null|number
+  // pageIndex?: number
+  // pageSize?: number
+  const hasNextPage = ()=> {
+    if(data?.totalCount  === null || data?.totalCount === undefined){
+      return false
+    }
+    // 如果返回的是 nextId 则没有 pageIndex 和 pageSize
+    if(data?.pageIndex === undefined){
+      return false
+    }
+    if(data?.pageSize === undefined){
+      return false
+    }
+    if(data.totalCount > (data.pageSize * data.pageIndex)){
+      return true
+    }
+  }
   
   // 加载更多数据
   const loadMore = () => {
-    //@ts-ignore
-    if (data?.nextId) {
+    // console.log(data?.nextId, data?.totalCount, data.pageSize * data.pageIndex)
+    // data.totalCount 可能为 null , 
+    // data.pageIndex 可能为 undefined
+    // null > NaN === false
+    
+    if (data?.nextId || hasNextPage()) {
       //@ts-ignore
       setNextId(data.nextId);
       setPage(prev => prev + 1);