feat: add chat history time label (#3024)

* feat:add chat and logs time

* feat: add chat history time label

* code perf

* code perf

---------

Co-authored-by: 勤劳上班的卑微小张 <jiazhan.zhang@ggimage.com>
This commit is contained in:
papapatrick 2024-11-04 10:52:58 +08:00 committed by archer
parent 469858877e
commit 727bd7144c
No known key found for this signature in database
GPG Key ID: 4446499B846D4A9E
17 changed files with 289 additions and 174 deletions

View File

@ -2,6 +2,7 @@ import dayjs from 'dayjs';
import cronParser from 'cron-parser'; import cronParser from 'cron-parser';
import utc from 'dayjs/plugin/utc'; import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone'; import timezone from 'dayjs/plugin/timezone';
import { i18nT } from '../../../web/i18n/utils';
dayjs.extend(utc); dayjs.extend(utc);
dayjs.extend(timezone); dayjs.extend(timezone);
@ -23,31 +24,51 @@ export const formatTimeToChatTime = (time: Date) => {
// 如果传入时间小于60秒返回刚刚 // 如果传入时间小于60秒返回刚刚
if (now.diff(target, 'second') < 60) { if (now.diff(target, 'second') < 60) {
return '刚刚'; return i18nT('common:just_now');
} }
// 如果时间是今天,展示几时:几分 // 如果时间是今天,展示几时:几分
//用#占位i18n生效后replace成:
if (now.isSame(target, 'day')) { if (now.isSame(target, 'day')) {
return target.format('HH : mm'); return 'common:' + target.format('HH#mm');
} }
// 如果是昨天,展示昨天 // 如果是昨天,展示昨天
if (now.subtract(1, 'day').isSame(target, 'day')) { if (now.subtract(1, 'day').isSame(target, 'day')) {
return '昨天'; return i18nT('common:yesterday');
}
// 如果是前天,展示前天
if (now.subtract(2, 'day').isSame(target, 'day')) {
return '前天';
} }
// 如果是今年,展示某月某日 // 如果是今年,展示某月某日
if (now.isSame(target, 'year')) { if (now.isSame(target, 'year')) {
return target.format('MM/DD'); return target.format('MM-DD');
} }
// 如果是更久之前,展示某年某月某日 // 如果是更久之前,展示某年某月某日
return target.format('YYYY/M/D'); return target.format('YYYY-M-D');
};
export const formatTimeToChatItemTime = (time: Date) => {
const now = dayjs();
const target = dayjs(time);
const detailTime = target.format('HH#mm');
// 如果时间是今天,展示几时:几分
if (now.isSame(target, 'day')) {
return 'common:' + detailTime;
}
// 如果是昨天,展示昨天+几时:几分
if (now.subtract(1, 'day').isSame(target, 'day')) {
return i18nT('common:yesterday_detail_time');
}
// 如果是今年,展示某月某日+几时:几分
if (now.isSame(target, 'year')) {
return target.format('MM-DD') + ' ' + detailTime;
}
// 如果是更久之前,展示某年某月某日+几时:几分
return target.format('YYYY-M-D') + ' ' + detailTime;
}; };
/* cron time parse */ /* cron time parse */

View File

@ -126,6 +126,7 @@ export type ChatSiteItemType = (UserChatItemType | SystemChatItemType | AIChatIt
moduleName?: string; moduleName?: string;
ttsBuffer?: Uint8Array; ttsBuffer?: Uint8Array;
responseData?: ChatHistoryItemResType[]; responseData?: ChatHistoryItemResType[];
time?: Date;
} & ChatBoxInputType & } & ChatBoxInputType &
ResponseTagItemType; ResponseTagItemType;

View File

@ -69,8 +69,12 @@
"code_error.system_error.community_version_num_limit": "Exceeded Open Source Version Limit, Please Upgrade to Commercial Version: https://tryfastgpt.ai", "code_error.system_error.community_version_num_limit": "Exceeded Open Source Version Limit, Please Upgrade to Commercial Version: https://tryfastgpt.ai",
"code_error.team_error.ai_points_not_enough": "Insufficient AI Points", "code_error.team_error.ai_points_not_enough": "Insufficient AI Points",
"code_error.team_error.app_amount_not_enough": "Application Limit Reached", "code_error.team_error.app_amount_not_enough": "Application Limit Reached",
"code_error.team_error.cannot_delete_default_group": "Cannot delete default group",
"code_error.team_error.dataset_amount_not_enough": "Dataset Limit Reached", "code_error.team_error.dataset_amount_not_enough": "Dataset Limit Reached",
"code_error.team_error.dataset_size_not_enough": "Insufficient Dataset Capacity, Please Expand", "code_error.team_error.dataset_size_not_enough": "Insufficient Dataset Capacity, Please Expand",
"code_error.team_error.group_name_duplicate": "Duplicate group name",
"code_error.team_error.group_name_empty": "Group name cannot be empty",
"code_error.team_error.group_not_exist": "Group does not exist",
"code_error.team_error.over_size": "error.team.overSize", "code_error.team_error.over_size": "error.team.overSize",
"code_error.team_error.plugin_amount_not_enough": "Plugin Limit Reached", "code_error.team_error.plugin_amount_not_enough": "Plugin Limit Reached",
"code_error.team_error.re_rank_not_enough": "Unauthorized to Use Re-Rank", "code_error.team_error.re_rank_not_enough": "Unauthorized to Use Re-Rank",
@ -1200,5 +1204,7 @@
"user.type": "Type", "user.type": "Type",
"verification": "Verification", "verification": "Verification",
"xx_search_result": "{{key}} Search Results", "xx_search_result": "{{key}} Search Results",
"yes": "Yes" "yes": "Yes",
"yesterday": "yesterday",
"yesterday_detail_time": "Yesterday {{time}}"
} }

View File

@ -18,6 +18,9 @@
"FAQ.switch_package_a": "套餐使用规则为优先使用更高级的套餐,因此,购买的新套餐若比当前套餐更高级,则新套餐立即生效:否则将继续使用当前套餐。", "FAQ.switch_package_a": "套餐使用规则为优先使用更高级的套餐,因此,购买的新套餐若比当前套餐更高级,则新套餐立即生效:否则将继续使用当前套餐。",
"FAQ.switch_package_q": "是否切换订阅套餐?", "FAQ.switch_package_q": "是否切换订阅套餐?",
"Folder": "文件夹", "Folder": "文件夹",
"just_now": "刚刚",
"yesterday": "昨天",
"yesterday_detail_time": "昨天 {{time}}",
"Login": "登录", "Login": "登录",
"Move": "移动", "Move": "移动",
"Name": "名称", "Name": "名称",

View File

@ -34,6 +34,7 @@ export type ChatProviderProps = OutLinkChatAuthProps & {
// not chat test params // not chat test params
chatId?: string; chatId?: string;
isLog?: boolean;
}; };
type useChatStoreType = OutLinkChatAuthProps & type useChatStoreType = OutLinkChatAuthProps &

View File

@ -1,5 +1,5 @@
import { Box, BoxProps, Card, Flex } from '@chakra-ui/react'; import { Box, BoxProps, Card, Flex } from '@chakra-ui/react';
import React, { useMemo } from 'react'; import React, { useMemo, useRef } from 'react';
import ChatController, { type ChatControllerProps } from './ChatController'; import ChatController, { type ChatControllerProps } from './ChatController';
import ChatAvatar from './ChatAvatar'; import ChatAvatar from './ChatAvatar';
import { MessageCardStyle } from '../constants'; import { MessageCardStyle } from '../constants';
@ -22,6 +22,9 @@ import { useTranslation } from 'next-i18next';
import { AIChatItemValueItemType, ChatItemValueItemType } from '@fastgpt/global/core/chat/type'; import { AIChatItemValueItemType, ChatItemValueItemType } from '@fastgpt/global/core/chat/type';
import { CodeClassNameEnum } from '@/components/Markdown/utils'; import { CodeClassNameEnum } from '@/components/Markdown/utils';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import { formatTimeToChatItemTime } from '@fastgpt/global/common/string/time';
import dayjs from 'dayjs';
const colorMap = { const colorMap = {
[ChatStatusEnum.loading]: { [ChatStatusEnum.loading]: {
@ -110,8 +113,10 @@ const AIContentCard = React.memo(function AIContentCard({
const ChatItem = (props: Props) => { const ChatItem = (props: Props) => {
const { type, avatar, statusBoxData, children, isLastChild, questionGuides = [], chat } = props; const { type, avatar, statusBoxData, children, isLastChild, questionGuides = [], chat } = props;
const styleMap: BoxProps = const { isPc } = useSystem();
type === ChatRoleEnum.Human
const styleMap: BoxProps = {
...(type === ChatRoleEnum.Human
? { ? {
order: 0, order: 0,
borderRadius: '8px 0 8px 8px', borderRadius: '8px 0 8px 8px',
@ -125,10 +130,16 @@ const ChatItem = (props: Props) => {
justifyContent: 'flex-start', justifyContent: 'flex-start',
textAlign: 'left', textAlign: 'left',
bg: 'myGray.50' bg: 'myGray.50'
}; }),
fontSize: 'mini',
fontWeight: '400',
color: 'myGray.500'
};
const { t } = useTranslation(); const { t } = useTranslation();
const isChatting = useContextSelector(ChatBoxContext, (v) => v.isChatting);
const { isChatting, isLog } = useContextSelector(ChatBoxContext, (v) => {
return { isChatting: v.isChatting, isLog: v.isLog };
});
const { copyData } = useCopyData(); const { copyData } = useCopyData();
@ -196,13 +207,32 @@ const ChatItem = (props: Props) => {
}, [chat.obj, chat.value, isChatting]); }, [chat.obj, chat.value, isChatting]);
return ( return (
<> <Box
_hover={{
'& .time-label': {
display: 'block'
}
}}
>
{/* control icon */} {/* control icon */}
<Flex w={'100%'} alignItems={'center'} gap={2} justifyContent={styleMap.justifyContent}> <Flex w={'100%'} alignItems={'flex-end'} gap={2} justifyContent={styleMap.justifyContent}>
{isChatting && type === ChatRoleEnum.AI && isLastChild ? null : ( {isChatting && type === ChatRoleEnum.AI && isLastChild ? null : (
<Box order={styleMap.order} ml={styleMap.ml}> <Flex order={styleMap.order} ml={styleMap.ml} align={'center'} gap={'0.62rem'}>
{chat.time && type === ChatRoleEnum.Human && (isPc || isLog) && (
<Box
className={'time-label'}
fontSize={styleMap.fontSize}
color={styleMap.color}
fontWeight={styleMap.fontWeight}
display={isLog ? 'block' : 'none'}
>
{t(formatTimeToChatItemTime(chat.time) as any, {
time: dayjs(chat.time).format('HH:mm')
}).replace('#', ':')}
</Box>
)}
<ChatController {...props} isLastChild={isLastChild} /> <ChatController {...props} isLastChild={isLastChild} />
</Box> </Flex>
)} )}
<ChatAvatar src={avatar} type={type} /> <ChatAvatar src={avatar} type={type} />
@ -290,7 +320,7 @@ const ChatItem = (props: Props) => {
</Card> </Card>
</Box> </Box>
))} ))}
</> </Box>
); );
}; };

View File

@ -67,6 +67,8 @@ import { useSystem } from '@fastgpt/web/hooks/useSystem';
import { useCreation, useMemoizedFn, useThrottleFn } from 'ahooks'; import { useCreation, useMemoizedFn, useThrottleFn } from 'ahooks';
import MyIcon from '@fastgpt/web/components/common/Icon'; import MyIcon from '@fastgpt/web/components/common/Icon';
import { mergeChatResponseData } from '@fastgpt/global/core/chat/utils'; import { mergeChatResponseData } from '@fastgpt/global/core/chat/utils';
import { formatTimeToChatItemTime } from '@fastgpt/global/common/string/time';
import dayjs from 'dayjs';
const ResponseTags = dynamic(() => import('./components/ResponseTags')); const ResponseTags = dynamic(() => import('./components/ResponseTags'));
const FeedbackModal = dynamic(() => import('./components/FeedbackModal')); const FeedbackModal = dynamic(() => import('./components/FeedbackModal'));
@ -108,6 +110,18 @@ type Props = OutLinkChatAuthProps &
onDelMessage?: (e: { contentId: string }) => void; onDelMessage?: (e: { contentId: string }) => void;
}; };
const ChatTimeBox = ({ time }: { time: Date }) => {
const { t } = useTranslation();
return (
<Box w={'100%'} fontSize={'mini'} textAlign={'center'} color={'myGray.500'} fontWeight={'400'}>
{t(formatTimeToChatItemTime(time) as any, {
time: dayjs(time).format('HH#mm')
}).replace('#', ':')}
</Box>
);
};
const ChatBox = ( const ChatBox = (
{ {
feedbackType = FeedbackTypeEnum.hidden, feedbackType = FeedbackTypeEnum.hidden,
@ -436,6 +450,7 @@ const ChatBox = (
{ {
dataId: getNanoid(24), dataId: getNanoid(24),
obj: ChatRoleEnum.Human, obj: ChatRoleEnum.Human,
time: new Date(),
value: [ value: [
...files.map((file) => ({ ...files.map((file) => ({
type: ChatItemValueTypeEnum.file, type: ChatItemValueTypeEnum.file,
@ -521,6 +536,7 @@ const ChatBox = (
return { return {
...item, ...item,
status: ChatStatusEnum.finish, status: ChatStatusEnum.finish,
time: new Date(),
responseData: item.responseData responseData: item.responseData
? mergeChatResponseData([...item.responseData, ...responseData]) ? mergeChatResponseData([...item.responseData, ...responseData])
: responseData : responseData
@ -562,6 +578,7 @@ const ChatBox = (
if (index !== state.length - 1) return item; if (index !== state.length - 1) return item;
return { return {
...item, ...item,
time: new Date(),
status: ChatStatusEnum.finish status: ChatStatusEnum.finish
}; };
}) })
@ -877,88 +894,98 @@ const ChatBox = (
{/* chat history */} {/* chat history */}
<Box id={'history'}> <Box id={'history'}>
{chatHistories.map((item, index) => ( {chatHistories.map((item, index) => (
<Box key={item.dataId} py={5}> <>
{item.obj === ChatRoleEnum.Human && ( {/* 并且时间和上一条的time相差超过十分钟 */}
<ChatItem {index !== 0 &&
type={item.obj} item.time &&
avatar={userAvatar} chatHistories[index - 1].time !== undefined &&
chat={item} new Date(item.time).getTime() -
onRetry={retryInput(item.dataId)} new Date(chatHistories[index - 1].time!).getTime() >
onDelete={delOneMessage(item.dataId)} 10 * 60 * 1000 && <ChatTimeBox time={item.time} />}
isLastChild={index === chatHistories.length - 1}
/> <Box key={item.dataId} py={6}>
)} {item.obj === ChatRoleEnum.Human && (
{item.obj === ChatRoleEnum.AI && (
<>
<ChatItem <ChatItem
type={item.obj} type={item.obj}
avatar={appAvatar} avatar={userAvatar}
chat={item} chat={item}
onRetry={retryInput(item.dataId)}
onDelete={delOneMessage(item.dataId)}
isLastChild={index === chatHistories.length - 1} isLastChild={index === chatHistories.length - 1}
{...{ />
showVoiceIcon, )}
shareId, {item.obj === ChatRoleEnum.AI && (
outLinkUid, <>
teamId, <ChatItem
teamToken, type={item.obj}
statusBoxData, avatar={appAvatar}
questionGuides, chat={item}
onMark: onMark( isLastChild={index === chatHistories.length - 1}
item, {...{
formatChatValue2InputType(chatHistories[index - 1]?.value)?.text showVoiceIcon,
), shareId,
onAddUserLike: onAddUserLike(item), outLinkUid,
onCloseUserLike: onCloseUserLike(item), teamId,
onAddUserDislike: onAddUserDislike(item), teamToken,
onReadUserDislike: onReadUserDislike(item) statusBoxData,
}} questionGuides,
> onMark: onMark(
<ResponseTags item,
showTags={index !== chatHistories.length - 1 || !isChatting} formatChatValue2InputType(chatHistories[index - 1]?.value)?.text
showDetail={!shareId && !teamId} ),
historyItem={item} onAddUserLike: onAddUserLike(item),
/> onCloseUserLike: onCloseUserLike(item),
onAddUserDislike: onAddUserDislike(item),
onReadUserDislike: onReadUserDislike(item)
}}
>
<ResponseTags
showTags={index !== chatHistories.length - 1 || !isChatting}
showDetail={!shareId && !teamId}
historyItem={item}
/>
{/* custom feedback */} {/* custom feedback */}
{item.customFeedbacks && item.customFeedbacks.length > 0 && ( {item.customFeedbacks && item.customFeedbacks.length > 0 && (
<Box> <Box>
<ChatBoxDivider <ChatBoxDivider
icon={'core/app/customFeedback'} icon={'core/app/customFeedback'}
text={t('common:core.app.feedback.Custom feedback')} text={t('common:core.app.feedback.Custom feedback')}
/> />
{item.customFeedbacks.map((text, i) => ( {item.customFeedbacks.map((text, i) => (
<Box key={i}> <Box key={i}>
<MyTooltip <MyTooltip
label={t('common:core.app.feedback.close custom feedback')} label={t('common:core.app.feedback.close custom feedback')}
>
<Checkbox
onChange={onCloseCustomFeedback(item, i)}
icon={<MyIcon name={'common/check'} w={'12px'} />}
> >
{text} <Checkbox
</Checkbox> onChange={onCloseCustomFeedback(item, i)}
</MyTooltip> icon={<MyIcon name={'common/check'} w={'12px'} />}
</Box> >
))} {text}
</Box> </Checkbox>
)} </MyTooltip>
{/* admin mark content */} </Box>
{showMarkIcon && item.adminFeedback && ( ))}
<Box fontSize={'sm'}>
<ChatBoxDivider
icon="core/app/markLight"
text={t('common:core.chat.Admin Mark Content')}
/>
<Box whiteSpace={'pre-wrap'}>
<Box color={'black'}>{item.adminFeedback.q}</Box>
<Box color={'myGray.600'}>{item.adminFeedback.a}</Box>
</Box> </Box>
</Box> )}
)} {/* admin mark content */}
</ChatItem> {showMarkIcon && item.adminFeedback && (
</> <Box fontSize={'sm'}>
)} <ChatBoxDivider
</Box> icon="core/app/markLight"
text={t('common:core.chat.Admin Mark Content')}
/>
<Box whiteSpace={'pre-wrap'}>
<Box color={'black'}>{item.adminFeedback.q}</Box>
<Box color={'myGray.600'}>{item.adminFeedback.a}</Box>
</Box>
</Box>
)}
</ChatItem>
</>
)}
</Box>
</>
))} ))}
</Box> </Box>
</Box> </Box>

View File

@ -43,7 +43,7 @@ const InformTable = () => {
<Flex alignItems={'center'}> <Flex alignItems={'center'}>
<Box fontWeight={'bold'}>{item.title}</Box> <Box fontWeight={'bold'}>{item.title}</Box>
<Box ml={2} color={'myGray.500'} flex={'1 0 0'}> <Box ml={2} color={'myGray.500'} flex={'1 0 0'}>
({formatTimeToChatTime(item.time)}) ({t(formatTimeToChatTime(item.time) as any).replace('#', ':')})
</Box> </Box>
{!item.read && ( {!item.read && (
<Button <Button

View File

@ -67,13 +67,13 @@ async function handler(
})(); })();
const fieldMap = { const fieldMap = {
[GetChatTypeEnum.normal]: `dataId obj value adminFeedback userBadFeedback userGoodFeedback ${ [GetChatTypeEnum.normal]: `dataId obj value adminFeedback userBadFeedback userGoodFeedback time ${
DispatchNodeResponseKeyEnum.nodeResponse DispatchNodeResponseKeyEnum.nodeResponse
} ${loadCustomFeedbacks ? 'customFeedbacks' : ''}`, } ${loadCustomFeedbacks ? 'customFeedbacks' : ''}`,
[GetChatTypeEnum.outLink]: `dataId obj value userGoodFeedback userBadFeedback adminFeedback ${ [GetChatTypeEnum.outLink]: `dataId obj value userGoodFeedback userBadFeedback adminFeedback time ${
shareChat?.responseDetail || isPlugin ? `${DispatchNodeResponseKeyEnum.nodeResponse}` : '' shareChat?.responseDetail || isPlugin ? `${DispatchNodeResponseKeyEnum.nodeResponse}` : ''
} `, } `,
[GetChatTypeEnum.team]: `dataId obj value userGoodFeedback userBadFeedback adminFeedback ${DispatchNodeResponseKeyEnum.nodeResponse}` [GetChatTypeEnum.team]: `dataId obj value userGoodFeedback userBadFeedback adminFeedback time ${DispatchNodeResponseKeyEnum.nodeResponse}`
}; };
const { total, histories } = await getChatItems({ const { total, histories } = await getChatItems({

View File

@ -180,6 +180,7 @@ const DetailLogsModal = ({
chatConfig={chat?.app?.chatConfig} chatConfig={chat?.app?.chatConfig}
appId={appId} appId={appId}
chatId={chatId} chatId={chatId}
isLog={true}
/> />
)} )}
</Box> </Box>

View File

@ -147,7 +147,7 @@ const FeiShu = ({ appId }: { appId: string }) => {
)} )}
<Td> <Td>
{item.lastTime {item.lastTime
? t(formatTimeToChatTime(item.lastTime) as any) ? t(formatTimeToChatTime(item.lastTime) as any).replace('#', ':')
: t('common:common.Un used')} : t('common:common.Un used')}
</Td> </Td>
<Td display={'flex'} alignItems={'center'}> <Td display={'flex'} alignItems={'center'}>

View File

@ -150,7 +150,9 @@ const Share = ({ appId }: { appId: string; type: PublishChannelEnum }) => {
</> </>
)} )}
<Td> <Td>
{item.lastTime ? formatTimeToChatTime(item.lastTime) : t('common:common.Un used')} {item.lastTime
? t(formatTimeToChatTime(item.lastTime) as any).replace('#', ':')
: t('common:common.Un used')}
</Td> </Td>
<Td display={'flex'} alignItems={'center'}> <Td display={'flex'} alignItems={'center'}>
<Button <Button

View File

@ -150,7 +150,7 @@ const OffiAccount = ({ appId }: { appId: string }) => {
)} )}
<Td> <Td>
{item.lastTime {item.lastTime
? t(formatTimeToChatTime(item.lastTime) as any) ? t(formatTimeToChatTime(item.lastTime) as any).replace('#', ':')
: t('common:common.Un used')} : t('common:common.Un used')}
</Td> </Td>
<Td display={'flex'} alignItems={'center'}> <Td display={'flex'} alignItems={'center'}>

View File

@ -126,7 +126,7 @@ const Wecom = ({ appId }: { appId: string }) => {
)} )}
<Td> <Td>
{item.lastTime {item.lastTime
? t(formatTimeToChatTime(item.lastTime) as any) ? t(formatTimeToChatTime(item.lastTime) as any).replace('#', ':')
: t('common:common.Un used')} : t('common:common.Un used')}
</Td> </Td>
<Td display={'flex'} alignItems={'center'}> <Td display={'flex'} alignItems={'center'}>

View File

@ -249,7 +249,9 @@ const ListItem = () => {
{isPc && ( {isPc && (
<HStack spacing={0.5} className="time"> <HStack spacing={0.5} className="time">
<MyIcon name={'history'} w={'0.85rem'} color={'myGray.400'} /> <MyIcon name={'history'} w={'0.85rem'} color={'myGray.400'} />
<Box color={'myGray.500'}>{formatTimeToChatTime(app.updateTime)}</Box> <Box color={'myGray.500'}>
{t(formatTimeToChatTime(app.updateTime) as any).replace('#', ':')}
</Box>
</HStack> </HStack>
)} )}
{(AppFolderTypeList.includes(app.type) {(AppFolderTypeList.includes(app.type)

View File

@ -13,12 +13,14 @@ import MyMenu from '@fastgpt/web/components/common/MyMenu';
import { useContextSelector } from 'use-context-selector'; import { useContextSelector } from 'use-context-selector';
import { ChatContext } from '@/web/core/chat/context/chatContext'; import { ChatContext } from '@/web/core/chat/context/chatContext';
import MyBox from '@fastgpt/web/components/common/MyBox'; import MyBox from '@fastgpt/web/components/common/MyBox';
import { formatTimeToChatTime } from '@fastgpt/global/common/string/time';
type HistoryItemType = { type HistoryItemType = {
id: string; id: string;
title: string; title: string;
customTitle?: string; customTitle?: string;
top?: boolean; top?: boolean;
updateTime: Date;
}; };
const ChatHistorySlider = ({ const ChatHistorySlider = ({
@ -59,11 +61,18 @@ const ChatHistorySlider = ({
const concatHistory = useMemo(() => { const concatHistory = useMemo(() => {
const formatHistories: HistoryItemType[] = histories.map((item) => { const formatHistories: HistoryItemType[] = histories.map((item) => {
return { id: item.chatId, title: item.title, customTitle: item.customTitle, top: item.top }; return {
id: item.chatId,
title: item.title,
customTitle: item.customTitle,
top: item.top,
updateTime: item.updateTime
};
}); });
const newChat: HistoryItemType = { const newChat: HistoryItemType = {
id: activeChatId, id: activeChatId,
title: t('common:core.chat.New Chat') title: t('common:core.chat.New Chat'),
updateTime: new Date()
}; };
const activeChat = histories.find((item) => item.chatId === activeChatId); const activeChat = histories.find((item) => item.chatId === activeChatId);
@ -188,7 +197,10 @@ const ChatHistorySlider = ({
_hover={{ _hover={{
bg: 'myGray.50', bg: 'myGray.50',
'& .more': { '& .more': {
visibility: 'visible' display: 'block'
},
'& .time': {
display: isPc ? 'none' : 'block'
} }
}} }}
bg={item.top ? '#E6F6F6 !important' : ''} bg={item.top ? '#E6F6F6 !important' : ''}
@ -214,69 +226,80 @@ const ChatHistorySlider = ({
{item.customTitle || item.title} {item.customTitle || item.title}
</Box> </Box>
{!!item.id && ( {!!item.id && (
<Box className="more" visibility={['visible', 'hidden']}> <Flex gap={2} alignItems={'center'}>
<MyMenu <Box
Button={ className="time"
<IconButton display={'block'}
size={'xs'} fontWeight={'400'}
variant={'whiteBase'} fontSize={'mini'}
icon={<MyIcon name={'more'} w={'14px'} p={1} />} color={'myGray.500'}
aria-label={''} >
/> {t(formatTimeToChatTime(item.updateTime) as any).replace('#', ':')}
} </Box>
menuList={[ <Box className="more" display={['block', 'none']}>
{ <MyMenu
children: [ Button={
...(onSetHistoryTop <IconButton
? [ size={'xs'}
{ variant={'whiteBase'}
label: item.top icon={<MyIcon name={'more'} w={'14px'} p={1} />}
? t('common:core.chat.Unpin') aria-label={''}
: t('common:core.chat.Pin'), />
icon: 'core/chat/setTopLight',
onClick: () => {
onSetHistoryTop({
chatId: item.id,
top: !item.top
});
}
}
]
: []),
...(onSetCustomTitle
? [
{
label: t('common:common.Custom Title'),
icon: 'common/customTitleLight',
onClick: () => {
onOpenModal({
defaultVal: item.customTitle || item.title,
onSuccess: (e) =>
onSetCustomTitle({
chatId: item.id,
title: e
})
});
}
}
]
: []),
{
label: t('common:common.Delete'),
icon: 'delete',
onClick: () => {
onDelHistory({ chatId: item.id });
if (item.id === activeChatId) {
onChangeChatId();
}
},
type: 'danger'
}
]
} }
]} menuList={[
/> {
</Box> children: [
...(onSetHistoryTop
? [
{
label: item.top
? t('common:core.chat.Unpin')
: t('common:core.chat.Pin'),
icon: 'core/chat/setTopLight',
onClick: () => {
onSetHistoryTop({
chatId: item.id,
top: !item.top
});
}
}
]
: []),
...(onSetCustomTitle
? [
{
label: t('common:common.Custom Title'),
icon: 'common/customTitleLight',
onClick: () => {
onOpenModal({
defaultVal: item.customTitle || item.title,
onSuccess: (e) =>
onSetCustomTitle({
chatId: item.id,
title: e
})
});
}
}
]
: []),
{
label: t('common:common.Delete'),
icon: 'delete',
onClick: () => {
onDelHistory({ chatId: item.id });
if (item.id === activeChatId) {
onChangeChatId();
}
},
type: 'danger'
}
]
}
]}
/>
</Box>
</Flex>
)} )}
</Flex> </Flex>
))} ))}

View File

@ -382,9 +382,7 @@ const TestHistories = React.memo(function TestHistories({
{item.text} {item.text}
</Box> </Box>
<Box flex={'0 0 70px'}> <Box flex={'0 0 70px'}>
{formatTimeToChatTime(item.time).includes('.') {t(formatTimeToChatTime(item.time) as any).replace('#', ':')}
? t(formatTimeToChatTime(item.time) as any)
: formatTimeToChatTime(item.time)}
</Box> </Box>
<MyTooltip label={t('common:core.dataset.test.delete test history')}> <MyTooltip label={t('common:core.dataset.test.delete test history')}>
<Box w={'14px'} h={'14px'}> <Box w={'14px'} h={'14px'}>