Spaces:
Running
Running
File size: 5,059 Bytes
22b1735 1904e4c 22b1735 1904e4c 978caa8 22b1735 1904e4c 22b1735 1904e4c 978caa8 22b1735 1904e4c 22b1735 978caa8 1904e4c 22b1735 1904e4c 978caa8 1904e4c 22b1735 1904e4c 22b1735 1904e4c 22b1735 1904e4c 22b1735 1904e4c 22b1735 1904e4c 22b1735 1904e4c 22b1735 1904e4c 978caa8 1904e4c 978caa8 1904e4c 978caa8 1904e4c 978caa8 1904e4c 22b1735 1904e4c 978caa8 1904e4c 22b1735 |
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 |
import { useState } from "react";
import { ArrowRight, RefreshCcw, Copy, Check, Trash2, RotateCcw, ListFilter, ChevronLeft, ChevronRight } from "lucide-react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { format } from "date-fns";
import { Avatar, AvatarFallback } from "../ui/avatar";
import { ChatMessage } from "./ChatMessage";
import { ThinkingAnimation } from "./ThinkingAnimation";
import { toast } from '../ui/sonner';
import { MessageActions } from "./MessageActions";
import { MessageVariationControls } from "./MessageVariationControls";
import { DeleteMessageDialog } from "./DeleteMessageDialog";
import { MessageVariation, Message } from "@/types/chat";
interface ChatBubbleProps {
message: Message;
onViewSearchResults?: (messageId: string) => void;
onRetry?: (messageId: string) => void;
onRegenerate?: (messageId: string) => void;
onDelete?: (messageId: string) => void;
onSelectVariation?: (messageId: string, variationId: string) => void;
}
export const ChatBubble = ({
message,
onViewSearchResults,
onRetry,
onRegenerate,
onDelete,
onSelectVariation
}: ChatBubbleProps) => {
const [copied, setCopied] = useState(false);
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const isWelcomeMessage = message.id === "welcomems";
const isSystem = message.sender === "system";
const hasVariations = isSystem && message.variations && message.variations.length > 0;
// If the message has variations, display the active one or the first one
const displayContent = isSystem && hasVariations && message.activeVariation
? message.variations.find(v => v.id === message.activeVariation)?.content || message.content
: message.content;
const copyToClipboard = () => {
// Strip thinking content before copying
const cleanContent = displayContent.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
navigator.clipboard.writeText(cleanContent);
setCopied(true);
toast.success('Copied to clipboard!');
setTimeout(() => setCopied(false), 2000);
};
const handleDelete = () => {
setDeleteDialogOpen(false);
if (onDelete) {
onDelete(message.id);
}
};
return (
<>
<div
className={cn(
"group flex w-full animate-slide-in mb-2",
isSystem ? "justify-start" : "justify-end"
)}
>
<div className={cn(
"flex gap-3 max-w-[80%]",
isSystem ? "flex-row" : "flex-row-reverse"
)}>
<Avatar className={cn(
"h-8 w-8 border",
isSystem
? "bg-financial-accent dark:text-white shadow-lg"
: "bg-muted"
)}>
<AvatarFallback className="text-xs font-semibold">
{isSystem ? "AI" : "You"}
</AvatarFallback>
</Avatar>
<div className="flex flex-col">
<div className={cn(
"rounded-2xl shadow-lg message-bubble backdrop-blur-sm",
isSystem
? "bg-white/90 dark:bg-card/90 border border-border text-foreground message-bubble-ai"
: "bg-financial-accent/30 border border-financial-accent/30 dark:text-white message-bubble-user",
message.error && "border-destructive dark:border-red-500"
)}>
{message.isLoading ? (
<ThinkingAnimation />
) : (
<ChatMessage
content={displayContent}
/>
)}
</div>
{/* Chat bubble footer */}
<div className="flex flex-row chat-bubble-footer justify-between items-center mt-1">
{/* Time */}
<div className={cn(
"text-xs text-muted-foreground",
isSystem ? "text-left" : "text-right"
)}>
{format(message.timestamp, "h:mm a")}
</div>
{/* Controls */}
{!isWelcomeMessage && (
<div className="controls flex gap-1 items-center">
{/* Variation controls */}
{hasVariations && message.variations && message.variations.length > 1 && (
<MessageVariationControls
message={message}
onSelectVariation={onSelectVariation}
/>
)}
<MessageActions
message={message}
onRetry={onRetry}
onRegenerate={onRegenerate}
onDelete={() => setDeleteDialogOpen(true)}
onCopy={copyToClipboard}
copied={copied}
/>
</div>
)}
</div>
</div>
</div>
</div>
<DeleteMessageDialog
isOpen={deleteDialogOpen}
onOpenChange={setDeleteDialogOpen}
onDelete={handleDelete}
/>
</>
);
};
|