quantumkv's picture
Hi Design Team,
20fda5d verified
class ClaudeChatContainer extends HTMLElement {
constructor() {
super();
this.autoScroll = true;
}
connectedCallback() {
this.attachShadow({ mode: 'open' });
this.render();
this.setupEventListeners();
this.loadConversationHistory();
}
render() {
this.shadowRoot.innerHTML = `
<style>
.chat-container {
max-width: 1200px;
margin: 0 auto;
padding: 1rem 1.5rem 2rem;
flex: 1;
overflow-y: auto;
max-height: calc(100vh - 240px);
display: flex;
flex-direction: column;
}
.messages-wrapper {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.message {
max-width: 85%;
animation: message-enter 0.3s ease-out;
}
.message.user {
align-self: flex-end;
}
.message.assistant {
align-self: flex-start;
}
.message-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.5rem;
}
.message-role {
font-size: 0.875rem;
font-weight: 600;
}
.user .message-role {
color: var(--primary-blue);
}
.assistant .message-role {
color: var(--success-green);
}
.message-timestamp {
font-size: 0.75rem;
color: #94a3b8;
}
.message-body {
padding: 1rem;
border-radius: 1rem;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
.user .message-body {
background: var(--primary-blue);
color: white;
}
.assistant .message-body {
background: white;
border: 1px solid #e2e8f0;
color: #1e293b;
}
.typing-indicator {
display: none;
align-items: center;
gap: 0.5rem;
padding: 1rem;
background: white;
border: 1px solid #e2e8f0;
border-radius: 1rem;
margin-bottom: 1rem;
align-self: flex-start;
max-width: 85%;
}
.typing-indicator.show {
display: flex;
}
.typing-dots {
display: flex;
gap: 0.25rem;
}
.typing-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: #64748b;
}
.empty-state {
text-align: center;
padding: 3rem 2rem;
color: #64748b;
}
.empty-title {
font-size: 1.5rem;
font-weight: 600;
margin-bottom: 1rem;
}
.empty-subtitle {
font-size: 1rem;
line-height: 1.6;
}
.scroll-to-bottom {
position: fixed;
bottom: 120px;
right: 2rem;
background: var(--primary-blue);
color: white;
border: none;
border-radius: 50%;
width: 48px;
height: 48px;
display: none;
align-items: center;
justify-content: center;
cursor: pointer;
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
transition: all 0.2s;
}
.scroll-to-bottom.show {
display: flex;
}
.scroll-to-bottom:hover {
background: var(--primary-blue-dark);
transform: scale(1.05);
}
@keyframes message-enter {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@media (max-width: 767px) {
.chat-container {
padding: 0.5rem 1rem 1rem;
max-height: calc(100vh - 200px);
}
.message {
max-width: 95%;
}
.scroll-to-bottom {
right: 1rem;
bottom: 100px;
}
}
</style>
<div class="chat-container" id="chatContainer">
<div class="messages-wrapper" id="messagesWrapper">
<div class="empty-state" id="emptyState">
<div class="empty-title">Welcome to ClaudeVerse AI</div>
<div class="empty-subtitle">
I'm here to help you with writing, analysis, coding, and much more.
Choose a Claude model above and start a conversation!
</div>
</div>
</div>
<button class="scroll-to-bottom" id="scrollToBottom">
<i data-feather="arrow-down" width="20" height="20"></i>
</button>
<div class="typing-indicator" id="typingIndicator">
<div class="typing-dots">
<div class="typing-dot"></div>
<div class="typing-dot"></div>
<div class="typing-dot"></div>
</div>
<span>Claude is typing...</span>
</div>
</div>
`;
setTimeout(() => {
feather.replace();
}, 100);
}
setupEventListeners() {
const chatContainer = this.shadowRoot.getElementById('chatContainer');
const scrollToBottom = this.shadowRoot.getElementById('scrollToBottom');
// Scroll behavior
chatContainer.addEventListener('scroll', () => {
const { scrollTop, scrollHeight, clientHeight } = chatContainer;
const isAtBottom = scrollHeight - scrollTop - clientHeight < 50;
if (isAtBottom) {
this.autoScroll = true;
scrollToBottom.classList.remove('show');
} else {
this.autoScroll = false;
scrollToBottom.classList.add('show');
}
});
// Scroll to bottom button
scrollToBottom.addEventListener('click', () => {
this.scrollToBottom();
});
// Global events
document.addEventListener('claude-show-typing', () => {
this.showTypingIndicator();
});
document.addEventListener('claude-hide-typing', () => {
this.hideTypingIndicator();
});
document.addEventListener('claude-clear-chat', () => {
this.clearMessages();
this.showEmptyState();
});
document.addEventListener('claude-new-chat', () => {
this.clearMessages();
this.showEmptyState();
});
// Listen for new messages
document.addEventListener('claude-message-added', (event) => {
const { message } = event.detail;
this.addMessage(message);
});
}
loadConversationHistory() {
const history = window.claudeApp.conversationHistory;
if (history.length === 0) {
this.showEmptyState();
} else {
this.hideEmptyState();
history.forEach(message => {
this.addMessage(message, false);
});
this.scrollToBottom();
}
}
addMessage(message, animate = true) {
this.hideEmptyState();
const messagesWrapper = this.shadowRoot.getElementById('messagesWrapper');
const messageElement = document.createElement('div');
messageElement.className = `message ${message.role}`;
if (animate) {
messageElement.classList.add('message-enter');
}
const timestamp = new Date(message.timestamp).toLocaleTimeString();
messageElement.innerHTML = `
<div class="message-header">
<span class="message-role">${message.role === 'user' ? 'You' : 'Assistant'}</span>
</div>
<div class="message-body">
${this.formatMessageContent(message.content)}
</div>
`;
messagesWrapper.appendChild(messageElement);
if (this.autoScroll) {
this.scrollToBottom();
}
// Update feather icons
setTimeout(() => {
feather.replace();
}, 100);
}
formatMessageContent(content) {
// Simple markdown parsing for code blocks and inline code
return content
.replace(/