mirror of
https://github.com/lobehub/lobehub.git
synced 2026-03-27 13:29:15 +07:00
💄 style: improve agent loading state (#11511)
* improve loading state * improve loading state * improve loading state
This commit is contained in:
@@ -203,6 +203,8 @@
|
||||
"noMembersYet": "This group doesn't have any members yet. Click the + button to invite agents.",
|
||||
"noSelectedAgents": "No members selected yet",
|
||||
"openInNewWindow": "Open in New Window",
|
||||
"operation.execAgentRuntime": "Preparing response",
|
||||
"operation.sendMessage": "Sending message",
|
||||
"owner": "Group owner",
|
||||
"pageCopilot.title": "Page Agent",
|
||||
"pageCopilot.welcome": "**Clearer, sharper writing**\n\nDraft, rewrite, or polish—tell me your intent and I’ll refine the rest.",
|
||||
|
||||
@@ -203,6 +203,8 @@
|
||||
"noMembersYet": "这个群组还没有成员。点击「+」邀请助理加入",
|
||||
"noSelectedAgents": "还未选择成员",
|
||||
"openInNewWindow": "在新窗口打开",
|
||||
"operation.execAgentRuntime": "准备响应中",
|
||||
"operation.sendMessage": "消息发送中",
|
||||
"owner": "群主",
|
||||
"pageCopilot.title": "文稿助理",
|
||||
"pageCopilot.welcome": "**让文字更清晰、更到位**\n\n起草、改写、润色都可以。你把意图说清楚,其余交给我打磨",
|
||||
|
||||
@@ -21,7 +21,7 @@ export const ChatInputProvider = memo<ChatInputProviderProps>(
|
||||
chatInputEditorRef,
|
||||
onMarkdownContentChange,
|
||||
mentionItems,
|
||||
allowExpand,
|
||||
allowExpand = true,
|
||||
}) => {
|
||||
const editor = useEditor();
|
||||
const slashMenuRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@@ -8,7 +8,6 @@ import { CollapsedMessage } from '../../AssistantGroup/components/CollapsedMessa
|
||||
import DisplayContent from '../../components/DisplayContent';
|
||||
import FileChunks from '../../components/FileChunks';
|
||||
import ImageFileListViewer from '../../components/ImageFileListViewer';
|
||||
import IntentUnderstanding from '../../components/IntentUnderstanding';
|
||||
import Reasoning from '../../components/Reasoning';
|
||||
import SearchGrounding from '../../components/SearchGrounding';
|
||||
import { useMarkdown } from '../useMarkdown';
|
||||
@@ -23,9 +22,6 @@ const MessageContent = memo<UIChatMessage>(
|
||||
|
||||
const isToolCallGenerating = generating && (content === LOADING_FLAT || !content) && !!tools;
|
||||
|
||||
// TODO: Need to implement isIntentUnderstanding selector in ConversationStore if needed
|
||||
const isIntentUnderstanding = false;
|
||||
|
||||
const showSearch = !!search && !!search.citations?.length;
|
||||
const showImageItems = !!imageList && imageList.length > 0;
|
||||
|
||||
@@ -46,18 +42,15 @@ const MessageContent = memo<UIChatMessage>(
|
||||
)}
|
||||
{showFileChunks && <FileChunks data={chunksList} />}
|
||||
{showReasoning && <Reasoning {...props.reasoning} id={id} />}
|
||||
{isIntentUnderstanding ? (
|
||||
<IntentUnderstanding />
|
||||
) : (
|
||||
<DisplayContent
|
||||
content={content}
|
||||
hasImages={showImageItems}
|
||||
isMultimodal={metadata?.isMultimodal}
|
||||
isToolCallGenerating={isToolCallGenerating}
|
||||
markdownProps={markdownProps}
|
||||
tempDisplayContent={metadata?.tempDisplayContent}
|
||||
/>
|
||||
)}
|
||||
<DisplayContent
|
||||
content={content}
|
||||
hasImages={showImageItems}
|
||||
id={id}
|
||||
isMultimodal={metadata?.isMultimodal}
|
||||
isToolCallGenerating={isToolCallGenerating}
|
||||
markdownProps={markdownProps}
|
||||
tempDisplayContent={metadata?.tempDisplayContent}
|
||||
/>
|
||||
{showImageItems && <ImageFileListViewer items={imageList} />}
|
||||
</Flexbox>
|
||||
);
|
||||
|
||||
@@ -52,6 +52,7 @@ const MessageContent = memo<UIChatMessage>(
|
||||
<DisplayContent
|
||||
content={content}
|
||||
hasImages={showImageItems}
|
||||
id={id}
|
||||
isMultimodal={metadata?.isMultimodal}
|
||||
isToolCallGenerating={isToolCallGenerating}
|
||||
markdownProps={markdownProps}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import { Flexbox, Text } from '@lobehub/ui';
|
||||
import { memo, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import BubblesLoading from '@/components/BubblesLoading';
|
||||
import { useChatStore } from '@/store/chat';
|
||||
import { operationSelectors } from '@/store/chat/selectors';
|
||||
import type { OperationType } from '@/store/chat/slices/operation/types';
|
||||
|
||||
const ELAPSED_TIME_THRESHOLD = 2100; // Show elapsed time after 2 seconds
|
||||
|
||||
interface ContentLoadingProps {
|
||||
id: string;
|
||||
}
|
||||
|
||||
const ContentLoading = memo<ContentLoadingProps>(({ id }) => {
|
||||
const { t } = useTranslation('chat');
|
||||
const operations = useChatStore(operationSelectors.getOperationsByMessage(id));
|
||||
const [elapsedSeconds, setElapsedSeconds] = useState(0);
|
||||
|
||||
// Get the running operation
|
||||
const runningOp = operations.find((op) => op.status === 'running');
|
||||
const operationType = runningOp?.type as OperationType | undefined;
|
||||
const startTime = runningOp?.metadata?.startTime;
|
||||
|
||||
// Track elapsed time, reset when operation type changes
|
||||
useEffect(() => {
|
||||
if (!startTime) {
|
||||
setElapsedSeconds(0);
|
||||
return;
|
||||
}
|
||||
|
||||
const updateElapsed = () => {
|
||||
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
||||
setElapsedSeconds(elapsed);
|
||||
};
|
||||
|
||||
updateElapsed();
|
||||
const interval = setInterval(updateElapsed, 1000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [startTime, operationType]);
|
||||
|
||||
// Get localized label based on operation type
|
||||
const operationLabel = operationType
|
||||
? (t(`operation.${operationType}` as any) as string)
|
||||
: undefined;
|
||||
|
||||
const showElapsedTime = elapsedSeconds >= ELAPSED_TIME_THRESHOLD / 1000;
|
||||
|
||||
return (
|
||||
<Flexbox align={'center'} horizontal>
|
||||
<BubblesLoading />
|
||||
{operationLabel && (
|
||||
<Text type={'secondary'}>
|
||||
{operationLabel}...
|
||||
{showElapsedTime && ` (${elapsedSeconds}s)`}
|
||||
</Text>
|
||||
)}
|
||||
</Flexbox>
|
||||
);
|
||||
});
|
||||
|
||||
export default ContentLoading;
|
||||
@@ -2,17 +2,18 @@ import { deserializeParts } from '@lobechat/utils';
|
||||
import { type MarkdownProps } from '@lobehub/ui';
|
||||
import { memo } from 'react';
|
||||
|
||||
import BubblesLoading from '@/components/BubblesLoading';
|
||||
import { LOADING_FLAT } from '@/const/message';
|
||||
import MarkdownMessage from '@/features/Conversation/Markdown';
|
||||
|
||||
import { normalizeThinkTags, processWithArtifact } from '../../utils/markdown';
|
||||
import ContentLoading from './ContentLoading';
|
||||
import { RichContentRenderer } from './RichContentRenderer';
|
||||
|
||||
const DisplayContent = memo<{
|
||||
addIdOnDOM?: boolean;
|
||||
content: string;
|
||||
hasImages?: boolean;
|
||||
id: string;
|
||||
isMultimodal?: boolean;
|
||||
isToolCallGenerating?: boolean;
|
||||
markdownProps?: Omit<MarkdownProps, 'className' | 'style' | 'children'>;
|
||||
@@ -25,11 +26,12 @@ const DisplayContent = memo<{
|
||||
hasImages,
|
||||
isMultimodal,
|
||||
tempDisplayContent,
|
||||
id,
|
||||
}) => {
|
||||
const message = normalizeThinkTags(processWithArtifact(content));
|
||||
if (isToolCallGenerating) return;
|
||||
|
||||
if ((!content && !hasImages) || content === LOADING_FLAT) return <BubblesLoading />;
|
||||
if ((!content && !hasImages) || content === LOADING_FLAT) return <ContentLoading id={id} />;
|
||||
|
||||
const contentParts = isMultimodal ? deserializeParts(tempDisplayContent || content) : null;
|
||||
|
||||
|
||||
@@ -229,6 +229,8 @@ export default {
|
||||
'noMembersYet': "This group doesn't have any members yet. Click the + button to invite agents.",
|
||||
'noSelectedAgents': 'No members selected yet',
|
||||
'openInNewWindow': 'Open in New Window',
|
||||
'operation.execAgentRuntime': 'Preparing response',
|
||||
'operation.sendMessage': 'Sending message',
|
||||
'owner': 'Group owner',
|
||||
'pageCopilot.title': 'Page Agent',
|
||||
'pageCopilot.welcome':
|
||||
|
||||
@@ -204,8 +204,9 @@ export const conversationLifecycle: StateCreator<
|
||||
);
|
||||
get().internal_toggleMessageLoading(true, tempId);
|
||||
|
||||
// Associate temp message with operation
|
||||
// Associate temp messages with operation
|
||||
get().associateMessageWithOperation(tempId, operationId);
|
||||
get().associateMessageWithOperation(tempAssistantId, operationId);
|
||||
|
||||
// Store editor state in operation metadata for cancel restoration
|
||||
const jsonState = mainInputEditor?.getJSONState();
|
||||
|
||||
Reference in New Issue
Block a user