王晓东 3 недель назад
Родитель
Сommit
e171c63140

+ 4 - 0
README.md

@@ -103,6 +103,10 @@ https://www.kdocs.cn/l/cgHRgaGkRuJu
 
 https://bluebook.feishu.cn/sheets/BW1dsW5NLhQKj7t3fnjc0xvsnpd
 
+ui bug
+
+https://bluebook.feishu.cn/sheets/BW1dsW5NLhQKj7t3fnjc0xvsnpd?from=from_copylink
+
 
 # 服务端接口文档  及 小程序 登录签名的脚手架 demo
 https://bluebook.feishu.cn/docx/Ckh0dLOBzo0rjyxrUkrc0ffdn2c   

+ 2 - 0
src/app.config.ts

@@ -5,6 +5,8 @@ export default defineAppConfig({
     'pages/profile/index',
     'pages/dashboard/index',
     'pages/dashboard-visited-detail/index',
+    'pages/dashboard-dislike-messages/index',
+    'pages/dashboard-visitor-message-editor/index',
     'pages/contact/index',
     'pages/knowledge/index',
     'pages/knowledge-item/index',

+ 99 - 0
src/pages/dashboard-dislike-messages/components/ShareToEntPopup/index.tsx

@@ -0,0 +1,99 @@
+/**
+ * 知识库
+ */
+
+import { Text, View, Image, ScrollView } from "@tarojs/components";
+
+
+import WemetaSwitch from "@/components/WemetaSwitch";
+
+import Popup from "@/components/popup/popup";
+import { useEffect, useState } from "react";
+import Taro, { useRouter, useDidShow } from "@tarojs/taro";
+import {
+  shareToEnt,
+} from "@/service/knowledge";
+import { useUserStore } from "@/store/userStore";
+import { TEntItem } from "@/types/user";
+import { isSuccess } from "@/utils";
+
+
+interface IProps {
+  knowledgeId: string|number
+  showPopup: boolean
+  setShowPopup: (show: boolean)=> void,
+}
+
+export default function Index({knowledgeId, showPopup, setShowPopup }:IProps) {
+
+  const {entList, fetchMyEntList} = useUserStore()
+  const [shareEntList, setShareEntList] = useState(entList ?? []);
+  
+  
+  const handleSwitchChange = async (ent: TEntItem, checked: boolean)=> {
+    if(!knowledgeId){
+      return false;
+    }
+    const response = await shareToEnt({
+      knowledgeIds: [knowledgeId],
+      toEntId: ent.entId,
+    })
+    if(isSuccess(response.status)){
+      Taro.showToast({title: checked ? '分享成功' : '取消分享成功'})
+      setShareEntList((prev) => {
+        if(checked){
+          return [...prev, ent]
+        }
+        return  prev.filter( item => item.entId !== ent.entId)
+      })
+    }
+  }
+
+  useDidShow(() => {
+    fetchMyEntList()
+  });
+
+  const renderContent = ()=> {
+    if(!entList.length){
+      return <View className="rounded-8 bg-gray-3 py-20 text-center">
+        <View className="leading-24 text-gray-45 font-medium text-14 mb-4">
+          暂未加入任何企业
+        </View>
+        
+        <View
+          className="text-primary"
+          onClick={() => {
+            Taro.navigateTo({
+              url: "/pages/contact-us/index",
+            });
+          }}
+        >
+          联系我们
+        </View>
+      </View>
+    }
+
+    return <View className="flex flex-col gap-12 pb-24" style={{maxHeight: '60vh', overflow: 'auto'}}>
+      {entList.map(ent=> {
+        return <View className="flex items-center rounded-8 bg-gray-3 px-16 py-24">
+            <View className="flex-1 text-black font-medium text-14 leading-22">{ent.entName}</View>
+            <WemetaSwitch
+              checked={!!shareEntList.find((item)=> item.entId === ent.entId)}
+              onChange={(checked) => handleSwitchChange(ent, checked)}
+            ></WemetaSwitch>
+        </View>
+      })}
+    </View>
+
+  }
+  
+
+  return (
+    <Popup setShow={setShowPopup} show={showPopup} title="共享至">
+      <View className="text-gray-45 leading-20 mb-16 text-12">开启后,该组织下的所有成员都可引用,可随时取消共享。</View>
+      
+      {renderContent()}
+      
+    </Popup>
+  );
+}

+ 5 - 0
src/pages/dashboard-dislike-messages/index.config.ts

@@ -0,0 +1,5 @@
+export default definePageConfig({
+  navigationBarTitleText: '待处理差评',
+  "usingComponents": {},
+  navigationStyle: 'custom'
+})

+ 11 - 0
src/pages/dashboard-dislike-messages/index.module.less

@@ -0,0 +1,11 @@
+page{
+  overflow: hidden;
+}
+.scrollContainer{
+  height: calc(100vh - 400px);
+  overflow: auto;
+}
+.scrollContainerHigher{
+  height: calc(100vh - 300px);
+  overflow: auto;
+}

+ 172 - 0
src/pages/dashboard-dislike-messages/index.tsx

@@ -0,0 +1,172 @@
+/**
+ * 知识库
+ */
+
+import { Text, View, Image, ScrollView } from "@tarojs/components";
+
+import PageCustom from "@/components/page-custom/index";
+import NavBarNormal from "@/components/NavBarNormal/index";
+import IconA from "@/components/icon/IconA";
+import IconQ from "@/components/icon/IconQ";
+import { useEffect, useState } from "react";
+import CardEditable from "@/components/card/card-editable/index";
+import Taro, { useRouter, useDidShow } from "@tarojs/taro";
+import {
+  getVisitorDislikeMessages
+} from "@/service/visitor";
+import { TVisitorDetail } from "@/types/visitor";
+
+import { useLoadMore } from "@/utils/loadMore";
+
+export default function Index() {
+  const router = useRouter()
+  const {agentId} = router.params as {agentId:string}
+  const [scrollTop, setScrollTop] = useState(0)
+  const [list, setList] = useState<TVisitorDetail[]>([]);
+
+  const fetcher = async ([_url, nextId, page, pageSize]) => {
+    const res = await getVisitorDislikeMessages({agentId, startId: nextId, pageSize });
+    return res.data;
+  };
+  const { data, loadMore } = useLoadMore<TVisitorDetail[]>({
+    url: `getVisitorDislikeMessages${agentId}`,
+    fetcher,
+  });
+    
+
+  const handleEdit = (qaId: number | string) => {
+
+    // Taro.navigateTo({
+    //   url: `/pages/knowledge-item-editor/index?knowledgeId=${knowledgeId}&qaId=${qaId}&knowledgeTitle=${detail?.title}`,
+    // });
+  };
+  
+  
+  
+  // 删除问答项
+  const handleDeleteItem = async (qaId: number | string) => {
+    // if (!detail) {
+    //   return;
+    // }
+    // showModal({
+    //   content: "确定删除该问答项吗?",
+    //   onConfirm: async () => {
+    //     const { status } = await deleteKnowledgeQa(detail.knowledgeId, qaId);
+    //     if (isSuccess(status)) {
+    //       Taro.showToast({ title: "删除成功", icon: "success" });
+    //       getDetail(detail.knowledgeId);
+    //     }
+    //   },
+    // });
+  };
+
+  const onScrollEnd = async () => {
+    console.log("toEnd");
+    loadMore();
+  };
+
+  
+
+  
+
+  useEffect(() => {
+    if (data?.data) {
+      setList([...list, ...data.data]);
+      setScrollTop(prev => prev + 1)
+    }
+  }, [data]);
+
+  useEffect(() => {
+    loadMore();
+  }, []);
+
+  const createCardOptions = (item: TQAListItem) => {
+    return [
+      <View onClick={() => handleDeleteItem(item.qaId)}>
+        删除
+      </View>,
+      <View onClick={() => handleEdit(item.qaId)}>查看对话</View>,
+      <View onClick={() => handleEdit(item.qaId)}>编辑</View>,
+    ]
+  }
+
+  return (
+    <PageCustom fullPage>
+      <NavBarNormal backText="待处理差评" scrollFadeIn></NavBarNormal>
+      <View className="w-full flex flex-col overflow-hidden h-full pt-2">
+        <View className="rounded-container-header"></View>
+        <View className="text-14 font-medium leading-22 px-16 pb-16">
+          问答列表共
+          <Text className="text-primary"> 10 </Text>条
+        </View>
+        <ScrollView
+          scrollY
+          onScrollEnd={onScrollEnd}
+          scrollTop={scrollTop}
+          style={{
+            flex: 1,
+            height: "100%", // 高度自适应
+          }}
+        >
+          {list.map((item) => {
+              return (
+                <View className="flex flex-col gap-16 px-16 mb-16">
+                  <CardEditable
+                    buttons={createCardOptions(item)}
+                  >
+                    <View className="flex items-start mb-8 gap-8">
+                      <View className="flex-center h-28">
+                        <IconQ />
+                      </View>
+                      <View className="flex-1 font-medium text-14 leading-28">
+                        {item.content}
+                      </View>
+                    </View>
+
+                    <View className="flex items-start gap-8">
+                      <View className="flex-center  h-28">
+                        <IconA />
+                      </View>
+                      <View className="flex-1 text-12 leading-20 text-gray-45 truncate">
+                        <View className="truncate">{item.correctionAnswer}</View>
+
+                        {!!item.correctionLinks?.length && (
+                          <View className="pb-12">
+                            {item.correctionLinks.map((link) => {
+                              return (
+                                <View>
+                                  查看链接 <Text>{link}</Text>
+                                </View>
+                              );
+                            })}
+                          </View>
+                        )}
+                        {!!item.correctionPics?.length && (
+                          <View className="pb-12">
+                            {item.correctionPics.map((pic) => {
+                              return (
+                                <View>
+                                  <Image
+                                    src={pic}
+                                    mode="widthFix"
+                                    className="w-full"
+                                  ></Image>
+                                </View>
+                              );
+                            })}
+                          </View>
+                        )}
+                      </View>
+                    </View>
+                  </CardEditable>
+                </View>
+              );
+            })}
+        </ScrollView>
+
+        
+        
+      </View>
+    </PageCustom>
+  );
+}

+ 5 - 0
src/pages/dashboard-visitor-message-editor/index.config.ts

@@ -0,0 +1,5 @@
+export default definePageConfig({
+  navigationBarTitleText: '知识库编辑',
+  "usingComponents": {},
+  navigationStyle: 'custom'
+})

+ 250 - 0
src/pages/dashboard-visitor-message-editor/index.tsx

@@ -0,0 +1,250 @@
+/**
+ * 知识库项目编辑
+ */
+
+import { Text, View } from "@tarojs/components";
+
+import PageCustom from "@/components/page-custom/index";
+import NavBarNormal from "@/components/NavBarNormal/index";
+import BottomBar from "@/components/BottomBar";
+
+import IconQ from "@/components/icon/IconQ";
+import IconA from "@/components/icon/IconA";
+
+import IconPlusColor14 from "@/components/icon/IconPlusColor14";
+import IconALink from "@/components/icon/IconALink";
+import IconDeleteGray16 from "@/components/icon/IconDeleteGray16";
+import { useEffect, useRef, useState } from "react";
+import IconAImage from "@/components/icon/IconAImage";
+import WemetaTextarea from "@/components/wemeta-textarea";
+import WemetaInput from "@/components/wemeta-input";
+import UploaderGrid from '@/components/UploaderGrid'
+import type { TMediaType } from "@/types/index";
+
+import { uploadFile } from "@/utils/http";
+import { EUploadFileScene } from "@/consts/enum";
+
+import Taro, { useRouter } from "@tarojs/taro";
+import { getKnowledgeQa, updateKnowledgeQa } from "@/service/knowledge";
+import { isSuccess } from "@/utils";
+
+type TFormdata = {q:string, a: string, links: string[], mediaList: TMediaType[]}
+
+
+interface IProps {
+  initialData?: TFormdata
+}
+export default function Index() {
+
+  const router = useRouter()
+  const { knowledgeId, qaId, knowledgeTitle } = router.params
+  
+  const [formData, setFormData] = useState<TFormdata>(
+    {
+      q: '',
+      a: '',
+      links: [],
+      mediaList: [] //{fileType: 'image',url: 'https://cdn.wehome.cn/cmn/png/53/META-H8UKWHWU-2JNUAG2BARJF55VHU9QS3-YBQGHDAM-IW.png'}
+    }
+  );
+
+  const handleInput = (value: string) => {
+    setFormData({
+      ...formData,
+      q: value,
+    });
+  };
+  const handleValueAInput = (value: string) => {
+    setFormData({
+      ...formData,
+      a: value,
+    });
+  };
+
+  // 添加链接
+  const addLink = () => {
+    setFormData({
+      ...formData,
+      links: [...formData.links, ""],
+    });
+  };
+
+  // 删除链接
+  const removeLink = (e, index) => {
+    e.stopPropagation();
+    const newLinks = [...formData.links];
+    newLinks.splice(index, 1);
+    setFormData({
+      ...formData,
+      links: newLinks,
+    });
+  };
+
+  // 更新单个链接
+  const handleLinkChange = (index:number, value: string) => {
+    const newLinks = [...formData.links];
+    newLinks[index] = value;
+    setFormData({
+      ...formData,
+      links: newLinks,
+    });
+  };
+
+  const handleDeleteMedia = (index:number) => {
+    setFormData(prev => ({
+      ...prev,
+      mediaList: prev.mediaList.filter((_, i) => i !== index)
+    }));
+  };
+
+  const handleSubmit = async () => {
+    if(!knowledgeId || !qaId){
+      return
+    }
+
+    const dataToSubmit = {
+      answer: formData.a,
+      questions: [formData.q],
+      qaId: parseInt(qaId),
+      links: formData.links.filter(link => link.trim() !== ''),
+      pics: formData.mediaList.map( item => item.url)
+    }
+    console.log(dataToSubmit)
+    const {status} = await updateKnowledgeQa(knowledgeId, qaId, dataToSubmit)
+    if(isSuccess(status)){
+      Taro.showToast({title: '保存成功', icon: 'success'})
+    }
+  }
+  const getQaDetail = async (knowledgeId: string, qaId: string)=> {
+    const {status, data} = await getKnowledgeQa(knowledgeId, qaId)
+    if(isSuccess(status)){
+      setFormData({
+        q: data.questions[0],
+        a: data.answer,
+        links: data.links,
+        mediaList: data.pics.map( pic => {
+          return {fileType: 'image', url: pic}
+        }),
+      })
+    }
+  }
+
+
+  const handleChooseMedia = ()=> {
+    Taro.chooseMedia({
+      count: 10,
+      mediaType: ["image"],
+      sourceType: ["album", "camera"],
+      async success(r) {
+        // const tempFiles = r.tempFiles;
+        for (const tempFile of r.tempFiles){
+          const result = await uploadFile(tempFile.tempFilePath, EUploadFileScene.OTHER)
+          if(result?.publicUrl){
+            setFormData(prev => {
+              return {...prev, mediaList: [{fileType:'image', url: result.publicUrl}, ...prev.mediaList]}
+            })
+          }
+        }
+      }
+    })
+  }
+
+  useEffect(()=> {
+    if(knowledgeId && qaId){
+        getQaDetail(knowledgeId, qaId)
+    }
+    
+  }, [knowledgeId, qaId])
+
+  
+
+  return (
+    <PageCustom>
+      <NavBarNormal scrollFadeIn backText={knowledgeTitle ?? '...'}></NavBarNormal>
+      <View className="w-full pb-120">
+        <View className="p-16">
+          <View className="flex flex-col gap-16">
+            <View className="flex flex-col">
+              <View className="flex items-start gap-8 mb-6">
+                <View className="flex-center h-28">
+                  <IconQ />
+                </View>
+                <View className="flex-1 text-14 leading-28">问题描述</View>
+              </View>
+              <View className="">
+                <WemetaTextarea
+                  value={formData.q}
+                  onInput={handleInput}
+                  placeholder="描述你想要生成的画面和动作。例如:职场精英在点头微笑"
+                />
+              </View>
+            </View>
+
+            {/* 回答 */}
+            <View className="flex flex-col">
+              <View className="flex items-start gap-8 mb-6">
+                <View className="flex-center h-28">
+                  <IconA />
+                </View>
+                <View className="flex-1 text-14 leading-28">回答</View>
+              </View>
+              <View>
+                <WemetaTextarea
+                  value={formData.a}
+                  onInput={handleValueAInput}
+                  placeholder="描述你想要生成的画面和动作。例如:职场精英在点头微笑"
+                />
+              </View>
+            </View>
+
+            {/* 链接 */}
+            <View className="flex flex-col">
+              <View className="flex items-start gap-8 mb-6">
+                <View className="flex-center h-28">
+                  <IconALink />
+                </View>
+                <View className="flex-1 text-14 leading-28">链接</View>
+                <View className="flex-center px-8 gap-4" onClick={addLink}>
+                  <IconPlusColor14 />
+                  <View className="text-primary">新增</View>
+                </View>
+              </View>
+              <View className="flex flex-col gap-8">
+                {formData.links.map((link, index) => {
+                  return (
+                    <WemetaInput
+                      value={link}
+                      onInput={(value) => handleLinkChange(index, value)}
+                      suffix={() => (
+                        <View onClick={(e) => removeLink(e, index)}>
+                          <IconDeleteGray16 />
+                        </View>
+                      )}
+                      placeholder="请输入查看链接,支持长按粘贴..."
+                    />
+                  );
+                })}
+              </View>
+            </View>
+
+            {/* 图片 */}
+            <View className="flex flex-col">
+              <View className="flex items-start gap-8 mb-6">
+                <View className="flex-center h-28">
+                  <IconAImage />
+                </View>
+                <View className="flex-1 text-14 leading-28">图片</View>
+              </View>
+              <UploaderGrid onNewUpload={handleChooseMedia}  list = {formData.mediaList} onChange={()=> {}} onDelete={handleDeleteMedia} />
+            </View>
+            
+          </View>
+        </View>
+
+        <BottomBar>
+          <View className="button-rounded button-primary flex-1" onClick={handleSubmit}>保存</View>
+        </BottomBar>
+      </View>
+    </PageCustom>
+  );
+}

+ 7 - 1
src/pages/dashboard/index.tsx

@@ -81,7 +81,13 @@ export default () => {
           <DataCard text="累计访问次数" unitText="次" value={summary?.sumPv ?? 0} />
           <DataCard text="累计对话人数" unitText="人" value={summary?.sumPv ?? 0} />
           <DataCard text="好评" unitText="条" value={summary?.unreadLikeCnt ?? 0} arrow />
-          <DataCard text="待处理差评" unitText="条" value={summary?.unprocessedDislikeCnt ?? 0} arrow />
+          <DataCard onClick={()=> {
+            const query = currentAgent?.agentId ? `agentId=${currentAgent.agentId}` : ''
+            Taro.navigateTo({
+              url: `/pages/dashboard-dislike-messages/index?${query}`
+            })
+            
+          }} text="待处理差评" unitText="条" value={summary?.unprocessedDislikeCnt ?? 0} arrow />
         </View>
 
         <VisitorList agentId={currentAgent?.agentId}></VisitorList>

+ 7 - 29
src/service/visitor.ts

@@ -3,26 +3,17 @@ import {
 } from '@/xiaolanbenlib/api/index'
 import request from '@/xiaolanbenlib/module/axios.js'
 
-import { TVisitorAgent, TVisitorMessage } from '@/types/visitor'
+import { TVisitorAgent, TVisitorMessage, TVisitorDetail } from '@/types/visitor'
 
 
 
 // 编辑差评记录的答案消息--编辑后则自动标识为已处理
-export const editVisitorDislikeAnswer = (data: {
-  "agentId": string,
-  "answer": string,
-  "visitorId": string,
-  "links": string[],
-  "msgId": string,
-  "pics": string[],
-  "question": string
-}) => {
-  return request.put(`${bluebookAiAgent}api/v1/my/visitor/dislike/message/answer`, data)
+export const editVisitorDislikeAnswer = (data: TVisitorDetail) => {
+  return request.put(`${bluebookAiAgent}api/v1/my/visitor/message/answer`, data)
 }
 
 // 删除指定的差评待处理记录
 export const deleteVisitorDislikeAnswer = (data: {
-  "agentId": string,
   "visitorId": string,
   "msgId": string,
 }) => {
@@ -32,22 +23,7 @@ export const deleteVisitorDislikeAnswer = (data: {
 // 获取未处理的差评记录列表
 export type TVisitorDislikeMessagesResponse = {
   "data": [
-    [
-      {
-        "agentId": string,
-        "visitorId": string,
-        "content": string,
-        "dislikeReason": string,
-        "isDislike": false,
-        "isLike": false,
-        "isStreaming": false,
-        "msgId": number,
-        "msgTime": "2025-05-15T09:20:38.928Z",
-        "msgUk": string,
-        "originalAgentId": string,
-        "role": string
-      }
-    ]
+    TVisitorDetail[]
   ],
   "nextId": string,
   "totalCount": number
@@ -58,10 +34,12 @@ export const getVisitorDislikeMessages = (data: {
   "startId": string,
   "pageSize": number,
 }) => {
-  return request.get<TVisitorDislikeMessagesResponse>(`${bluebookAiAgent}api/v1/my/visitor/dislike/message`, {params: data})
+  return request.get<TVisitorDislikeMessagesResponse>(`${bluebookAiAgent}api/v1/my/visitor/dislike/messages`, {params: data})
 }
 
 
+
+
 // 访客访问详情--访客信息
 export type TVisotorInfoResponse = {
   "agentId": string,

+ 20 - 1
src/types/visitor.ts

@@ -2,7 +2,26 @@
 // agentId (string, optional): 
 
 
-
+// 访客的会话详情 
+export type TVisitorDetail = {
+  agentId: string ,
+  content: string  // 对方消息内容 ,
+  contentType: string // 消息类型:text/plain; application/json; aiseek/audio_chunk; aiseek/thinking; aiseek/function_call; aiseek/multimodal; aiseek/qa; ,
+  correctionAnswer: string // 纠错回答内容 ,
+  correctionId :number //  纠错记录ID ,
+  correctionLinks: string[] // 纠错链接 ,
+  correctionPics: string[] // 纠错图片 ,
+  correctionTime :string  // 纠错时间 ,
+  dislikeReason :string  // 差评原因 ,
+  isDislike: boolean // 差评状态 ,
+  isLike: boolean // 点赞状态 ,
+  isStreaming: boolean // 该消息是否还正处理流式返回中 ,
+  msgId :number //  记录ID ,
+  msgTime :string  // 消息时间 ,
+  msgUk :string  // 消息唯一值,由前端生成的UUID ,
+  role :string  // 角色:system/user/assistant/function/tool ,
+  visitorId: number//  会话用户对应的访客记录ID
+}
 
 
 export type TVisitorMessage = {