File size: 5,549 Bytes
e454383 | 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 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 | "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;
|