* fix: chat module link

* fix: url fetch check

* fix: import file ui

* feat: app logs

* perf: iframe icon

* imgs cdn

* perf: click range and pg
This commit is contained in:
Archer
2023-08-24 21:08:49 +08:00
committed by GitHub
parent 2b50dac0e7
commit 9415e22de9
30 changed files with 506 additions and 63 deletions

View File

@@ -47,7 +47,7 @@ FastGPT 是一个基于 LLM 大语言模型的知识库问答系统,提供开
- [x] 支持直接分段导入
- [x] 支持 QA 拆分导入
- [x] 支持手动输入内容
- [ ] 支持 url 读取导入
- [x] 支持 url 读取导入
- [x] 支持 CSV 批量导入问答对
- [ ] 支持知识库单独设置向量模型
- [ ] 源文件存储

View File

@@ -15,10 +15,10 @@
"@dqbd/tiktoken": "^1.0.7",
"@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.6",
"@mozilla/readability": "^0.4.4",
"@next/font": "13.1.6",
"@tanstack/react-query": "^4.24.10",
"@types/nprogress": "^0.2.0",
"@mozilla/readability": "^0.4.4",
"axios": "^1.3.3",
"cookie": "^0.5.0",
"crypto": "^1.0.1",
@@ -31,6 +31,7 @@
"i18next": "^22.5.1",
"immer": "^9.0.19",
"js-cookie": "^3.0.5",
"jsdom": "^22.1.0",
"jsonwebtoken": "^9.0.0",
"lodash": "^4.17.21",
"mammoth": "^1.5.1",
@@ -59,7 +60,6 @@
"request-ip": "^3.3.0",
"sass": "^1.58.3",
"tunnel": "^0.0.6",
"jsdom": "^22.1.0",
"winston": "^3.10.0",
"winston-mongodb": "^5.1.1",
"zustand": "^4.3.5"
@@ -69,6 +69,7 @@
"@types/cookie": "^0.5.1",
"@types/formidable": "^2.0.5",
"@types/js-cookie": "^3.0.3",
"@types/jsdom": "^21.1.1",
"@types/jsonwebtoken": "^9.0.1",
"@types/lodash": "^4.14.191",
"@types/node": "18.14.0",
@@ -79,7 +80,6 @@
"@types/react-syntax-highlighter": "^15.5.6",
"@types/request-ip": "^0.0.37",
"@types/tunnel": "^0.0.3",
"@types/jsdom": "^21.1.1",
"eslint": "8.34.0",
"eslint-config-next": "13.1.6",
"typescript": "4.9.5"

View File

@@ -1,7 +1,8 @@
### Fast GPT V4.1
### Fast GPT V4.2
1. 新增 - 高级编排导入导出功能
2. 优化对话存储结构
3. 优化日志存储
4. [点击查看高级编排介绍文档](https://doc.fastgpt.run/docs/workflow)
5. 填写个人 OpenAI 账号后,分享和 API 功能也不会走平台余额扣费。
1. 新增 - 应用日志初版,可观测到所有对话记录
2. 新增 - 好友邀请链接,[点击查看](/account?currentTab=promotion)
3. 新增 - Iframe 嵌入页面图标可拖拽
4. 优化 - 知识库搜索提示词
5. 优化 - [使用文档](https://doc.fastgpt.run/docs/intro/)
6. [点击查看高级编排介绍文档](https://doc.fastgpt.run/docs/workflow)

View File

@@ -20,11 +20,9 @@ async function embedChatbot() {
const ChatBtn = document.createElement('div');
ChatBtn.id = chatBtnId;
ChatBtn.style.cssText =
'position: fixed; bottom: 1rem; right: 1rem; width: 40px; height: 40px; cursor: pointer; z-index: 2147483647; ';
'position: fixed; bottom: 1rem; right: 1rem; width: 40px; height: 40px; cursor: pointer; z-index: 2147483647; transition: 0;';
const ChatBtnDiv = document.createElement('div');
ChatBtnDiv.style.cssText =
'transition: all 0.2s ease-in-out 0s; left: unset; transform: scale(1); :hover {transform: scale(1.1);} display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; z-index: 9999;';
ChatBtnDiv.innerHTML = MessageIcon;
const iframe = document.createElement('iframe');
@@ -38,7 +36,15 @@ async function embedChatbot() {
document.body.appendChild(iframe);
let chatBtnDragged = false;
let chatBtnDown = false;
let chatBtnMouseX;
let chatBtnMouseY;
ChatBtn.addEventListener('click', function () {
if (chatBtnDragged) {
chatBtnDragged = false;
return;
}
const chatWindow = document.getElementById(chatWindowId);
if (!chatWindow) return;
@@ -52,6 +58,31 @@ async function embedChatbot() {
}
});
ChatBtn.addEventListener('mousedown', (e) => {
if (!chatBtnMouseX && !chatBtnMouseY) {
chatBtnMouseX = e.clientX;
chatBtnMouseY = e.clientY;
}
chatBtnDown = true;
});
ChatBtn.addEventListener('mousemove', (e) => {
if (!chatBtnDown) return;
chatBtnDragged = true;
const transformX = e.clientX - chatBtnMouseX;
const transformY = e.clientY - chatBtnMouseY;
ChatBtn.style.transform = `translate3d(${transformX}px, ${transformY}px, 0)`;
e.stopPropagation();
});
ChatBtn.addEventListener('mouseup', (e) => {
chatBtnDown = false;
});
ChatBtn.addEventListener('mouseleave', (e) => {
chatBtnDown = false;
});
ChatBtn.appendChild(ChatBtnDiv);
document.body.appendChild(ChatBtn);
}

View File

@@ -4,9 +4,12 @@
"Confirm": "Yes",
"Running": "Running",
"Warning": "Warning",
"UnKnow": "UnKnow",
"app": {
"Advance App TestTip": "The current application is advanced editing mode \n. If you need to switch to [simple mode], please click the save button on the left",
"App Detail": "App Detail",
"Chat Logs Tips": "Logs record the app's online, shared, and API conversations",
"Chat logs": "Chat Logs",
"Confirm Del App Tip": "Confirm to delete the app and all its chats",
"Confirm Save App Tip": "The application may be in advanced orchestration mode, and the advanced orchestration configuration will be overwritten after saving, please confirm!",
"Connection is invalid": "Connecting is invalid",
@@ -18,6 +21,11 @@
"Import Config Failed": "Failed to import the configuration, please ensure that the configuration is normal!",
"Import Configs": "Import Configs",
"Input Field Settings": "Input Field Settings",
"Logs Empty": "Logs is empty",
"Logs Message Total": "Message Count",
"Logs Source": "Source",
"Logs Time": "Time",
"Logs Title": "Title",
"My Apps": "My Apps",
"Output Field Settings": "Output Field Settings",
"Paste Config": "Paste Config"
@@ -28,7 +36,13 @@
"Exit Chat": "Exit",
"History": "History",
"New Chat": "New Chat",
"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": {
"api": "API",
"online": "Online Chat",
"share": "Share",
"test": "Test Chat "
}
},
"commom": {
"Password inconsistency": "Password inconsistency"
@@ -149,6 +163,10 @@
"Update Password": "Update Password",
"Update password failed": "Update password failed",
"Update password succseful": "Update password succseful",
"Usage Record": "Usage"
"Usage Record": "Usage",
"promotion": {
"pay": "",
"register": ""
}
}
}

View File

@@ -4,9 +4,12 @@
"Confirm": "确认",
"Running": "运行中",
"Warning": "提示",
"UnKnow": "未知",
"app": {
"Advance App TestTip": "当前应用为高级编排模式\n如需切换为【简易模式】请点击左侧保存按键",
"App Detail": "应用详情",
"Chat Logs Tips": "日志会记录该应用的在线、分享和 API 对话记录",
"Chat logs": "对话日志",
"Confirm Del App Tip": "确认删除该应用及其所有聊天记录?",
"Confirm Save App Tip": "该应用可能为高级编排模式,保存后将会覆盖高级编排配置,请确认!",
"Connection is invalid": "连接无效",
@@ -18,6 +21,11 @@
"Import Config Failed": "导入配置失败,请确保配置正常!",
"Import Configs": "导入配置",
"Input Field Settings": "输入字段编辑",
"Logs Empty": "还没有日志噢~",
"Logs Message Total": "消息总数",
"Logs Source": "来源",
"Logs Time": "时间",
"Logs Title": "标题",
"My Apps": "我的应用",
"Output Field Settings": "输出字段编辑",
"Paste Config": "粘贴配置"
@@ -28,7 +36,13 @@
"Exit Chat": "退出聊天",
"History": "记录",
"New Chat": "新对话",
"You need to a chat app": "你需要创建一个应用"
"You need to a chat app": "你需要创建一个应用",
"logs": {
"api": "API 调用",
"online": "在线使用",
"share": "外部链接调用",
"test": "测试"
}
},
"commom": {
"Password inconsistency": "两次密码不一致"
@@ -151,8 +165,8 @@
"Update password succseful": "修改密码成功",
"Usage Record": "使用记录",
"promotion": {
"register": "好友注册",
"pay": "好友充值"
"pay": "好友充值",
"register": "好友注册"
}
}
}

View File

@@ -51,3 +51,6 @@ export const getAppTotalUsage = (data: { appId: string }) =>
start: addDays(new Date(), -13),
end: addDays(new Date(), 1)
}).then((res) => (res.length === 0 ? [{ date: new Date(), total: 0 }] : res));
export const getAppChatLogs = (data: RequestPaging & { appId: string }) =>
POST(`/chat/getChatLogs`, data);

View File

@@ -96,6 +96,7 @@ const QuoteModal = ({
_notLast={{ mb: 2 }}
position={'relative'}
_hover={{ '& .edit': { display: 'flex' } }}
overflow={'hidden'}
>
{item.source && <Box color={'myGray.600'}>({item.source})</Box>}
<Box>{item.q}</Box>

View File

@@ -141,7 +141,7 @@ const ChatBox = (
variableModules?: VariableItemType[];
welcomeText?: string;
onUpdateVariable?: (e: Record<string, any>) => void;
onStartChat: (e: StartChatFnProps) => Promise<{
onStartChat?: (e: StartChatFnProps) => Promise<{
responseText: string;
[TaskResponseKeyEnum.responseData]: ChatHistoryItemResType[];
}>;
@@ -239,8 +239,7 @@ const ChatBox = (
// 复制内容
const onclickCopy = useCallback(
(value: string) => {
const val = value.replace(/\n+/g, '\n');
copyData(val);
copyData(value);
},
[copyData]
);
@@ -264,6 +263,7 @@ const ChatBox = (
*/
const sendPrompt = useCallback(
async (variables: Record<string, any> = {}, inputVal = '') => {
if (!onStartChat) return;
if (isChatting) {
toast({
title: '正在聊天中...请等待结束',
@@ -272,7 +272,7 @@ const ChatBox = (
return;
}
// get input value
const val = inputVal.trim().replace(/\n\s*/g, '\n');
const val = inputVal.trim();
if (!val) {
toast({
@@ -698,7 +698,7 @@ const ChatBox = (
</Box>
</Box>
{/* input */}
{variableIsFinish ? (
{onStartChat && variableIsFinish ? (
<Box m={['0 auto', '10px auto']} w={'100%'} maxW={['auto', 'min(750px, 100%)']} px={[0, 5]}>
<Box
py={'18px'}

View File

@@ -0,0 +1,14 @@
<?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="1692867122828"
class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4021"
xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64">
<path
d="M269.844659 81.4308h44.821057v166.626082h-44.821057zM677.140966 491.719232c52.335426 0 102.092273 19.937769 140.105639 56.13883 38.126482 36.31053 60.461599 85.284073 62.891788 137.900467 2.5056 54.276658-16.27424 106.280032-52.881549 146.431672-36.60731 40.15164-86.65972 63.643469-140.936379 66.150285-3.180653 0.147174-6.401444 0.221369-9.576016 0.221369-52.341508 0-102.102004-19.936552-140.114153-56.136398-38.126482-36.309314-60.461599-85.284073-62.891789-137.902899-2.5056-54.276658 16.27424-106.280032 52.88155-146.431672 36.60731-40.15164 86.65972-63.643469 140.936379-66.149069a208.122961 208.122961 0 0 1 9.576016-0.221369h0.008514m-0.00973-44.822274c-3.859355 0-7.746684 0.088791-11.642528 0.268805-136.951744 6.3236-242.847422 122.470346-236.525038 259.422091 6.143586 133.0559 115.942406 236.793842 247.779562 236.793842 3.859355 0 7.747901-0.088791 11.642529-0.268804 136.951744-6.322384 242.847422-122.470346 236.525037-259.422091-6.143586-133.057117-115.942406-236.798708-247.779562-236.793843z"
p-id="4022"></path>
<path
d="M490.264524 891.110734a272.361206 272.361206 0 0 1-32.682275-37.369937H180.453104c-20.912034 0-37.927007-17.013757-37.927007-37.92579v-590.263526c0-20.912034 17.013757-37.927007 37.927007-37.927007H732.799354c20.912034 0 37.925791 17.013757 37.925791 37.927007V441.15597a268.605238 268.605238 0 0 1 44.821057 21.463023V225.551481c0-45.70045-37.047614-82.746848-82.746848-82.746849H180.453104c-45.70045 0-82.746848 37.047614-82.746848 82.746849v590.263526c0 45.70045 37.047614 82.746848 82.746848 82.746848h317.980164a273.587248 273.587248 0 0 1-8.168744-7.451121z"
p-id="4023"></path>
<path
d="M770.725145 489.61623a225.243754 225.243754 0 0 1 44.821057 27.231985v-0.21407a225.182938 225.182938 0 0 0-44.821057-27.114003v0.096088zM812.590566 778.530212H646.820768V576.105667h44.821057v157.604704h120.948741zM209.55091 380.121489h498.255687v44.821057H209.55091zM600.682445 81.4308h44.821058v166.626082h-44.821058zM406.842623 712.17437H209.55091v44.821057h203.864657a272.351476 272.351476 0 0 1-6.572944-44.821057zM450.941192 546.147929H209.55091v44.821057h217.435038a268.707408 268.707408 0 0 1 23.955244-44.821057z"
p-id="4024"></path>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -76,7 +76,8 @@ const map = {
addCircle: require('./icons/circle/add.svg').default,
playFill: require('./icons/fill/play.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
};
export type IconName = keyof typeof map;

View File

@@ -31,7 +31,7 @@ export const ChatRoleMap = {
};
export enum ChatSourceEnum {
'test' = 'test',
test = 'test',
online = 'online',
share = 'share',
api = 'api'
@@ -39,16 +39,16 @@ export enum ChatSourceEnum {
export const ChatSourceMap = {
[ChatSourceEnum.test]: {
name: '调试测试'
name: 'chat.logs.test'
},
[ChatSourceEnum.online]: {
name: '在线使用'
name: 'chat.logs.online'
},
[ChatSourceEnum.share]: {
name: '链接分享'
name: 'chat.logs.share'
},
[ChatSourceEnum.api]: {
name: 'API调用'
name: 'chat.logs.api'
}
};
@@ -56,7 +56,8 @@ export enum ChatModuleEnum {
'AIChat' = 'AI Chat',
'KBSearch' = 'KB Search',
'CQ' = 'Classify Question',
'Extract' = 'Content Extract'
'Extract' = 'Content Extract',
'Http' = 'Http'
}
export enum OutLinkTypeEnum {

View File

@@ -158,7 +158,8 @@ export const ChatModule: FlowModuleTemplateType = {
label: '系统提示词',
valueType: FlowValueTypeEnum.string,
description: ChatModelSystemTip,
placeholder: ChatModelSystemTip
placeholder: ChatModelSystemTip,
value: ''
},
{
key: 'limitPrompt',
@@ -166,7 +167,8 @@ export const ChatModule: FlowModuleTemplateType = {
valueType: FlowValueTypeEnum.string,
label: '限定词',
description: ChatModelLimitTip,
placeholder: ChatModelLimitTip
placeholder: ChatModelLimitTip,
value: ''
},
Input_Template_TFSwitch,
{
@@ -281,6 +283,7 @@ export const AnswerModule: FlowModuleTemplateType = {
key: SpecialInputKeyEnum.answerText,
type: FlowInputItemTypeEnum.textarea,
valueType: FlowValueTypeEnum.string,
value: '',
label: '回复的内容',
description:
'可以使用 \\n 来实现换行。也可以通过外部模块输入实现回复,外部模块输入时会覆盖当前填写的内容'
@@ -337,6 +340,7 @@ export const ClassifyQuestionModule: FlowModuleTemplateType = {
key: 'systemPrompt',
type: FlowInputItemTypeEnum.textarea,
valueType: FlowValueTypeEnum.string,
value: '',
label: '系统提示词',
description:
'你可以添加一些特定内容的介绍,从而更好的识别用户的问题类型。这个内容通常是给模型介绍一个它不知道的内容。',
@@ -398,6 +402,7 @@ export const ContextExtractModule: FlowModuleTemplateType = {
key: ContextExtractEnum.description,
type: FlowInputItemTypeEnum.textarea,
valueType: FlowValueTypeEnum.string,
value: '',
label: '提取要求描述',
description: '写一段提取要求,告诉 AI 需要提取哪些内容',
required: true,

View File

@@ -28,7 +28,7 @@ const queryClient = new QueryClient({
queries: {
refetchOnWindowFocus: false,
retry: false,
cacheTime: 0
cacheTime: 10
}
}
});

View File

@@ -0,0 +1,72 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { Chat, ChatItem, connectToDatabase } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
import type { PagingData } from '@/types';
import { AppLogsListItemType } from '@/types/app';
import { Types } from 'mongoose';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const {
pageNum = 1,
pageSize = 20,
appId
} = req.body as { pageNum: number; pageSize: number; appId: string };
if (!appId) {
throw new Error('缺少参数');
}
await connectToDatabase();
// 凭证校验
const { userId } = await authUser({ req, authToken: true });
const where = {
appId: new Types.ObjectId(appId),
userId: new Types.ObjectId(userId)
};
const [data, total] = await Promise.all([
Chat.aggregate([
{ $match: where },
{ $sort: { updateTime: -1 } },
{ $skip: (pageNum - 1) * pageSize },
{ $limit: pageSize },
{
$lookup: {
from: 'chatitems',
localField: 'chatId',
foreignField: 'chatId',
as: 'messageCount'
}
},
{
$project: {
id: '$chatId',
title: 1,
source: 1,
time: '$updateTime',
messageCount: { $size: '$messageCount' },
callbackCount: { $literal: 0 }
}
}
]),
Chat.countDocuments(where)
]);
jsonRes<PagingData<AppLogsListItemType>>(res, {
data: {
pageNum,
pageSize,
data,
total
}
});
} catch (error) {
jsonRes(res, {
code: 500,
error
});
}
}

View File

@@ -27,6 +27,7 @@ import { pushTaskBill } from '@/service/events/pushBill';
import { BillSourceEnum } from '@/constants/user';
import { ChatHistoryItemResType } from '@/types/chat';
import { UserModelSchema } from '@/types/mongoSchema';
import { SystemInputEnum } from '@/constants/app';
export type MessageItemType = ChatCompletionRequestMessage & { dataId?: string };
type FastGptWebChatProps = {
@@ -302,6 +303,8 @@ export async function dispatchModules({
if (!set.has(module.moduleId) && checkInputFinish()) {
set.add(module.moduleId);
// remove switch
updateInputValue(SystemInputEnum.switch, undefined);
return moduleRun(module);
}
})
@@ -324,6 +327,7 @@ export async function dispatchModules({
// find module
const targetModule = runningModules.find((item) => item.moduleId === target.moduleId);
if (!targetModule) return;
return moduleInput(targetModule, { [target.key]: outputItem.value });
})
);
@@ -332,7 +336,6 @@ export async function dispatchModules({
}
async function moduleRun(module: RunningModuleItemType): Promise<any> {
if (res.closed) return Promise.resolve();
// console.log('run=========', module.flowType);
if (stream && detail && module.showStatus) {
responseStatus({

View File

@@ -12,7 +12,7 @@ export type UrlFetchResponse = FetchResultItem[];
const fetchContent = async (req: NextApiRequest, res: NextApiResponse) => {
try {
const { urlList = [] } = req.body as { urlList: string[] };
let { urlList = [] } = req.body as { urlList: string[] };
if (!urlList || urlList.length === 0) {
throw new Error('urlList is empty');
@@ -20,9 +20,12 @@ const fetchContent = async (req: NextApiRequest, res: NextApiResponse) => {
await authUser({ req });
urlList = urlList.filter((url) => /^(http|https):\/\/[^ "]+$/.test(url));
const response = (
await Promise.allSettled(
urlList.map(async (url) => {
try {
const fetchRes = await axios.get(url, {
timeout: 30000
});
@@ -41,6 +44,12 @@ const fetchContent = async (req: NextApiRequest, res: NextApiResponse) => {
url,
content: simpleText(`${article?.title}\n${content}`)
};
} catch (error) {
return {
url,
content: ''
};
}
})
)
)

View File

@@ -0,0 +1,237 @@
import React, { useMemo, useRef, useState } from 'react';
import {
Flex,
Box,
TableContainer,
Table,
Thead,
Tr,
Th,
Td,
Tbody,
useTheme
} from '@chakra-ui/react';
import MyIcon from '@/components/Icon';
import { useTranslation } from 'next-i18next';
import { usePagination } from '@/hooks/usePagination';
import { getAppChatLogs } from '@/api/app';
import dayjs from 'dayjs';
import { ChatSourceMap, HUMAN_ICON } from '@/constants/chat';
import { AppLogsListItemType } from '@/types/app';
import { useGlobalStore } from '@/store/global';
import MyTooltip from '@/components/MyTooltip';
import ChatBox, { type ComponentRef } from '@/components/ChatBox';
import { useQuery } from '@tanstack/react-query';
import { getInitChatSiteInfo } from '@/api/chat';
import Tag from '@/components/Tag';
const Logs = ({ appId }: { appId: string }) => {
const { t } = useTranslation();
const { isPc } = useGlobalStore();
const {
data: logs,
isLoading,
Pagination,
getData,
pageNum
} = usePagination<AppLogsListItemType>({
api: getAppChatLogs,
pageSize: 20,
params: {
appId
}
});
const [detailLogsId, setDetailLogsId] = useState<string>();
return (
<Flex flexDirection={'column'} h={'100%'} pt={[1, 5]} position={'relative'}>
<Box px={[4, 8]}>
{isPc && (
<>
<Box fontWeight={'bold'} fontSize={['md', 'xl']} mb={2}>
{t('app.Chat logs')}
</Box>
<Box color={'myGray.500'} fontSize={'sm'}>
{t('app.Chat Logs Tips')}
</Box>
</>
)}
</Box>
{/* table */}
<TableContainer mt={[0, 3]} flex={'1 0 0'} h={0} overflowY={'auto'} px={[4, 8]}>
<Table variant={'simple'} fontSize={'sm'}>
<Thead>
<Tr>
<Th>{t('app.Logs Source')}</Th>
<Th>{t('app.Logs Time')}</Th>
<Th>{t('app.Logs Title')}</Th>
<Th>{t('app.Logs Message Total')}</Th>
</Tr>
</Thead>
<Tbody>
{logs.map((item) => (
<Tr
key={item.id}
_hover={{ bg: 'myWhite.600' }}
cursor={'pointer'}
title={'点击查看对话详情'}
onClick={() => setDetailLogsId(item.id)}
>
<Td>{t(ChatSourceMap[item.source]?.name || 'UnKnow')}</Td>
<Td>{dayjs(item.time).format('YYYY/MM/DD HH:mm')}</Td>
<Td className="textEllipsis" maxW={'250px'}>
{item.title}
</Td>
<Td>{item.messageCount}</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
<Box p={4}>
<Pagination />
</Box>
{logs.length === 0 && !isLoading && (
<Flex h={'100%'} flexDirection={'column'} alignItems={'center'} pt={'10vh'}>
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
<Box mt={2} color={'myGray.500'}>
{t('app.Logs Empty')}
</Box>
</Flex>
)}
{!!detailLogsId && (
<DetailLogsModal
appId={appId}
chatId={detailLogsId}
onClose={() => setDetailLogsId(undefined)}
/>
)}
</Flex>
);
};
export default Logs;
function DetailLogsModal({
appId,
chatId,
onClose
}: {
appId: string;
chatId: string;
onClose: () => void;
}) {
const ChatBoxRef = useRef<ComponentRef>(null);
const { isPc } = useGlobalStore();
const theme = useTheme();
const { data: chat } = useQuery(
['getChatDetail', chatId],
() => getInitChatSiteInfo({ appId, chatId }),
{
onSuccess(res) {
const history = res.history.map((item) => ({
...item,
status: 'finish' as any
}));
ChatBoxRef.current?.resetHistory(history);
ChatBoxRef.current?.resetVariables(res.variables);
if (res.history.length > 0) {
setTimeout(() => {
ChatBoxRef.current?.scrollToBottom('auto');
}, 500);
}
}
}
);
const history = useMemo(() => (chat?.history ? chat.history : []), [chat]);
const title = useMemo(() => {
return history[history.length - 2]?.value?.slice(0, 8);
}, [history]);
return (
<>
<Flex
zIndex={3}
flexDirection={'column'}
position={['fixed', 'absolute']}
top={[0, '2%']}
right={0}
h={['100%', '96%']}
w={'100%'}
maxW={['100%', '600px']}
bg={'white'}
boxShadow={'3px 0 20px rgba(0,0,0,0.2)'}
borderRadius={'md'}
overflow={'hidden'}
transition={'.2s ease'}
>
<Flex
alignItems={'center'}
px={[3, 5]}
h={['46px', '60px']}
borderBottom={theme.borders.base}
borderBottomColor={'gray.200'}
color={'myGray.900'}
>
{isPc ? (
<>
<Box mr={3} color={'myGray.1000'}>
{title}
</Box>
<Tag>
<MyIcon name={'history'} w={'14px'} />
<Box ml={1}>{`${history.length}条记录`}</Box>
</Tag>
{!!chat?.app?.chatModels && (
<Tag ml={2} colorSchema={'green'}>
<MyIcon name={'chatModelTag'} w={'14px'} />
<Box ml={1}>{chat.app.chatModels.join(',')}</Box>
</Tag>
)}
<Box flex={1} />
</>
) : (
<>
<Flex px={3} alignItems={'center'} flex={'1 0 0'} w={0} justifyContent={'center'}>
<Box ml={1} className="textEllipsis">
{title}
</Box>
</Flex>
</>
)}
<Flex
alignItems={'center'}
justifyContent={'center'}
w={'20px'}
h={'20px'}
borderRadius={'50%'}
cursor={'pointer'}
_hover={{ bg: 'myGray.100' }}
onClick={onClose}
>
<MyIcon name={'closeLight'} w={'12px'} h={'12px'} color={'myGray.700'} />
</Flex>
</Flex>
<Box pt={2} flex={'1 0 0'}>
<ChatBox
ref={ChatBoxRef}
chatId={chatId}
appAvatar={chat?.app.avatar}
userAvatar={HUMAN_ICON}
variableModules={chat?.app.variableModules}
welcomeText={chat?.app.welcomeText}
onUpdateVariable={(e) => {}}
/>
</Box>
</Flex>
<Box zIndex={2} position={'fixed'} top={0} left={0} bottom={0} right={0} onClick={onClose} />
</>
);
}

View File

@@ -26,11 +26,15 @@ const OutLink = dynamic(() => import('./components/OutLink'), {
const API = dynamic(() => import('./components/API'), {
ssr: false
});
const Logs = dynamic(() => import('./components/Logs'), {
ssr: false
});
enum TabEnum {
'basicEdit' = 'basicEdit',
'adEdit' = 'adEdit',
'outLink' = 'outLink',
'logs' = 'logs',
'API' = 'API'
}
@@ -59,6 +63,7 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
{ label: '高级编排', id: TabEnum.adEdit, icon: 'settingLight' },
{ label: '外部使用', id: TabEnum.outLink, icon: 'shareLight' },
{ label: 'API访问', id: TabEnum.API, icon: 'apiLight' },
{ label: '对话日志', id: TabEnum.logs, icon: 'logsLight' },
{ label: '立即对话', id: 'startChat', icon: 'chat' }
],
[]
@@ -148,7 +153,7 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
</Flex>
</Box>
{/* phone tab */}
<Box display={['block', 'none']} textAlign={'center'} px={5} py={3}>
<Box display={['block', 'none']} textAlign={'center'} py={3}>
<Box className="textlg" fontSize={'xl'} fontWeight={'bold'}>
{appDetail.name}
</Box>
@@ -174,6 +179,7 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
<AdEdit app={appDetail} onCloseSettings={() => setCurrentTab(TabEnum.basicEdit)} />
)}
{currentTab === TabEnum.API && <API appId={appId} />}
{currentTab === TabEnum.logs && <Logs appId={appId} />}
{currentTab === TabEnum.outLink && <OutLink appId={appId} />}
</Box>
</Flex>

View File

@@ -132,7 +132,7 @@ const ChunkImport = ({ kbId }: { kbId: string }) => {
};
return (
<Box display={['block', 'flex']} h={['auto', '100%']}>
<Box display={['block', 'flex']} h={['auto', '100%']} overflow={'overlay'}>
<Flex
flexDirection={'column'}
flex={'1 0 0'}
@@ -152,7 +152,7 @@ const ChunkImport = ({ kbId }: { kbId: string }) => {
{!emptyFiles && (
<>
<Box py={4} px={2} maxH={'400px'} overflow={'auto'}>
<Box py={4} px={2} minH={['auto', '100px']} maxH={'400px'} overflow={'auto'}>
{files.map((item) => (
<Flex
key={item.id}

View File

@@ -77,7 +77,7 @@ const CsvImport = ({ kbId }: { kbId: string }) => {
maxW: '400px'
};
return (
<Box display={['block', 'flex']} h={['auto', '100%']}>
<Box display={['block', 'flex']} h={['auto', '100%']} overflow={'overlay'}>
<Flex
flexDirection={'column'}
flex={'1 0 0'}
@@ -100,7 +100,7 @@ const CsvImport = ({ kbId }: { kbId: string }) => {
{!emptyFiles && (
<>
<Box py={4} px={2} maxH={'400px'} overflow={'auto'}>
<Box py={4} minH={['auto', '100px']} px={2} maxH={'400px'} overflow={'auto'}>
{files.map((item) => (
<Flex
key={item.id}

View File

@@ -65,7 +65,7 @@ const ManualImport = ({ kbId }: { kbId: string }) => {
});
return (
<Box p={[4, 8]}>
<Box p={[4, 8]} h={'100%'} overflow={'overlay'}>
<Box display={'flex'} flexDirection={['column', 'row']}>
<Box flex={1} mr={[0, 4]} mb={[4, 0]} h={['50%', '100%']}>
<Box h={'30px'}>{'匹配的知识点'}</Box>

View File

@@ -123,7 +123,7 @@ const QAImport = ({ kbId }: { kbId: string }) => {
};
return (
<Box display={['block', 'flex']} h={['auto', '100%']}>
<Box display={['block', 'flex']} h={['auto', '100%']} overflow={'overlay'}>
<Flex
flexDirection={'column'}
flex={'1 0 0'}
@@ -143,7 +143,7 @@ const QAImport = ({ kbId }: { kbId: string }) => {
{!emptyFiles && (
<>
<Box py={4} px={2} maxH={'400px'} overflow={'auto'}>
<Box py={4} px={2} minH={['auto', '100px']} maxH={'400px'} overflow={'auto'}>
{files.map((item) => (
<Flex
key={item.id}

View File

@@ -64,6 +64,7 @@ try {
ChatItemSchema.index({ time: -1 });
ChatItemSchema.index({ userId: 1 });
ChatItemSchema.index({ appId: 1 });
ChatItemSchema.index({ chatId: 1 });
} catch (error) {
console.log(error);
}

View File

@@ -10,6 +10,6 @@ export const dispatchHistory = (props: Record<string, any>) => {
const { maxContext = 5, history = [] } = props as HistoryProps;
return {
history: history.slice(-maxContext)
history: maxContext > 0 ? history.slice(-maxContext) : []
};
};

View File

@@ -1,4 +1,6 @@
import { ChatModuleEnum, TaskResponseKeyEnum } from '@/constants/chat';
import { HttpPropsEnum } from '@/constants/flow/flowField';
import { ChatHistoryItemResType } from '@/types/chat';
import type { NextApiResponse } from 'next';
export type HttpRequestProps = {
@@ -11,6 +13,7 @@ export type HttpRequestProps = {
export type HttpResponse = {
[HttpPropsEnum.finish]: boolean;
[HttpPropsEnum.failed]?: boolean;
[TaskResponseKeyEnum.responseData]: ChatHistoryItemResType;
[key: string]: any;
};
@@ -22,12 +25,22 @@ export const dispatchHttpRequest = async (props: Record<string, any>): Promise<H
return {
[HttpPropsEnum.finish]: true,
[TaskResponseKeyEnum.responseData]: {
moduleName: ChatModuleEnum.Http,
price: 0,
httpResult: response
},
...response
};
} catch (error) {
return {
[HttpPropsEnum.finish]: true,
[HttpPropsEnum.failed]: true
[HttpPropsEnum.failed]: true,
[TaskResponseKeyEnum.responseData]: {
moduleName: ChatModuleEnum.Http,
price: 0,
httpResult: {}
}
};
}
};

View File

@@ -2,7 +2,7 @@ import { Pool } from 'pg';
import type { QueryResultRow } from 'pg';
import { PgTrainingTableName } from '@/constants/plugin';
export const connectPg = async () => {
export const connectPg = async (): Promise<Pool> => {
if (global.pgClient) {
return global.pgClient;
}
@@ -17,6 +17,7 @@ export const connectPg = async () => {
global.pgClient.on('error', (err) => {
console.log(err);
global.pgClient = null;
connectPg();
});
try {
@@ -25,7 +26,7 @@ export const connectPg = async () => {
return global.pgClient;
} catch (error) {
global.pgClient = null;
return Promise.reject(error);
return connectPg();
}
};

View File

@@ -7,7 +7,7 @@ import {
VariableInputEnum
} from '../constants/app';
import type { FlowInputItemType, FlowOutputItemType, FlowOutputTargetItemType } from './flow';
import type { AppSchema, kbSchema } from './mongoSchema';
import type { AppSchema, ChatSchema, kbSchema } from './mongoSchema';
import { ChatModelType } from '@/constants/model';
import { FlowValueTypeEnum } from '@/constants/flow';
@@ -107,3 +107,12 @@ export type RunningModuleItemType = {
}[];
}[];
};
export type AppLogsListItemType = {
id: string;
source: ChatSchema['source'];
time: Date;
title: string;
messageCount: number;
callbackCount: number;
};

View File

@@ -71,4 +71,7 @@ export type ChatHistoryItemResType = {
// content extract
extractDescription?: string;
extractResult?: Record<string, any>;
// http
httpResult?: Record<string, any>;
};

View File

@@ -6,7 +6,7 @@
},
{
"source": "/imgs/:path*",
"destination": "https://cdn.jsdelivr.us/gh/yangchuansheng/FastGPT@main/docSite/assets/imgs/:path*"
"destination": "https://cdn.jsdelivr.us/gh/labring/FastGPT@main/docSite/assets/imgs/:path*"
}
],
"headers": [