* 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

@@ -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,27 +20,36 @@ 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) => {
const fetchRes = await axios.get(url, {
timeout: 30000
});
try {
const fetchRes = await axios.get(url, {
timeout: 30000
});
const dom = new JSDOM(fetchRes.data, {
url,
contentType: 'text/html'
});
const dom = new JSDOM(fetchRes.data, {
url,
contentType: 'text/html'
});
const reader = new Readability(dom.window.document);
const article = reader.parse();
const reader = new Readability(dom.window.document);
const article = reader.parse();
const content = article?.textContent || '';
const content = article?.textContent || '';
return {
url,
content: simpleText(`${article?.title}\n${content}`)
};
return {
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}