test / src /components /message.tsx
aiioe's picture
Automated build & deploy
e454383 verified
Raw History Blame Contribute Delete
5.55 kB
"use client";
import { Button } from "@/components/ui/button";
import type { Message } from "@/lib/types";
import { cn } from "@/lib/utils";
import { Bot, FileText, Star, User, Copy, Check } from "lucide-react";
import { useState } from "react";
import ReactMarkdown from "react-markdown";
import { useToast } from "@/hooks/use-toast";
type Props = {
message: Message;
handleTranscriptClick: (transcript: Message["transcripts"][0]) => void;
onEvaluate: () => void;
};
const MessageBlock = ({
message,
handleTranscriptClick,
onEvaluate,
}: Props) => {
const Icon = message.role === "user" ? User : Bot;
const role = message.role === "user" ? "You" : "AI Assistant";
const [copied, setCopied] = useState(false);
const { toast } = useToast();
const handleCopy = async () => {
await navigator.clipboard.writeText(message.content);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
toast({
title: "Copied to clipboard",
description: "The response has been copied successfully.",
});
};
return (
<div className="w-full">
<div
className={cn(
"rounded-2xl p-4 sm:p-6 transition-all duration-200",
"max-w-full overflow-x-auto break-words", // Prevent overflow on mobile
message.role === "user"
? "bg-gradient-to-br from-primary/10 to-primary/5 border border-primary/20 ml-0 sm:ml-12"
: "bg-gradient-to-br from-card to-card/50 border border-border/50 mr-0 sm:mr-12"
)}
style={{ wordBreak: "break-word" }} // Extra safety for long words
>
<div
className={cn(
"flex items-center gap-2 sm:gap-3 mb-3 sm:mb-4",
message.role === "assistant" ? "justify-start" : "justify-end"
)}
>
<div
className={cn(
"flex items-center gap-2",
message.role === "user" && "flex-row-reverse"
)}
>
<div
className={cn(
"w-7 h-7 sm:w-8 sm:h-8 rounded-full flex items-center justify-center flex-shrink-0",
message.role === "user"
? "bg-primary text-primary-foreground"
: "bg-gradient-to-br from-blue-500 to-purple-600 text-white"
)}
>
<Icon className="h-3.5 w-3.5 sm:h-4 sm:w-4" />
</div>
<div
className={cn(
"flex flex-col",
message.role === "user" ? "items-end" : "items-start"
)}
>
<span className="font-medium text-xs sm:text-sm">{role}</span>
<span className="text-xs text-muted-foreground">
{message.timestamp.toLocaleTimeString()}
</span>
</div>
</div>
</div>
<div
className={cn(
"prose prose-sm dark:prose-invert max-w-full text-sm sm:text-base", // max-w-full for mobile
message.role === "user" ? "text-right" : "text-left"
)}
style={{ wordBreak: "break-word" }} // Extra safety for markdown content
>
<ReactMarkdown
components={{
a: ({ href, children, ...props }) => (
<a
href={href}
target="_blank"
rel="noopener noreferrer"
{...props}
>
{children}
</a>
),
}}
>
{message.content}
</ReactMarkdown>
</div>
{message.transcripts.length > 0 && (
<div className="flex flex-wrap mt-4 sm:mt-6 gap-2">
{message.transcripts.map((transcript) => (
<Button
key={transcript.id}
variant="secondary"
size="sm"
className="text-xs h-auto min-h-7 sm:min-h-8 bg-muted/50 hover:bg-muted border border-border/50 transition-all duration-200 hover:scale-105 py-2 max-w-full overflow-x-auto"
onClick={() => handleTranscriptClick(transcript)}
style={{ wordBreak: "break-word" }}
>
<FileText className="mr-1 sm:mr-1.5 w-3" />
<span className="text-start whitespace-normal break-words">
{transcript.filename} @ {transcript.timestamp}
</span>
</Button>
))}
</div>
)}
{message.role === "assistant" && (
<div className="mt-4 sm:mt-6 flex flex-col sm:flex-row justify-start gap-2">
{/* <Button
onClick={onEvaluate}
variant="ghost"
size="sm"
className="h-8 text-xs hover:bg-accent/50 transition-all duration-200 justify-start sm:justify-center"
>
<Star className="h-3.5 w-3.5 mr-1.5" />
Rate Answer
</Button> */}
<Button
variant="ghost"
size="sm"
className="h-8 text-xs hover:bg-accent/50 transition-all duration-200 justify-start sm:justify-center"
onClick={handleCopy}
>
{copied ? (
<Check className="h-3.5 w-3.5 mr-1.5 text-green-500" />
) : (
<Copy className="h-3.5 w-3.5 mr-1.5" />
)}
{copied ? "Copied!" : "Copy Response"}
</Button>
</div>
)}
</div>
</div>
);
};
export default MessageBlock;