Spaces:
Sleeping
Sleeping
| import React, { useRef, useEffect, useState } from 'react'; | |
| import { FaRobot, FaUser, FaCopy, FaSearch, FaEdit, FaSave, FaTimes } from 'react-icons/fa'; | |
| import './ChatDetailModal.css'; | |
| /** | |
| * Modal component for displaying and editing agent chat messages in detail | |
| * @param {Object} props | |
| * @param {boolean} props.isOpen - Whether the modal is open | |
| * @param {function} props.onClose - Function to call when the modal is closed | |
| * @param {string} props.agentName - The name of the agent | |
| * @param {string} props.agentId - The ID of the agent | |
| * @param {number} props.turn - The turn number | |
| * @param {string} props.content - The chat message content | |
| * @param {function} props.onSave - Function to call when the content is saved | |
| */ | |
| const ChatDetailModal = ({ isOpen, onClose, agentName, agentId, turn, content, onSave }) => { | |
| const modalRef = useRef(null); | |
| const [isEditing, setIsEditing] = useState(false); | |
| const [editedContent, setEditedContent] = useState(''); | |
| const textareaRef = useRef(null); | |
| // Initialize the editor with the current content when editing starts | |
| useEffect(() => { | |
| if (isEditing && content) { | |
| // Remove any HTML tags to get plain text for editing | |
| const plainText = content.replace(/<[^>]*>/g, ''); | |
| setEditedContent(plainText); | |
| // Focus the textarea when editing starts | |
| setTimeout(() => { | |
| if (textareaRef.current) { | |
| textareaRef.current.focus(); | |
| } | |
| }, 100); | |
| } | |
| }, [isEditing, content]); | |
| // Reset edited content when content changes (even if modal is already open) | |
| useEffect(() => { | |
| if (content && isOpen) { | |
| // If we're currently editing, update the edited content | |
| if (isEditing) { | |
| const plainText = content.replace(/<[^>]*>/g, ''); | |
| setEditedContent(plainText); | |
| } | |
| } | |
| }, [content, isOpen]); | |
| // Handle clicks outside the modal to close it | |
| useEffect(() => { | |
| const handleClickOutside = (event) => { | |
| if (modalRef.current && !modalRef.current.contains(event.target)) { | |
| onClose(); | |
| } | |
| }; | |
| if (isOpen) { | |
| document.addEventListener('mousedown', handleClickOutside); | |
| } | |
| return () => { | |
| document.removeEventListener('mousedown', handleClickOutside); | |
| }; | |
| }, [isOpen, onClose]); | |
| // Copy content to clipboard | |
| const handleCopyContent = () => { | |
| const plainText = content.replace(/<[^>]*>/g, ''); | |
| navigator.clipboard.writeText(plainText); | |
| // Show a mini toast or feedback | |
| const copyButton = document.querySelector('.copy-button'); | |
| if (copyButton) { | |
| copyButton.classList.add('copied'); | |
| setTimeout(() => { | |
| copyButton.classList.remove('copied'); | |
| }, 2000); | |
| } | |
| }; | |
| // Start editing the content | |
| const handleStartEditing = () => { | |
| setIsEditing(true); | |
| }; | |
| // Cancel editing and reset | |
| const handleCancelEdit = () => { | |
| setIsEditing(false); | |
| setEditedContent(''); | |
| }; | |
| // Save the edited content | |
| const handleSaveEdit = () => { | |
| if (onSave) { | |
| onSave(agentId, turn, editedContent); | |
| } | |
| setIsEditing(false); | |
| }; | |
| // Don't render anything if the modal is not open | |
| if (!isOpen) return null; | |
| // Get agent icon based on agent ID or name | |
| const getAgentIcon = () => { | |
| if (agentId === 'researcher') { | |
| return <FaSearch />; | |
| } | |
| return <FaRobot />; | |
| }; | |
| return ( | |
| <div className="chat-modal-overlay"> | |
| <div className="chat-modal-content" ref={modalRef}> | |
| <div className="chat-modal-header"> | |
| <div className="agent-info"> | |
| <div className="agent-avatar"> | |
| {getAgentIcon()} | |
| </div> | |
| <div className="agent-details"> | |
| <h3>{agentName}</h3> | |
| <span className="turn-badge">Turn {turn}</span> | |
| </div> | |
| </div> | |
| <div className="modal-actions"> | |
| {!isEditing ? ( | |
| <> | |
| <button | |
| className="edit-button" | |
| onClick={handleStartEditing} | |
| title="Edit content" | |
| > | |
| <FaEdit /> | |
| </button> | |
| <button | |
| className="copy-button" | |
| onClick={handleCopyContent} | |
| title="Copy to clipboard" | |
| > | |
| <FaCopy /> | |
| </button> | |
| </> | |
| ) : ( | |
| <> | |
| <button | |
| className="save-button" | |
| onClick={handleSaveEdit} | |
| title="Save changes" | |
| > | |
| <FaSave /> | |
| </button> | |
| <button | |
| className="cancel-button" | |
| onClick={handleCancelEdit} | |
| title="Cancel editing" | |
| > | |
| <FaTimes /> | |
| </button> | |
| </> | |
| )} | |
| <button className="close-button" onClick={onClose}>×</button> | |
| </div> | |
| </div> | |
| <div className="chat-modal-body"> | |
| {!isEditing ? ( | |
| <div className="content-box" dangerouslySetInnerHTML={{ __html: content }} /> | |
| ) : ( | |
| <textarea | |
| ref={textareaRef} | |
| className="content-editor" | |
| value={editedContent} | |
| onChange={(e) => setEditedContent(e.target.value)} | |
| placeholder="Edit the content..." | |
| /> | |
| )} | |
| </div> | |
| <div className="chat-modal-footer"> | |
| {!isEditing ? ( | |
| <button className="modal-button close-btn" onClick={onClose}>Close</button> | |
| ) : ( | |
| <> | |
| <button className="modal-button cancel-btn" onClick={handleCancelEdit}>Cancel</button> | |
| <button className="modal-button save-btn" onClick={handleSaveEdit}>Save Changes</button> | |
| </> | |
| )} | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| export default ChatDetailModal; |