|
|
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'); |
|
|
|
|
|
|
|
|
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'); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
scrollToBottom.addEventListener('click', () => { |
|
|
this.scrollToBottom(); |
|
|
}); |
|
|
|
|
|
|
|
|
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(); |
|
|
}); |
|
|
|
|
|
|
|
|
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(); |
|
|
} |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
feather.replace(); |
|
|
}, 100); |
|
|
} |
|
|
|
|
|
formatMessageContent(content) { |
|
|
|
|
|
return content |
|
|
.replace(/ |