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:
parent
469858877e
commit
727bd7144c
@ -2,6 +2,7 @@ import dayjs from 'dayjs';
|
||||
import cronParser from 'cron-parser';
|
||||
import utc from 'dayjs/plugin/utc';
|
||||
import timezone from 'dayjs/plugin/timezone';
|
||||
import { i18nT } from '../../../web/i18n/utils';
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
@ -23,31 +24,51 @@ export const formatTimeToChatTime = (time: Date) => {
|
||||
|
||||
// 如果传入时间小于60秒,返回刚刚
|
||||
if (now.diff(target, 'second') < 60) {
|
||||
return '刚刚';
|
||||
return i18nT('common:just_now');
|
||||
}
|
||||
|
||||
// 如果时间是今天,展示几时:几分
|
||||
//用#占位,i18n生效后replace成:
|
||||
if (now.isSame(target, 'day')) {
|
||||
return target.format('HH : mm');
|
||||
return 'common:' + target.format('HH#mm');
|
||||
}
|
||||
|
||||
// 如果是昨天,展示昨天
|
||||
if (now.subtract(1, 'day').isSame(target, 'day')) {
|
||||
return '昨天';
|
||||
}
|
||||
|
||||
// 如果是前天,展示前天
|
||||
if (now.subtract(2, 'day').isSame(target, 'day')) {
|
||||
return '前天';
|
||||
return i18nT('common:yesterday');
|
||||
}
|
||||
|
||||
// 如果是今年,展示某月某日
|
||||
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 */
|
||||
|
||||
1
packages/global/core/chat/type.d.ts
vendored
1
packages/global/core/chat/type.d.ts
vendored
@ -126,6 +126,7 @@ export type ChatSiteItemType = (UserChatItemType | SystemChatItemType | AIChatIt
|
||||
moduleName?: string;
|
||||
ttsBuffer?: Uint8Array;
|
||||
responseData?: ChatHistoryItemResType[];
|
||||
time?: Date;
|
||||
} & ChatBoxInputType &
|
||||
ResponseTagItemType;
|
||||
|
||||
|
||||
@ -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.team_error.ai_points_not_enough": "Insufficient AI Points",
|
||||
"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_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.plugin_amount_not_enough": "Plugin Limit Reached",
|
||||
"code_error.team_error.re_rank_not_enough": "Unauthorized to Use Re-Rank",
|
||||
@ -1200,5 +1204,7 @@
|
||||
"user.type": "Type",
|
||||
"verification": "Verification",
|
||||
"xx_search_result": "{{key}} Search Results",
|
||||
"yes": "Yes"
|
||||
"yes": "Yes",
|
||||
"yesterday": "yesterday",
|
||||
"yesterday_detail_time": "Yesterday {{time}}"
|
||||
}
|
||||
|
||||
@ -18,6 +18,9 @@
|
||||
"FAQ.switch_package_a": "套餐使用规则为优先使用更高级的套餐,因此,购买的新套餐若比当前套餐更高级,则新套餐立即生效:否则将继续使用当前套餐。",
|
||||
"FAQ.switch_package_q": "是否切换订阅套餐?",
|
||||
"Folder": "文件夹",
|
||||
"just_now": "刚刚",
|
||||
"yesterday": "昨天",
|
||||
"yesterday_detail_time": "昨天 {{time}}",
|
||||
"Login": "登录",
|
||||
"Move": "移动",
|
||||
"Name": "名称",
|
||||
|
||||
@ -34,6 +34,7 @@ export type ChatProviderProps = OutLinkChatAuthProps & {
|
||||
|
||||
// not chat test params
|
||||
chatId?: string;
|
||||
isLog?: boolean;
|
||||
};
|
||||
|
||||
type useChatStoreType = OutLinkChatAuthProps &
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
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 ChatAvatar from './ChatAvatar';
|
||||
import { MessageCardStyle } from '../constants';
|
||||
@ -22,6 +22,9 @@ import { useTranslation } from 'next-i18next';
|
||||
import { AIChatItemValueItemType, ChatItemValueItemType } from '@fastgpt/global/core/chat/type';
|
||||
import { CodeClassNameEnum } from '@/components/Markdown/utils';
|
||||
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 = {
|
||||
[ChatStatusEnum.loading]: {
|
||||
@ -110,8 +113,10 @@ const AIContentCard = React.memo(function AIContentCard({
|
||||
const ChatItem = (props: Props) => {
|
||||
const { type, avatar, statusBoxData, children, isLastChild, questionGuides = [], chat } = props;
|
||||
|
||||
const styleMap: BoxProps =
|
||||
type === ChatRoleEnum.Human
|
||||
const { isPc } = useSystem();
|
||||
|
||||
const styleMap: BoxProps = {
|
||||
...(type === ChatRoleEnum.Human
|
||||
? {
|
||||
order: 0,
|
||||
borderRadius: '8px 0 8px 8px',
|
||||
@ -125,10 +130,16 @@ const ChatItem = (props: Props) => {
|
||||
justifyContent: 'flex-start',
|
||||
textAlign: 'left',
|
||||
bg: 'myGray.50'
|
||||
};
|
||||
|
||||
}),
|
||||
fontSize: 'mini',
|
||||
fontWeight: '400',
|
||||
color: 'myGray.500'
|
||||
};
|
||||
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();
|
||||
|
||||
@ -196,13 +207,32 @@ const ChatItem = (props: Props) => {
|
||||
}, [chat.obj, chat.value, isChatting]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
_hover={{
|
||||
'& .time-label': {
|
||||
display: 'block'
|
||||
}
|
||||
}}
|
||||
>
|
||||
{/* 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 : (
|
||||
<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} />
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
<ChatAvatar src={avatar} type={type} />
|
||||
|
||||
@ -290,7 +320,7 @@ const ChatItem = (props: Props) => {
|
||||
</Card>
|
||||
</Box>
|
||||
))}
|
||||
</>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -67,6 +67,8 @@ import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
import { useCreation, useMemoizedFn, useThrottleFn } from 'ahooks';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
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 FeedbackModal = dynamic(() => import('./components/FeedbackModal'));
|
||||
@ -108,6 +110,18 @@ type Props = OutLinkChatAuthProps &
|
||||
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 = (
|
||||
{
|
||||
feedbackType = FeedbackTypeEnum.hidden,
|
||||
@ -436,6 +450,7 @@ const ChatBox = (
|
||||
{
|
||||
dataId: getNanoid(24),
|
||||
obj: ChatRoleEnum.Human,
|
||||
time: new Date(),
|
||||
value: [
|
||||
...files.map((file) => ({
|
||||
type: ChatItemValueTypeEnum.file,
|
||||
@ -521,6 +536,7 @@ const ChatBox = (
|
||||
return {
|
||||
...item,
|
||||
status: ChatStatusEnum.finish,
|
||||
time: new Date(),
|
||||
responseData: item.responseData
|
||||
? mergeChatResponseData([...item.responseData, ...responseData])
|
||||
: responseData
|
||||
@ -562,6 +578,7 @@ const ChatBox = (
|
||||
if (index !== state.length - 1) return item;
|
||||
return {
|
||||
...item,
|
||||
time: new Date(),
|
||||
status: ChatStatusEnum.finish
|
||||
};
|
||||
})
|
||||
@ -877,88 +894,98 @@ const ChatBox = (
|
||||
{/* chat history */}
|
||||
<Box id={'history'}>
|
||||
{chatHistories.map((item, index) => (
|
||||
<Box key={item.dataId} py={5}>
|
||||
{item.obj === ChatRoleEnum.Human && (
|
||||
<ChatItem
|
||||
type={item.obj}
|
||||
avatar={userAvatar}
|
||||
chat={item}
|
||||
onRetry={retryInput(item.dataId)}
|
||||
onDelete={delOneMessage(item.dataId)}
|
||||
isLastChild={index === chatHistories.length - 1}
|
||||
/>
|
||||
)}
|
||||
{item.obj === ChatRoleEnum.AI && (
|
||||
<>
|
||||
<>
|
||||
{/* 并且时间和上一条的time相差超过十分钟 */}
|
||||
{index !== 0 &&
|
||||
item.time &&
|
||||
chatHistories[index - 1].time !== undefined &&
|
||||
new Date(item.time).getTime() -
|
||||
new Date(chatHistories[index - 1].time!).getTime() >
|
||||
10 * 60 * 1000 && <ChatTimeBox time={item.time} />}
|
||||
|
||||
<Box key={item.dataId} py={6}>
|
||||
{item.obj === ChatRoleEnum.Human && (
|
||||
<ChatItem
|
||||
type={item.obj}
|
||||
avatar={appAvatar}
|
||||
avatar={userAvatar}
|
||||
chat={item}
|
||||
onRetry={retryInput(item.dataId)}
|
||||
onDelete={delOneMessage(item.dataId)}
|
||||
isLastChild={index === chatHistories.length - 1}
|
||||
{...{
|
||||
showVoiceIcon,
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken,
|
||||
statusBoxData,
|
||||
questionGuides,
|
||||
onMark: onMark(
|
||||
item,
|
||||
formatChatValue2InputType(chatHistories[index - 1]?.value)?.text
|
||||
),
|
||||
onAddUserLike: onAddUserLike(item),
|
||||
onCloseUserLike: onCloseUserLike(item),
|
||||
onAddUserDislike: onAddUserDislike(item),
|
||||
onReadUserDislike: onReadUserDislike(item)
|
||||
}}
|
||||
>
|
||||
<ResponseTags
|
||||
showTags={index !== chatHistories.length - 1 || !isChatting}
|
||||
showDetail={!shareId && !teamId}
|
||||
historyItem={item}
|
||||
/>
|
||||
/>
|
||||
)}
|
||||
{item.obj === ChatRoleEnum.AI && (
|
||||
<>
|
||||
<ChatItem
|
||||
type={item.obj}
|
||||
avatar={appAvatar}
|
||||
chat={item}
|
||||
isLastChild={index === chatHistories.length - 1}
|
||||
{...{
|
||||
showVoiceIcon,
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken,
|
||||
statusBoxData,
|
||||
questionGuides,
|
||||
onMark: onMark(
|
||||
item,
|
||||
formatChatValue2InputType(chatHistories[index - 1]?.value)?.text
|
||||
),
|
||||
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 */}
|
||||
{item.customFeedbacks && item.customFeedbacks.length > 0 && (
|
||||
<Box>
|
||||
<ChatBoxDivider
|
||||
icon={'core/app/customFeedback'}
|
||||
text={t('common:core.app.feedback.Custom feedback')}
|
||||
/>
|
||||
{item.customFeedbacks.map((text, i) => (
|
||||
<Box key={i}>
|
||||
<MyTooltip
|
||||
label={t('common:core.app.feedback.close custom feedback')}
|
||||
>
|
||||
<Checkbox
|
||||
onChange={onCloseCustomFeedback(item, i)}
|
||||
icon={<MyIcon name={'common/check'} w={'12px'} />}
|
||||
{/* custom feedback */}
|
||||
{item.customFeedbacks && item.customFeedbacks.length > 0 && (
|
||||
<Box>
|
||||
<ChatBoxDivider
|
||||
icon={'core/app/customFeedback'}
|
||||
text={t('common:core.app.feedback.Custom feedback')}
|
||||
/>
|
||||
{item.customFeedbacks.map((text, i) => (
|
||||
<Box key={i}>
|
||||
<MyTooltip
|
||||
label={t('common:core.app.feedback.close custom feedback')}
|
||||
>
|
||||
{text}
|
||||
</Checkbox>
|
||||
</MyTooltip>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
{/* admin mark content */}
|
||||
{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>
|
||||
<Checkbox
|
||||
onChange={onCloseCustomFeedback(item, i)}
|
||||
icon={<MyIcon name={'common/check'} w={'12px'} />}
|
||||
>
|
||||
{text}
|
||||
</Checkbox>
|
||||
</MyTooltip>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</ChatItem>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
{/* admin mark content */}
|
||||
{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>
|
||||
)}
|
||||
</ChatItem>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@ -43,7 +43,7 @@ const InformTable = () => {
|
||||
<Flex alignItems={'center'}>
|
||||
<Box fontWeight={'bold'}>{item.title}</Box>
|
||||
<Box ml={2} color={'myGray.500'} flex={'1 0 0'}>
|
||||
({formatTimeToChatTime(item.time)})
|
||||
({t(formatTimeToChatTime(item.time) as any).replace('#', ':')})
|
||||
</Box>
|
||||
{!item.read && (
|
||||
<Button
|
||||
|
||||
@ -67,13 +67,13 @@ async function handler(
|
||||
})();
|
||||
|
||||
const fieldMap = {
|
||||
[GetChatTypeEnum.normal]: `dataId obj value adminFeedback userBadFeedback userGoodFeedback ${
|
||||
[GetChatTypeEnum.normal]: `dataId obj value adminFeedback userBadFeedback userGoodFeedback time ${
|
||||
DispatchNodeResponseKeyEnum.nodeResponse
|
||||
} ${loadCustomFeedbacks ? 'customFeedbacks' : ''}`,
|
||||
[GetChatTypeEnum.outLink]: `dataId obj value userGoodFeedback userBadFeedback adminFeedback ${
|
||||
[GetChatTypeEnum.outLink]: `dataId obj value userGoodFeedback userBadFeedback adminFeedback time ${
|
||||
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({
|
||||
|
||||
@ -180,6 +180,7 @@ const DetailLogsModal = ({
|
||||
chatConfig={chat?.app?.chatConfig}
|
||||
appId={appId}
|
||||
chatId={chatId}
|
||||
isLog={true}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@ -147,7 +147,7 @@ const FeiShu = ({ appId }: { appId: string }) => {
|
||||
)}
|
||||
<Td>
|
||||
{item.lastTime
|
||||
? t(formatTimeToChatTime(item.lastTime) as any)
|
||||
? t(formatTimeToChatTime(item.lastTime) as any).replace('#', ':')
|
||||
: t('common:common.Un used')}
|
||||
</Td>
|
||||
<Td display={'flex'} alignItems={'center'}>
|
||||
|
||||
@ -150,7 +150,9 @@ const Share = ({ appId }: { appId: string; type: PublishChannelEnum }) => {
|
||||
</>
|
||||
)}
|
||||
<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 display={'flex'} alignItems={'center'}>
|
||||
<Button
|
||||
|
||||
@ -150,7 +150,7 @@ const OffiAccount = ({ appId }: { appId: string }) => {
|
||||
)}
|
||||
<Td>
|
||||
{item.lastTime
|
||||
? t(formatTimeToChatTime(item.lastTime) as any)
|
||||
? t(formatTimeToChatTime(item.lastTime) as any).replace('#', ':')
|
||||
: t('common:common.Un used')}
|
||||
</Td>
|
||||
<Td display={'flex'} alignItems={'center'}>
|
||||
|
||||
@ -126,7 +126,7 @@ const Wecom = ({ appId }: { appId: string }) => {
|
||||
)}
|
||||
<Td>
|
||||
{item.lastTime
|
||||
? t(formatTimeToChatTime(item.lastTime) as any)
|
||||
? t(formatTimeToChatTime(item.lastTime) as any).replace('#', ':')
|
||||
: t('common:common.Un used')}
|
||||
</Td>
|
||||
<Td display={'flex'} alignItems={'center'}>
|
||||
|
||||
@ -249,7 +249,9 @@ const ListItem = () => {
|
||||
{isPc && (
|
||||
<HStack spacing={0.5} className="time">
|
||||
<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>
|
||||
)}
|
||||
{(AppFolderTypeList.includes(app.type)
|
||||
|
||||
@ -13,12 +13,14 @@ import MyMenu from '@fastgpt/web/components/common/MyMenu';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { ChatContext } from '@/web/core/chat/context/chatContext';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { formatTimeToChatTime } from '@fastgpt/global/common/string/time';
|
||||
|
||||
type HistoryItemType = {
|
||||
id: string;
|
||||
title: string;
|
||||
customTitle?: string;
|
||||
top?: boolean;
|
||||
updateTime: Date;
|
||||
};
|
||||
|
||||
const ChatHistorySlider = ({
|
||||
@ -59,11 +61,18 @@ const ChatHistorySlider = ({
|
||||
|
||||
const concatHistory = useMemo(() => {
|
||||
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 = {
|
||||
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);
|
||||
|
||||
@ -188,7 +197,10 @@ const ChatHistorySlider = ({
|
||||
_hover={{
|
||||
bg: 'myGray.50',
|
||||
'& .more': {
|
||||
visibility: 'visible'
|
||||
display: 'block'
|
||||
},
|
||||
'& .time': {
|
||||
display: isPc ? 'none' : 'block'
|
||||
}
|
||||
}}
|
||||
bg={item.top ? '#E6F6F6 !important' : ''}
|
||||
@ -214,69 +226,80 @@ const ChatHistorySlider = ({
|
||||
{item.customTitle || item.title}
|
||||
</Box>
|
||||
{!!item.id && (
|
||||
<Box className="more" visibility={['visible', 'hidden']}>
|
||||
<MyMenu
|
||||
Button={
|
||||
<IconButton
|
||||
size={'xs'}
|
||||
variant={'whiteBase'}
|
||||
icon={<MyIcon name={'more'} w={'14px'} p={1} />}
|
||||
aria-label={''}
|
||||
/>
|
||||
}
|
||||
menuList={[
|
||||
{
|
||||
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'
|
||||
}
|
||||
]
|
||||
<Flex gap={2} alignItems={'center'}>
|
||||
<Box
|
||||
className="time"
|
||||
display={'block'}
|
||||
fontWeight={'400'}
|
||||
fontSize={'mini'}
|
||||
color={'myGray.500'}
|
||||
>
|
||||
{t(formatTimeToChatTime(item.updateTime) as any).replace('#', ':')}
|
||||
</Box>
|
||||
<Box className="more" display={['block', 'none']}>
|
||||
<MyMenu
|
||||
Button={
|
||||
<IconButton
|
||||
size={'xs'}
|
||||
variant={'whiteBase'}
|
||||
icon={<MyIcon name={'more'} w={'14px'} p={1} />}
|
||||
aria-label={''}
|
||||
/>
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</Box>
|
||||
menuList={[
|
||||
{
|
||||
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>
|
||||
))}
|
||||
|
||||
@ -382,9 +382,7 @@ const TestHistories = React.memo(function TestHistories({
|
||||
{item.text}
|
||||
</Box>
|
||||
<Box flex={'0 0 70px'}>
|
||||
{formatTimeToChatTime(item.time).includes('.')
|
||||
? t(formatTimeToChatTime(item.time) as any)
|
||||
: formatTimeToChatTime(item.time)}
|
||||
{t(formatTimeToChatTime(item.time) as any).replace('#', ':')}
|
||||
</Box>
|
||||
<MyTooltip label={t('common:core.dataset.test.delete test history')}>
|
||||
<Box w={'14px'} h={'14px'}>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user