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 73 74 75 76 77 78 79 80 81 82 83 84 | 1x 1x 1x 1x 1x 1x 10x 10x 10x 10x 10x | import { type UIMessage, isToolUIPart } from "ai";
import { getToolParts, getMutationSuccessCard } from "./tool";
import { type RefObject } from "react";
import { LoadingIndicator } from "@/components/chat/loading-indicator";
import { ChatEmptyState, SuggestionChips } from "@/components/chat/empty-state";
import { CHAT_TRANSCRIPT_COPY } from "@/constants/chat";
import type { Suggestion } from "@/types/chat";
import { ChatMessage } from "./message";
type ChatTranscriptProps = {
containerRef: RefObject<HTMLDivElement | null>;
messages: UIMessage[];
isLoading: boolean;
userAvatarUrl?: string;
userAvatarLabel?: string;
userInitials?: string;
suggestions: Suggestion[];
messageFiles?: Map<string, File>;
onSelectPrompt: (prompt: string) => void;
onToolApproval: (id: string, approved: boolean) => void;
onFilePreview?: (file: File) => void;
};
export function ChatTranscript({
containerRef,
messages,
isLoading,
userAvatarUrl,
userAvatarLabel = "User avatar",
userInitials = CHAT_TRANSCRIPT_COPY.userBadge,
suggestions,
messageFiles,
onSelectPrompt,
onToolApproval,
onFilePreview,
}: ChatTranscriptProps) {
const lastMessage = messages.at(-1);
const hasApprovalResponded =
!isLoading &&
lastMessage?.role === "assistant" &&
lastMessage.parts.some((p) => isToolUIPart(p) && p.state === "approval-responded");
const hasMutationSuccess =
!isLoading &&
lastMessage?.role === "assistant" &&
getToolParts(lastMessage).some((p) => getMutationSuccessCard(p) !== null);
const showSuggestions = (hasApprovalResponded || hasMutationSuccess) && suggestions.length > 0;
return (
<div ref={containerRef} className="min-h-0 flex-1 overflow-y-auto px-4 py-6 sm:px-6 lg:px-8">
{messages.length === 0 ? (
<ChatEmptyState suggestions={suggestions} onSelectPrompt={onSelectPrompt} />
) : (
<div className="mx-auto flex w-full max-w-3xl flex-col gap-8">
{messages.map((message) => (
<ChatMessage
key={message.id}
message={message}
isLastMessage={message.id === lastMessage?.id}
isLoading={isLoading}
userAvatarUrl={userAvatarUrl}
userAvatarLabel={userAvatarLabel}
userInitials={userInitials}
attachedFile={messageFiles?.get(message.id)}
onSelectPrompt={onSelectPrompt}
onToolApproval={onToolApproval}
onFilePreview={onFilePreview}
/>
))}
{isLoading && lastMessage?.role === "user" ? (
<LoadingIndicator label="Thinking" />
) : null}
{showSuggestions && (
<div className="mx-auto w-fit rounded-2xl border border-violet-400/20 bg-violet-500/8 px-5 py-4 backdrop-blur-sm light:border-violet-200 light:bg-violet-50/60">
<SuggestionChips suggestions={suggestions} onSelectPrompt={onSelectPrompt} />
</div>
)}
</div>
)}
</div>
);
}
|