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 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 */
|
||||||
|
|||||||
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;
|
moduleName?: string;
|
||||||
ttsBuffer?: Uint8Array;
|
ttsBuffer?: Uint8Array;
|
||||||
responseData?: ChatHistoryItemResType[];
|
responseData?: ChatHistoryItemResType[];
|
||||||
|
time?: Date;
|
||||||
} & ChatBoxInputType &
|
} & ChatBoxInputType &
|
||||||
ResponseTagItemType;
|
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.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}}"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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": "名称",
|
||||||
|
|||||||
@ -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 &
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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({
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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'}>
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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'}>
|
||||||
|
|||||||
@ -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'}>
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -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'}>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user