234 lines
6.7 KiB
TypeScript
234 lines
6.7 KiB
TypeScript
"use client"
|
|
|
|
import * as React from "react"
|
|
import { RotateCwIcon } from "lucide-react"
|
|
|
|
import { Bubble, BubbleContent } from "@/styles/radix-rhea/ui/bubble"
|
|
import { Button } from "@/styles/radix-rhea/ui/button"
|
|
import {
|
|
Card,
|
|
CardAction,
|
|
CardContent,
|
|
CardDescription,
|
|
CardFooter,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from "@/styles/radix-rhea/ui/card"
|
|
import { Marker, MarkerContent } from "@/styles/radix-rhea/ui/marker"
|
|
import {
|
|
Message,
|
|
MessageContent,
|
|
MessageHeader,
|
|
} from "@/styles/radix-rhea/ui/message"
|
|
import {
|
|
MessageScroller,
|
|
MessageScrollerButton,
|
|
MessageScrollerContent,
|
|
MessageScrollerItem,
|
|
MessageScrollerProvider,
|
|
MessageScrollerViewport,
|
|
} from "@/styles/radix-rhea/ui/message-scroller"
|
|
import {
|
|
Tooltip,
|
|
TooltipContent,
|
|
TooltipTrigger,
|
|
} from "@/styles/radix-rhea/ui/tooltip"
|
|
|
|
const currentUser = "Grace"
|
|
|
|
const initialItems = [
|
|
{
|
|
id: "group-1",
|
|
type: "message",
|
|
sender: "Grace",
|
|
role: "participant",
|
|
text: "@mary, the astrophage line keeps matching Venus energy output. Can you check my math?",
|
|
},
|
|
{
|
|
id: "group-2",
|
|
type: "message",
|
|
sender: "Mary (Agent)",
|
|
role: "assistant",
|
|
text: "Yes. Confirmed. The curve points to a microorganism harvesting stellar energy and breeding near carbon dioxide. If @rocky agrees, this is the clue we need.",
|
|
},
|
|
{
|
|
id: "group-3",
|
|
type: "message",
|
|
sender: "Grace",
|
|
role: "participant",
|
|
text: "ping @rocky",
|
|
scrollAnchor: true,
|
|
},
|
|
] satisfies GroupChatItem[]
|
|
|
|
const rockyMarker = {
|
|
id: "group-4",
|
|
type: "event",
|
|
text: "Rocky has joined the chat",
|
|
scrollAnchor: true,
|
|
} satisfies GroupChatItem
|
|
|
|
const rockyMessage = {
|
|
id: "group-5",
|
|
type: "message",
|
|
sender: "Rocky",
|
|
role: "participant",
|
|
text: "Amaze. Astrophage eats light, makes heat, goes to carbon dioxide. Rocky has fuel model. Grace is smart.",
|
|
} satisfies GroupChatItem
|
|
|
|
type GroupChatItem =
|
|
| {
|
|
id: string
|
|
type: "event"
|
|
text: string
|
|
scrollAnchor?: boolean
|
|
}
|
|
| {
|
|
id: string
|
|
type: "message"
|
|
sender: string
|
|
role: "assistant" | "participant"
|
|
text: string
|
|
scrollAnchor?: boolean
|
|
}
|
|
|
|
export function MessageScrollerGroupChat() {
|
|
const [demoKey, setDemoKey] = React.useState(0)
|
|
const [rockyTurn, setRockyTurn] = React.useState<
|
|
"idle" | "marker" | "message"
|
|
>("idle")
|
|
const items =
|
|
rockyTurn === "message"
|
|
? [...initialItems, rockyMarker, rockyMessage]
|
|
: rockyTurn === "marker"
|
|
? [...initialItems, rockyMarker]
|
|
: initialItems
|
|
const buttonLabel =
|
|
rockyTurn === "idle" ? "Add Rocky" : "Send Message as Rocky"
|
|
const isComplete = rockyTurn === "message"
|
|
|
|
return (
|
|
<MessageScrollerProvider>
|
|
<div className="relative flex flex-col gap-4">
|
|
<Card className="mx-auto h-140 w-full max-w-sm gap-0">
|
|
<CardHeader className="gap-1 border-b">
|
|
<CardTitle>Group Chat</CardTitle>
|
|
<CardDescription>
|
|
A group chat with several participants and an assistant. The
|
|
Marker is marked as a turn.
|
|
</CardDescription>
|
|
<CardAction>
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
size="icon"
|
|
aria-label="Reset conversation"
|
|
disabled={rockyTurn === "idle"}
|
|
onClick={() => {
|
|
setRockyTurn("idle")
|
|
setDemoKey((key) => key + 1)
|
|
}}
|
|
>
|
|
<RotateCwIcon />
|
|
</Button>
|
|
</TooltipTrigger>
|
|
<TooltipContent>
|
|
<p>Reset</p>
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
</CardAction>
|
|
</CardHeader>
|
|
<CardContent className="min-h-0 flex-1 p-0">
|
|
<MessageScrollerProvider>
|
|
<MessageScroller key={demoKey}>
|
|
<MessageScrollerViewport>
|
|
<MessageScrollerContent className="p-(--card-spacing)">
|
|
{items.map((item) =>
|
|
item.type === "message" ? (
|
|
<GroupChatMessage key={item.id} item={item} />
|
|
) : (
|
|
<GroupChatMarker
|
|
key={item.id}
|
|
item={item}
|
|
scrollAnchor={item.scrollAnchor}
|
|
/>
|
|
)
|
|
)}
|
|
</MessageScrollerContent>
|
|
</MessageScrollerViewport>
|
|
<MessageScrollerButton />
|
|
</MessageScroller>
|
|
</MessageScrollerProvider>
|
|
</CardContent>
|
|
<CardFooter className="flex flex-col items-center gap-2 border-t">
|
|
<Button
|
|
type="button"
|
|
disabled={isComplete}
|
|
onClick={() =>
|
|
setRockyTurn((turn) => (turn === "idle" ? "marker" : "message"))
|
|
}
|
|
className="w-full"
|
|
variant="secondary"
|
|
>
|
|
{buttonLabel}
|
|
</Button>
|
|
<p className="text-xs text-muted-foreground">
|
|
{rockyTurn === "idle"
|
|
? "This will create a marker and make it the anchor"
|
|
: "Now send Rocky's reply into the conversation"}
|
|
</p>
|
|
</CardFooter>
|
|
</Card>
|
|
<div className="mx-auto max-w-sm px-0.5 text-center text-xs text-balance text-muted-foreground">
|
|
When a user joins, a marker is created. scrollAnchor on the marker
|
|
marks it as the next turn
|
|
</div>
|
|
</div>
|
|
</MessageScrollerProvider>
|
|
)
|
|
}
|
|
|
|
function GroupChatMessage({
|
|
item,
|
|
}: {
|
|
item: Extract<GroupChatItem, { type: "message" }>
|
|
}) {
|
|
const isCurrentUser = item.sender === currentUser
|
|
const variant = isCurrentUser
|
|
? "muted"
|
|
: item.role === "assistant"
|
|
? "ghost"
|
|
: "tinted"
|
|
|
|
return (
|
|
<MessageScrollerItem messageId={item.id} scrollAnchor={item.scrollAnchor}>
|
|
<Message align={isCurrentUser ? "end" : "start"}>
|
|
<MessageContent>
|
|
{!isCurrentUser && <MessageHeader>{item.sender}</MessageHeader>}
|
|
<Bubble variant={variant}>
|
|
<BubbleContent>{item.text}</BubbleContent>
|
|
</Bubble>
|
|
</MessageContent>
|
|
</Message>
|
|
</MessageScrollerItem>
|
|
)
|
|
}
|
|
|
|
function GroupChatMarker({
|
|
item,
|
|
scrollAnchor = false,
|
|
}: {
|
|
item: Extract<GroupChatItem, { type: "event" }>
|
|
scrollAnchor?: boolean
|
|
}) {
|
|
return (
|
|
<MessageScrollerItem scrollAnchor={scrollAnchor}>
|
|
<Marker variant="separator">
|
|
<MarkerContent>{item.text}</MarkerContent>
|
|
</Marker>
|
|
</MessageScrollerItem>
|
|
)
|
|
}
|