From 85de3c1d64dca8222b29328f1b5c733674513b7b Mon Sep 17 00:00:00 2001 From: papapatrick <109422393+Patrickill@users.noreply.github.com> Date: Mon, 22 Jul 2024 18:25:25 +0800 Subject: [PATCH] Chat-perf (#2117) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 新增对话框底部复制按钮 * fix: 对话框底部复制按钮定位问题 * feat: 过长引用折叠功能 * merge: 合并主仓库代码 * refactor: 删除不必要代码 * doc: 文档补充的部分修改成英文 --- dev.md | 17 ++- .../web/components/common/Icon/constants.ts | 2 + .../Icon/icons/core/chat/chevronDown.svg | 3 + .../common/Icon/icons/core/chat/chevronUp.svg | 3 + .../ChatBox/components/ChatItem.tsx | 33 +++- .../ChatBox/components/ResponseTags.tsx | 143 +++++++++++++----- 6 files changed, 157 insertions(+), 44 deletions(-) create mode 100644 packages/web/components/common/Icon/icons/core/chat/chevronDown.svg create mode 100644 packages/web/components/common/Icon/icons/core/chat/chevronUp.svg diff --git a/dev.md b/dev.md index f6154854e..94cfe5ed0 100644 --- a/dev.md +++ b/dev.md @@ -23,12 +23,20 @@ pnpm dev make dev name=app ``` +Note: If the Node version is >= 20, you need to pass the `--no-node-snapshot` parameter to Node when running `pnpm i` + +```sh +NODE_OPTIONS=--no-node-snapshot pnpm i +``` + ## I18N + ### Install i18n-ally Plugin 1. Open the Extensions Marketplace in VSCode, search for and install the `i18n Ally` plugin. ### Code Optimization Examples + #### Fetch Specific Namespace Translations in `getServerSideProps` ```typescript @@ -42,7 +50,9 @@ export async function getServerSideProps(context: any) { }; } ``` + #### Use useTranslation Hook in Page + ```typescript // pages/yourPage.tsx import { useTranslation } from 'next-i18next'; @@ -64,7 +74,9 @@ const YourComponent = () => { export default YourComponent; ``` + #### Handle Static File Translations + ```typescript // utils/i18n.ts import { i18nT } from '@fastgpt/web/i18n/utils'; @@ -77,12 +89,12 @@ const staticContent = { export default staticContent; ``` + ### Standardize Translation Format + - Use the t(namespace:key) format to ensure consistent naming. - Translation keys should use lowercase letters and underscores, e.g., common.close. - - ## Build ```sh @@ -96,4 +108,3 @@ docker build -f ./projects/app/Dockerfile -t registry.cn-hangzhou.aliyuncs.com/f # Make cmd: Build image with proxy make build name=app image=registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.1 proxy=taobao ``` - diff --git a/packages/web/components/common/Icon/constants.ts b/packages/web/components/common/Icon/constants.ts index 14a58fd82..94f9450cf 100644 --- a/packages/web/components/common/Icon/constants.ts +++ b/packages/web/components/common/Icon/constants.ts @@ -106,6 +106,8 @@ export const iconPaths = { 'core/chat/fileSelect': () => import('./icons/core/chat/fileSelect.svg'), 'core/chat/finishSpeak': () => import('./icons/core/chat/finishSpeak.svg'), 'core/chat/quoteFill': () => import('./icons/core/chat/quoteFill.svg'), + 'core/chat/chevronDown': () => import('./icons/core/chat/chevronDown.svg'), + 'core/chat/chevronUp': () => import('./icons/core/chat/chevronUp.svg'), 'core/chat/quoteSign': () => import('./icons/core/chat/quoteSign.svg'), 'core/chat/recordFill': () => import('./icons/core/chat/recordFill.svg'), 'core/chat/sendFill': () => import('./icons/core/chat/sendFill.svg'), diff --git a/packages/web/components/common/Icon/icons/core/chat/chevronDown.svg b/packages/web/components/common/Icon/icons/core/chat/chevronDown.svg new file mode 100644 index 000000000..cc8ed229c --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/chat/chevronDown.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/web/components/common/Icon/icons/core/chat/chevronUp.svg b/packages/web/components/common/Icon/icons/core/chat/chevronUp.svg new file mode 100644 index 000000000..bdca3776b --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/chat/chevronUp.svg @@ -0,0 +1,3 @@ + + + diff --git a/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ChatItem.tsx b/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ChatItem.tsx index de8dd7d32..2cf15aed2 100644 --- a/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ChatItem.tsx +++ b/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ChatItem.tsx @@ -1,5 +1,5 @@ import { Box, BoxProps, Card, Flex } from '@chakra-ui/react'; -import React, { useMemo } from 'react'; +import React, { useMemo, useTransition } from 'react'; import ChatController, { type ChatControllerProps } from './ChatController'; import ChatAvatar from './ChatAvatar'; import { MessageCardStyle } from '../constants'; @@ -11,7 +11,10 @@ import FilesBlock from './FilesBox'; import { ChatBoxContext } from '../Provider'; import { useContextSelector } from 'use-context-selector'; import AIResponseBox from '../../../components/AIResponseBox'; - +import { useCopyData } from '@/web/common/hooks/useCopyData'; +import MyIcon from '@fastgpt/web/components/common/Icon'; +import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; +import { useTranslation } from 'next-i18next'; const colorMap = { [ChatStatusEnum.loading]: { bg: 'myGray.100', @@ -62,9 +65,11 @@ const ChatItem = ({ bg: 'myGray.50' }; + const { t } = useTranslation(); const isChatting = useContextSelector(ChatBoxContext, (v) => v.isChatting); const { chat } = chatControllerProps; - + const { copyData } = useCopyData(); + const chatText = useMemo(() => formatChatValue2InputType(chat.value).text || '', [chat.value]); const ContentCard = useMemo(() => { if (type === 'Human') { const { text, files = [] } = formatChatValue2InputType(chat.value); @@ -148,6 +153,28 @@ const ChatItem = ({ > {ContentCard} {children} + {/* 对话框底部的复制按钮 */} + {type == ChatRoleEnum.AI && (!isChatting || (isChatting && !isLastChild)) && ( + + + copyData(chatText)} + /> + + + )} diff --git a/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ResponseTags.tsx b/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ResponseTags.tsx index 4e10637fd..06a33a9f5 100644 --- a/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ResponseTags.tsx +++ b/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ResponseTags.tsx @@ -1,7 +1,7 @@ -import React, { useMemo, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { type ChatHistoryItemResType } from '@fastgpt/global/core/chat/type.d'; import { DispatchNodeResponseType } from '@fastgpt/global/core/workflow/runtime/type.d'; -import { Flex, useDisclosure, useTheme, Box } from '@chakra-ui/react'; +import { Flex, useDisclosure, Box, Collapse } from '@chakra-ui/react'; import { useTranslation } from 'next-i18next'; import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type'; import dynamic from 'next/dynamic'; @@ -13,6 +13,7 @@ import ChatBoxDivider from '@/components/core/chat/Divider'; import { strIsLink } from '@fastgpt/global/common/string/tools'; import MyIcon from '@fastgpt/web/components/common/Icon'; import { useSystem } from '@fastgpt/web/hooks/useSystem'; +import { useSize } from 'ahooks'; const QuoteModal = dynamic(() => import('./QuoteModal')); const ContextModal = dynamic(() => import('./ContextModal')); @@ -28,9 +29,9 @@ const ResponseTags = ({ flowResponses?: ChatHistoryItemResType[]; showDetail: boolean; }) => { - const theme = useTheme(); const { isPc } = useSystem(); const { t } = useTranslation(); + const quoteListRef = React.useRef(null); const [quoteModalData, setQuoteModalData] = useState<{ rawSearch: SearchDataResponseItemType[]; metadata?: { @@ -39,6 +40,8 @@ const ResponseTags = ({ sourceName: string; }; }>(); + const [isOverflow, setIsOverflow] = useState(true); + const [quoteFolded, setQuoteFolded] = useState(true); const [contextModalData, setContextModalData] = useState(); const { @@ -47,6 +50,13 @@ const ResponseTags = ({ onClose: onCloseWholeModal } = useDisclosure(); + const quoteListSize = useSize(quoteListRef); + useEffect(() => { + setIsOverflow( + quoteListRef.current ? quoteListRef.current.scrollHeight > (isPc ? 50 : 55) : true + ); + }, [isOverflow, quoteListSize]); + const { llmModuleAccount, quoteList = [], @@ -64,7 +74,6 @@ const ResponseTags = ({ .flat(); const chatData = flatResponse.find(isLLMNode); - const quoteList = flatResponse .filter((item) => item.moduleType === FlowNodeTypeEnum.datasetSearchNode) .map((item) => item.quoteList) @@ -80,7 +89,6 @@ const ResponseTags = ({ }, {} ); - return { llmModuleAccount: flatResponse.filter(isLLMNode).length, quoteList, @@ -102,44 +110,103 @@ const ResponseTags = ({ <> {sourceList.length > 0 && ( <> - - - {sourceList.map((item) => ( - + + + {' '} + + {quoteFolded && isOverflow && ( + setQuoteFolded(!quoteFolded)} + /> + )} + + + + { + { - e.stopPropagation(); - setQuoteModalData({ - rawSearch: quoteList, - metadata: { - collectionId: item.collectionId, - sourceId: item.sourceId, - sourceName: item.sourceName - } - }); - }} + flexWrap={'wrap'} + gap={2} + height={quoteFolded && isOverflow ? ['55px', '50px'] : 'auto'} + overflow={'hidden'} + _after={ + quoteFolded && isOverflow + ? { + content: '""', + position: 'absolute', + zIndex: 2, + bottom: 0, + left: 0, + width: '100%', + height: '50%', + background: + 'linear-gradient(to bottom, rgba(247,247,247,0), rgba(247, 247, 247, 0.91))', + pointerEvents: 'none' + } + : {} + } > - - - {item.sourceName} - + {sourceList.map((item) => { + return ( + + { + e.stopPropagation(); + setQuoteModalData({ + rawSearch: quoteList, + metadata: { + collectionId: item.collectionId, + sourceId: item.sourceId, + sourceName: item.sourceName + } + }); + }} + > + + + {item.sourceName} + + + + ); + })} + {isOverflow && !quoteFolded && ( + setQuoteFolded(!quoteFolded)} + /> + )} - - ))} + + } )}