mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-10-15 04:18:45 +00:00
105 lines
2.9 KiB
TypeScript
105 lines
2.9 KiB
TypeScript
import { useMemo } from "react"
|
|
import { CopyIcon, CheckIcon } from "@radix-ui/react-icons"
|
|
import { BrainCircuit, CircleUserRound } from "lucide-react"
|
|
|
|
import { cn } from "@/lib/utils"
|
|
import { useClipboard } from "@/components/ui/hooks"
|
|
import { Badge } from "@/components/ui"
|
|
import { Markdown } from "@/components/ui/markdown"
|
|
|
|
import { BadgeData, ChatHandler, Message, MessageAnnotationType } from "./types"
|
|
import { ChatMessageProvider } from "./ChatMessageProvider"
|
|
import { useChatUI } from "./useChatUI"
|
|
import { useChatMessage } from "./useChatMessage"
|
|
|
|
interface ChatMessageProps {
|
|
message: Message
|
|
isLast: boolean
|
|
isHeaderVisible: boolean
|
|
isLoading?: boolean
|
|
append?: ChatHandler["append"]
|
|
}
|
|
|
|
export function ChatMessage({ message, isLast, isHeaderVisible, isLoading, append }: ChatMessageProps) {
|
|
const badges = useMemo(
|
|
() =>
|
|
message.annotations
|
|
?.filter(({ type }) => type === MessageAnnotationType.BADGE)
|
|
.map(({ data }) => data as BadgeData),
|
|
[message.annotations],
|
|
)
|
|
|
|
return (
|
|
<ChatMessageProvider value={{ message, isLast }}>
|
|
<div
|
|
className={cn("relative group flex flex-col text-secondary-foreground", {
|
|
"bg-vscode-input-background/50": message.role === "user",
|
|
})}>
|
|
{isHeaderVisible && <ChatMessageHeader badges={badges} />}
|
|
<ChatMessageContent isHeaderVisible={isHeaderVisible} />
|
|
<ChatMessageActions />
|
|
</div>
|
|
</ChatMessageProvider>
|
|
)
|
|
}
|
|
|
|
interface ChatMessageHeaderProps {
|
|
badges?: BadgeData[]
|
|
}
|
|
|
|
function ChatMessageHeader({ badges }: ChatMessageHeaderProps) {
|
|
return (
|
|
<div className="flex flex-row items-center justify-between border-t border-accent px-3 pt-3 pb-1">
|
|
<ChatMessageAvatar />
|
|
{badges?.map(({ label, variant = "outline" }) => (
|
|
<Badge variant={variant} key={label}>
|
|
{label}
|
|
</Badge>
|
|
))}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
const icons: Record<string, React.ReactNode> = {
|
|
user: <CircleUserRound className="h-4 w-4" />,
|
|
assistant: <BrainCircuit className="h-4 w-4" />,
|
|
}
|
|
|
|
function ChatMessageAvatar() {
|
|
const { assistantName } = useChatUI()
|
|
const { message } = useChatMessage()
|
|
|
|
return icons[message.role] ? (
|
|
<div className="flex flex-row items-center gap-1">
|
|
<div className="opacity-25 select-none">{icons[message.role]}</div>
|
|
<div className="text-muted">{message.role === "user" ? "You" : assistantName}</div>
|
|
</div>
|
|
) : null
|
|
}
|
|
|
|
interface ChatMessageContentProps {
|
|
isHeaderVisible: boolean
|
|
}
|
|
|
|
function ChatMessageContent({ isHeaderVisible }: ChatMessageContentProps) {
|
|
const { message } = useChatMessage()
|
|
|
|
return (
|
|
<div className={cn("flex flex-col gap-4 flex-1 min-w-0 px-4 pb-6", { "pt-4": isHeaderVisible })}>
|
|
<Markdown content={message.content} />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function ChatMessageActions() {
|
|
const { message } = useChatMessage()
|
|
const { isCopied, copy } = useClipboard()
|
|
|
|
return (
|
|
<div
|
|
className="absolute right-2 bottom-2 opacity-0 group-hover:opacity-25 cursor-pointer"
|
|
onClick={() => copy(message.content)}>
|
|
{isCopied ? <CheckIcon /> : <CopyIcon />}
|
|
</div>
|
|
)
|
|
}
|