mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-24 02:39:42 +02:00
feat(chat): skeleton while switching to an un-cached session
Switching to a session whose messages aren't cached showed the empty state (starter prompts) for the ~300ms the fetch took — jarring, because you're clicking into an existing conversation, not starting a new one. Now there's a three-branch render: skeleton while loading, empty state for real new-chat (activeSessionId === null), messages when ready. Cached switches still return data synchronously — no flash. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -75,6 +75,33 @@ export function ChatMessageList({
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Placeholder shown while `chat_message` for a session is being fetched
|
||||
* (initial refresh, or switching to an un-cached session). Shape roughly
|
||||
* mirrors an assistant → user → assistant exchange so the window doesn't
|
||||
* shift under the user when real messages arrive.
|
||||
*/
|
||||
export function ChatMessageSkeleton() {
|
||||
return (
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<div className="mx-auto w-full max-w-4xl px-5 py-4 space-y-5">
|
||||
<div className="space-y-2">
|
||||
<div className="h-3.5 w-3/4 rounded bg-muted animate-pulse" />
|
||||
<div className="h-3.5 w-1/2 rounded bg-muted animate-pulse" />
|
||||
</div>
|
||||
<div className="flex justify-end">
|
||||
<div className="h-8 w-48 rounded-2xl bg-muted animate-pulse" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="h-3.5 w-2/3 rounded bg-muted animate-pulse" />
|
||||
<div className="h-3.5 w-5/6 rounded bg-muted animate-pulse" />
|
||||
<div className="h-3.5 w-1/3 rounded bg-muted animate-pulse" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function toTimelineItem(m: TaskMessagePayload): ChatTimelineItem {
|
||||
return {
|
||||
seq: m.seq,
|
||||
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
} from "@multica/core/chat/queries";
|
||||
import { useCreateChatSession, useMarkChatSessionRead } from "@multica/core/chat/mutations";
|
||||
import { useChatStore } from "@multica/core/chat";
|
||||
import { ChatMessageList } from "./chat-message-list";
|
||||
import { ChatMessageList, ChatMessageSkeleton } from "./chat-message-list";
|
||||
import { ChatInput } from "./chat-input";
|
||||
import { ChatResizeHandles } from "./chat-resize-handles";
|
||||
import { useChatResize } from "./use-chat-resize";
|
||||
@@ -52,11 +52,15 @@ export function ChatWindow() {
|
||||
const { data: members = [] } = useQuery(memberListOptions(wsId));
|
||||
const { data: sessions = [] } = useQuery(chatSessionsOptions(wsId));
|
||||
const { data: allSessions = [] } = useQuery(allChatSessionsOptions(wsId));
|
||||
const { data: rawMessages } = useQuery(
|
||||
const { data: rawMessages, isLoading: messagesLoading } = useQuery(
|
||||
chatMessagesOptions(activeSessionId ?? ""),
|
||||
);
|
||||
// When no active session, always show empty — don't use stale cache
|
||||
const messages = activeSessionId ? rawMessages ?? [] : [];
|
||||
// Skeleton only shows for an un-cached session fetch. Cached switches
|
||||
// return data synchronously — no flash. `enabled: false` (new chat)
|
||||
// keeps isLoading false so the starter prompts aren't hidden.
|
||||
const showSkeleton = !!activeSessionId && messagesLoading;
|
||||
|
||||
// Server-authoritative pending task. Survives refresh / reopen / session
|
||||
// switch because it's keyed on sessionId in the Query cache; WS events
|
||||
@@ -374,8 +378,10 @@ export function ChatWindow() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Messages or Empty State */}
|
||||
{hasMessages ? (
|
||||
{/* Messages / skeleton / empty state */}
|
||||
{showSkeleton ? (
|
||||
<ChatMessageSkeleton />
|
||||
) : hasMessages ? (
|
||||
<ChatMessageList
|
||||
messages={messages}
|
||||
pendingTaskId={pendingTaskId}
|
||||
|
||||
Reference in New Issue
Block a user