Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | 1x 6x 1x 14x 10x 10x 9x 9x 1x 15x 15x 15x 15x 15x 15x 5x 15x 15x 15x 15x | import { useMemo } from "react";
import { BOT } from "@/constants/chat";
import { ChatMessage, SuggestionChip } from "@/types/chat";
const findSuggestions = (message?: ChatMessage): (string | SuggestionChip)[] =>
message?.blocks?.find((b) => b.type === "suggestions")?.items ?? [];
/**
* A bot turn that resolved into nothing renders as an empty bubble, so it is
* dropped: an explicit confirmation whose pending action is gone, and a turn
* that carries neither blocks nor text.
*/
const isRenderable = (message: ChatMessage) => {
if (message.user._id !== BOT._id) return true;
const hasConfirmationBlock = message.blocks?.some((b) => b.type === "confirmation");
if (hasConfirmationBlock && !message.pendingAction) return false;
const hasNoContent = (!message.blocks || message.blocks.length === 0) && !message.text?.trim();
return !(hasNoContent && !message.pendingAction);
};
interface UseConversationViewArgs {
messages: ChatMessage[];
isInitializing: boolean;
}
export const useConversationView = ({ messages, isInitializing }: UseConversationViewArgs) => {
// Messages are stored newest-first; reverse to oldest-first so the list
// renders top-to-bottom with the newest message at the bottom.
const visibleMessages = useMemo(() => [...messages.filter(isRenderable)].reverse(), [messages]);
/** Only the bot's welcome turn is visible — the user has not spoken yet. */
const isWelcomeOnly = visibleMessages.length === 1 && visibleMessages[0]?.user._id === BOT._id;
const isInitialising = messages.length === 0 && isInitializing;
// Nothing to render a conversation from means the greeting, whatever the
// reason — still starting, failed to start, or an agent turn that resolved
// into no renderable message at all. Any of those left to MessageList would
// be a blank screen.
const isShowingGreeting = visibleMessages.length === 0 || isWelcomeOnly;
// The agent owns the welcome sentence — it ships it in the welcome message's
// text block, phrased for what THIS actor is allowed to do. The client cannot
// know that before the thread exists, so an empty result renders a skeleton
// rather than a stand-in the agent might contradict.
const welcomeDescription = useMemo(() => {
if (!isWelcomeOnly) return "";
return visibleMessages[0]?.blocks?.find((b) => b.type === "text")?.text?.trim() ?? "";
}, [isWelcomeOnly, visibleMessages]);
/**
* Suggestions from the bot's latest turn, pinned above the composer. The
* welcome turn is the latest turn like any other, so its chips ride the same
* strip — the greeting does not get a second, differently-shaped chip list.
*/
const activeSuggestions = useMemo(() => {
const lastMessage = visibleMessages[visibleMessages.length - 1];
return lastMessage?.user._id === BOT._id ? findSuggestions(lastMessage) : [];
}, [visibleMessages]);
return {
visibleMessages,
isInitialising,
isShowingGreeting,
welcomeDescription,
activeSuggestions,
};
};
|