diff --git a/client/src/components/ChatBox/QuoteModal.tsx b/client/src/components/ChatBox/QuoteModal.tsx index 6bf3ef68e..5d785f754 100644 --- a/client/src/components/ChatBox/QuoteModal.tsx +++ b/client/src/components/ChatBox/QuoteModal.tsx @@ -13,21 +13,17 @@ import MyIcon from '@/components/Icon'; import InputDataModal from '@/pages/kb/detail/components/InputDataModal'; import { getKbDataItemById } from '@/api/plugins/kb'; import { useLoading } from '@/hooks/useLoading'; -import { useQuery } from '@tanstack/react-query'; -import { getHistoryQuote, updateHistoryQuote } from '@/api/chat'; import { useToast } from '@/hooks/useToast'; import { getErrText } from '@/utils/tools'; import { QuoteItemType } from '@/types/chat'; const QuoteModal = ({ - chatId, - contentId, + onUpdateQuote, rawSearch = [], onClose }: { - chatId?: string; - contentId?: string; - rawSearch?: QuoteItemType[]; + onUpdateQuote: (quoteId: string, sourceText: string) => Promise; + rawSearch: QuoteItemType[]; onClose: () => void; }) => { const theme = useTheme(); @@ -40,47 +36,6 @@ const QuoteModal = ({ 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 */ @@ -91,7 +46,7 @@ const QuoteModal = ({ const data = (await getKbDataItemById(item.id)) as QuoteItemType; if (!data) { - updateQuoteStatus(item.id, '已删除'); + onUpdateQuote(item.id, '已删除'); throw new Error('该数据已被删除'); } @@ -109,7 +64,7 @@ const QuoteModal = ({ } setIsLoading(false); }, - [setIsLoading, toast, updateQuoteStatus] + [setIsLoading, toast, onUpdateQuote] ); return ( @@ -123,14 +78,14 @@ const QuoteModal = ({ overflow={'overlay'} > - 知识库引用({quote.length}条) + 知识库引用({rawSearch.length}条) - 注意: 修改知识库内容成功后,此处不会显示。点击编辑后,才是显示最新的内容。 + 注意: 修改知识库内容成功后,此处不会显示变更情况。点击编辑后,会显示知识库最新的内容。 - {quote.map((item) => ( + {rawSearch.map((item) => ( ))} - + {editDataItem && ( setEditDataItem(undefined)} - onSuccess={() => updateQuoteStatus(editDataItem.dataId, '手动修改')} - onDelete={() => updateQuoteStatus(editDataItem.dataId, '已删除')} + onSuccess={() => onUpdateQuote(editDataItem.dataId, '手动修改')} + onDelete={() => onUpdateQuote(editDataItem.dataId, '已删除')} kbId={editDataItem.kbId} defaultValues={editDataItem} /> diff --git a/client/src/components/ChatBox/ResponseDetailModal.tsx b/client/src/components/ChatBox/ResponseDetailModal.tsx index 3756a57b1..de1f90209 100644 --- a/client/src/components/ChatBox/ResponseDetailModal.tsx +++ b/client/src/components/ChatBox/ResponseDetailModal.tsx @@ -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 = () => { - return
ResponseDetailModal
; +const ResponseDetailModal = ({ + chatId, + contentId, + responseData = [] +}: { + chatId?: string; + contentId?: string; + responseData?: ChatHistoryItemResType[]; +}) => { + console.log(responseData); + + const [quoteModalData, setQuoteModalData] = useState(); + + 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 : ( + + {quoteList.length > 0 && ( + + setQuoteModalData(quoteList)} + > + {quoteList.length}条引用 + + + )} + {completeMessages.length > 0 && ( + + {completeMessages.length}条上下文 + + )} + {tokens > 0 && ( + + {tokens}tokens + + )} + {/* */} + {!!quoteModalData && ( + setQuoteModalData(undefined)} + /> + )} + + ); }; export default ResponseDetailModal; diff --git a/client/src/components/ChatBox/index.tsx b/client/src/components/ChatBox/index.tsx index c7e115ca7..e3e1c11ec 100644 --- a/client/src/components/ChatBox/index.tsx +++ b/client/src/components/ChatBox/index.tsx @@ -23,7 +23,7 @@ import { hasVoiceApi, getErrText } 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 { Types } from 'mongoose'; import { EventNameEnum } from '../Markdown/constant'; @@ -38,12 +38,11 @@ import { fileDownload } from '@/utils/file'; import { htmlTemplate } from '@/constants/common'; import { useRouter } from 'next/router'; import { useGlobalStore } from '@/store/global'; -import { QuoteItemType } from '@/types/chat'; import { FlowModuleTypeEnum } from '@/constants/flow'; import { TaskResponseKeyEnum } from '@/constants/chat'; import dynamic from 'next/dynamic'; -const QuoteModal = dynamic(() => import('./QuoteModal')); +const ResponseDetailModal = dynamic(() => import('./ResponseDetailModal')); import MyIcon from '@/components/Icon'; import Avatar from '@/components/Avatar'; @@ -108,15 +107,22 @@ const Empty = () => { ); }; -const ChatAvatar = ({ - src, - ml, - mr -}: { - src?: string; - ml?: (string | number) | (string | number)[]; - mr?: (string | number) | (string | number)[]; -}) => ; +const ChatAvatar = ({ src, type }: { src?: string; type: 'Human' | 'AI' }) => { + const theme = useTheme(); + return ( + + + + ); +}; const ChatBox = ( { @@ -157,10 +163,6 @@ const ChatBox = ( const [refresh, setRefresh] = useState(false); const [variables, setVariables] = useState>({}); const [chatHistory, setChatHistory] = useState([]); - const [quoteModalData, setQuoteModalData] = useState<{ - contentId?: string; - rawSearch?: QuoteItemType[]; - }>(); const isChatting = useMemo( () => chatHistory[chatHistory.length - 1]?.status === 'loading', @@ -394,13 +396,16 @@ const ChatBox = ( color: 'myGray.400', display: ['flex', 'none'], pl: 1, - mt: 2, - position: 'absolute' as any, - zIndex: 1, - w: '100%' + mt: 2 + }; + const MessageCardStyle: BoxProps = { + 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( () => @@ -439,27 +444,19 @@ const ChatBox = ( h={0} w={'100%'} overflow={'overlay'} - px={[2, 5]} + px={[4, 0]} mt={[0, 5]} pb={3} > - + {showEmpty && } {!!welcomeText && ( - + {/* avatar */} - + {/* message */} - + + {/* avatar */} - + {/* message */} {variableModules.map((item) => ( @@ -540,8 +535,8 @@ const ChatBox = ( {item.obj === 'Human' && ( <> - - - {item.value} - - + + )} + + + + + {item.value} + - )} {item.obj === 'AI' && ( <> - - - - - {/* {(!!item[quoteLenKey] || !!item[rawSearchKey]?.length) && ( - - )} */} - - - + + + )} + + + + + + )} @@ -753,14 +738,6 @@ const ChatBox = ( ) : null} - {/* quote modal */} - {!!quoteModalData && ( - setQuoteModalData(undefined)} - /> - )} ); }; diff --git a/client/src/components/MyTooltip/index.tsx b/client/src/components/MyTooltip/index.tsx index b7058de77..40aaee0b1 100644 --- a/client/src/components/MyTooltip/index.tsx +++ b/client/src/components/MyTooltip/index.tsx @@ -1,8 +1,14 @@ import React from 'react'; import { Tooltip, TooltipProps } from '@chakra-ui/react'; +import { useGlobalStore } from '@/store/global'; -const MyTooltip = ({ children, ...props }: TooltipProps) => { - return ( +interface Props extends TooltipProps { + forceShow?: boolean; +} + +const MyTooltip = ({ children, forceShow = false, ...props }: Props) => { + const { isPc } = useGlobalStore(); + return isPc || forceShow ? ( { > {children} + ) : ( + <>{children} ); }; diff --git a/client/src/pages/api/chat/history/getHistoryQuote.ts b/client/src/pages/api/chat/history/getHistoryQuote.ts deleted file mode 100644 index 6d799c884..000000000 --- a/client/src/pages/api/chat/history/getHistoryQuote.ts +++ /dev/null @@ -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 - }); - } -} diff --git a/client/src/pages/api/chat/init.ts b/client/src/pages/api/chat/init.ts index d1e1cba6b..d12d0c043 100644 --- a/client/src/pages/api/chat/init.ts +++ b/client/src/pages/api/chat/init.ts @@ -1,6 +1,6 @@ import type { NextApiRequest, NextApiResponse } from 'next'; 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 { authUser } from '@/service/utils/auth'; import { ChatItemType } from '@/types/chat'; @@ -59,7 +59,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) { $project: { content: { - $slice: ['$content', -50] // 返回 content 数组的最后50个元素 + $slice: ['$content', -30] // 返回 content 数组的最后 30 个元素 } } }, diff --git a/client/src/pages/api/openapi/v1/chat/getHistory.ts b/client/src/pages/api/openapi/v1/chat/getHistory.ts index f487aeaf8..f14f3f945 100644 --- a/client/src/pages/api/openapi/v1/chat/getHistory.ts +++ b/client/src/pages/api/openapi/v1/chat/getHistory.ts @@ -36,7 +36,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) export async function getChatHistory({ chatId, userId, - limit = 50 + limit = 20 }: Props & { userId: string }): Promise { if (!chatId) { return { history: [] }; @@ -47,7 +47,7 @@ export async function getChatHistory({ { $project: { content: { - $slice: ['$content', -limit] // 返回 content 数组的最后50个元素 + $slice: ['$content', -limit] // 返回 content 数组的最后20个元素 } } }, diff --git a/client/src/pages/app/detail/components/BasicEdit/index.tsx b/client/src/pages/app/detail/components/BasicEdit/index.tsx index 250fb1516..7a9640926 100644 --- a/client/src/pages/app/detail/components/BasicEdit/index.tsx +++ b/client/src/pages/app/detail/components/BasicEdit/index.tsx @@ -140,7 +140,7 @@ const Settings = ({ appId }: { appId: string }) => { }, [appModule2Form]); const BoxStyles: BoxProps = { - bg: 'myWhite.300', + bg: 'myWhite.200', px: 4, py: 3, borderRadius: 'lg', diff --git a/client/src/pages/chat/index.tsx b/client/src/pages/chat/index.tsx index 1adaf3b30..9cef9b671 100644 --- a/client/src/pages/chat/index.tsx +++ b/client/src/pages/chat/index.tsx @@ -159,7 +159,7 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => { if (res.history.length > 0) { setTimeout(() => { ChatBoxRef.current?.scrollToBottom('auto'); - }, 200); + }, 500); } } catch (e: any) { // reset all chat tore diff --git a/client/src/pages/chat/share.tsx b/client/src/pages/chat/share.tsx index 2bfe73c1c..b18d914da 100644 --- a/client/src/pages/chat/share.tsx +++ b/client/src/pages/chat/share.tsx @@ -131,7 +131,9 @@ const ShareChat = ({ shareId, chatId }: { shareId: string; chatId: string }) => } if (history.chats.length > 0) { - ChatBoxRef.current?.scrollToBottom('auto'); + setTimeout(() => { + ChatBoxRef.current?.scrollToBottom('auto'); + }, 500); } return history;