Ver código fonte

feat: 新建智能体默认形象

王晓东 4 dias atrás
pai
commit
742f7dcce7
30 arquivos alterados com 413 adições e 275 exclusões
  1. 6 6
      project.private.config.json
  2. 95 0
      src/components/AgentCard/index.module.less
  3. 37 0
      src/components/AgentCard/index.tsx
  4. 9 4
      src/components/AgentPage/index.tsx
  5. 1 1
      src/components/AvatarMedia/index.tsx
  6. 2 2
      src/components/EmptyData/index.tsx
  7. 14 8
      src/components/KnowledgeList/index.tsx
  8. 2 2
      src/components/KnowledgePicker/index.module.less
  9. 25 21
      src/components/KnowledgePicker/index.tsx
  10. 1 1
      src/components/chat-message/Message.tsx
  11. 1 1
      src/components/component-list/components/card-contacts/index.tsx
  12. 0 67
      src/components/contact-card/index.module.less
  13. 0 32
      src/components/contact-card/index.tsx
  14. 93 0
      src/components/page-wrapper/CustomBg.tsx
  15. 4 28
      src/components/page-wrapper/index.tsx
  16. 3 0
      src/config/index.ts
  17. 2 2
      src/pages/agent-avatars/index.module.less
  18. 25 5
      src/pages/chat/components/input-bar/chatInput.ts
  19. 1 4
      src/pages/chat/index.tsx
  20. 16 1
      src/pages/contact/components/contact-card/index.tsx
  21. 19 21
      src/pages/dashboard/components/AgentList/index.tsx
  22. 5 11
      src/pages/editor-pages/editor-link-contact/components/MyAgentsScrollList/index.tsx
  23. 3 9
      src/pages/editor-pages/editor-link-contact/components/MyContactsScrollList/index.tsx
  24. 3 7
      src/pages/editor-pages/editor-link-contact/components/MyEntAgentsScrollList/index.tsx
  25. 23 31
      src/pages/editor-pages/editor-link-contact/index.tsx
  26. 1 1
      src/pages/editor-pages/editor-link/index.tsx
  27. 1 0
      src/pages/editor-pages/editor-media/index.tsx
  28. 12 3
      src/store/appStore.ts
  29. 6 4
      src/store/textChat.ts
  30. 3 3
      src/types/bot.ts

+ 6 - 6
project.private.config.json

@@ -9,16 +9,16 @@
     "miniprogram": {
       "list": [
         {
-          "name": "contactus",
-          "pathName": "pages/contact-us/index",
-          "query": "",
+          "name": "pages/agent/index",
+          "pathName": "pages/agent/index",
+          "query": "agentId=p_2e73c9d7efaYfDo2-agent_991",
           "scene": null,
           "launchMode": "default"
         },
         {
-          "name": "pages/agent/index",
-          "pathName": "pages/agent/index",
-          "query": "agentId=p_2e73c9d7efaYfDo2-agent_991",
+          "name": "contactus",
+          "pathName": "pages/contact-us/index",
+          "query": "",
           "launchMode": "default",
           "scene": null
         },

+ 95 - 0
src/components/AgentCard/index.module.less

@@ -0,0 +1,95 @@
+.contactCard{
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  border-radius: 12px;
+  align-self: stretch;;
+  background: white;
+  color: #262626;
+  font-family: "PingFang SC";
+}
+
+.avatar{
+  display: flex;
+  width: 55px;
+  height: 55px;
+  justify-content: center;
+  align-items: center;
+  border-radius: 55px;
+  overflow: hidden;
+  flex-shrink: 0;
+  border: 1px solid rgba(0, 0, 0, 0.04);
+}
+.contactCard{
+  display: flex;
+  padding: 16px;
+  align-items: flex-start;
+  gap: 8px;
+  // border-radius: 12px;
+  color: #262626;
+  font-family: "PingFang SC";
+}
+.avatarContainer{
+  position: relative;
+  width: 48px;
+  height: 48px;
+}
+.avatar{
+  position: relative;
+  width: 48px;
+  height: 48px;
+  border-radius: 100%;
+  flex-shrink: 0;
+  overflow: hidden;
+}
+.infoColumn{
+  width: 100%;
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+}
+.nameRow{
+  display: flex;
+  align-items: center;
+  gap: 4px;
+  font-style: normal;
+}
+.nickName{
+  font-size: 14px;
+  font-style: normal;
+  font-weight: 500;
+  line-height: 20px;
+}
+.tipCnt{
+  position: absolute;
+  z-index: 1;
+  top: 6px;
+  right: 0px;
+  font-size: 0px;
+  height: 8px;
+  width: 8px;
+  border-radius: 8px;
+  background: #FF4747;
+  border: 1px solid white;
+}
+.certificationContainer{
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background-color: rgba(#31BE59, .1);
+  width: 20px;
+  height: 16px;
+  border-radius: 4px;
+}
+
+.subInfo{
+  color: #414A64;
+  font-size: 12px;
+  font-style: normal;
+  font-weight: 400;
+  line-height: 16px;
+}
+.lastMsg {
+  .subInfo();
+  color: #A1A7BA;
+}

+ 37 - 0
src/components/AgentCard/index.tsx

@@ -0,0 +1,37 @@
+import { View, Image } from '@tarojs/components'
+import IconCertificateColor from "@/components/icon/icon-certificate-color";
+import style from './index.module.less'
+import { TContactItem } from '@/types/contact'
+import { TAgent } from '@/types/agent'
+import { AvatarMedia } from '../AvatarMedia';
+interface Props {
+  data: TContactItem|TAgent
+  onClick?: (data: TContactItem|TAgent)=>void
+  renderRight?: ()=> JSX.Element | JSX.Element[]
+  className?: string
+}
+const Index = ({onClick, data, className, renderRight}: Props)=> {
+  return (
+    <View className={`${style.contactCard} ${className}`} onClick={()=>{onClick?.(data)}}>
+      <View className={style.avatar}>
+        <AvatarMedia source={data.avatarUrl ?? ''} className='w-[55px] h-[55px]' mode="aspectFill" />
+      </View>
+      <View className={`${style.infoColumn} truncate`}>
+        <View className={`${style.nameRow} truncate`}>
+          <View className={`${style.nickName} truncate`}>{data.name}</View>
+          {data.isEnt && <View className={style.certificationContainer}><IconCertificateColor></IconCertificateColor></View>}
+        </View>
+        <View className='flex flex-col w-full gap-8 truncate'>
+          <View className={`flex items-center flex-1 ${style.subInfo} truncate`}>
+            <View className='max-w-156 truncate'>{!!data.entName?.length ? data.entName : '暂无企业名称'}</View>
+            <View className='px-4'>|</View>
+            <View className='truncate'>{!!data.position?.length ? data.position : '暂无职位名称'}</View>
+          </View>
+        </View>
+      </View>
+      {renderRight && renderRight()}
+    </View>
+  )
+}
+
+export default Index

+ 9 - 4
src/components/AgentPage/index.tsx

@@ -5,10 +5,11 @@ import { View } from "@tarojs/components";
 import Logo from "@/components/logo";
 import SummaryBar from "./components/SummaryBar";
 import { useAgentStore } from "@/store/agentStore";
-import { useEffect } from "react";
+import { useEffect, useState } from "react";
 import { useComponentStore } from "@/store/componentStore";
 import ComponentList from "@/components/component-list";
 import { useUserStore } from "@/store/userStore";
+import {  DEFAULT_AVATAR_BG } from '@/config'
 
 import { useDidShow } from "@tarojs/taro";
 
@@ -20,13 +21,17 @@ export default function Index({ agentId }: IProps) {
   const { fetchAgent } = useAgentStore();
   const agent = useAgentStore((state)=> state.agent);
   const { fetchMyEntList } = useUserStore();
+  const [bg, setBg] = useState('')
   
   const { setComponentList } = useComponentStore()
   const components = useComponentStore((state) => state.components);
 
-  const fetchData = ()=> {
+  const fetchData = async ()=> {
     if (agentId) {
-      fetchAgent(agentId)
+      const _agent = await fetchAgent(agentId)
+      // 要等请求结束才能判断要不要显示默认形象图
+      // 设置 背景
+      setBg(_agent?.avatarUrl ?? DEFAULT_AVATAR_BG)
     }
   }
 
@@ -50,7 +55,7 @@ export default function Index({ agentId }: IProps) {
   
 
   return (
-    <PageCustom styleBg={agent?.avatarUrl} >
+    <PageCustom styleBg={bg} >
       <NavBarNormal blur scrollFadeIn leftColumn={Logo}></NavBarNormal>
       <View className="blur-rounded-container">
         {(!!agent) ? <SummaryBar isVisitor={false} agent={agent}></SummaryBar> : <></>}

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

@@ -59,7 +59,7 @@ export const AvatarMedia = ({ source, className, roundedFull = true, mode = 'wid
 
   const _source = source?.length ? source : DEFAULT_AVATAR
   return (
-    <View className={`overflow-hidden ${roundedFull ? 'rounded-full': ''} ${className}`}>
+    <View className={`overflow-hidden shrink-0 ${roundedFull ? 'rounded-full': ''} ${className}`}>
       <Image
         mode={mode}
         className={`${className}`}

+ 2 - 2
src/components/EmptyData/index.tsx

@@ -1,11 +1,11 @@
 import { View } from "@tarojs/components";
 interface IProps {
-  type: 'plane'|'search'|'chat' | 'box' | 'whiteboard' | (string & {}),
+  type?: 'plane'|'search'|'chat' | 'box' | 'whiteboard' | (string & {}),
   text?: string,
   children?: JSX.Element|JSX.Element[]
   className?: string
 }
-export default ({type, className='', text = '暂无数据', children}: IProps)=> {
+export default ({type = 'plane', className='', text = '暂无数据', children}: IProps)=> {
   return (
     <View className={`flex flex-col items-center mt-44 ${className}`}>
       <View className={`data-empty data-empty-${type}`}></View>

+ 14 - 8
src/components/KnowledgeList/index.tsx

@@ -1,6 +1,5 @@
 import { ScrollView, View, Image } from "@tarojs/components";
 
-import FigureList from "@/components/list/FigureList";
 import FigureListItem from "@/components/list/FigureListItem";
 import WemetaRadio from "@/components/WemetaRadio/index";
 
@@ -21,9 +20,8 @@ export interface IProps {
   placeholder?: ()=> JSX.Element
 }
 const Index = ({ types, multi, entId, placeholder, onChange }: IProps) => {
-  const { list, checkedValue, initLoad, setCheckedValue, loadMore } =
+  const {list, checkedValue, initLoad, setCheckedValue, loadMore } =
     useKnowledge({ types: types, entId });
-
   const onScrollToLower = async () => {
     loadMore();
   };
@@ -72,7 +70,7 @@ const Index = ({ types, multi, entId, placeholder, onChange }: IProps) => {
   };
   const rightRenderer = (item: TKnowledgeItem) => {
     return (
-      <View className="flex items-center">
+      <View className="flex self-center">
         <WemetaRadio
           checkbox
           checked={
@@ -89,7 +87,7 @@ const Index = ({ types, multi, entId, placeholder, onChange }: IProps) => {
     if(placeholder){
       return <>{placeholder()}</>
     }
-    return <View className="flex flex-col gap-8 items-center pt-45"> <EmptyData type={"plane"}>
+    return <View className="flex flex-col gap-8 items-center pt-45"> <EmptyData type={'search'}>
       <View className="text-gray-45 flex flex-col items-center">
         <View>暂无数据</View>
         <View>可在电脑端管理知识库</View>
@@ -98,6 +96,13 @@ const Index = ({ types, multi, entId, placeholder, onChange }: IProps) => {
     </View>
   }
 
+  const renderFigure = (item: TKnowledgeItem) => {
+    if(item.picUrl){
+      return <View className="w-36 h-36 overflow-hidden"><Image className="w-36 h-36" src={item.picUrl} mode="widthFix" ></Image></View>
+    }
+    return <IconFIleLink />
+  }
+
   return (
     <ScrollView
       scrollY
@@ -107,13 +112,14 @@ const Index = ({ types, multi, entId, placeholder, onChange }: IProps) => {
         height: "100%", // 高度自适应
       }}
     >
-      <FigureList>
+      <View className="rounded-8 flex flex-col gap-12 h-full">
         <>
           {!list.length && ( renderPlaceHolder() )}
           {list.map((item) => {
+            item.picUrl
             return (
               <FigureListItem
-                figure={() => <IconFIleLink />}
+                figure={()=> renderFigure(item)}
                 underline
                 onClick={() => handleClick(item)}
                 rightRenderer={() => rightRenderer(item)}
@@ -130,7 +136,7 @@ const Index = ({ types, multi, entId, placeholder, onChange }: IProps) => {
             );
           })}
         </>
-      </FigureList>
+      </View>
     </ScrollView>
   );
 };

+ 2 - 2
src/components/KnowledgePicker/index.module.less

@@ -1,7 +1,7 @@
 .tabContainer{
   padding-top: 16px;
-  height: 60vh;
-  margin-bottom: 100px;
+  height: 54vh;
+  margin-bottom: 120px;
 }
 .dataEmpty{
   display: flex;

+ 25 - 21
src/components/KnowledgePicker/index.tsx

@@ -13,15 +13,17 @@ import Taro from "@tarojs/taro";
 import { useUserStore } from "@/store/userStore";
 import { TKnowledgeItem } from "@/types/knowledge";
 import { TEntItem } from "@/types/user";
+import EmptyData from "../EmptyData";
 export interface IProps {
   show: boolean;
   multi?: boolean; // 是否多选
+  title?: string; //弹层 title 
   types?: EKnowlegeTypes[]; // 列表类型
   setShow: (show: boolean) => void;
   onPicked: (picked?: TKnowledgeItem[]) => void
 }
 
-export default function Index({ show, setShow, multi, types, onPicked }: IProps) {
+export default function Index({title = '知识库',  show, setShow, multi, types, onPicked }: IProps) {
   const entList = useUserStore((state) => state.entList);
   const [ent, setEnt] = useState<TEntItem>();
   const [picked, setPicked] = useState<TKnowledgeItem[]>();
@@ -67,31 +69,33 @@ export default function Index({ show, setShow, multi, types, onPicked }: IProps)
   const renderEntContent = () => {
     if (!entList.length) {
       return (
-        <View className={style.dataEmpty}>
-          <View className="pt-20 text-center">
-            <View className="leading-24 text-black font-medium text-16 mb-4">
-              你还没有加入任何企业
-            </View>
-            <View className="leading-20 text-gray-45 mb-12">
-              访问公司统一知识内容,提升回复效率与专业度
-            </View>
-            <View
-              className="text-primary"
-              onClick={() => {
-                Taro.navigateTo({
-                  url: "/pages/contact-us/index",
-                });
-              }}
-            >
-              联系我们
+        <View className="bg-gray-3 rounded-12 pt-44 h-full">
+          <EmptyData type='box' className="mt-0">
+            <View className="pt-20 text-center">
+              <View className="leading-24 text-black font-medium text-16 mb-4 font-pingfangSCMedium">
+                你还没有加入任何企业
+              </View>
+              <View className="leading-20 text-gray-45 mb-12 text-14">
+                访问公司统一知识内容, 提升回复效率与专业度
+              </View>
+              <View
+                className="button-rounded button-primary-light button-border-primary button-inline-flex"
+                onClick={() => {
+                  Taro.navigateTo({
+                    url: "/pages/contact-us/index",
+                  });
+                }}
+              >
+                联系我们
+              </View>
             </View>
-          </View>
+          </EmptyData>
         </View>
       );
     }
 
     return (
-      <View>
+      <View className="h-full pb-40">
         <PickerSingleColumn
           options={options}
           selected={selected}
@@ -152,7 +156,7 @@ export default function Index({ show, setShow, multi, types, onPicked }: IProps)
   ];
 
   return (
-    <Popup setShow={setShow} show={show} title="知识库-链接">
+    <Popup setShow={setShow} show={show} title={title}>
       <WemetaTabs current="1" list={tabList}></WemetaTabs>
       <BottomBar>
         <View

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

@@ -9,7 +9,7 @@ export default ({text}:Props) => {
     <View className={`${style.message } ${style.messageMe}`}>
       <View className={`${style.messageContent} text-white`}>
         {text.length === 0 && <ThinkAnimation></ThinkAnimation>}
-        <Text>{text}</Text>
+        <Text user-select>{text}</Text>
       </View>
     </View>
   </View>

+ 1 - 1
src/components/component-list/components/card-contacts/index.tsx

@@ -1,7 +1,7 @@
 import Taro, { useDidShow } from "@tarojs/taro";
 import { View, Image } from "@tarojs/components";
 import WidgetCard from "@/components/widgets/widget-card/index";
-import ContactCard from "@/components/contact-card/index";
+import ContactCard from "@/components/AgentCard/index";
 import style from "./index.module.less";
 import IconArrowRight from "@/images/icon_24_right.png";
 import { axios } from "taro-axios";

+ 0 - 67
src/components/contact-card/index.module.less

@@ -1,67 +0,0 @@
-.contactCard{
-  display: flex;
-  align-items: center;
-  gap: 12px;
-  border-radius: 12px;
-  align-self: stretch;;
-  background: white;
-  color: #262626;
-  font-family: "PingFang SC";
-}
-
-.avatar{
-  display: flex;
-  width: 55px;
-  height: 55px;
-  justify-content: center;
-  align-items: center;
-  border-radius: 55px;
-  overflow: hidden;
-  flex-shrink: 0;
-  border: 1px solid rgba(0, 0, 0, 0.04);
-}
-.avatarImg{
-  display: block;
-  width: 55px;
-  height: 55px;
-}
-.infoColumn{
-  display: flex;
-  flex:1;
-  flex-direction: column;
-  gap: 10;
-}
-.nameRow{
-  display: flex;
-  align-items: center;
-  gap: 4px;
-  font-style: normal;
-  letter-spacing: 0.8px;
-}
-.nickName{
-  font-size: 17px;
-  font-style: normal;
-  font-weight: 600;
-  line-height: 26px;
-  white-space: nowrap;
-}
-.nameMarkup{
-  display: flex;
-  padding: 2px 4px;
-  justify-content: center;
-  align-items: center;
-  font-size: 11px;
-  font-style: normal;
-  font-weight: 600;
-  line-height: 14px;
-  border-radius: 4px;
-  background: #CBF706;
-}
-
-.subInfo{
-  color: rgba(0, 0, 0, 0.65);
-  font-size: 12px;
-  font-style: normal;
-  font-weight: 400;
-  line-height: 20px;
-}

+ 0 - 32
src/components/contact-card/index.tsx

@@ -1,32 +0,0 @@
-import { View, Image } from '@tarojs/components'
-import IconCertificateColor from "@/components/icon/icon-certificate-color";
-import style from './index.module.less'
-import { TContactItem } from '@/types/contact'
-import { TAgent } from '@/types/agent'
-import { AvatarMedia } from '../AvatarMedia';
-interface Props {
-  data: TContactItem|TAgent
-  onClick?: (data: TContactItem|TAgent)=>void
-  renderRight?: ()=> JSX.Element | JSX.Element[]
-  className?: string
-}
-const Index = ({onClick, data, className, renderRight}: Props)=> {
-  return (
-    <View className={`${style.contactCard} ${className}`} onClick={()=>{onClick?.(data)}}>
-      <View className={style.avatar}>
-      <AvatarMedia source={data.avatarUrl ?? ''} className='w-[55px] h-[55px]' mode="aspectFill" />
-      </View>
-      <View className={`${style.infoColumn} truncate`}>
-        <View className={style.nameRow}>
-          <View className={`flex items-center ${style.nickName} truncate`}>{data.name} {data?.isEnt && <View className="text-12 leading-12"><IconCertificateColor/></View>}</View>
-          {/* {data.badges?.map(item=><View className={style.nameMarkup}>{item}</View>)} */}
-        </View>
-        <View className={`${style.subInfo} truncate`}>{data?.entName}</View>
-        <View className={`${style.subInfo} truncate`}>{data?.position}</View>
-      </View>
-      {renderRight && renderRight()}
-    </View>
-  )
-}
-
-export default Index

+ 93 - 0
src/components/page-wrapper/CustomBg.tsx

@@ -0,0 +1,93 @@
+/**
+ * 用于 page 根目录的样式等设置
+ */
+import { View, Video } from "@tarojs/components";
+import React, { useEffect, useState } from "react";
+import pageStyle from "./index.module.less";
+import Taro from "@tarojs/taro";
+import { useAppStore } from "@/store/appStore";
+interface Props {
+  styleBg?: string|null;
+}
+
+const Index: React.FC<Props> = ({styleBg}) => {
+  // 自定义背景样式
+  const {windowHeight, windowWidth} = useAppStore()
+
+  const [offsetMargin, setOffsetMargin] = useState({ x: 0, y: 0 });
+  const [bgSize, setBgSize] = useState({ width: windowWidth, height: windowHeight });
+
+  const  bg =  styleBg || ''  
+  
+  const bgImageStyle = {
+    backgroundImage: `url(${bg})`,
+    marginLeft: `${offsetMargin.x}px`,
+    marginTop: `${offsetMargin.y}px`,
+    height: `${bgSize.height}px`,
+    width: `${bgSize.width}px`,
+    backgroundSize: '100% 100%',
+    backgroundPosition: 'center center'
+  };
+
+  const isMp4 = bg?.lastIndexOf('.mp4') > -1
+
+  useEffect(()=> {
+    if(!bg) {
+      return;
+    }
+    if(isMp4){
+      return 
+    }
+    Taro.getImageInfo({
+      src: bg,
+      success(res) {
+        const imgWidth = res.width;
+        const imgHeight = res.height;
+
+        let ratio = windowHeight / imgHeight;
+        let scaledWidth = imgWidth * ratio;
+        let scaledHeight = windowHeight;
+        let offsetX = 0;
+        let offsetY = 0;
+
+        if (scaledWidth >= windowWidth) {
+          offsetX = (scaledWidth - windowWidth) / 2;
+          offsetY = 0;
+        } else {
+          ratio = windowWidth / imgWidth;
+          scaledWidth = windowWidth;
+          scaledHeight = imgHeight * ratio;
+          offsetX = 0;
+          offsetY = (scaledHeight - windowHeight) / 2;
+        }
+
+        setOffsetMargin({ x: -offsetX, y: -offsetY });
+        setBgSize({ width: scaledWidth, height: scaledHeight });
+      },
+      fail(err) {
+        console.error('获取图片信息失败', err)
+      }
+    })
+  }, [styleBg])
+
+  if(!bg) {
+    return <></>
+  }
+
+  if(isMp4){ 
+    return <Video
+      controls={false}
+      showCenterPlayBtn={false}
+      loop={true}
+      muted={true}
+      autoplay
+      objectFit="cover"
+      className={pageStyle.bg}
+      src={bg}
+    />
+  }
+  
+  return <View className={pageStyle.bg} style={bgImageStyle}></View>
+};
+
+export default Index;

+ 4 - 28
src/components/page-wrapper/index.tsx

@@ -1,10 +1,11 @@
 /**
  * 用于 page 根目录的样式等设置
  */
-import { View, Video } from "@tarojs/components";
-import React from "react";
+import { View, Video, Image } from "@tarojs/components";
+import React, { useState } from "react";
 import pageStyle from "./index.module.less";
 import { GlobalModal } from '@/components/GlobalModal/index';
+import Custombg from './CustomBg'
 interface Props {
   children?: React.ReactChild | React.ReactChild[];
   style?: React.CSSProperties;
@@ -13,31 +14,6 @@ interface Props {
 }
 
 const Index: React.FC<Props> = ({ children, styleBg, style, onClick }) => {
-  // 自定义背景样式
-  
-  
-  const bgImageStyle = {
-    backgroundImage: `url(${styleBg})`,
-  };
-
-  const renderBg = () => {
-    if(!styleBg) {
-      return <></>
-    }
-    if(styleBg.lastIndexOf('.mp4') > -1){
-      return <Video
-        controls={false}
-        showCenterPlayBtn={false}
-        loop={true}
-        muted={true}
-        autoplay
-        objectFit="cover"
-        className={pageStyle.bg}
-        src={styleBg}
-      />
-    }
-    return <View className={pageStyle.bg} style={bgImageStyle}></View>
-  }
   //  盖在背景上面的是视口高度的 渐变色
   const handleClick = (e:any)=> {
     onClick && onClick(e)
@@ -48,7 +24,7 @@ const Index: React.FC<Props> = ({ children, styleBg, style, onClick }) => {
       {/* cover 背景图覆盖从上至下渐变 */}
       <View className={`global-linear-gradient-bg ${pageStyle.bgVerticalGradient}`}></View>
       {/* bg 背景图 */}
-      {renderBg()}
+      <Custombg styleBg={styleBg}></Custombg>
       <View className="relative z-0 h-full w-full" style={style}>{children}</View>
       <GlobalModal></GlobalModal>
     </View>

+ 3 - 0
src/config/index.ts

@@ -47,6 +47,8 @@ const IS_MINI_GREEN_LEAF = APP_NAME === 'miniGreenLeaf'
 const ENABLE_UPGRADE_VIP = IS_MINI_GREEN_LEAF
 
 const DEFAULT_AVATAR = defaultAvatar
+// const DEFAULT_AVATAR_BG = 'https://51saas.oss-cn-hangzhou.aliyuncs.com/u260727532/20250731/YYq5ngJESF_d5cdfe5753ff4f86bac2d38dc82df8fa.png'
+const DEFAULT_AVATAR_BG = 'https://51saas.oss-cn-hangzhou.aliyuncs.com/u260727532/20250731/Eej79YOizH_16c3bae6b107493e87fc34717265ef78.png'
 const DEFAULT_AGENT = {
     avatar: DEFAULT_AVATAR,
     name: "小蓝本助手",
@@ -66,6 +68,7 @@ export {
 	IS_MINI_GREEN_LEAF,
 	DEFAULT_AVATAR,
 	DEFAULT_AGENT,
+	DEFAULT_AVATAR_BG,
 }
 
 

+ 2 - 2
src/pages/agent-avatars/index.module.less

@@ -16,11 +16,11 @@
   border-radius: 12px;
   overflow: hidden;
   background-color: white;
-  border: 1px solid transparent;
 }
 .gridItemActived {
   .gridItem();
-  border: 1px solid var(--color-primary);
+  background-color: var(--color-primary);
+  border: 2px solid var(--color-primary);
 }
 
 .icon{

+ 25 - 5
src/pages/chat/components/input-bar/chatInput.ts

@@ -7,7 +7,7 @@ import { TAgentDetail } from "@/types/agent";
 import { delay, getLoginId, isSuccess } from "@/utils";
 import { EAI_MODEL } from "@/consts/enum";
 import { useUnload } from "@tarojs/taro";
-import { EChatRole, EContentType } from "@/types/bot";
+import { EChatRole, EContentType, TRobotMessage } from "@/types/bot";
 
 import { usePostMessage } from './message'
 
@@ -34,7 +34,7 @@ export const useChatInput = ({ agent, setShowWelcome, setDisabled, }: Props) =>
     setQuestions,
     questions,
   } = useTextChat();
-  const { startTimedMessage, stopTimedMessage, isTimedMessageRunning, saveMessageToServer } = usePostMessage(getCurrentRobotMessage);
+  const { startTimedMessage, stopTimedMessage, saveMessageToServer } = usePostMessage(getCurrentRobotMessage);
   
   let myMsgUk = '';
   let mySessionId = '';
@@ -153,8 +153,8 @@ export const useChatInput = ({ agent, setShowWelcome, setDisabled, }: Props) =>
         
         // 将智能体的回答保存至服务器
         // currentRobotMessage.content 保存的是当前完整的智能体回复信息文本
-        const content = currentRobotMessage.content ?? currentRobotMessage?.body?.content
-        updateRobotMessage(content, currentRobotMessage?.body, 2)
+        const content = currentRobotMessage.content as string
+        updateRobotMessage(content, currentRobotMessage?.body, 2, true)
         await saveMessageToServer({
           loginId,
           messages: [{
@@ -179,8 +179,28 @@ export const useChatInput = ({ agent, setShowWelcome, setDisabled, }: Props) =>
         }
 
       },
-      onComplete: ()=> {
+      onComplete: async ()=> {
         stopTimedMessage()
+        console.log('回复 onComplete')
+        // 为防止服务端没有终止消息,当接口请求结束时强制再保存一次消息体,以防定时保存的消息体漏掉最后一部分
+        const currentRobotMessage = getCurrentRobotMessage();
+        if(currentRobotMessage && agent.agentId){
+          const content = currentRobotMessage.content as string
+          await saveMessageToServer({
+            loginId,
+            messages: [{
+              saveStatus: 2,
+              content: content,
+              contentType: currentRobotMessage?.body?.contentType ?? EContentType.TextPlain,
+              isStreaming: false,
+              role: currentRobotMessage.role,
+              msgUk: currentRobotMessage.msgUk,
+            }],
+            agentId: agent.agentId,
+            sessionId,
+          })
+        }
+        
         setDisabled?.(false);
       },
       onError: () => {

+ 1 - 4
src/pages/chat/index.tsx

@@ -49,11 +49,8 @@ const agent = useAgentStore((state) => {
   const { destroy, setScrollTop, genSessionId, setAutoScroll } = useTextChat();
   const scrollTop = useTextChat((state) => state.scrollTop);
   // const autoScroll = useTextChat((state) => state.autoScroll);
-
+  
   const fetcher = async ([_url,{ nextId, pageSize}]) => {
-    if (!agent) {
-      return null;
-    }
     const _nextId = nextId ? decodeURIComponent(nextId) : nextId;
     const res = await getMessageHistories({
       agentId,

+ 16 - 1
src/pages/contact/components/contact-card/index.tsx

@@ -15,6 +15,21 @@ interface Props {
 const Index = ({data, deleteable, className, refresh, fromContact}: Props)=> {
   const handleClick = (data: TContactItem)=>{
     console.log(data, fromContact)
+    if(data.agentStatus === 'normal'){
+      // 如果是正常智能体,则去聊天
+      Taro.navigateTo({
+        url: `/pages/chat/index?agentId=${data.agentId}&isVisitor=true`,
+      });
+    }else{
+      // 否则跳到此智能体
+      Taro.navigateTo({
+        url: "/pages/profile/index?agentId=" + data.agentId,
+      });
+    }
+  }
+  
+  const handleAvatarClick = (e:any)=>{
+    e.stopPropagation();
     Taro.navigateTo({
       url: "/pages/profile/index?agentId=" + data.agentId,
     });
@@ -23,7 +38,7 @@ const Index = ({data, deleteable, className, refresh, fromContact}: Props)=> {
   
   return (
     <View className={`${style.contactCard} ${className}`} onClick={()=>{handleClick(data)}}>
-      <View className={style.avatarContainer}>
+      <View className={style.avatarContainer} onClick={handleAvatarClick}>
         <AvatarMedia source={data.avatarUrl || ''} mode="aspectFill" className={style.avatar}></AvatarMedia>
         {(!!data?.tipCnt) && <View className={style.tipCnt}>{data.tipCnt}</View>}
       </View>

+ 19 - 21
src/pages/dashboard/components/AgentList/index.tsx

@@ -8,6 +8,7 @@ import WemetaRadio from "@/components/WemetaRadio/index";
 import IconCertificateColor from "@/components/icon/icon-certificate-color";
 import { TAgent } from "@/types/agent";
 import { AvatarMedia } from "@/components/AvatarMedia";
+import AgentCard from "@/components/AgentCard/index";
 
 interface IProps {
   currentAgent: TAgent|null
@@ -47,27 +48,24 @@ export default ({currentAgent,  setCurrentAgent, show, setShow }: IProps) => {
       <View className="flex flex-col gap-12 w-full overflow-y-auto max-h-[440px]">
         {agents.map((item) => {
           return (
-            <View className="flex items-center gap-8 p-12 rounded-8 overflow-hidden shrink-0 bg-[#F8F8F8]" onClick={()=> handleClick(item)}>
-              <View className="flex items-start w-40 shrink-0">
-                <AvatarMedia
-                  source={item.avatarLogo || ""}
-                  className="w-40 h-40 overflow-hidden"
-                ></AvatarMedia>
-              </View>
-              <View className="flex flex-col flex-1 overflow-hidden">
-                <View className="text-24 text-black leading-24 truncate">{item.name}</View>
-                <View className="flex items-center gap-4">
-                  <View className="text-12 text-gray-65 leading-20 truncate">{item?.entName || '-'}</View>
-                  {item?.isEnt && <View><IconCertificateColor/></View>}
-                </View>
-              </View>
-              <View className="shrink-0">
-                <WemetaRadio
-                  checkbox
-                  checked={currentAgent?.agentId == item.agentId}
-                 />
-              </View>
-            </View>
+            <AgentCard
+              className="border-bottom1-gray py-16"
+              key={item.agentId}
+              data={item}
+              onClick={()=> handleClick(item)}
+              renderRight={() => {
+                return (
+                  <View className="flex self-center">
+                    <WemetaRadio
+                      checkbox
+                      checked={currentAgent?.agentId == item.agentId}
+                    />
+                  </View>
+                );
+              }}>
+
+            </AgentCard>
+            
           );
         })}
       </View>

+ 5 - 11
src/pages/editor-pages/editor-link-contact/components/MyAgentsScrollList/index.tsx

@@ -5,10 +5,11 @@ import { useEffect, useState } from "react";
 import { getContactList } from "@/service/contact";
 import { useLoadMore } from "@/utils/loadMore";
 import { TContactItem } from "@/types/contact";
-import ContactCard from "@/components/contact-card/index";
+import AgentCard from "@/components/AgentCard/index";
 import WemetaRadio from "@/components/WemetaRadio/index";
 import { useAgentStore } from "@/store/agentStore";
 import { TAgent } from "@/types/agent";
+import EmptyData from "@/components/EmptyData";
 
 export interface IProps {
   selected: any[]
@@ -38,7 +39,7 @@ export default function Index({selected, setSelected}: IProps) {
     if (myAgents?.length) {
       return myAgents.map((item) => (
         <View className='bg-white'>
-          <ContactCard
+          <AgentCard
               className="border-bottom1-gray py-16"
               key={item.agentId}
               data={item}
@@ -53,18 +54,11 @@ export default function Index({selected, setSelected}: IProps) {
                   </View>
                 );
               }}
-            ></ContactCard>
+            ></AgentCard>
         </View>
       ));
     }
-    return (
-      <View className="flex flex-col pt-56 items-center">
-        <View className="data-empty"></View>
-        <View className="text-12 text-gray-45 text-center leading-24 mt-12">
-          暂无数据
-        </View>
-      </View>
-    );
+    return <EmptyData type='search'></EmptyData>
   };
 
   return (

+ 3 - 9
src/pages/editor-pages/editor-link-contact/components/MyContactsScrollList/index.tsx

@@ -4,10 +4,11 @@ import style from "./index.module.less";
 import { useEffect, useState } from "react";
 import { getContactList } from "@/service/contact";
 import { TContactItem } from "@/types/contact";
-import ContactCard from "@/components/contact-card/index";
+import ContactCard from "@/components/AgentCard/index";
 import WemetaRadio from "@/components/WemetaRadio/index";
 
 import { createKey, useLoadMoreInfinite } from "@/utils/loadMoreInfinite";
+import EmptyData from "@/components/EmptyData";
 
 export interface IProps {
   selected: any[]
@@ -67,14 +68,7 @@ export default function Index({selected, setSelected}: IProps) {
         </View>
       ));
     }
-    return (
-      <View className="flex flex-col pt-56 items-center">
-        <View className="data-empty"></View>
-        <View className="text-12 text-gray-45 text-center leading-24 mt-12">
-          暂无数据
-        </View>
-      </View>
-    );
+    return <EmptyData type='search'></EmptyData>;
   };
 
   return (

+ 3 - 7
src/pages/editor-pages/editor-link-contact/components/MyEntAgentsScrollList/index.tsx

@@ -1,11 +1,12 @@
 import Taro, { useReachBottom,} from "@tarojs/taro";
 import { View, ScrollView } from "@tarojs/components";
 import { getEntAgentPartners } from "@/service/agent";
-import ContactCard from "@/components/contact-card/index";
+import ContactCard from "@/components/AgentCard/index";
 import WemetaRadio from "@/components/WemetaRadio/index";
 import { useAgentStore } from "@/store/agentStore";
 import { TAgent } from "@/types/agent";
 import { createKey, useLoadMoreInfinite } from "@/utils/loadMoreInfinite";
+import EmptyData from "@/components/EmptyData";
 export interface IProps {
   selected: any[]
   setSelected: (values) => void
@@ -70,12 +71,7 @@ export default function Index({selected, setSelected}: IProps) {
       ));
     }
     return (
-      <View className="flex flex-col pt-56 items-center">
-        <View className="data-empty"></View>
-        <View className="text-12 text-gray-45 text-center leading-24 mt-12">
-          暂无数据
-        </View>
-      </View>
+      <EmptyData type='search'></EmptyData>
     );
   };
 

+ 23 - 31
src/pages/editor-pages/editor-link-contact/index.tsx

@@ -4,8 +4,7 @@ import Taro, { useRouter } from "@tarojs/taro";
 import PageCustom from "@/components/page-custom/index";
 import NavBarNormal from "@/components/NavBarNormal/index";
 import ButtonCardAdd from "@/components/button-card-add";
-import ContactCard from "@/components/contact-card/index";
-import editorStyle from "../editor.module.less";
+import ContactCard from "@/components/AgentCard/index";
 import style from './index.module.less'
 import Popup from "@/components/popup/popup";
 import WemetaTabs from "@/components/wemeta-tabs/index";
@@ -21,6 +20,7 @@ import PopupSheets from "@/components/popup/popup-sheets";
 import { EComponentType } from "@/consts/enum";
 import { useComponentStore } from "@/store/componentStore";
 import { useAgentStore } from "@/store/agentStore";
+import EmptyData from "@/components/EmptyData";
 
 export default function Index() {
   const { saveComponent } = useComponentStore();
@@ -105,32 +105,25 @@ export default function Index() {
   const renderContactList = () => {
     if (!picked.length) {
       return (
-        <View className="flex flex-col pt-56 items-center">
-          <View className="data-empty"></View>
-          <View className="text-12 text-gray-45 text-center leading-24 mt-12">
-            暂无数据
-          </View>
-        </View>
-      );
+        <EmptyData type='plane'></EmptyData>
+      );  
     }
     return (
-      <View className="flex flex-col gap-8">
+      <View className="flex flex-col gap-8 w-full">
         {picked.map((item) => {
           return (
-            <View className=''>
-              <ContactCard
-                className="p-16"
-                key={item.agentId}
-                data={item}
-                renderRight={() => {
-                  return (
-                    <View onClick={() => handleClickMore(item)}>
-                      <IconMoreBlack />
-                    </View>
-                  );
-                }}
-              ></ContactCard>
-            </View>
+            <ContactCard
+              className="p-16"
+              key={item.agentId}
+              data={item}
+              renderRight={() => {
+                return (
+                  <View className="flex self-center" onClick={() => handleClickMore(item)}>
+                    <IconMoreBlack />
+                  </View>
+                );
+              }}
+            ></ContactCard>
           );
         })}
       </View>
@@ -180,18 +173,21 @@ export default function Index() {
       <NavBarNormal>智能体</NavBarNormal>
       <View>
       </View>
-      <View className="flex flex-col items-center w-full">
-        <View className={editorStyle.container}>
+      <View className="w-full">
+        <View className='flex flex-col items-center w-full'>
           <ButtonCardAdd
             text={`添加展示的智能体`}
             onClick={handleClick}
           ></ButtonCardAdd>
 
-          <View className="pt-24 pb-162">
+          <View className="pt-16 pb-162 px-16 w-full">
             {renderContactList()}
           </View>
         </View>
       </View>
+      <BottomBar>
+        <View className="button-rounded button-primary flex-1" onClick={handleSubmit}>保存</View>
+      </BottomBar>
       <Popup setShow={setShow} show={show} title="智能体">
         <WemetaTabs current="1" list={tabList}></WemetaTabs>
         <BottomBar>
@@ -213,10 +209,6 @@ export default function Index() {
           ]}
         >
       </PopupSheets>
-
-      <BottomBar>
-        <View className="button-rounded button-primary flex-1" onClick={handleSubmit}>保存</View>
-      </BottomBar>
     </PageCustom>
   );
 }

+ 1 - 1
src/pages/editor-pages/editor-link/index.tsx

@@ -118,7 +118,7 @@ export default function Index() {
         </BottomBar>
       </View>
       
-      <KnowledgePicker types={[EKnowlegeTypes.web]} onPicked={onPicked} setShow={setShowPopup} show={showPopup}></KnowledgePicker>
+      <KnowledgePicker title="知识库-链接" types={[EKnowlegeTypes.web]} onPicked={onPicked} setShow={setShowPopup} show={showPopup}></KnowledgePicker>
       
     </PageCustom>
   );

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

@@ -135,6 +135,7 @@ export default function Index({ editMode }: Props) {
         />
       </View>
       <KnowledgePicker
+        title="知识库-图片/视频"
         types={[EKnowlegeTypes.image]}
         multi
         onPicked={onPicked}

+ 12 - 3
src/store/appStore.ts

@@ -6,6 +6,9 @@ import Taro from '@tarojs/taro'
 export interface AppState {
   value: number
   headerHeight: number
+  screenHeight: number
+  windowWidth: number
+  windowHeight: number
   topDistance: number
   statusBarHeight: number
   bottomSafeHeight: number
@@ -26,6 +29,9 @@ export const useAppStore = create<AppState>((set) => ({
   value: 0,
   topDistance: 0, // 内容距离顶部的距离
   headerHeight: 0, // 头部高度
+  screenHeight: 0, // 屏幕高度
+  windowHeight: 0, // 可用高度(减去状态栏等)
+  windowWidth: 0, // 可用高度(减去状态栏等)
   statusBarHeight: 0, // 头部状态高度
   capsuleInfo: {
     /** 下边界坐标,单位:px */
@@ -58,12 +64,15 @@ export const useAppStore = create<AppState>((set) => ({
     // 状态栏高度 - 6 偏差值为垂直居中对齐胶囊按钮
     // statusBarHeight 离顶部导航栏位置
     const statusBarHeight = (isNaN(info.statusBarHeight!) ? 24 : info.statusBarHeight || 47) - 6;
-    
     const system = info.system.toLowerCase() 
     const isIos = system.includes('ios')
     let bottomSafeHeight = 0
     let desktopPopupTips = false;
     const capsuleInfo = Taro.getMenuButtonBoundingClientRect()
+
+    const screenHeight = info.screenHeight
+    const windowWidth = info.windowWidth
+    const windowHeight = info.windowHeight // 更精确的可用高度(减去状态栏等)
     
     // 导航栏高度 = 胶囊和状态栏之间的留白 * 2 胶囊高度
     let navbarHeight = (capsuleInfo.top - statusBarHeight) * 2 + capsuleInfo.height
@@ -81,11 +90,11 @@ export const useAppStore = create<AppState>((set) => ({
       desktopPopupTips = true
     }
     if(info.safeArea?.bottom){
-      bottomSafeHeight = info.screenHeight - info.safeArea?.bottom;
+      bottomSafeHeight = screenHeight - info.safeArea?.bottom;
     }
     
     
-    set({ systemInfo: info, capsuleInfo, statusBarHeight, headerHeight, isIos, desktopPopupTips, bottomSafeHeight})
+    set({ systemInfo: info, capsuleInfo, statusBarHeight, headerHeight, isIos, desktopPopupTips, bottomSafeHeight, screenHeight, windowHeight, windowWidth})
     
       
   },

+ 6 - 4
src/store/textChat.ts

@@ -37,7 +37,7 @@ export interface TextChat {
   // 更新自己发出的气泡框
   updateMessage: (content: string, msgUk: string) => string;
   // 更新机器人汽泡框内的内容实现 gpt 的效果
-  updateRobotMessage: (content: string, body?: Record<string,any>, saveStatus?: number) => void;
+  updateRobotMessage: (content: string, body?: Record<string,any>, saveStatus?: number, replaceContent?: boolean) => void;
   updateRobotReasoningMessage: (msgUk: string, reasoningContent: string, body?:Record<string,any>) => void;
   getCurrentRobotMessage:() => TRobotMessage|undefined
   deleteMessage: (msgUk: string) => void;
@@ -112,7 +112,7 @@ export const useTextChat = create<TextChat>((set, get) => ({
     set((state) => {
       return {
         list: [...state.list,  newMessage],
-        scrollTop: state.scrollTop + 1,
+        autoScroll: true
       };
     });
     setTimeout(()=> {
@@ -137,12 +137,14 @@ export const useTextChat = create<TextChat>((set, get) => ({
     });
     return msgUk
   },
-  updateRobotMessage: (content, body={}, saveStatus: number = 0) => {
+  updateRobotMessage: (content, body={}, saveStatus: number = 0, replaceContent: boolean = false) => {
     set((state) => {
       const updatedList = state.list.map((message) => {
         if (message.msgUk === state.currentRobotMsgUk) {
+          // 如果是 replaceContent 则认为需要更新全部内容,否则在原 content 上追加
+          const _content = replaceContent ? content :  message.content + content;
           // 更新消息后, saveStatus 变为 0,说明又需要上报此消息
-          return { ...message, content: message.content + content, body, saveStatus: saveStatus } as TRobotMessage  // 更新 content
+          return { ...message, content: _content, body, saveStatus: saveStatus } as TRobotMessage  // 更新 content
         }
         return message; // 返回未修改的 message
       });

+ 3 - 3
src/types/bot.ts

@@ -52,19 +52,19 @@ export type TMessage = {
   msgTime?: string,
   msgUk: string,
   originalAgentId?: string,
-  role: EChatRole.User,
+  role: TChatRole,
   body?: TMessageBody
 }
 
 export type TRobotMessage = {
   reasoningContent: string
-  role:  EChatRole.Assistant | EChatRole.System | EChatRole.Function
+  role:  TChatRole
   robot?: {
     avatar: string;
     agentId: string;
     name: string;
   };
-} & TMessage;
+} & Omit<TMessage, 'role'> ;
 
 export type TAnyMessage = TMessage | TRobotMessage;