Commit 5fcdf28c by Archer Committed by GitHub

user feedback and admin mark (#228)

* fix: csv empty data

* feat: user feedback and mark answer

* version intro

* perf: chat logs sort
parent 2556c19a
### 常见问题 ### 常见问题
- [**Git 地址**,点击查看项目地址](https://github.com/labring/FastGPT) - [**Git 地址**点击查看项目地址](https://github.com/labring/FastGPT)
- [本地部署 FastGPT](https://doc.fastgpt.run/docs/installation) - [本地部署 FastGPT](https://doc.fastgpt.run/docs/installation)
- [API 文档](https://kjqvjse66l.feishu.cn/docx/DmLedTWtUoNGX8xui9ocdUEjnNh?pre_pathname=%2Fdrive%2Fhome%2F) - [API 文档](https://kjqvjse66l.feishu.cn/docx/DmLedTWtUoNGX8xui9ocdUEjnNh?pre_pathname=%2Fdrive%2Fhome%2F)
- **反馈问卷**: 如果你遇到任何使用问题或有期望的功能,可以[填写该问卷](https://www.wjx.cn/vm/rLIw1uD.aspx#) - **反馈问卷**: 如果你遇到任何使用问题或有期望的功能,可以[填写该问卷](https://www.wjx.cn/vm/rLIw1uD.aspx#)
- **问题文档**: [先看文档,再提问](https://kjqvjse66l.feishu.cn/docx/HtrgdT0pkonP4kxGx8qcu6XDnGh) - **问题文档**: [先看文档,再提问](https://kjqvjse66l.feishu.cn/docx/HtrgdT0pkonP4kxGx8qcu6XDnGh)
- [点击查看商业版文档](https://fael3z0zfze.feishu.cn/docx/F155dbirfo8vDDx2WgWc6extnwf)
**价格表** **价格表**
| 计费项 | 价格: 元/ 1K tokens(包含上下文)| | 计费项 | 价格: 元/ 1K tokens(包含上下文)|
......
### Fast GPT V4.2 ### Fast GPT V4.2.2
1. 新增 - 应用日志初版,可观测到所有对话记录 1. **新增 - 用户反馈和管理员标注预期答案,以不断提高模型回复准确率。** 该功能为测试版,未来交互可能会有变化,欢迎大家提出宝贵意见。
2. 新增 - 好友邀请链接,[点击查看](/account?currentTab=promotion) 2. 优化 - 知识库搜索提示词,更适配问答场景。
3. 新增 - Iframe 嵌入页面图标可拖拽 3. 新增 - 好友邀请链接,[点击查看](/account?currentTab=promotion)
4. 优化 - 知识库搜索提示词 4. 优化 - [使用文档](https://doc.fastgpt.run/docs/intro/)
5. 优化 - [使用文档](https://doc.fastgpt.run/docs/intro/) 5. [点击查看高级编排介绍文档](https://doc.fastgpt.run/docs/workflow)
6. [点击查看高级编排介绍文档](https://doc.fastgpt.run/docs/workflow)
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
"Cancel": "No", "Cancel": "No",
"Confirm": "Yes", "Confirm": "Yes",
"Running": "Running", "Running": "Running",
"Select value is empty": "Select value is empty",
"UnKnow": "UnKnow", "UnKnow": "UnKnow",
"Warning": "Warning", "Warning": "Warning",
"app": { "app": {
...@@ -17,6 +18,7 @@ ...@@ -17,6 +18,7 @@
"Copy Module Config": "Copy config", "Copy Module Config": "Copy config",
"Export Config Successful": "The configuration has been copied. Please check for important data", "Export Config Successful": "The configuration has been copied. Please check for important data",
"Export Configs": "Export Configs", "Export Configs": "Export Configs",
"Feedback Count": "User Feedback",
"Import Config": "Import Config", "Import Config": "Import Config",
"Import Config Failed": "Failed to import the configuration, please ensure that the configuration is normal!", "Import Config Failed": "Failed to import the configuration, please ensure that the configuration is normal!",
"Import Configs": "Import Configs", "Import Configs": "Import Configs",
...@@ -26,16 +28,33 @@ ...@@ -26,16 +28,33 @@
"Logs Source": "Source", "Logs Source": "Source",
"Logs Time": "Time", "Logs Time": "Time",
"Logs Title": "Title", "Logs Title": "Title",
"Mark Count": "Mark Count",
"My Apps": "My Apps", "My Apps": "My Apps",
"Output Field Settings": "Output Field Settings", "Output Field Settings": "Output Field Settings",
"Paste Config": "Paste Config" "Paste Config": "Paste Config"
}, },
"chat": { "chat": {
"Admin Mark Content": "Corrected response",
"Complete Response": "Complete Response", "Complete Response": "Complete Response",
"Confirm to clear history": "Confirm to clear history?", "Confirm to clear history": "Confirm to clear history?",
"Exit Chat": "Exit", "Exit Chat": "Exit",
"Feedback Failed": "Feedback Failed",
"Feedback Mark": "Mark",
"Feedback Modal": "Feedback",
"Feedback Modal Tip": "Enter what you find unsatisfactory",
"Feedback Close": "Close Feedback",
"Feedback Submit": "Submit",
"Feedback Success": "Feedback Success",
"Feedback Update Failed": "Feedback Update Failed",
"History": "History", "History": "History",
"Mark": "Mark",
"Mark Description": "The annotation feature is currently in beta. \n\n After clicking Add annotation, you need to select a knowledge base in order to store annotation data. You can use this feature to quickly annotate questions and expected answers to guide the model to the next answer. At present, the annotation function, like other data in the knowledge base, is affected by the model, which does not mean that the annotation meets 100% expectations. The \n\n annotation data is only unidirectional synchronization with the knowledge base. If the knowledge base modifies the annotation data, the annotation data displayed in the log cannot be synchronized",
"Mark Description Title": "Mark Description",
"New Chat": "New Chat", "New Chat": "New Chat",
"Read Mark Description": "Read mark description",
"Read User Feedback": "Read user feedback",
"Select Mark Kb": "Select Dataset",
"Select Mark Kb Desc": "Select a dataset to store the expected answers",
"You need to a chat app": "You need to a chat app", "You need to a chat app": "You need to a chat app",
"logs": { "logs": {
"api": "API", "api": "API",
......
...@@ -3,8 +3,9 @@ ...@@ -3,8 +3,9 @@
"Cancel": "取消", "Cancel": "取消",
"Confirm": "确认", "Confirm": "确认",
"Running": "运行中", "Running": "运行中",
"Warning": "提示", "Select value is empty": "选择的内容为空",
"UnKnow": "未知", "UnKnow": "未知",
"Warning": "提示",
"app": { "app": {
"Advance App TestTip": "当前应用为高级编排模式\n如需切换为【简易模式】请点击左侧保存按键", "Advance App TestTip": "当前应用为高级编排模式\n如需切换为【简易模式】请点击左侧保存按键",
"App Detail": "应用详情", "App Detail": "应用详情",
...@@ -17,6 +18,7 @@ ...@@ -17,6 +18,7 @@
"Copy Module Config": "复制配置", "Copy Module Config": "复制配置",
"Export Config Successful": "已复制配置,请注意检查是否有重要数据", "Export Config Successful": "已复制配置,请注意检查是否有重要数据",
"Export Configs": "导出配置", "Export Configs": "导出配置",
"Feedback Count": "用户反馈",
"Import Config": "导入配置", "Import Config": "导入配置",
"Import Config Failed": "导入配置失败,请确保配置正常!", "Import Config Failed": "导入配置失败,请确保配置正常!",
"Import Configs": "导入配置", "Import Configs": "导入配置",
...@@ -26,16 +28,33 @@ ...@@ -26,16 +28,33 @@
"Logs Source": "来源", "Logs Source": "来源",
"Logs Time": "时间", "Logs Time": "时间",
"Logs Title": "标题", "Logs Title": "标题",
"Mark Count": "标注答案数量",
"My Apps": "我的应用", "My Apps": "我的应用",
"Output Field Settings": "输出字段编辑", "Output Field Settings": "输出字段编辑",
"Paste Config": "粘贴配置" "Paste Config": "粘贴配置"
}, },
"chat": { "chat": {
"Admin Mark Content": "纠正后的回复",
"Complete Response": "完整响应", "Complete Response": "完整响应",
"Confirm to clear history": "确认清空该应用的聊天记录?", "Confirm to clear history": "确认清空该应用的聊天记录?",
"Exit Chat": "退出聊天", "Exit Chat": "退出聊天",
"Feedback Failed": "提交反馈异常",
"Feedback Mark": "标注",
"Feedback Modal": "结果反馈",
"Feedback Modal Tip": "输入你觉得回答不满意的地方",
"Feedback Close": "关闭反馈",
"Feedback Submit": "提交反馈",
"Feedback Success": "反馈成功!",
"Feedback Update Failed": "更新反馈状态失败",
"History": "记录", "History": "记录",
"Mark": "标注预期回答",
"Mark Description": "当前标注功能为测试版。\n\n点击添加标注后,需要选择一个知识库,以便存储标注数据。你可以通过该功能快速的标注问题和预期回答,以便引导模型下次的回答。\n\n目前,标注功能同知识库其他数据一样,受模型的影响,不代表标注后 100% 符合预期。\n\n标注数据仅单向与知识库同步,如果知识库修改了该标注数据,日志展示的标注数据无法同步",
"Mark Description Title": "标注功能介绍",
"New Chat": "新对话", "New Chat": "新对话",
"Read Mark Description": "查看标注功能介绍",
"Read User Feedback": "查看用户反馈",
"Select Mark Kb": "选择知识库",
"Select Mark Kb Desc": "选择一个知识库存储预期答案",
"You need to a chat app": "你需要创建一个应用", "You need to a chat app": "你需要创建一个应用",
"logs": { "logs": {
"api": "API 调用", "api": "API 调用",
......
...@@ -5,6 +5,7 @@ import { RequestPaging } from '../types/index'; ...@@ -5,6 +5,7 @@ import { RequestPaging } from '../types/index';
import type { OutLinkSchema } from '@/types/mongoSchema'; import type { OutLinkSchema } from '@/types/mongoSchema';
import type { ShareChatEditType } from '@/types/app'; import type { ShareChatEditType } from '@/types/app';
import type { Props as UpdateHistoryProps } from '@/pages/api/chat/history/updateChatHistory'; import type { Props as UpdateHistoryProps } from '@/pages/api/chat/history/updateChatHistory';
import { AdminUpdateFeedbackParams } from './request/chat';
/** /**
* 获取初始化聊天内容 * 获取初始化聊天内容
...@@ -64,3 +65,9 @@ export const getShareChatList = (appId: string) => ...@@ -64,3 +65,9 @@ export const getShareChatList = (appId: string) =>
* delete a shareChat * delete a shareChat
*/ */
export const delShareChatById = (id: string) => DELETE(`/chat/shareChat/delete?id=${id}`); export const delShareChatById = (id: string) => DELETE(`/chat/shareChat/delete?id=${id}`);
export const userUpdateChatFeedback = (data: { chatItemId: string; userFeedback?: string }) =>
POST('/chat/feedback/userUpdate', data);
export const adminUpdateChatFeedback = (data: AdminUpdateFeedbackParams) =>
POST('/chat/feedback/adminUpdate', data);
...@@ -67,6 +67,14 @@ export const postKbDataFromList = (data: PushDataProps) => ...@@ -67,6 +67,14 @@ export const postKbDataFromList = (data: PushDataProps) =>
POST<PushDateResponse>(`/openapi/kb/pushData`, data); POST<PushDateResponse>(`/openapi/kb/pushData`, data);
/** /**
* insert one data to dataset
*/
export const insertData2Kb = (data: {
kbId: string;
data: { a: string; q: string; source?: string };
}) => POST<string>(`/plugins/kb/data/insertData`, data);
/**
* 更新一条数据 * 更新一条数据
*/ */
export const putKbDataById = (data: UpdateDataProps) => PUT('/openapi/kb/updateData', data); export const putKbDataById = (data: UpdateDataProps) => PUT('/openapi/kb/updateData', data);
......
export type AdminUpdateFeedbackParams = {
chatItemId: string;
kbId: string;
dataId: string;
content: string;
};
import React, { useRef } from 'react';
import { ModalBody, Textarea, ModalFooter, Button } from '@chakra-ui/react';
import MyModal from '../MyModal';
import { useRequest } from '@/hooks/useRequest';
import { useTranslation } from 'next-i18next';
import { userUpdateChatFeedback } from '@/api/chat';
const FeedbackModal = ({
chatItemId,
onSuccess,
onClose
}: {
chatItemId: string;
onSuccess: (e: string) => void;
onClose: () => void;
}) => {
const ref = useRef<HTMLTextAreaElement>(null);
const { t } = useTranslation();
const { mutate, isLoading } = useRequest({
mutationFn: async () => {
const val = ref.current?.value || 'N/A';
return userUpdateChatFeedback({
chatItemId,
userFeedback: val
});
},
onSuccess() {
onSuccess(ref.current?.value || 'N/A');
},
successToast: t('chat.Feedback Success'),
errorToast: t('chat.Feedback Failed')
});
return (
<MyModal isOpen={true} onClose={onClose} title={t('chat.Feedback Modal')}>
<ModalBody>
<Textarea
ref={ref}
rows={10}
placeholder={t('chat.Feedback Modal Tip') || 'chat.Feedback Modal Tip'}
/>
</ModalBody>
<ModalFooter>
<Button variant={'base'} mr={2} onClick={onClose}>
{t('Cancel')}
</Button>
<Button isLoading={isLoading} onClick={mutate}>
{t('chat.Feedback Submit')}
</Button>
</ModalFooter>
</MyModal>
);
};
export default FeedbackModal;
import React from 'react';
import { ModalBody, ModalFooter, Button } from '@chakra-ui/react';
import MyModal from '../MyModal';
import { useRequest } from '@/hooks/useRequest';
import { useTranslation } from 'next-i18next';
import { userUpdateChatFeedback } from '@/api/chat';
const ReadFeedbackModal = ({
chatItemId,
content,
isMarked,
onMark,
onSuccess,
onClose
}: {
chatItemId: string;
content: string;
isMarked: boolean;
onMark: () => void;
onSuccess: () => void;
onClose: () => void;
}) => {
const { t } = useTranslation();
const { mutate, isLoading } = useRequest({
mutationFn: async () => {
return userUpdateChatFeedback({
chatItemId,
userFeedback: undefined
});
},
onSuccess() {
onSuccess();
},
errorToast: t('chat.Feedback Update Failed')
});
return (
<MyModal isOpen={true} onClose={onClose} title={t('chat.Feedback Modal')}>
<ModalBody>{content}</ModalBody>
<ModalFooter>
{!isMarked && (
<Button variant={'base'} mr={2} onClick={onMark}>
{t('chat.Feedback Mark')}
</Button>
)}
<Button isLoading={isLoading} onClick={mutate}>
{t('chat.Feedback Close')}
</Button>
</ModalFooter>
</MyModal>
);
};
export default React.memo(ReadFeedbackModal);
import React, { useRef, useState } from 'react';
import {
ModalBody,
useTheme,
ModalFooter,
Button,
ModalHeader,
Box,
Card,
Flex
} from '@chakra-ui/react';
import MyModal from '../MyModal';
import { useTranslation } from 'next-i18next';
import { useQuery } from '@tanstack/react-query';
import { useUserStore } from '@/store/user';
import { useToast } from '@/hooks/useToast';
import Avatar from '../Avatar';
import MyIcon from '@/components/Icon';
import { useGlobalStore } from '@/store/global';
const SelectDataset = ({
onSuccess,
onClose
}: {
onSuccess: (kbId: string) => void;
onClose: () => void;
}) => {
const { t } = useTranslation();
const theme = useTheme();
const { isPc } = useGlobalStore();
const { toast } = useToast();
const { myKbList, loadKbList } = useUserStore();
const [selectedId, setSelectedId] = useState<string>();
useQuery(['loadKbList'], loadKbList);
return (
<MyModal isOpen={true} onClose={onClose} w={'100%'} maxW={['90vw', '900px']} isCentered={!isPc}>
<Flex flexDirection={'column'} h={['90vh', 'auto']}>
<ModalHeader>
<Box>{t('chat.Select Mark Kb')}</Box>
<Box fontSize={'sm'} color={'myGray.500'} fontWeight={'normal'}>
{t('chat.Select Mark Kb Desc')}
</Box>
</ModalHeader>
<ModalBody
flex={['1 0 0', '0 0 auto']}
maxH={'80vh'}
overflowY={'auto'}
display={'grid'}
gridTemplateColumns={['repeat(1,1fr)', 'repeat(2,1fr)', 'repeat(3,1fr)']}
gridGap={3}
userSelect={'none'}
>
{myKbList.map((item) =>
(() => {
const selected = selectedId === item._id;
return (
<Card
key={item._id}
p={3}
border={theme.borders.base}
boxShadow={'sm'}
h={'80px'}
cursor={'pointer'}
_hover={{
boxShadow: 'md'
}}
{...(selected
? {
bg: 'myBlue.300'
}
: {})}
onClick={() => {
setSelectedId(item._id);
}}
>
<Flex alignItems={'center'} h={'38px'}>
<Avatar src={item.avatar} w={['24px', '28px', '32px']}></Avatar>
<Box ml={3} fontWeight={'bold'} fontSize={['md', 'lg', 'xl']}>
{item.name}
</Box>
</Flex>
<Flex justifyContent={'flex-end'} alignItems={'center'} fontSize={'sm'}>
<MyIcon mr={1} name="kbTest" w={'12px'} />
<Box color={'myGray.500'}>{item.vectorModel.name}</Box>
</Flex>
</Card>
);
})()
)}
</ModalBody>
<ModalFooter>
<Button variant={'base'} mr={2} onClick={onClose}>
{t('Cancel')}
</Button>
<Button
onClick={() => {
if (!selectedId) {
return toast({
status: 'warning',
title: t('Select value is empty')
});
}
onSuccess(selectedId);
}}
>
{t('Confirm')}
</Button>
</ModalFooter>
</Flex>
</MyModal>
);
};
export default SelectDataset;
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1693117978437"
class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5051"
xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64">
<path
d="M967.974201 449.978501c11.329434 12.669367 20.158992 29.00055 26.498675 48.987551 6.329684 19.986001 9.499525 40.654967 9.499525 62.013899 0 13.329334-1.499925 25.828709-4.509775 37.498125s-7.179641 22.163892-12.509375 31.483426c-7.339633 11.329434-16.674166 20.158992-27.9986 26.493675-11.329434 6.339683-23.658817 9.504525-36.988151 9.504525L787.959202 665.959702c72.656367 132.658367 90.660467 231.652417 54.012299 296.969152-3.329834 5.989701-7.329634 11.829409-11.9994 17.499125-4.669767 5.674716-9.339533 10.509475-14.014299 14.499275-4.674766 3.984801-9.834508 7.654617-15.484226 11.00445-5.654717 3.349833-11.159442 6.184691-16.514174 8.509575-5.354732 2.324884-10.684466 4.159792-15.9992 5.504725-5.309735 1.344933-10.474476 2.349883-15.484226 3.00985-5.014749 0.659967-9.849508 0.989951-14.499275 0.989951-6.674666 0-12.339383-2.164892-16.989151-6.494675-4.649768-4.329784-7.644618-9.494525-8.989551-15.484226-2.669867-22.678866-10.164492-46.347683-22.493875-71.00645-12.329384-24.658767-28.498575-49.817509-48.507575-75.481226s-43.347833-50.492475-70.011499-74.491275c-79.993-71.9914-170.321484-124.99375-270.985451-158.99205-5.9917 12.669367-15.326234 23.163842-27.9956 31.483426-12.671366 8.319584-26.333683 12.489376-40.989951 12.514374L77.011749 665.9937c-21.330933 0-39.495025-7.499625-54.492275-22.498875C7.522224 628.501575 0.025599 610.337483 0.025599 589.00955L0.025599 128.053597c0-21.330933 7.496625-39.495025 22.493875-54.492275C37.515724 58.567072 55.679816 51.067447 77.011749 51.067447l154.0023 0c15.9992 0 30.333483 4.501775 43.00485 13.504325 12.671366 8.99955 22.0039 20.828959 27.9986 35.483226 31.335433-4.0098 59.00205-9.171541 83.00085-15.486226 23.9988-6.311684 41.157942-11.816409 51.482426-16.509175s23.828809-11.359432 40.509975-19.999c20.009-11.326434 36.508175-19.496025 49.497525-24.510774 12.989351-5.011749 35.328234-10.174491 67.00665-15.484226 31.678416-5.311734 69.506525-7.979601 113.494325-7.9996 18.664067 0 36.328184 2.334883 52.987351 7.00665 16.659167 4.669767 31.653417 11.336433 44.987751 19.996 25.9987 17.344133 41.9979 40.337983 47.9976 68.986551 20.668967 8.661567 37.9981 27.325634 51.9924 55.9972 14.674266 29.9905 22.013899 59.994 22.013899 90.0075l0 7.9996c0 2.0049-0.329984 4.0098-0.989951 6.014699 4.669767 3.327834 8.99955 7.157642 12.989351 11.486426s7.819609 9.161542 11.489426 14.494275c3.669817 5.334733 6.834658 11.339433 9.499525 18.016099 15.339233 29.330533 23.00885 60.323984 23.00885 92.982351 0 10.666467-0.99995 20.498975-3.00985 29.503525-2.0049 8.99955-4.99975 17.171141-8.989551 24.508775-1.344933 1.344933-2.014899 2.346883-2.014899 3.00685l-0.99495 0L967.976201 449.978501zM230.053097 102.0069 77.073746 102.0069c-7.336633 0-13.501325 2.496875-18.494075 7.486626-4.989751 4.99175-7.486626 11.156442-7.486626 18.494075l0 460.950952c0 6.679666 2.496875 12.514374 7.486626 17.509125 4.99175 4.989751 11.156442 7.484626 18.494075 7.484626l152.979351 0c7.339633 0 13.504325-2.494875 18.496075-7.484626 4.989751-4.99475 7.486626-10.829459 7.486626-17.509125L256.035798 127.988601c0-2.664867-0.502975-5.331733-1.504925-7.9996-1.00195-2.664867-2.164892-4.99975-3.486826-7.00665-1.321934-2.0049-3.156842-3.839808-5.504725-5.501725-2.344883-1.664917-4.681766-2.99685-7.00665-3.9998s-5.161742-1.504925-8.509575-1.504925L230.053097 102.0069zM938.986651 497.974101c-7.339633-13.9943-13.669317-20.988951-19.00905-20.988951-3.324834 0-6.659667-0.672966-10.0095-2.017899-3.349833-1.341933-6.189691-3.176841-8.514574-5.501725-2.324884-2.324884-4.159792-5.161742-5.504725-8.511574-1.339933-3.349833-2.014899-6.686666-2.014899-10.014499 0-6.676666 2.494875-12.511374 7.489626-17.504125 4.989751-4.989751 11.154442-7.486626 18.494075-7.486626 2.664867 0 4.669767-1.331933 6.014699-3.9998 3.324834-6.674666 4.99475-16.339183 4.99475-28.988551 0-24.658767-5.334733-48.327584-15.9992-71.00145-9.349533-18.666067-17.679116-27.9986-24.99375-27.9986-7.314634 0-13.479326-2.494875-18.494075-7.486626-5.00975-4.99175-7.509625-10.826459-7.489626-17.501125 0-4.671766 1.00495-8.844558 3.00985-12.511374 2.0099-3.669817 4.99975-6.836658 8.989551-9.504525 0.664967-5.9917 0.159992-13.821309-1.499925-23.485826-1.664917-9.661517-4.659767-20.658967-8.989551-32.988351-4.329784-12.329384-9.494525-22.823859-15.489226-31.485426-7.9996-11.326434-14.99925-16.989151-20.988951-16.989151-7.339633 0-13.504325-2.496875-18.499075-7.486626-4.989751-4.99175-7.484626-10.826459-7.484626-17.504125 0-21.330933-9.504525-37.660117-28.508575-48.987551s-41.502925-16.99115-67.481626-16.99115c-30.678466 0-57.167142 1.00195-79.486026 3.00685-22.308885 2.0069-41.477926 5.344733-57.497125 10.016499s-28.188591 8.841558-36.508175 12.511374c-8.319584 3.666817-19.156042 9.171541-32.510374 16.509175-13.331333 6.676666-24.168792 12.0094-32.508375 15.9992-8.339583 3.989801-20.510974 8.989551-36.508175 15.00625-15.9992 6.014699-34.163292 11.176441-54.492275 15.486226-20.328984 4.309785-43.824809 8.136593-70.491475 11.486426l0 418.970051c111.329434 36.668167 210.661467 94.330283 297.9921 172.981351 83.320834 75.321234 134.313284 150.652467 152.977351 225.9977 18.0091-5.334733 31.338433-15.669217 39.998-31.00845 4.669767-9.339533 7.839608-20.508975 9.504525-33.498325 1.664917-12.99435 1.164942-30.658467-1.504925-52.987351-2.664867-22.338883-11.169442-50.507475-25.498725-84.505775-14.339283-34.0033-34.168292-72.0064-59.487026-114.0093-4.669767-8.659567-4.669767-17.334133 0-26.013699 2.0099-4.0098 5.00475-7.179641 8.989551-9.499525 3.989801-2.329884 8.319584-3.489826 12.99435-3.489826l178.99605 0c9.339533 0 16.679166-3.9998 22.0089-11.9994l2.019899-4.989751c1.339933-2.0099 2.344883-4.0098 3.00985-6.019699 0.659967-2.0049 1.159942-4.169792 1.499925-6.494675 0.339983-2.324884 0.839958-4.819759 1.504925-7.484626l0-7.9996c0.659967-2.669867 0.99495-5.334733 0.99495-7.9996 0-23.338833-4.674766-44.337783-14.019299-63.00385L938.986651 497.974101z"
p-id="5052"></path>
</svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1693123058703" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="16328" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M875.65 912h-380a36 36 0 0 1 0-72h380a36 36 0 1 1 0 72zM812.26 284.82L285.39 811.69l-88.11 15.1L212 738l526.72-526.72 73.54 73.54m90.51-11.31L750 120.77a16 16 0 0 0-22.62 0L152.5 695.68a34.11 34.11 0 0 0-9.5 18.56l-25.95 156.23a32 32 0 0 0 37 36.78l155.38-26.62a34.2 34.2 0 0 0 18.38-9.52l575-575a16 16 0 0 0 0-22.63z" p-id="16329"></path></svg>
\ No newline at end of file
...@@ -77,7 +77,9 @@ const map = { ...@@ -77,7 +77,9 @@ const map = {
playFill: require('./icons/fill/play.svg').default, playFill: require('./icons/fill/play.svg').default,
courseLight: require('./icons/light/course.svg').default, courseLight: require('./icons/light/course.svg').default,
promotionLight: require('./icons/light/promotion.svg').default, promotionLight: require('./icons/light/promotion.svg').default,
logsLight: require('./icons/light/logs.svg').default logsLight: require('./icons/light/logs.svg').default,
badLight: require('./icons/light/bad.svg').default,
markLight: require('./icons/light/mark.svg').default
}; };
export type IconName = keyof typeof map; export type IconName = keyof typeof map;
......
...@@ -11,6 +11,6 @@ export const defaultKbDetail: KbItemType = { ...@@ -11,6 +11,6 @@ export const defaultKbDetail: KbItemType = {
name: 'Embedding-2', name: 'Embedding-2',
price: 0.2, price: 0.2,
defaultToken: 500, defaultToken: 500,
maxToken: 8000 maxToken: 3000
} }
}; };
import type { NextApiRequest, NextApiResponse } from 'next'; import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response'; import { jsonRes } from '@/service/response';
import { Chat, ChatItem, connectToDatabase } from '@/service/mongo'; import { Chat, connectToDatabase } from '@/service/mongo';
import { authUser } from '@/service/utils/auth'; import { authUser } from '@/service/utils/auth';
import type { PagingData } from '@/types'; import type { PagingData } from '@/types';
import { AppLogsListItemType } from '@/types/app'; import { AppLogsListItemType } from '@/types/app';
...@@ -30,25 +30,48 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) ...@@ -30,25 +30,48 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const [data, total] = await Promise.all([ const [data, total] = await Promise.all([
Chat.aggregate([ Chat.aggregate([
{ $match: where }, { $match: where },
{ $sort: { updateTime: -1 } },
{ $skip: (pageNum - 1) * pageSize },
{ $limit: pageSize },
{ {
$lookup: { $lookup: {
from: 'chatitems', from: 'chatitems',
localField: 'chatId', localField: 'chatId',
foreignField: 'chatId', foreignField: 'chatId',
as: 'messageCount' as: 'chatitems'
} }
}, },
{ {
$addFields: {
feedbackCount: {
$size: {
$filter: {
input: '$chatitems',
as: 'item',
cond: { $ifNull: ['$$item.userFeedback', false] }
}
}
},
markCount: {
$size: {
$filter: {
input: '$chatitems',
as: 'item',
cond: { $ifNull: ['$$item.adminFeedback', false] }
}
}
}
}
},
{ $sort: { feedbackCount: -1, updateTime: -1 } },
{ $skip: (pageNum - 1) * pageSize },
{ $limit: pageSize },
{
$project: { $project: {
id: '$chatId', id: '$chatId',
title: 1, title: 1,
source: 1, source: 1,
time: '$updateTime', time: '$updateTime',
messageCount: { $size: '$messageCount' }, messageCount: { $size: '$chatitems' },
callbackCount: { $literal: 0 } feedbackCount: 1,
markCount: 1
} }
} }
]), ]),
......
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, ChatItem } from '@/service/mongo';
import { AdminUpdateFeedbackParams } from '@/api/request/chat';
import { authUser } from '@/service/utils/auth';
/* 初始化我的聊天框,需要身份验证 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
const { chatItemId, kbId, dataId, content = undefined } = req.body as AdminUpdateFeedbackParams;
if (!chatItemId || !kbId || !dataId || !content) {
throw new Error('missing parameter');
}
const { userId } = await authUser({ req, authToken: true });
await ChatItem.findOneAndUpdate(
{
userId,
dataId: chatItemId
},
{
adminFeedback: {
kbId,
dataId,
content
}
}
);
jsonRes(res);
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, ChatItem } from '@/service/mongo';
/* 初始化我的聊天框,需要身份验证 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
const { chatItemId, userFeedback = undefined } = req.body as {
chatItemId: string;
userFeedback?: string;
};
if (!chatItemId) {
throw new Error('chatItemId is required');
}
await ChatItem.findOneAndUpdate(
{
dataId: chatItemId
},
{
...(userFeedback ? { userFeedback } : { $unset: { userFeedback: '' } })
}
);
jsonRes(res);
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}
...@@ -54,7 +54,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) ...@@ -54,7 +54,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
chatId, chatId,
userId userId
}, },
`dataId obj value ${TaskResponseKeyEnum.responseData}` `dataId obj value adminFeedback userFeedback ${TaskResponseKeyEnum.responseData}`
) )
.sort({ _id: -1 }) .sort({ _id: -1 })
.limit(30) .limit(30)
......
...@@ -25,7 +25,10 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex ...@@ -25,7 +25,10 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
await connectToDatabase(); await connectToDatabase();
// auth user and get kb // auth user and get kb
const [{ userId }, kb] = await Promise.all([authUser({ req }), KB.findById(kbId, 'model')]); const [{ userId }, kb] = await Promise.all([
authUser({ req }),
KB.findById(kbId, 'vectorModel')
]);
if (!kb) { if (!kb) {
throw new Error("Can't find database"); throw new Error("Can't find database");
......
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, KB } from '@/service/mongo';
import { authKb, authUser } from '@/service/utils/auth';
import { withNextCors } from '@/service/utils/tools';
import { PgTrainingTableName } from '@/constants/plugin';
import { insertKbItem, PgClient } from '@/service/pg';
import { modelToolMap } from '@/utils/plugin';
import { getVectorModel } from '@/service/utils/data';
import { getVector } from '@/pages/api/openapi/plugin/vector';
export type Props = {
kbId: string;
data: { a: string; q: string; source?: string };
};
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { kbId, data = { q: '', a: '' } } = req.body as Props;
if (!kbId || !data?.q) {
throw new Error('缺少参数');
}
// 凭证校验
const { userId } = await authUser({ req });
// auth kb
const kb = await authKb({ kbId, userId });
const q = data?.q?.replace(/\\n/g, '\n').trim().replace(/'/g, '"');
const a = data?.a?.replace(/\\n/g, '\n').trim().replace(/'/g, '"');
// token check
const token = modelToolMap.countTokens({
model: 'gpt-3.5-turbo',
messages: [{ obj: 'System', value: q }]
});
if (token > getVectorModel(kb.vectorModel).maxToken) {
throw new Error('Over Tokens');
}
const { rows: existsRows } = await PgClient.query(`
SELECT COUNT(*) > 0 AS exists
FROM ${PgTrainingTableName}
WHERE md5(q)=md5('${q}') AND md5(a)=md5('${a}') AND user_id='${userId}' AND kb_id='${kbId}'
`);
const exists = existsRows[0]?.exists || false;
if (exists) {
throw new Error('已经存在完全一致的数据');
}
const { vectors } = await getVector({
model: kb.vectorModel,
input: [q],
userId
});
const response = await insertKbItem({
userId,
kbId,
data: [
{
q,
a,
source: data.source,
vector: vectors[0]
}
]
});
// @ts-ignore
const id = response?.rows?.[0]?.id || '';
jsonRes(res, {
data: id
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
});
...@@ -9,7 +9,9 @@ import { ...@@ -9,7 +9,9 @@ import {
Th, Th,
Td, Td,
Tbody, Tbody,
useTheme useTheme,
useDisclosure,
ModalBody
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import MyIcon from '@/components/Icon'; import MyIcon from '@/components/Icon';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
...@@ -24,12 +26,19 @@ import ChatBox, { type ComponentRef } from '@/components/ChatBox'; ...@@ -24,12 +26,19 @@ import ChatBox, { type ComponentRef } from '@/components/ChatBox';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { getInitChatSiteInfo } from '@/api/chat'; import { getInitChatSiteInfo } from '@/api/chat';
import Tag from '@/components/Tag'; import Tag from '@/components/Tag';
import MyModal from '@/components/MyModal';
const Logs = ({ appId }: { appId: string }) => { const Logs = ({ appId }: { appId: string }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { isPc } = useGlobalStore(); const { isPc } = useGlobalStore();
const { const {
isOpen: isOpenMarkDesc,
onOpen: onOpenMarkDesc,
onClose: onCloseMarkDesc
} = useDisclosure();
const {
data: logs, data: logs,
isLoading, isLoading,
Pagination, Pagination,
...@@ -54,7 +63,16 @@ const Logs = ({ appId }: { appId: string }) => { ...@@ -54,7 +63,16 @@ const Logs = ({ appId }: { appId: string }) => {
{t('app.Chat logs')} {t('app.Chat logs')}
</Box> </Box>
<Box color={'myGray.500'} fontSize={'sm'}> <Box color={'myGray.500'} fontSize={'sm'}>
{t('app.Chat Logs Tips')} {t('app.Chat Logs Tips')},{' '}
<Box
as={'span'}
mr={2}
textDecoration={'underline'}
cursor={'pointer'}
onClick={onOpenMarkDesc}
>
{t('chat.Read Mark Description')}
</Box>
</Box> </Box>
</> </>
)} )}
...@@ -69,6 +87,8 @@ const Logs = ({ appId }: { appId: string }) => { ...@@ -69,6 +87,8 @@ const Logs = ({ appId }: { appId: string }) => {
<Th>{t('app.Logs Time')}</Th> <Th>{t('app.Logs Time')}</Th>
<Th>{t('app.Logs Title')}</Th> <Th>{t('app.Logs Title')}</Th>
<Th>{t('app.Logs Message Total')}</Th> <Th>{t('app.Logs Message Total')}</Th>
<Th>{t('app.Feedback Count')}</Th>
<Th>{t('app.Mark Count')}</Th>
</Tr> </Tr>
</Thead> </Thead>
<Tbody> <Tbody>
...@@ -86,6 +106,27 @@ const Logs = ({ appId }: { appId: string }) => { ...@@ -86,6 +106,27 @@ const Logs = ({ appId }: { appId: string }) => {
{item.title} {item.title}
</Td> </Td>
<Td>{item.messageCount}</Td> <Td>{item.messageCount}</Td>
<Td w={'100px'}>
{!!item?.feedbackCount ? (
<Box display={'inline-block'}>
<Flex
bg={'#FFF2EC'}
color={'#C96330'}
px={3}
py={1}
alignItems={'center'}
borderRadius={'lg'}
fontWeight={'bold'}
>
<MyIcon mr={1} name={'badLight'} color={'#C96330'} w={'14px'} />
{item.feedbackCount}
</Flex>
</Box>
) : (
<>-</>
)}
</Td>
<Td>{item.markCount}</Td>
</Tr> </Tr>
))} ))}
</Tbody> </Tbody>
...@@ -109,6 +150,13 @@ const Logs = ({ appId }: { appId: string }) => { ...@@ -109,6 +150,13 @@ const Logs = ({ appId }: { appId: string }) => {
onClose={() => setDetailLogsId(undefined)} onClose={() => setDetailLogsId(undefined)}
/> />
)} )}
<MyModal
isOpen={isOpenMarkDesc}
onClose={onCloseMarkDesc}
title={t('chat.Mark Description Title')}
>
<ModalBody whiteSpace={'pre-wrap'}>{t('chat.Mark Description')}</ModalBody>
</MyModal>
</Flex> </Flex>
); );
}; };
...@@ -222,6 +270,7 @@ function DetailLogsModal({ ...@@ -222,6 +270,7 @@ function DetailLogsModal({
<Box pt={2} flex={'1 0 0'}> <Box pt={2} flex={'1 0 0'}>
<ChatBox <ChatBox
ref={ChatBoxRef} ref={ChatBoxRef}
isLogs
chatId={chatId} chatId={chatId}
appAvatar={chat?.app.avatar} appAvatar={chat?.app.avatar}
userAvatar={HUMAN_ICON} userAvatar={HUMAN_ICON}
......
...@@ -38,7 +38,10 @@ const CsvImport = ({ kbId }: { kbId: string }) => { ...@@ -38,7 +38,10 @@ const CsvImport = ({ kbId }: { kbId: string }) => {
const { mutate: onclickUpload, isLoading: uploading } = useMutation({ const { mutate: onclickUpload, isLoading: uploading } = useMutation({
mutationFn: async () => { mutationFn: async () => {
const chunks = files.map((file) => file.chunks).flat(); const chunks = files
.map((file) => file.chunks)
.flat()
.filter((item) => item?.q);
const filterChunks = chunks.filter((item) => item.q.length < maxToken); const filterChunks = chunks.filter((item) => item.q.length < maxToken);
......
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Box, Textarea, Button } from '@chakra-ui/react'; import { Box, Textarea, Button, Flex } from '@chakra-ui/react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { useToast } from '@/hooks/useToast'; import { useToast } from '@/hooks/useToast';
import { useRequest } from '@/hooks/useRequest'; import { useRequest } from '@/hooks/useRequest';
...@@ -7,6 +7,8 @@ import { getErrText } from '@/utils/tools'; ...@@ -7,6 +7,8 @@ import { getErrText } from '@/utils/tools';
import { postKbDataFromList } from '@/api/plugins/kb'; import { postKbDataFromList } from '@/api/plugins/kb';
import { TrainingModeEnum } from '@/constants/plugin'; import { TrainingModeEnum } from '@/constants/plugin';
import { useUserStore } from '@/store/user'; import { useUserStore } from '@/store/user';
import MyTooltip from '@/components/MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
type ManualFormType = { q: string; a: string }; type ManualFormType = { q: string; a: string };
...@@ -70,7 +72,12 @@ const ManualImport = ({ kbId }: { kbId: string }) => { ...@@ -70,7 +72,12 @@ const ManualImport = ({ kbId }: { kbId: string }) => {
<Box p={[4, 8]} h={'100%'} overflow={'overlay'}> <Box p={[4, 8]} h={'100%'} overflow={'overlay'}>
<Box display={'flex'} flexDirection={['column', 'row']}> <Box display={'flex'} flexDirection={['column', 'row']}>
<Box flex={1} mr={[0, 4]} mb={[4, 0]} h={['50%', '100%']} position={'relative'}> <Box flex={1} mr={[0, 4]} mb={[4, 0]} h={['50%', '100%']} position={'relative'}>
<Box h={'30px'}>{'匹配的知识点'}</Box> <Flex>
<Box h={'30px'}>{'匹配的知识点'}</Box>
<MyTooltip label={'被向量化的部分,通常是问题,也可以是一段陈述描述'}>
<QuestionOutlineIcon ml={1} />
</MyTooltip>
</Flex>
<Textarea <Textarea
placeholder={`匹配的知识点。这部分内容会被搜索,请把控内容的质量。最多 ${maxToken} 字。`} placeholder={`匹配的知识点。这部分内容会被搜索,请把控内容的质量。最多 ${maxToken} 字。`}
maxLength={maxToken} maxLength={maxToken}
...@@ -87,7 +94,14 @@ const ManualImport = ({ kbId }: { kbId: string }) => { ...@@ -87,7 +94,14 @@ const ManualImport = ({ kbId }: { kbId: string }) => {
</Box> </Box>
</Box> </Box>
<Box flex={1} h={['50%', '100%']}> <Box flex={1} h={['50%', '100%']}>
<Box h={'30px'}>补充知识</Box> <Flex>
<Box h={'30px'}>{'补充知识'}</Box>
<MyTooltip
label={'匹配的知识点被命中后,这部分内容会随匹配知识点一起注入模型,引导模型回答'}
>
<QuestionOutlineIcon ml={1} />
</MyTooltip>
</Flex>
<Textarea <Textarea
placeholder={ placeholder={
'补充知识。这部分内容不会被搜索,但会作为"匹配的知识点"的内容补充,你可以讲一些细节的内容填写在这里。总和最多 3000 字。' '补充知识。这部分内容不会被搜索,但会作为"匹配的知识点"的内容补充,你可以讲一些细节的内容填写在这里。总和最多 3000 字。'
......
import React, { useState, useCallback } from 'react'; import React, { useState, useCallback } from 'react';
import { Box, Flex, Button, Textarea, IconButton } from '@chakra-ui/react'; import { Box, Flex, Button, Textarea, IconButton } from '@chakra-ui/react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { postKbDataFromList, putKbDataById, delOneKbDataByDataId } from '@/api/plugins/kb'; import { insertData2Kb, putKbDataById, delOneKbDataByDataId } from '@/api/plugins/kb';
import { useToast } from '@/hooks/useToast'; import { useToast } from '@/hooks/useToast';
import { TrainingModeEnum } from '@/constants/plugin';
import { getErrText } from '@/utils/tools'; import { getErrText } from '@/utils/tools';
import { vectorModelList } from '@/store/static';
import MyIcon from '@/components/Icon'; import MyIcon from '@/components/Icon';
import MyModal from '@/components/MyModal'; import MyModal from '@/components/MyModal';
import MyTooltip from '@/components/MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import { useUserStore } from '@/store/user';
import { useQuery } from '@tanstack/react-query';
export type FormData = { dataId?: string; a: string; q: string }; export type FormData = { dataId?: string; a: string; q: string; source?: string };
const InputDataModal = ({ const InputDataModal = ({
onClose, onClose,
...@@ -30,16 +32,20 @@ const InputDataModal = ({ ...@@ -30,16 +32,20 @@ const InputDataModal = ({
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const { toast } = useToast(); const { toast } = useToast();
const { kbDetail, getKbDetail } = useUserStore();
const { register, handleSubmit, reset } = useForm<FormData>({ const { register, handleSubmit, reset } = useForm<FormData>({
defaultValues defaultValues
}); });
const maxToken = kbDetail.vectorModel?.maxToken || 2000;
/** /**
* 确认导入新数据 * 确认导入新数据
*/ */
const sureImportData = useCallback( const sureImportData = useCallback(
async (e: FormData) => { async (e: FormData) => {
if (e.a.length + e.q.length >= 3000) { if (e.q.length >= maxToken) {
toast({ toast({
title: '总长度超长了', title: '总长度超长了',
status: 'warning' status: 'warning'
...@@ -50,31 +56,24 @@ const InputDataModal = ({ ...@@ -50,31 +56,24 @@ const InputDataModal = ({
try { try {
const data = { const data = {
dataId: '',
a: e.a, a: e.a,
q: e.q, q: e.q,
source: '手动录入' source: '手动录入'
}; };
const { insertLen } = await postKbDataFromList({ data.dataId = await insertData2Kb({
kbId, kbId,
mode: TrainingModeEnum.index, data
data: [data]
}); });
if (insertLen === 0) { toast({
toast({ title: '导入数据成功,需要一段时间训练',
title: '已存在完全一致的数据', status: 'success'
status: 'warning' });
}); reset({
} else { a: '',
toast({ q: ''
title: '导入数据成功,需要一段时间训练', });
status: 'success'
});
reset({
a: '',
q: ''
});
}
onSuccess(data); onSuccess(data);
} catch (err: any) { } catch (err: any) {
...@@ -85,7 +84,7 @@ const InputDataModal = ({ ...@@ -85,7 +84,7 @@ const InputDataModal = ({
} }
setLoading(false); setLoading(false);
}, },
[kbId, onSuccess, reset, toast] [kbId, maxToken, onSuccess, reset, toast]
); );
const updateData = useCallback( const updateData = useCallback(
...@@ -121,6 +120,11 @@ const InputDataModal = ({ ...@@ -121,6 +120,11 @@ const InputDataModal = ({
[defaultValues.a, defaultValues.q, kbId, onClose, onSuccess, toast] [defaultValues.a, defaultValues.q, kbId, onClose, onSuccess, toast]
); );
useQuery(['getKbDetail'], () => {
if (kbDetail._id === kbId) return null;
return getKbDetail(kbId);
});
return ( return (
<MyModal <MyModal
isOpen={true} isOpen={true}
...@@ -142,10 +146,15 @@ const InputDataModal = ({ ...@@ -142,10 +146,15 @@ const InputDataModal = ({
pb={2} pb={2}
> >
<Box flex={1} mr={[0, 4]} mb={[4, 0]} h={['50%', '100%']}> <Box flex={1} mr={[0, 4]} mb={[4, 0]} h={['50%', '100%']}>
<Box h={'30px'}>{'匹配的知识点'}</Box> <Flex>
<Box h={'30px'}>{'匹配的知识点'}</Box>
<MyTooltip label={'被向量化的部分,通常是问题,也可以是一段陈述描述'}>
<QuestionOutlineIcon ml={1} />
</MyTooltip>
</Flex>
<Textarea <Textarea
placeholder={'匹配的知识点。这部分内容会被搜索,请把控内容的质量。总和最多 3000 字。'} placeholder={`匹配的知识点。这部分内容会被搜索,请把控内容的质量,最多 ${maxToken} 字。`}
maxLength={3000} maxLength={maxToken}
resize={'none'} resize={'none'}
h={'calc(100% - 30px)'} h={'calc(100% - 30px)'}
{...register(`q`, { {...register(`q`, {
...@@ -154,7 +163,14 @@ const InputDataModal = ({ ...@@ -154,7 +163,14 @@ const InputDataModal = ({
/> />
</Box> </Box>
<Box flex={1} h={['50%', '100%']}> <Box flex={1} h={['50%', '100%']}>
<Box h={'30px'}>补充知识</Box> <Flex>
<Box h={'30px'}>{'补充知识'}</Box>
<MyTooltip
label={'匹配的知识点被命中后,这部分内容会随匹配知识点一起注入模型,引导模型回答'}
>
<QuestionOutlineIcon ml={1} />
</MyTooltip>
</Flex>
<Textarea <Textarea
placeholder={ placeholder={
'补充知识。这部分内容不会被搜索,但会作为"匹配的知识点"的内容补充,你可以讲一些细节的内容填写在这里。总和最多 3000 字。' '补充知识。这部分内容不会被搜索,但会作为"匹配的知识点"的内容补充,你可以讲一些细节的内容填写在这里。总和最多 3000 字。'
......
...@@ -41,7 +41,11 @@ const ChatItemSchema = new Schema({ ...@@ -41,7 +41,11 @@ const ChatItemSchema = new Schema({
type: String type: String
}, },
adminFeedback: { adminFeedback: {
type: String type: {
kbId: String,
dataId: String,
content: String
}
}, },
[TaskResponseKeyEnum.responseData]: { [TaskResponseKeyEnum.responseData]: {
type: [ type: [
......
...@@ -211,8 +211,10 @@ function filterQuote({ ...@@ -211,8 +211,10 @@ function filterQuote({
const quotePrompt = const quotePrompt =
filterQuoteQA.length > 0 filterQuoteQA.length > 0
? `"""${filterQuoteQA ? `"""${filterQuoteQA
.map((item) => (item.a ? `[${item.q}\n${item.a}]` : `${item.q}`)) .map((item) =>
.join('\n\n')}"""` item.a ? `{instruction:"${item.q}",output:"${item.a}"}` : `{instruction:"${item.q}"}`
)
.join('\n')}"""`
: ''; : '';
return { return {
...@@ -240,11 +242,11 @@ function getChatMessages({ ...@@ -240,11 +242,11 @@ function getChatMessages({
return limitPrompt; return limitPrompt;
} }
const defaultPrompt = const defaultPrompt =
'三引号引用的内容是你的补充知识,它们是最新的,根据引用内容来回答我的问题。'; '三引号是我提供给你的专属知识,它们拥有最高优先级。instruction 是相关介绍,output 是预期回答,使用引用内容来回答我下面的问题。';
if (limitPrompt) { if (limitPrompt) {
return `${defaultPrompt}${limitPrompt}`; return `${defaultPrompt}${limitPrompt}`;
} }
return `${defaultPrompt}你仅回答引用包含的内容,如果问题不包含在引用中,你直接回复: "你的问题没有在知识库中体现"`; return `${defaultPrompt}\n回答内容限制:你仅回答三引号中提及的内容,下面我提出的问题与引用内容无关时,你可以直接回复: "你的问题没有在知识库中体现"`;
})(); })();
const messages: ChatItemType[] = [ const messages: ChatItemType[] = [
......
...@@ -142,7 +142,9 @@ class Pg { ...@@ -142,7 +142,9 @@ class Pg {
} }
const fields = props.values[0].map((item) => item.key).join(','); const fields = props.values[0].map((item) => item.key).join(',');
const sql = `INSERT INTO ${table} (${fields}) VALUES ${this.getInsertValStr(props.values)} `; const sql = `INSERT INTO ${table} (${fields}) VALUES ${this.getInsertValStr(
props.values
)} RETURNING id`;
const pg = await connectPg(); const pg = await connectPg();
return pg.query(sql); return pg.query(sql);
} }
......
...@@ -2,7 +2,15 @@ export const getChatModel = (model?: string) => { ...@@ -2,7 +2,15 @@ export const getChatModel = (model?: string) => {
return global.chatModels.find((item) => item.model === model); return global.chatModels.find((item) => item.model === model);
}; };
export const getVectorModel = (model?: string) => { export const getVectorModel = (model?: string) => {
return global.vectorModels.find((item) => item.model === model) || global.vectorModels[0]; return (
global.vectorModels.find((item) => item.model === model) || {
model: 'UnKnow',
name: 'UnKnow',
defaultToken: 500,
price: 0,
maxToken: 3000
}
);
}; };
export const getModel = (model?: string) => { export const getModel = (model?: string) => {
......
...@@ -114,5 +114,6 @@ export type AppLogsListItemType = { ...@@ -114,5 +114,6 @@ export type AppLogsListItemType = {
time: Date; time: Date;
title: string; title: string;
messageCount: number; messageCount: number;
callbackCount: number; feedbackCount: number;
markCount: number;
}; };
...@@ -2,6 +2,7 @@ import { ChatRoleEnum } from '@/constants/chat'; ...@@ -2,6 +2,7 @@ import { ChatRoleEnum } from '@/constants/chat';
import type { InitChatResponse, InitShareChatResponse } from '@/api/response/chat'; import type { InitChatResponse, InitShareChatResponse } from '@/api/response/chat';
import { TaskResponseKeyEnum } from '@/constants/chat'; import { TaskResponseKeyEnum } from '@/constants/chat';
import { ClassifyQuestionAgentItemType } from './app'; import { ClassifyQuestionAgentItemType } from './app';
import { ChatItemSchema } from './mongoSchema';
export type ExportChatType = 'md' | 'pdf' | 'html'; export type ExportChatType = 'md' | 'pdf' | 'html';
...@@ -9,6 +10,8 @@ export type ChatItemType = { ...@@ -9,6 +10,8 @@ export type ChatItemType = {
dataId?: string; dataId?: string;
obj: `${ChatRoleEnum}`; obj: `${ChatRoleEnum}`;
value: string; value: string;
userFeedback?: string;
adminFeedback?: ChatItemSchema['adminFeedback'];
[TaskResponseKeyEnum.responseData]?: ChatHistoryItemResType[]; [TaskResponseKeyEnum.responseData]?: ChatHistoryItemResType[];
}; };
......
...@@ -103,7 +103,11 @@ export interface ChatItemSchema extends ChatItemType { ...@@ -103,7 +103,11 @@ export interface ChatItemSchema extends ChatItemType {
appId: string; appId: string;
time: Date; time: Date;
userFeedback?: string; userFeedback?: string;
adminFeedback?: string; adminFeedback?: {
kbId: string;
dataId: string;
content: string;
};
} }
export type BillListItemType = { export type BillListItemType = {
...@@ -137,7 +141,7 @@ export interface OpenApiSchema { ...@@ -137,7 +141,7 @@ export interface OpenApiSchema {
userId: string; userId: string;
createTime: Date; createTime: Date;
lastUsedTime?: Date; lastUsedTime?: Date;
apiKey: String; apiKey: string;
} }
export interface PromotionRecordSchema { export interface PromotionRecordSchema {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment