mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-29 17:55:24 +00:00
perf: quote modal
This commit is contained in:
@@ -13,21 +13,17 @@ import MyIcon from '@/components/Icon';
|
|||||||
import InputDataModal from '@/pages/kb/detail/components/InputDataModal';
|
import InputDataModal from '@/pages/kb/detail/components/InputDataModal';
|
||||||
import { getKbDataItemById } from '@/api/plugins/kb';
|
import { getKbDataItemById } from '@/api/plugins/kb';
|
||||||
import { useLoading } from '@/hooks/useLoading';
|
import { useLoading } from '@/hooks/useLoading';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
import { getHistoryQuote, updateHistoryQuote } from '@/api/chat';
|
|
||||||
import { useToast } from '@/hooks/useToast';
|
import { useToast } from '@/hooks/useToast';
|
||||||
import { getErrText } from '@/utils/tools';
|
import { getErrText } from '@/utils/tools';
|
||||||
import { QuoteItemType } from '@/types/chat';
|
import { QuoteItemType } from '@/types/chat';
|
||||||
|
|
||||||
const QuoteModal = ({
|
const QuoteModal = ({
|
||||||
chatId,
|
onUpdateQuote,
|
||||||
contentId,
|
|
||||||
rawSearch = [],
|
rawSearch = [],
|
||||||
onClose
|
onClose
|
||||||
}: {
|
}: {
|
||||||
chatId?: string;
|
onUpdateQuote: (quoteId: string, sourceText: string) => Promise<void>;
|
||||||
contentId?: string;
|
rawSearch: QuoteItemType[];
|
||||||
rawSearch?: QuoteItemType[];
|
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@@ -40,47 +36,6 @@ const QuoteModal = ({
|
|||||||
q: string;
|
q: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const {
|
|
||||||
data: quote = [],
|
|
||||||
refetch,
|
|
||||||
isLoading
|
|
||||||
} = useQuery(['getHistoryQuote'], () => {
|
|
||||||
if (chatId && contentId) {
|
|
||||||
return getHistoryQuote({ chatId, contentId });
|
|
||||||
}
|
|
||||||
if (rawSearch.length > 0) {
|
|
||||||
return rawSearch;
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* update kbData, update mongo status and reload quotes
|
|
||||||
*/
|
|
||||||
const updateQuoteStatus = useCallback(
|
|
||||||
async (quoteId: string, sourceText: string) => {
|
|
||||||
if (!chatId || !contentId) return;
|
|
||||||
setIsLoading(true);
|
|
||||||
try {
|
|
||||||
await updateHistoryQuote({
|
|
||||||
contentId,
|
|
||||||
chatId,
|
|
||||||
quoteId,
|
|
||||||
sourceText
|
|
||||||
});
|
|
||||||
// reload quote
|
|
||||||
refetch();
|
|
||||||
} catch (err) {
|
|
||||||
toast({
|
|
||||||
status: 'warning',
|
|
||||||
title: getErrText(err)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
setIsLoading(false);
|
|
||||||
},
|
|
||||||
[contentId, chatId, refetch, setIsLoading, toast]
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* click edit, get new kbDataItem
|
* click edit, get new kbDataItem
|
||||||
*/
|
*/
|
||||||
@@ -91,7 +46,7 @@ const QuoteModal = ({
|
|||||||
const data = (await getKbDataItemById(item.id)) as QuoteItemType;
|
const data = (await getKbDataItemById(item.id)) as QuoteItemType;
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
updateQuoteStatus(item.id, '已删除');
|
onUpdateQuote(item.id, '已删除');
|
||||||
throw new Error('该数据已被删除');
|
throw new Error('该数据已被删除');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,7 +64,7 @@ const QuoteModal = ({
|
|||||||
}
|
}
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
},
|
},
|
||||||
[setIsLoading, toast, updateQuoteStatus]
|
[setIsLoading, toast, onUpdateQuote]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -123,14 +78,14 @@ const QuoteModal = ({
|
|||||||
overflow={'overlay'}
|
overflow={'overlay'}
|
||||||
>
|
>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
知识库引用({quote.length}条)
|
知识库引用({rawSearch.length}条)
|
||||||
<Box fontSize={'sm'} fontWeight={'normal'}>
|
<Box fontSize={'sm'} fontWeight={'normal'}>
|
||||||
注意: 修改知识库内容成功后,此处不会显示。点击编辑后,才是显示最新的内容。
|
注意: 修改知识库内容成功后,此处不会显示变更情况。点击编辑后,会显示知识库最新的内容。
|
||||||
</Box>
|
</Box>
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
<ModalCloseButton />
|
<ModalCloseButton />
|
||||||
<ModalBody pt={0} whiteSpace={'pre-wrap'} textAlign={'justify'} fontSize={'sm'}>
|
<ModalBody pt={0} whiteSpace={'pre-wrap'} textAlign={'justify'} fontSize={'sm'}>
|
||||||
{quote.map((item) => (
|
{rawSearch.map((item) => (
|
||||||
<Box
|
<Box
|
||||||
key={item.id}
|
key={item.id}
|
||||||
flex={'1 0 0'}
|
flex={'1 0 0'}
|
||||||
@@ -172,14 +127,14 @@ const QuoteModal = ({
|
|||||||
</Box>
|
</Box>
|
||||||
))}
|
))}
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<Loading loading={isLoading} fixed={false} />
|
<Loading fixed={false} />
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
{editDataItem && (
|
{editDataItem && (
|
||||||
<InputDataModal
|
<InputDataModal
|
||||||
onClose={() => setEditDataItem(undefined)}
|
onClose={() => setEditDataItem(undefined)}
|
||||||
onSuccess={() => updateQuoteStatus(editDataItem.dataId, '手动修改')}
|
onSuccess={() => onUpdateQuote(editDataItem.dataId, '手动修改')}
|
||||||
onDelete={() => updateQuoteStatus(editDataItem.dataId, '已删除')}
|
onDelete={() => onUpdateQuote(editDataItem.dataId, '已删除')}
|
||||||
kbId={editDataItem.kbId}
|
kbId={editDataItem.kbId}
|
||||||
defaultValues={editDataItem}
|
defaultValues={editDataItem}
|
||||||
/>
|
/>
|
||||||
|
@@ -1,7 +1,96 @@
|
|||||||
import React from 'react';
|
import React, { useCallback, useMemo, useState } from 'react';
|
||||||
|
import { ChatModuleEnum } from '@/constants/chat';
|
||||||
|
import { ChatHistoryItemResType, QuoteItemType } from '@/types/chat';
|
||||||
|
import { Flex, BoxProps } from '@chakra-ui/react';
|
||||||
|
import { updateHistoryQuote } from '@/api/chat';
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
import Tag from '../Tag';
|
||||||
|
import MyTooltip from '../MyTooltip';
|
||||||
|
const QuoteModal = dynamic(() => import('./QuoteModal'), { ssr: false });
|
||||||
|
|
||||||
const ResponseDetailModal = () => {
|
const ResponseDetailModal = ({
|
||||||
return <div>ResponseDetailModal</div>;
|
chatId,
|
||||||
|
contentId,
|
||||||
|
responseData = []
|
||||||
|
}: {
|
||||||
|
chatId?: string;
|
||||||
|
contentId?: string;
|
||||||
|
responseData?: ChatHistoryItemResType[];
|
||||||
|
}) => {
|
||||||
|
console.log(responseData);
|
||||||
|
|
||||||
|
const [quoteModalData, setQuoteModalData] = useState<QuoteItemType[]>();
|
||||||
|
|
||||||
|
const {
|
||||||
|
tokens = 0,
|
||||||
|
quoteList = [],
|
||||||
|
completeMessages = []
|
||||||
|
} = useMemo(() => {
|
||||||
|
const chatData = responseData.find((item) => item.moduleName === ChatModuleEnum.AIChat);
|
||||||
|
if (!chatData) return {};
|
||||||
|
return {
|
||||||
|
tokens: chatData.tokens,
|
||||||
|
quoteList: chatData.quoteList,
|
||||||
|
completeMessages: chatData.completeMessages
|
||||||
|
};
|
||||||
|
}, [responseData]);
|
||||||
|
|
||||||
|
const isEmpty = useMemo(
|
||||||
|
() => quoteList.length === 0 && completeMessages.length === 0 && tokens === 0,
|
||||||
|
[completeMessages.length, quoteList.length, tokens]
|
||||||
|
);
|
||||||
|
|
||||||
|
const updateQuote = useCallback(async (quoteId: string, sourceText: string) => {}, []);
|
||||||
|
|
||||||
|
const TagStyles: BoxProps = {
|
||||||
|
mr: 2,
|
||||||
|
bg: 'transparent'
|
||||||
|
};
|
||||||
|
|
||||||
|
return isEmpty ? null : (
|
||||||
|
<Flex alignItems={'center'} mt={2} flexWrap={'wrap'}>
|
||||||
|
{quoteList.length > 0 && (
|
||||||
|
<MyTooltip label="查看引用">
|
||||||
|
<Tag
|
||||||
|
colorSchema="blue"
|
||||||
|
cursor={'pointer'}
|
||||||
|
{...TagStyles}
|
||||||
|
onClick={() => setQuoteModalData(quoteList)}
|
||||||
|
>
|
||||||
|
{quoteList.length}条引用
|
||||||
|
</Tag>
|
||||||
|
</MyTooltip>
|
||||||
|
)}
|
||||||
|
{completeMessages.length > 0 && (
|
||||||
|
<Tag colorSchema="green" cursor={'default'} {...TagStyles}>
|
||||||
|
{completeMessages.length}条上下文
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
|
{tokens > 0 && (
|
||||||
|
<Tag colorSchema="gray" cursor={'default'} {...TagStyles}>
|
||||||
|
{tokens}tokens
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
|
{/* <Button
|
||||||
|
size={'sm'}
|
||||||
|
variant={'base'}
|
||||||
|
borderRadius={'md'}
|
||||||
|
fontSize={'xs'}
|
||||||
|
px={2}
|
||||||
|
lineHeight={1}
|
||||||
|
py={1}
|
||||||
|
>
|
||||||
|
完整参数
|
||||||
|
</Button> */}
|
||||||
|
{!!quoteModalData && (
|
||||||
|
<QuoteModal
|
||||||
|
rawSearch={quoteModalData}
|
||||||
|
onUpdateQuote={updateQuote}
|
||||||
|
onClose={() => setQuoteModalData(undefined)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ResponseDetailModal;
|
export default ResponseDetailModal;
|
||||||
|
@@ -23,7 +23,7 @@ import {
|
|||||||
hasVoiceApi,
|
hasVoiceApi,
|
||||||
getErrText
|
getErrText
|
||||||
} from '@/utils/tools';
|
} from '@/utils/tools';
|
||||||
import { Box, Card, Flex, Input, Textarea, Button, useTheme } from '@chakra-ui/react';
|
import { Box, Card, Flex, Input, Textarea, Button, useTheme, BoxProps } from '@chakra-ui/react';
|
||||||
import { feConfigs } from '@/store/static';
|
import { feConfigs } from '@/store/static';
|
||||||
import { Types } from 'mongoose';
|
import { Types } from 'mongoose';
|
||||||
import { EventNameEnum } from '../Markdown/constant';
|
import { EventNameEnum } from '../Markdown/constant';
|
||||||
@@ -38,12 +38,11 @@ import { fileDownload } from '@/utils/file';
|
|||||||
import { htmlTemplate } from '@/constants/common';
|
import { htmlTemplate } from '@/constants/common';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useGlobalStore } from '@/store/global';
|
import { useGlobalStore } from '@/store/global';
|
||||||
import { QuoteItemType } from '@/types/chat';
|
|
||||||
import { FlowModuleTypeEnum } from '@/constants/flow';
|
import { FlowModuleTypeEnum } from '@/constants/flow';
|
||||||
import { TaskResponseKeyEnum } from '@/constants/chat';
|
import { TaskResponseKeyEnum } from '@/constants/chat';
|
||||||
|
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
const QuoteModal = dynamic(() => import('./QuoteModal'));
|
const ResponseDetailModal = dynamic(() => import('./ResponseDetailModal'));
|
||||||
|
|
||||||
import MyIcon from '@/components/Icon';
|
import MyIcon from '@/components/Icon';
|
||||||
import Avatar from '@/components/Avatar';
|
import Avatar from '@/components/Avatar';
|
||||||
@@ -108,15 +107,22 @@ const Empty = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ChatAvatar = ({
|
const ChatAvatar = ({ src, type }: { src?: string; type: 'Human' | 'AI' }) => {
|
||||||
src,
|
const theme = useTheme();
|
||||||
ml,
|
return (
|
||||||
mr
|
<Box
|
||||||
}: {
|
w={['28px', '34px']}
|
||||||
src?: string;
|
h={['28px', '34px']}
|
||||||
ml?: (string | number) | (string | number)[];
|
p={'2px'}
|
||||||
mr?: (string | number) | (string | number)[];
|
borderRadius={'lg'}
|
||||||
}) => <Avatar src={src} w={['24px', '34px']} h={['24px', '34px']} ml={ml} mr={mr} />;
|
border={theme.borders.base}
|
||||||
|
boxShadow={'0 0 5px rgba(0,0,0,0.1)'}
|
||||||
|
bg={type === 'Human' ? 'white' : 'myBlue.100'}
|
||||||
|
>
|
||||||
|
<Avatar src={src} w={'100%'} h={'100%'} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const ChatBox = (
|
const ChatBox = (
|
||||||
{
|
{
|
||||||
@@ -157,10 +163,6 @@ const ChatBox = (
|
|||||||
const [refresh, setRefresh] = useState(false);
|
const [refresh, setRefresh] = useState(false);
|
||||||
const [variables, setVariables] = useState<Record<string, any>>({});
|
const [variables, setVariables] = useState<Record<string, any>>({});
|
||||||
const [chatHistory, setChatHistory] = useState<ChatSiteItemType[]>([]);
|
const [chatHistory, setChatHistory] = useState<ChatSiteItemType[]>([]);
|
||||||
const [quoteModalData, setQuoteModalData] = useState<{
|
|
||||||
contentId?: string;
|
|
||||||
rawSearch?: QuoteItemType[];
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const isChatting = useMemo(
|
const isChatting = useMemo(
|
||||||
() => chatHistory[chatHistory.length - 1]?.status === 'loading',
|
() => chatHistory[chatHistory.length - 1]?.status === 'loading',
|
||||||
@@ -394,13 +396,16 @@ const ChatBox = (
|
|||||||
color: 'myGray.400',
|
color: 'myGray.400',
|
||||||
display: ['flex', 'none'],
|
display: ['flex', 'none'],
|
||||||
pl: 1,
|
pl: 1,
|
||||||
mt: 2,
|
mt: 2
|
||||||
position: 'absolute' as any,
|
};
|
||||||
zIndex: 1,
|
const MessageCardStyle: BoxProps = {
|
||||||
w: '100%'
|
px: 4,
|
||||||
|
py: 3,
|
||||||
|
borderRadius: '0 8px 8px 8px',
|
||||||
|
boxShadow: '0 0 8px rgba(0,0,0,0.15)'
|
||||||
};
|
};
|
||||||
|
|
||||||
const messageCardMaxW = ['calc(100% - 48px)', 'calc(100% - 65px)'];
|
const messageCardMaxW = ['calc(100% - 25px)', 'calc(100% - 40px)'];
|
||||||
|
|
||||||
const showEmpty = useMemo(
|
const showEmpty = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -439,27 +444,19 @@ const ChatBox = (
|
|||||||
h={0}
|
h={0}
|
||||||
w={'100%'}
|
w={'100%'}
|
||||||
overflow={'overlay'}
|
overflow={'overlay'}
|
||||||
px={[2, 5]}
|
px={[4, 0]}
|
||||||
mt={[0, 5]}
|
mt={[0, 5]}
|
||||||
pb={3}
|
pb={3}
|
||||||
>
|
>
|
||||||
<Box maxW={['100%', '1000px', '1200px']} h={'100%'} mx={'auto'}>
|
<Box maxW={['100%', '92%']} h={'100%'} mx={'auto'}>
|
||||||
{showEmpty && <Empty />}
|
{showEmpty && <Empty />}
|
||||||
|
|
||||||
{!!welcomeText && (
|
{!!welcomeText && (
|
||||||
<Flex alignItems={'flex-start'} py={2}>
|
<Flex flexDirection={'column'} alignItems={'flex-start'} py={2}>
|
||||||
{/* avatar */}
|
{/* avatar */}
|
||||||
<ChatAvatar src={appAvatar} mr={['6px', 2]} />
|
<ChatAvatar src={appAvatar} type={'AI'} />
|
||||||
{/* message */}
|
{/* message */}
|
||||||
<Card
|
<Card order={2} mt={2} {...MessageCardStyle} bg={'white'} maxW={messageCardMaxW}>
|
||||||
order={2}
|
|
||||||
mt={2}
|
|
||||||
px={4}
|
|
||||||
py={3}
|
|
||||||
bg={'white'}
|
|
||||||
maxW={messageCardMaxW}
|
|
||||||
borderRadius={'0 8px 8px 8px'}
|
|
||||||
>
|
|
||||||
<Markdown
|
<Markdown
|
||||||
source={`~~~guide \n${welcomeText}`}
|
source={`~~~guide \n${welcomeText}`}
|
||||||
isChatting={false}
|
isChatting={false}
|
||||||
@@ -474,19 +471,17 @@ const ChatBox = (
|
|||||||
)}
|
)}
|
||||||
{/* variable input */}
|
{/* variable input */}
|
||||||
{!!variableModules?.length && (
|
{!!variableModules?.length && (
|
||||||
<Flex alignItems={'flex-start'} py={2}>
|
<Flex flexDirection={'column'} alignItems={'flex-start'} py={2}>
|
||||||
{/* avatar */}
|
{/* avatar */}
|
||||||
<ChatAvatar src={appAvatar} mr={['6px', 2]} />
|
<ChatAvatar src={appAvatar} type={'AI'} />
|
||||||
{/* message */}
|
{/* message */}
|
||||||
<Card
|
<Card
|
||||||
order={2}
|
order={2}
|
||||||
mt={2}
|
mt={2}
|
||||||
bg={'white'}
|
bg={'white'}
|
||||||
px={4}
|
w={'400px'}
|
||||||
py={3}
|
|
||||||
borderRadius={'0 8px 8px 8px'}
|
|
||||||
flex={'0 0 400px'}
|
|
||||||
maxW={messageCardMaxW}
|
maxW={messageCardMaxW}
|
||||||
|
{...MessageCardStyle}
|
||||||
>
|
>
|
||||||
{variableModules.map((item) => (
|
{variableModules.map((item) => (
|
||||||
<Box key={item.id} mb={4}>
|
<Box key={item.id} mb={4}>
|
||||||
@@ -540,8 +535,8 @@ const ChatBox = (
|
|||||||
<Flex
|
<Flex
|
||||||
position={'relative'}
|
position={'relative'}
|
||||||
key={item._id}
|
key={item._id}
|
||||||
alignItems={'flex-start'}
|
flexDirection={'column'}
|
||||||
justifyContent={item.obj === 'Human' ? 'flex-end' : 'flex-start'}
|
alignItems={item.obj === 'Human' ? 'flex-end' : 'flex-start'}
|
||||||
py={5}
|
py={5}
|
||||||
_hover={{
|
_hover={{
|
||||||
'& .control': {
|
'& .control': {
|
||||||
@@ -551,18 +546,8 @@ const ChatBox = (
|
|||||||
>
|
>
|
||||||
{item.obj === 'Human' && (
|
{item.obj === 'Human' && (
|
||||||
<>
|
<>
|
||||||
<Box position={'relative'} maxW={messageCardMaxW}>
|
<Flex w={'100%'} alignItems={'center'} justifyContent={'flex-end'}>
|
||||||
<Card
|
<Flex {...controlContainerStyle} justifyContent={'flex-end'} mr={3}>
|
||||||
className="markdown"
|
|
||||||
whiteSpace={'pre-wrap'}
|
|
||||||
px={4}
|
|
||||||
py={3}
|
|
||||||
borderRadius={'8px 0 8px 8px'}
|
|
||||||
bg={'myBlue.300'}
|
|
||||||
>
|
|
||||||
<Box as={'p'}>{item.value}</Box>
|
|
||||||
</Card>
|
|
||||||
<Flex {...controlContainerStyle} justifyContent={'flex-end'}>
|
|
||||||
<MyTooltip label={'复制'}>
|
<MyTooltip label={'复制'}>
|
||||||
<MyIcon
|
<MyIcon
|
||||||
{...controlIconStyle}
|
{...controlIconStyle}
|
||||||
@@ -591,38 +576,25 @@ const ChatBox = (
|
|||||||
</MyTooltip>
|
</MyTooltip>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
<ChatAvatar src={userAvatar} type={'Human'} />
|
||||||
|
</Flex>
|
||||||
|
<Box position={'relative'} maxW={messageCardMaxW} mt={['6px', 2]}>
|
||||||
|
<Card
|
||||||
|
className="markdown"
|
||||||
|
whiteSpace={'pre-wrap'}
|
||||||
|
{...MessageCardStyle}
|
||||||
|
bg={'myBlue.300'}
|
||||||
|
>
|
||||||
|
<Box as={'p'}>{item.value}</Box>
|
||||||
|
</Card>
|
||||||
</Box>
|
</Box>
|
||||||
<ChatAvatar src={userAvatar} ml={['6px', 2]} />
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{item.obj === 'AI' && (
|
{item.obj === 'AI' && (
|
||||||
<>
|
<>
|
||||||
<ChatAvatar src={appAvatar} mr={['6px', 2]} />
|
<Flex w={'100%'} alignItems={'center'}>
|
||||||
<Box position={'relative'} maxW={messageCardMaxW}>
|
<ChatAvatar src={appAvatar} type={'AI'} />
|
||||||
<Card bg={'white'} px={4} py={3} borderRadius={'0 8px 8px 8px'}>
|
<Flex {...controlContainerStyle} ml={3}>
|
||||||
<Markdown
|
|
||||||
source={item.value}
|
|
||||||
isChatting={index === chatHistory.length - 1 && isChatting}
|
|
||||||
/>
|
|
||||||
{/* {(!!item[quoteLenKey] || !!item[rawSearchKey]?.length) && (
|
|
||||||
<Button
|
|
||||||
size={'xs'}
|
|
||||||
variant={'base'}
|
|
||||||
mt={2}
|
|
||||||
w={'80px'}
|
|
||||||
onClick={() => {
|
|
||||||
setQuoteModalData({
|
|
||||||
contentId: item._id,
|
|
||||||
rawSearch: item[rawSearchKey]
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{item[quoteLenKey] || item[rawSearchKey]?.length}条引用
|
|
||||||
</Button>
|
|
||||||
)} */}
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Flex {...controlContainerStyle}>
|
|
||||||
<MyTooltip label={'复制'}>
|
<MyTooltip label={'复制'}>
|
||||||
<MyIcon
|
<MyIcon
|
||||||
{...controlIconStyle}
|
{...controlIconStyle}
|
||||||
@@ -660,6 +632,19 @@ const ChatBox = (
|
|||||||
</MyTooltip>
|
</MyTooltip>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
<Box position={'relative'} maxW={messageCardMaxW} mt={['6px', 2]}>
|
||||||
|
<Card bg={'white'} {...MessageCardStyle}>
|
||||||
|
<Markdown
|
||||||
|
source={item.value}
|
||||||
|
isChatting={index === chatHistory.length - 1 && isChatting}
|
||||||
|
/>
|
||||||
|
<ResponseDetailModal
|
||||||
|
chatId={chatId}
|
||||||
|
contentId={item._id}
|
||||||
|
responseData={item.responseData}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -753,14 +738,6 @@ const ChatBox = (
|
|||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
) : null}
|
) : null}
|
||||||
{/* quote modal */}
|
|
||||||
{!!quoteModalData && (
|
|
||||||
<QuoteModal
|
|
||||||
chatId={chatId}
|
|
||||||
{...quoteModalData}
|
|
||||||
onClose={() => setQuoteModalData(undefined)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@@ -1,8 +1,14 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Tooltip, TooltipProps } from '@chakra-ui/react';
|
import { Tooltip, TooltipProps } from '@chakra-ui/react';
|
||||||
|
import { useGlobalStore } from '@/store/global';
|
||||||
|
|
||||||
const MyTooltip = ({ children, ...props }: TooltipProps) => {
|
interface Props extends TooltipProps {
|
||||||
return (
|
forceShow?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MyTooltip = ({ children, forceShow = false, ...props }: Props) => {
|
||||||
|
const { isPc } = useGlobalStore();
|
||||||
|
return isPc || forceShow ? (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
bg={'white'}
|
bg={'white'}
|
||||||
arrowShadowColor={' rgba(0,0,0,0.1)'}
|
arrowShadowColor={' rgba(0,0,0,0.1)'}
|
||||||
@@ -19,6 +25,8 @@ const MyTooltip = ({ children, ...props }: TooltipProps) => {
|
|||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
<>{children}</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -1,52 +0,0 @@
|
|||||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
||||||
import { jsonRes } from '@/service/response';
|
|
||||||
import { connectToDatabase, Chat } from '@/service/mongo';
|
|
||||||
import { authUser } from '@/service/utils/auth';
|
|
||||||
import { Types } from 'mongoose';
|
|
||||||
|
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|
||||||
try {
|
|
||||||
const { chatId, contentId } = req.query as {
|
|
||||||
chatId: string;
|
|
||||||
contentId: string;
|
|
||||||
};
|
|
||||||
await connectToDatabase();
|
|
||||||
|
|
||||||
const { userId } = await authUser({ req, authToken: true });
|
|
||||||
|
|
||||||
if (!chatId || !contentId) {
|
|
||||||
throw new Error('params is error');
|
|
||||||
}
|
|
||||||
|
|
||||||
const history = await Chat.aggregate([
|
|
||||||
{
|
|
||||||
$match: {
|
|
||||||
_id: new Types.ObjectId(chatId),
|
|
||||||
userId: new Types.ObjectId(userId)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
$unwind: '$content'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
$match: {
|
|
||||||
'content._id': new Types.ObjectId(contentId)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
$project: {
|
|
||||||
// [rawSearchKey]: `$content.${rawSearchKey}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
jsonRes(res, {
|
|
||||||
// data: history[0]?.[rawSearchKey] || []
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
jsonRes(res, {
|
|
||||||
code: 500,
|
|
||||||
error: err
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,6 +1,6 @@
|
|||||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||||
import { jsonRes } from '@/service/response';
|
import { jsonRes } from '@/service/response';
|
||||||
import { connectToDatabase, Chat, App } from '@/service/mongo';
|
import { connectToDatabase, Chat } from '@/service/mongo';
|
||||||
import type { InitChatResponse } from '@/api/response/chat';
|
import type { InitChatResponse } from '@/api/response/chat';
|
||||||
import { authUser } from '@/service/utils/auth';
|
import { authUser } from '@/service/utils/auth';
|
||||||
import { ChatItemType } from '@/types/chat';
|
import { ChatItemType } from '@/types/chat';
|
||||||
@@ -59,7 +59,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
{
|
{
|
||||||
$project: {
|
$project: {
|
||||||
content: {
|
content: {
|
||||||
$slice: ['$content', -50] // 返回 content 数组的最后50个元素
|
$slice: ['$content', -30] // 返回 content 数组的最后 30 个元素
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -36,7 +36,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
export async function getChatHistory({
|
export async function getChatHistory({
|
||||||
chatId,
|
chatId,
|
||||||
userId,
|
userId,
|
||||||
limit = 50
|
limit = 20
|
||||||
}: Props & { userId: string }): Promise<Response> {
|
}: Props & { userId: string }): Promise<Response> {
|
||||||
if (!chatId) {
|
if (!chatId) {
|
||||||
return { history: [] };
|
return { history: [] };
|
||||||
@@ -47,7 +47,7 @@ export async function getChatHistory({
|
|||||||
{
|
{
|
||||||
$project: {
|
$project: {
|
||||||
content: {
|
content: {
|
||||||
$slice: ['$content', -limit] // 返回 content 数组的最后50个元素
|
$slice: ['$content', -limit] // 返回 content 数组的最后20个元素
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -140,7 +140,7 @@ const Settings = ({ appId }: { appId: string }) => {
|
|||||||
}, [appModule2Form]);
|
}, [appModule2Form]);
|
||||||
|
|
||||||
const BoxStyles: BoxProps = {
|
const BoxStyles: BoxProps = {
|
||||||
bg: 'myWhite.300',
|
bg: 'myWhite.200',
|
||||||
px: 4,
|
px: 4,
|
||||||
py: 3,
|
py: 3,
|
||||||
borderRadius: 'lg',
|
borderRadius: 'lg',
|
||||||
|
@@ -159,7 +159,7 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
|
|||||||
if (res.history.length > 0) {
|
if (res.history.length > 0) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
ChatBoxRef.current?.scrollToBottom('auto');
|
ChatBoxRef.current?.scrollToBottom('auto');
|
||||||
}, 200);
|
}, 500);
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
// reset all chat tore
|
// reset all chat tore
|
||||||
|
@@ -131,7 +131,9 @@ const ShareChat = ({ shareId, chatId }: { shareId: string; chatId: string }) =>
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (history.chats.length > 0) {
|
if (history.chats.length > 0) {
|
||||||
|
setTimeout(() => {
|
||||||
ChatBoxRef.current?.scrollToBottom('auto');
|
ChatBoxRef.current?.scrollToBottom('auto');
|
||||||
|
}, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
return history;
|
return history;
|
||||||
|
Reference in New Issue
Block a user