Files
auditly/components/chat/MessageThread.tsx
Ra cf565df13e feat: major UI overhaul with new components and enhanced UX
- Add comprehensive Company Wiki feature with complete state management
  - CompanyWikiManager, empty states, invite modals
- Implement new Chat system with enhanced layout and components
  - ChatLayout, ChatSidebar, MessageThread, FileUploadInput
- Create modern Login and OTP verification flows
  - LoginNew page, OTPVerification component
- Add new Employee Forms system with enhanced controller
- Introduce Figma-based design components and multiple choice inputs
- Add new font assets (NeueMontreal) and robot images for onboarding
- Enhance existing components with improved styling and functionality
- Update build configuration and dependencies
- Remove deprecated ModernLogin component
2025-08-20 04:06:49 -07:00

118 lines
5.7 KiB
TypeScript

import React from 'react';
interface Message {
id: string;
text: string;
isUser: boolean;
timestamp: number;
files?: string[];
}
interface MessageBubbleProps {
message: Message;
}
const MessageBubble: React.FC<MessageBubbleProps> = ({ message }) => {
const formatTime = (timestamp: number) => {
return new Date(timestamp).toLocaleTimeString('en-US', {
hour: 'numeric',
minute: '2-digit'
});
};
if (message.isUser) {
return (
<div className="flex justify-end mb-4">
<div className="max-w-[70%] flex flex-col items-end">
<div className="bg-Brand-Orange text-white px-4 py-3 rounded-2xl rounded-br-md">
{message.files && message.files.length > 0 && (
<div className="mb-2 flex flex-wrap gap-2">
{message.files.map((file, index) => (
<div key={index} className="px-2 py-1 bg-white/20 rounded text-xs">
📎 {file}
</div>
))}
</div>
)}
<div className="text-sm leading-relaxed">{message.text}</div>
</div>
<div className="text-xs text-Neutrals-NeutralSlate400 mt-1">
{formatTime(message.timestamp)}
</div>
</div>
</div>
);
}
return (
<div className="flex justify-start mb-4">
<div className="max-w-[85%] flex items-start gap-3">
{/* AI Avatar */}
<div className="w-8 h-8 bg-gradient-to-br from-Brand-Orange to-orange-600 rounded-full flex items-center justify-center flex-shrink-0 mt-1">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 2C8.73438 2 9.375 2.64062 9.375 3.375V4.5C9.375 5.23438 8.73438 5.875 8 5.875C7.26562 5.875 6.625 5.23438 6.625 4.5V3.375C6.625 2.64062 7.26562 2 8 2ZM8 10.125C8.73438 10.125 9.375 10.7656 9.375 11.5V12.625C9.375 13.3594 8.73438 14 8 14C7.26562 14 6.625 13.3594 6.625 12.625V11.5C6.625 10.7656 7.26562 10.125 8 10.125ZM12.625 6.625C13.3594 6.625 14 7.26562 14 8C14 8.73438 13.3594 9.375 12.625 9.375H11.5C10.7656 9.375 10.125 8.73438 10.125 8C10.125 7.26562 10.7656 6.625 11.5 6.625H12.625ZM5.875 8C5.875 8.73438 5.23438 9.375 4.5 9.375H3.375C2.64062 9.375 2 8.73438 2 8C2 7.26562 2.64062 6.625 3.375 6.625H4.5C5.23438 6.625 5.875 7.26562 5.875 8Z" fill="white" />
</svg>
</div>
<div className="flex flex-col">
<div className="bg-Neutrals-NeutralSlate100 text-Neutrals-NeutralSlate950 px-4 py-3 rounded-2xl rounded-bl-md">
<div className="text-sm leading-relaxed whitespace-pre-wrap">{message.text}</div>
</div>
<div className="text-xs text-Neutrals-NeutralSlate400 mt-1">
AI {formatTime(message.timestamp)}
</div>
</div>
</div>
</div>
);
};
interface LoadingIndicatorProps {
className?: string;
}
const LoadingIndicator: React.FC<LoadingIndicatorProps> = ({ className = '' }) => (
<div className={`flex justify-start mb-4 ${className}`}>
<div className="flex items-start gap-3">
{/* AI Avatar */}
<div className="w-8 h-8 bg-gradient-to-br from-Brand-Orange to-orange-600 rounded-full flex items-center justify-center flex-shrink-0 mt-1">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 2C8.73438 2 9.375 2.64062 9.375 3.375V4.5C9.375 5.23438 8.73438 5.875 8 5.875C7.26562 5.875 6.625 5.23438 6.625 4.5V3.375C6.625 2.64062 7.26562 2 8 2ZM8 10.125C8.73438 10.125 9.375 10.7656 9.375 11.5V12.625C9.375 13.3594 8.73438 14 8 14C7.26562 14 6.625 13.3594 6.625 12.625V11.5C6.625 10.7656 7.26562 10.125 8 10.125ZM12.625 6.625C13.3594 6.625 14 7.26562 14 8C14 8.73438 13.3594 9.375 12.625 9.375H11.5C10.7656 9.375 10.125 8.73438 10.125 8C10.125 7.26562 10.7656 6.625 11.5 6.625H12.625ZM5.875 8C5.875 8.73438 5.23438 9.375 4.5 9.375H3.375C2.64062 9.375 2 8.73438 2 8C2 7.26562 2.64062 6.625 3.375 6.625H4.5C5.23438 6.625 5.875 7.26562 5.875 8Z" fill="white" />
</svg>
</div>
<div className="bg-Neutrals-NeutralSlate100 px-4 py-3 rounded-2xl rounded-bl-md">
<div className="flex items-center gap-2">
<div className="w-2 h-2 bg-Neutrals-NeutralSlate400 rounded-full animate-bounce" style={{ animationDelay: '0ms' }} />
<div className="w-2 h-2 bg-Neutrals-NeutralSlate400 rounded-full animate-bounce" style={{ animationDelay: '150ms' }} />
<div className="w-2 h-2 bg-Neutrals-NeutralSlate400 rounded-full animate-bounce" style={{ animationDelay: '300ms' }} />
</div>
</div>
</div>
</div>
);
interface MessageThreadProps {
messages: Message[];
isLoading?: boolean;
className?: string;
}
const MessageThread: React.FC<MessageThreadProps> = ({
messages,
isLoading = false,
className = ''
}) => {
return (
<div className={`flex flex-col ${className}`}>
{messages.map((message) => (
<MessageBubble key={message.id} message={message} />
))}
{isLoading && <LoadingIndicator />}
</div>
);
};
export default MessageThread;
export { MessageBubble, LoadingIndicator };