From 41c1949a2efa4afe63b4c9a73b3f94d3ebbff39b Mon Sep 17 00:00:00 2001 From: duanshuwen Date: Tue, 21 Apr 2026 20:57:03 +0800 Subject: [PATCH] feat: add ChatEmptyState component and integrate it into ChatMessageList for improved user experience --- src/components/chat/ChatEmptyState.tsx | 48 ++++++++++++++ src/components/chat/ChatMessageList.tsx | 11 ++-- src/components/chat/index.ts | 1 + src/i18n/locales/en/chat.json | 19 ++++++ src/i18n/locales/th/chat.json | 19 ++++++ src/i18n/locales/zh/chat.json | 19 ++++++ src/pages/Home/index.tsx | 86 +++++++++++++++++-------- src/stores/chat.ts | 3 +- 8 files changed, 172 insertions(+), 34 deletions(-) create mode 100644 src/components/chat/ChatEmptyState.tsx diff --git a/src/components/chat/ChatEmptyState.tsx b/src/components/chat/ChatEmptyState.tsx new file mode 100644 index 0000000..c473135 --- /dev/null +++ b/src/components/chat/ChatEmptyState.tsx @@ -0,0 +1,48 @@ +import { useI18n } from '../../i18n'; + +export default function ChatEmptyState() { + const { t } = useI18n(); + + const suggestions = [ + { + key: 'task', + title: t('conversation.emptyState.suggestions.task.title'), + }, + { + key: 'continuous', + title: t('conversation.emptyState.suggestions.continuous.title'), + }, + { + key: 'parallel', + title: t('conversation.emptyState.suggestions.parallel.title'), + }, + ] as const; + + return ( +
+
+
+
+

+ {t('conversation.emptyState.title')} +

+ +
+ {suggestions.map((suggestion) => ( +
+ {suggestion.title} +
+ ))} +
+
+
+
+
+ ); +} diff --git a/src/components/chat/ChatMessageList.tsx b/src/components/chat/ChatMessageList.tsx index 543035a..633b600 100644 --- a/src/components/chat/ChatMessageList.tsx +++ b/src/components/chat/ChatMessageList.tsx @@ -1,15 +1,18 @@ import { memo, useEffect, useRef } from 'react'; import type { ChatMessageItem } from './types'; import { useI18n } from '../../i18n'; +import ChatEmptyState from './ChatEmptyState'; type ChatMessageListProps = { messages: ChatMessageItem[]; loading?: boolean; + showWelcomeState?: boolean; }; -function ChatMessageList({ messages, loading }: ChatMessageListProps) { +function ChatMessageList({ messages, loading, showWelcomeState }: ChatMessageListProps) { const containerRef = useRef(null); const { t } = useI18n(); + const shouldShowWelcomeState = !loading && (showWelcomeState || messages.length === 0); useEffect(() => { const container = containerRef.current; @@ -25,11 +28,7 @@ function ChatMessageList({ messages, loading }: ChatMessageListProps) { {t('conversation.messageList.loading')} ) : null} - {!loading && messages.length === 0 ? ( -
- {t('conversation.messageList.emptyHint')} -
- ) : null} + {shouldShowWelcomeState ? : null} {messages.map((message) => (
void; + onSend: () => void; + onAttach: (files: File[]) => void | Promise; + onRemoveAttachment: (index: number) => void; +}; + +function HomeChatComposerSection({ + value, + attachments, + onChange, + onSend, + onAttach, + onRemoveAttachment, +}: HomeChatComposerSectionProps) { const error = useChatStore((state) => state.error); const isSending = useChatStore((state) => state.sending); - const [inputMessage, setInputMessage] = useState(''); - const [attachments, setAttachments] = useState([]); - - async function handleSendMessage(): Promise { - const sent = await chatStore.sendMessage(inputMessage, attachments); - if (sent) { - setInputMessage(''); - setAttachments([]); - } - } - - async function handleAttach(files: File[]): Promise { - const stagedFiles = await chatStore.stageAttachmentFiles(files); - setAttachments((currentAttachments) => [...currentAttachments, ...stagedFiles]); - } return ( { - setAttachments((currentAttachments) => currentAttachments.filter((_, currentIndex) => currentIndex !== index)); - }} - onSend={() => { - void handleSendMessage(); - }} + onRemoveAttachment={onRemoveAttachment} + onSend={onSend} onStop={() => { void chatStore.abortRun(); }} @@ -252,6 +249,8 @@ export default function HomePage() { title: string; } | null>(null); const [deletingConversation, setDeletingConversation] = useState(false); + const [inputMessage, setInputMessage] = useState(''); + const [attachments, setAttachments] = useState([]); useEffect(() => { void agentsStore.init(); @@ -289,6 +288,11 @@ export default function HomePage() { return buckets; }, [chatSessionLabels, chatSessionLastActivity, chatSessions, locale, t]); + const hasConversationHistory = useMemo( + () => historyBuckets.some((bucket) => bucket.sessions.length > 0), + [historyBuckets], + ); + const visibleMessages = useMemo( () => mapMessages(chatMessages, chatStreamingMessage, locale, t), [chatMessages, chatStreamingMessage, locale, t], @@ -381,6 +385,19 @@ export default function HomePage() { setTaskCenterNotice(result.error || t('task.taskCenter.notices.retryFailed')); } + async function handleSendMessage(): Promise { + const sent = await chatStore.sendMessage(inputMessage, attachments); + if (sent) { + setInputMessage(''); + setAttachments([]); + } + } + + async function handleAttach(files: File[]): Promise { + const stagedFiles = await chatStore.stageAttachmentFiles(files); + setAttachments((currentAttachments) => [...currentAttachments, ...stagedFiles]); + } + function handleRenameConversation(conversationId: string): void { const nextTitle = chatSessionLabels[conversationId] || chatSessions.find((session) => session.key === conversationId)?.displayName @@ -484,8 +501,23 @@ export default function HomePage() {
- - + + { + setAttachments((currentAttachments) => currentAttachments.filter((_, currentIndex) => currentIndex !== index)); + }} + onSend={() => { + void handleSendMessage(); + }} + />
{/*
diff --git a/src/stores/chat.ts b/src/stores/chat.ts index f5344f0..24c798c 100644 --- a/src/stores/chat.ts +++ b/src/stores/chat.ts @@ -675,6 +675,7 @@ async function deleteSession(sessionKey: string): Promise { if (state.currentSessionKey === sessionKey) { const nextSession = remaining[0]?.key ?? getDefaultMainSessionKey(); + const hasRemainingSessions = remaining.length > 0; patchState({ ...basePatch, currentSessionKey: nextSession, @@ -688,7 +689,7 @@ async function deleteSession(sessionKey: string): Promise { lastUserMessageAt: null, }); - if (nextSession) { + if (hasRemainingSessions && nextSession) { await loadHistory(nextSession); } return;