import React, { useEffect, useRef, useState } from "react";
import ReactMarkdown from 'react-markdown';
import './ChatWindow.css';
import FileListComponent from "./FileList";
import DiffViewer from "./DiffViewer";
import CodeChangePlan from "./CodeChangePlan";
import { FaRegCopy,FaCopy } from 'react-icons/fa'; // Import the copy icon
// Individual message component
const ChatMessage = ({ message }) => {
const [selectedFile, setSelectedFile] = useState(null);
const [isReasoningExpanded, setIsReasoningExpanded] = useState(false);
const toggleReasoning = () => {
setIsReasoningExpanded(!isReasoningExpanded);
};
const handleCloseDiff = () => {
setSelectedFile(null);
};
const handleFileClick = (file) => {
setSelectedFile(file);
};
// Function to copy text to clipboard
const handleCopy = async () => {
if (message.text) {
try {
await navigator.clipboard.writeText(message.text);
// Optional: Add a state or a toast notification to show "Copied!"
} catch (err) {
console.error('Failed to copy text: ', err);
}
}
};
const assistantMessageClasses = `p-4 rounded-lg shadow-md w-full bg-gray-200 dark:bg-gray-700 text-gray-900 dark:text-gray-100 assistant-message`;
const userMessageClasses = `max-w-md p-4 rounded-lg shadow-md bg-indigo-500 text-white ml-auto`;
return (
<div className={message.isUser ? userMessageClasses : assistantMessageClasses}>
{message.reasoning && (
<div className="mb-2">
<button
onClick={toggleReasoning}
className="text-sm font-semibold text-indigo-700 hover:text-indigo-800 dark:text-gray-300 dark:hover:text-gray-100 focus:outline-none"
>
{isReasoningExpanded ? "Hide Reasoning ▲" : "Show Reasoning ▼"}
</button>
<div
className={`mt-2 text-xs transition-max-h duration-500 ease-in-out overflow-hidden ${
isReasoningExpanded ? "max-h-96" : "max-h-0"
} text-gray-700 dark:text-gray-300`}
>
<ReactMarkdown>{message.reasoning}</ReactMarkdown>
</div>
</div>
)}
<ReactMarkdown>{message.text}</ReactMarkdown>
{message.isPureAnswer && (
<div className="justify-end items-center mt-2">
{/* Horizontal line */}
<div className="border-b border-gray-400 dark:border-gray-600"></div>
<div className="flex justify-end items-center mt-2">
{/* Copy Icon - positioned above the bottom line */}
<button
onClick={handleCopy}
className="relative p-2 rounded-right-2 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition-colors z-10 group"
aria-label="Copy message text"
>
{/* Outline icon */}
<FaRegCopy size={18} className="transition-opacity opacity-100 group-hover:opacity-0 absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2" />
{/* Solid icon (initially hidden) */}
<FaCopy size={18} className="transition-opacity opacity-0 group-hover:opacity-100 absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2" />
</button>
</div>
</div>
)}
{message.code_changes && (
<FileListComponent code_changes={message.code_changes} onFileClick={handleFileClick} />
)}
{message.steps && (
<CodeChangePlan steps={message.steps} />
)}
{selectedFile && <DiffViewer oldContent={selectedFile.old} newContent={selectedFile.new} filePath={selectedFile.filepath} onClose={handleCloseDiff} />}
</div>
);
};
// Main ChatWindow component with dynamic height calculation
const ChatWindow = ({ chatHistory, maxHeight }) => {
const containerRef = useRef(null);
useEffect(() => {
if (containerRef.current) {
containerRef.current.scrollTop = containerRef.current.scrollHeight;
}
}, [chatHistory]);
return (
<div
ref={containerRef}
style={{ maxHeight: maxHeight, overflowY: 'auto' }}
className="px-2 py-4 space-y-4 bg-transparent"
>
{chatHistory.map((message, index) => (
<div
key={index}
className={`flex ${message.isUser ? "justify-end" : "justify-start"} w-full`}
>
<ChatMessage message={message} />
</div>
))}
</div>
);
};
export default ChatWindow;