Files
agent-chat-ui/src/components/thread/messages/ai.tsx

122 lines
3.7 KiB
TypeScript
Raw Normal View History

import { useStreamContext } from "@/providers/Stream";
2025-03-03 13:24:24 -08:00
import { Checkpoint, Message } from "@langchain/langgraph-sdk";
import { getContentString } from "../utils";
import { BranchSwitcher, CommandBar } from "./shared";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { MarkdownText } from "../markdown-text";
2025-03-05 16:35:39 +01:00
import { LoadExternalComponent } from "@langchain/langgraph-sdk/react-ui";
2025-03-04 14:37:39 +01:00
import { cn } from "@/lib/utils";
2025-03-04 14:59:16 -08:00
import { ToolCalls, ToolResult } from "./tool-calls";
2025-03-03 12:40:24 -08:00
function CustomComponent({
message,
thread,
}: {
message: Message;
thread: ReturnType<typeof useStreamContext>;
}) {
const meta = thread.getMessagesMetadata(message);
const seenState = meta?.firstSeenState;
const customComponent = seenState?.values.ui
2025-03-04 19:29:31 +01:00
?.slice()
2025-03-03 12:40:24 -08:00
.reverse()
.find(
({ additional_kwargs }) =>
additional_kwargs.run_id === seenState.metadata?.run_id,
);
if (!customComponent) {
return null;
}
return (
<div key={message.id}>
{customComponent && (
<LoadExternalComponent
assistantId="agent"
stream={thread}
message={customComponent}
2025-03-05 16:35:39 +01:00
meta={{ ui: customComponent }}
2025-03-03 12:40:24 -08:00
/>
)}
</div>
);
}
export function AssistantMessage({
message,
isLoading,
2025-03-03 13:24:24 -08:00
handleRegenerate,
}: {
message: Message;
isLoading: boolean;
2025-03-03 13:24:24 -08:00
handleRegenerate: (parentCheckpoint: Checkpoint | null | undefined) => void;
}) {
2025-03-03 17:53:00 -08:00
const contentString = getContentString(message.content);
const thread = useStreamContext();
const meta = thread.getMessagesMetadata(message);
const parentCheckpoint = meta?.firstSeenState?.parent_checkpoint;
const hasToolCalls =
"tool_calls" in message &&
message.tool_calls &&
message.tool_calls.length > 0;
2025-03-04 14:59:16 -08:00
const isToolResult = message.type === "tool";
return (
<div className="flex items-start mr-auto gap-2 group">
<Avatar>
<AvatarFallback>A</AvatarFallback>
</Avatar>
2025-03-04 14:59:16 -08:00
{isToolResult ? (
<ToolResult message={message} />
) : (
<div className="flex flex-col gap-2">
{contentString.length > 0 && (
<div className="rounded-2xl bg-muted px-4 py-2">
<MarkdownText>{contentString}</MarkdownText>
</div>
2025-03-04 14:37:39 +01:00
)}
{hasToolCalls && <ToolCalls toolCalls={message.tool_calls} />}
<CustomComponent message={message} thread={thread} />
2025-03-04 14:59:16 -08:00
<div
className={cn(
"flex gap-2 items-center mr-auto transition-opacity",
"opacity-0 group-focus-within:opacity-100 group-hover:opacity-100",
)}
>
<BranchSwitcher
branch={meta?.branch}
branchOptions={meta?.branchOptions}
onSelect={(branch) => thread.setBranch(branch)}
isLoading={isLoading}
/>
<CommandBar
content={contentString}
isLoading={isLoading}
isAiMessage={true}
handleRegenerate={() => handleRegenerate(parentCheckpoint)}
/>
</div>
</div>
2025-03-04 14:59:16 -08:00
)}
</div>
);
}
export function AssistantMessageLoading() {
return (
<div className="flex items-start mr-auto gap-2">
<Avatar>
<AvatarFallback>A</AvatarFallback>
</Avatar>
<div className="flex items-center gap-1 rounded-2xl bg-muted px-4 py-2 h-8">
<div className="w-1.5 h-1.5 rounded-full bg-foreground/50 animate-[pulse_1.5s_ease-in-out_infinite]"></div>
<div className="w-1.5 h-1.5 rounded-full bg-foreground/50 animate-[pulse_1.5s_ease-in-out_0.5s_infinite]"></div>
<div className="w-1.5 h-1.5 rounded-full bg-foreground/50 animate-[pulse_1.5s_ease-in-out_1s_infinite]"></div>
</div>
</div>
);
}