mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-15 01:18:26 +08:00
Feat: If the interval between two outputs exceeds 600ms, a loading state is displayed at the end. (#16861)
### Summary Feat: If the interval between two outputs exceeds 600ms, a loading state is displayed at the end.
This commit is contained in:
39
web/src/components/loading-dots/index.module.less
Normal file
39
web/src/components/loading-dots/index.module.less
Normal file
@@ -0,0 +1,39 @@
|
||||
.dot {
|
||||
display: inline-block;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background-color: currentColor;
|
||||
animation: loadingDots 1.4s ease-in-out infinite both;
|
||||
margin-right: 4px;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
&:nth-child(1) {
|
||||
animation-delay: -0.32s;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
animation-delay: -0.16s;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
animation-delay: 0s;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes loadingDots {
|
||||
0%,
|
||||
80%,
|
||||
100% {
|
||||
opacity: 0.3;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
|
||||
40% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
15
web/src/components/loading-dots/index.tsx
Normal file
15
web/src/components/loading-dots/index.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import styles from './index.module.less';
|
||||
|
||||
interface LoadingDotsProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function LoadingDots({ className }: LoadingDotsProps) {
|
||||
return (
|
||||
<span className={className} aria-label="Loading">
|
||||
<span className={styles.dot} />
|
||||
<span className={styles.dot} />
|
||||
<span className={styles.dot} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import 'katex/dist/katex.min.css'; // `rehype-katex` does not import the CSS for you
|
||||
|
||||
import { useFetchDocumentThumbnailsByIds } from '@/hooks/use-document-request';
|
||||
import { useLoadingPause } from '@/hooks/use-loading-pause';
|
||||
import {
|
||||
currentReg,
|
||||
parseCitationIndex,
|
||||
@@ -30,6 +31,7 @@ import classNames from 'classnames';
|
||||
import { omit } from 'lodash';
|
||||
import { pipe } from 'lodash/fp';
|
||||
import reactStringReplace from 'react-string-replace';
|
||||
import { LoadingDots } from '../loading-dots';
|
||||
import { Button } from '../ui/button';
|
||||
import {
|
||||
HoverCard,
|
||||
@@ -52,6 +54,7 @@ const MarkdownContent = ({
|
||||
reference,
|
||||
clickDocumentButton,
|
||||
content,
|
||||
loading,
|
||||
}: {
|
||||
content: string;
|
||||
loading: boolean;
|
||||
@@ -260,6 +263,7 @@ const MarkdownContent = ({
|
||||
);
|
||||
|
||||
const dir = getDirAttribute(content.replace(citationMarkerReg, ''));
|
||||
const showLoadingDots = useLoadingPause(loading, content);
|
||||
|
||||
return (
|
||||
<div dir={dir} className={styles.markdownContentWrapper}>
|
||||
@@ -298,6 +302,9 @@ const MarkdownContent = ({
|
||||
>
|
||||
{contentWithCursor}
|
||||
</Markdown>
|
||||
{showLoadingDots && (
|
||||
<LoadingDots className="ml-1 inline-block text-text-secondary" />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import { MessageType } from '@/constants/chat';
|
||||
import { IRegenerateMessage, IRemoveMessageById } from '@/hooks/logic-hooks';
|
||||
import {
|
||||
IMessage,
|
||||
IReference,
|
||||
IReferenceChunk,
|
||||
UploadResponseDataType,
|
||||
} from '@/interfaces/database/chat';
|
||||
import classNames from 'classnames';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { IRegenerateMessage, IRemoveMessageById } from '@/hooks/logic-hooks';
|
||||
import { cn } from '@/lib/utils';
|
||||
import classNames from 'classnames';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { DocumentDownloadButton } from '../document-download-button';
|
||||
import { LoadingDots } from '../loading-dots';
|
||||
import MarkdownContent from '../markdown-content';
|
||||
import { ReferenceDocumentList } from '../next-message-item/reference-document-list';
|
||||
import { ReferenceImageList } from '../next-message-item/reference-image-list';
|
||||
@@ -54,7 +53,6 @@ const MessageItem = ({
|
||||
visibleAvatar = true,
|
||||
nickname,
|
||||
}: IProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { theme } = useTheme();
|
||||
const isAssistant = item.role === MessageType.Assistant;
|
||||
const isUser = item.role === MessageType.User;
|
||||
@@ -151,7 +149,7 @@ const MessageItem = ({
|
||||
)}
|
||||
>
|
||||
{sendLoading && isEmpty(messageContent) ? (
|
||||
t('common.running')
|
||||
<LoadingDots className="text-text-secondary" />
|
||||
) : (
|
||||
<MarkdownContent
|
||||
loading={loading}
|
||||
|
||||
@@ -29,11 +29,13 @@ import { citationMarkerReg } from '@/utils/citation-utils';
|
||||
import { getDirAttribute } from '@/utils/text-direction';
|
||||
|
||||
import { useFetchDocumentThumbnailsByIds } from '@/hooks/use-document-request';
|
||||
import { useLoadingPause } from '@/hooks/use-loading-pause';
|
||||
import { cn } from '@/lib/utils';
|
||||
import classNames from 'classnames';
|
||||
import { omit } from 'lodash';
|
||||
import { pipe } from 'lodash/fp';
|
||||
import reactStringReplace from 'react-string-replace';
|
||||
import { LoadingDots } from '../loading-dots';
|
||||
import { Button } from '../ui/button';
|
||||
import {
|
||||
HoverCard,
|
||||
@@ -159,6 +161,7 @@ function MarkdownContent({
|
||||
reference,
|
||||
clickDocumentButton,
|
||||
content,
|
||||
loading,
|
||||
}: {
|
||||
content: string;
|
||||
loading: boolean;
|
||||
@@ -353,6 +356,7 @@ function MarkdownContent({
|
||||
);
|
||||
|
||||
const dir = getDirAttribute(content.replace(citationMarkerReg, ''));
|
||||
const showLoadingDots = useLoadingPause(loading, content);
|
||||
|
||||
return (
|
||||
<div dir={dir} className={styles.markdownContentWrapper}>
|
||||
@@ -435,6 +439,9 @@ function MarkdownContent({
|
||||
>
|
||||
{contentWithCursor}
|
||||
</Markdown>
|
||||
{showLoadingDots && (
|
||||
<LoadingDots className="ml-1 inline-block text-text-secondary" />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { IRegenerateMessage, IRemoveMessageById } from '@/hooks/logic-hooks';
|
||||
import { INodeEvent, MessageEventType } from '@/hooks/use-send-message';
|
||||
@@ -27,6 +26,7 @@ import { getDirAttribute } from '@/utils/text-direction';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { Atom, ChevronDown, ChevronUp } from 'lucide-react';
|
||||
import { DocumentDownloadButton } from '../document-download-button';
|
||||
import { LoadingDots } from '../loading-dots';
|
||||
import MarkdownContent from '../next-markdown-content';
|
||||
import { RAGFlowAvatar } from '../ragflow-avatar';
|
||||
import SvgIcon from '../svg-icon';
|
||||
@@ -82,7 +82,6 @@ function MessageItem({
|
||||
isShare,
|
||||
nickname,
|
||||
}: IProps) {
|
||||
const { t } = useTranslation();
|
||||
const { theme } = useTheme();
|
||||
const isAssistant = item.role === MessageType.Assistant;
|
||||
const isUser = item.role === MessageType.User;
|
||||
@@ -147,7 +146,7 @@ function MessageItem({
|
||||
{hasCustomChildren ? (
|
||||
children
|
||||
) : sendLoading && isEmpty(messageContent) ? (
|
||||
<>{!isShare && t('common.running')}</>
|
||||
<>{!isShare && <LoadingDots className="text-text-secondary" />}</>
|
||||
) : (
|
||||
<MarkdownContent
|
||||
loading={loading}
|
||||
@@ -168,7 +167,6 @@ function MessageItem({
|
||||
messageContent,
|
||||
reference,
|
||||
sendLoading,
|
||||
t,
|
||||
theme,
|
||||
]);
|
||||
|
||||
|
||||
24
web/src/hooks/use-loading-pause.ts
Normal file
24
web/src/hooks/use-loading-pause.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export function useLoadingPause(
|
||||
loading: boolean,
|
||||
content: string,
|
||||
delay = 600,
|
||||
) {
|
||||
const [show, setShow] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading || !content) {
|
||||
setShow(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
setShow(true);
|
||||
}, delay);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [loading, content, delay]);
|
||||
|
||||
return show;
|
||||
}
|
||||
Reference in New Issue
Block a user