mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-23 05:12:39 +00:00
Chat-perf (#2117)
* feat: 新增对话框底部复制按钮 * fix: 对话框底部复制按钮定位问题 * feat: 过长引用折叠功能 * merge: 合并主仓库代码 * refactor: 删除不必要代码 * doc: 文档补充的部分修改成英文
This commit is contained in:
17
dev.md
17
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
|
||||
```
|
||||
|
||||
|
@@ -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'),
|
||||
|
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 19 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.237 6.21967C4.5299 5.92678 5.00477 5.92678 5.29766 6.21967L9.26733 10.1893L13.237 6.21967C13.5299 5.92678 14.0048 5.92678 14.2977 6.21967C14.5906 6.51256 14.5906 6.98744 14.2977 7.28033L9.79766 11.7803C9.50477 12.0732 9.0299 12.0732 8.737 11.7803L4.237 7.28033C3.94411 6.98744 3.94411 6.51256 4.237 6.21967Z" />
|
||||
</svg>
|
After Width: | Height: | Size: 444 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.96967 11.7803C4.26256 12.0732 4.73744 12.0732 5.03033 11.7803L9 7.81066L12.9697 11.7803C13.2626 12.0732 13.7374 12.0732 14.0303 11.7803C14.3232 11.4874 14.3232 11.0126 14.0303 10.7197L9.53033 6.21967C9.23744 5.92678 8.76256 5.92678 8.46967 6.21967L3.96967 10.7197C3.67678 11.0126 3.67678 11.4874 3.96967 11.7803Z" fill="#667085"/>
|
||||
</svg>
|
After Width: | Height: | Size: 486 B |
@@ -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)) && (
|
||||
<Box
|
||||
position={'absolute'}
|
||||
bottom={0}
|
||||
right={[0, -2]}
|
||||
color={'myGray.400'}
|
||||
transform={'translateX(100%)'}
|
||||
>
|
||||
<MyTooltip label={t('common.Copy')}>
|
||||
<MyIcon
|
||||
w={'14px'}
|
||||
cursor="pointer"
|
||||
p="5px"
|
||||
bg="white"
|
||||
name={'copy'}
|
||||
_hover={{ color: 'primary.600' }}
|
||||
onClick={() => copyData(chatText)}
|
||||
/>
|
||||
</MyTooltip>
|
||||
</Box>
|
||||
)}
|
||||
</Card>
|
||||
</Box>
|
||||
</>
|
||||
|
@@ -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<HTMLDivElement>(null);
|
||||
const [quoteModalData, setQuoteModalData] = useState<{
|
||||
rawSearch: SearchDataResponseItemType[];
|
||||
metadata?: {
|
||||
@@ -39,6 +40,8 @@ const ResponseTags = ({
|
||||
sourceName: string;
|
||||
};
|
||||
}>();
|
||||
const [isOverflow, setIsOverflow] = useState<boolean>(true);
|
||||
const [quoteFolded, setQuoteFolded] = useState<boolean>(true);
|
||||
const [contextModalData, setContextModalData] =
|
||||
useState<DispatchNodeResponseType['historyPreview']>();
|
||||
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 && (
|
||||
<>
|
||||
<ChatBoxDivider icon="core/chat/quoteFill" text={t('common:core.chat.Quote')} />
|
||||
<Flex alignItems={'center'} flexWrap={'wrap'} gap={2}>
|
||||
{sourceList.map((item) => (
|
||||
<MyTooltip key={item.collectionId} label={t('common:core.chat.quote.Read Quote')}>
|
||||
<Flex justifyContent={'space-between'} alignItems={'center'}>
|
||||
<Box width={'100%'}>
|
||||
<ChatBoxDivider icon="core/chat/quoteFill" text={t('common:core.chat.Quote')} />{' '}
|
||||
</Box>
|
||||
{quoteFolded && isOverflow && (
|
||||
<MyIcon
|
||||
_hover={{ color: 'primary.500', cursor: 'pointer' }}
|
||||
name="core/chat/chevronDown"
|
||||
w={'14px'}
|
||||
onClick={() => setQuoteFolded(!quoteFolded)}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
<Flex alignItems={'center'} flexWrap={'wrap'} gap={2} position={'relative'}>
|
||||
{
|
||||
<Collapse
|
||||
startingHeight={isPc ? '50px' : '55px'}
|
||||
in={(!quoteFolded && isOverflow) || !isOverflow}
|
||||
>
|
||||
<Flex
|
||||
ref={quoteListRef}
|
||||
alignItems={'center'}
|
||||
fontSize={'xs'}
|
||||
border={theme.borders.sm}
|
||||
py={1.5}
|
||||
px={2}
|
||||
borderRadius={'sm'}
|
||||
_hover={{
|
||||
'.controller': {
|
||||
display: 'flex'
|
||||
}
|
||||
}}
|
||||
overflow={'hidden'}
|
||||
position={'relative'}
|
||||
cursor={'pointer'}
|
||||
onClick={(e) => {
|
||||
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'
|
||||
}
|
||||
: {}
|
||||
}
|
||||
>
|
||||
<MyIcon name={item.icon as any} mr={1} flexShrink={0} w={'12px'} />
|
||||
<Box className="textEllipsis3" wordBreak={'break-all'} flex={'1 0 0'}>
|
||||
{item.sourceName}
|
||||
</Box>
|
||||
{sourceList.map((item) => {
|
||||
return (
|
||||
<MyTooltip key={item.collectionId} label={t('core.chat.quote.Read Quote')}>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
fontSize={'xs'}
|
||||
border={'sm'}
|
||||
py={1.5}
|
||||
px={2}
|
||||
borderRadius={'sm'}
|
||||
_hover={{
|
||||
'.controller': {
|
||||
display: 'flex'
|
||||
}
|
||||
}}
|
||||
overflow={'hidden'}
|
||||
position={'relative'}
|
||||
cursor={'pointer'}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setQuoteModalData({
|
||||
rawSearch: quoteList,
|
||||
metadata: {
|
||||
collectionId: item.collectionId,
|
||||
sourceId: item.sourceId,
|
||||
sourceName: item.sourceName
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<MyIcon name={item.icon as any} mr={1} flexShrink={0} w={'12px'} />
|
||||
<Box className="textEllipsis3" wordBreak={'break-all'} flex={'1 0 0'}>
|
||||
{item.sourceName}
|
||||
</Box>
|
||||
</Flex>
|
||||
</MyTooltip>
|
||||
);
|
||||
})}
|
||||
{isOverflow && !quoteFolded && (
|
||||
<MyIcon
|
||||
position={'absolute'}
|
||||
bottom={0}
|
||||
right={0}
|
||||
_hover={{ color: 'primary.500', cursor: 'pointer' }}
|
||||
name="core/chat/chevronUp"
|
||||
w={'14px'}
|
||||
onClick={() => setQuoteFolded(!quoteFolded)}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
</MyTooltip>
|
||||
))}
|
||||
</Collapse>
|
||||
}
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
|
Reference in New Issue
Block a user