|
<style> |
|
#gemini-fab { |
|
position: fixed; |
|
bottom: 25px; |
|
right: 25px; |
|
width: 60px; |
|
height: 60px; |
|
background-color: #4285f4; |
|
border-radius: 50%; |
|
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25); |
|
display: flex; |
|
justify-content: center; |
|
align-items: center; |
|
cursor: pointer; |
|
z-index: 1000; |
|
transition: |
|
transform 0.2s ease-in-out, |
|
box-shadow 0.2s ease-in-out, |
|
width 0.2s ease, |
|
height 0.2s ease; |
|
} |
|
|
|
#gemini-fab:hover { |
|
transform: scale(1.1); |
|
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3); |
|
} |
|
|
|
#gemini-fab img { |
|
width: 38px; |
|
height: 38px; |
|
transition: |
|
width 0.2s ease, |
|
height 0.2s ease; |
|
} |
|
|
|
#gemini-chat-container { |
|
position: fixed; |
|
background-color: #ffffff; |
|
border: 1px solid #dadce0; |
|
border-radius: 12px; |
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15); |
|
display: none; |
|
flex-direction: column; |
|
overflow: hidden; |
|
z-index: 999; |
|
font-family: 'Roboto', 'Segoe UI', Arial, sans-serif; |
|
transition: |
|
width 0.3s ease, |
|
height 0.3s ease, |
|
bottom 0.3s ease, |
|
right 0.3s ease, |
|
left 0.3s ease; |
|
width: 400px; |
|
max-height: 600px; |
|
bottom: 95px; |
|
right: 25px; |
|
} |
|
|
|
#gemini-chat-container.visible { |
|
display: flex; |
|
animation: slideUpFadeIn 0.3s ease-out; |
|
} |
|
|
|
@keyframes slideUpFadeIn { |
|
from { |
|
opacity: 0; |
|
transform: translateY(20px); |
|
} |
|
to { |
|
opacity: 1; |
|
transform: translateY(0); |
|
} |
|
} |
|
|
|
.gemini-chat-header { |
|
background-color: #f1f3f4; |
|
padding: 12px 18px; |
|
display: flex; |
|
justify-content: space-between; |
|
align-items: center; |
|
border-bottom: 1px solid #e0e0e0; |
|
flex-shrink: 0; |
|
} |
|
.gemini-chat-header h3 { |
|
margin: 0; |
|
font-size: 1.05rem; |
|
color: #202124; |
|
font-weight: 500; |
|
} |
|
.gemini-chat-header .close-chat-btn { |
|
background: none; |
|
border: none; |
|
font-size: 1.5rem; |
|
font-weight: 300; |
|
line-height: 1; |
|
cursor: pointer; |
|
color: #5f6368; |
|
padding: 0; |
|
} |
|
.gemini-chat-header .close-chat-btn:hover { |
|
color: #202124; |
|
} |
|
|
|
#gemini-chat-log { |
|
flex-grow: 1; |
|
padding: 15px 18px; |
|
overflow-y: auto; |
|
display: flex; |
|
flex-direction: column; |
|
gap: 12px; |
|
background-color: #f8f9fa; |
|
} |
|
|
|
.chat-message { |
|
padding: 10px 15px; |
|
padding-bottom: 35px; |
|
border-radius: 18px; |
|
max-width: 85%; |
|
line-height: 1.45; |
|
font-size: 0.92rem; |
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); |
|
position: relative; |
|
} |
|
.chat-message span { |
|
white-space: pre-wrap; |
|
word-wrap: break-word; |
|
} |
|
|
|
.chat-message pre { |
|
background: #1e1e1e; |
|
padding: 0.8em 1em; |
|
overflow: auto; |
|
border-radius: 5px; |
|
color: #ccc; |
|
margin: 0.5em 0; |
|
font-size: 0.85em; |
|
line-height: 1.5; |
|
position: relative; |
|
white-space: pre; |
|
word-wrap: normal; |
|
} |
|
.chat-message pre code, |
|
.chat-message pre code.hljs { |
|
font-family: 'monospace'; |
|
display: block; |
|
padding: 0; |
|
} |
|
|
|
.chat-message.user { |
|
background-color: #d1eaff; |
|
color: #004085; |
|
align-self: flex-end; |
|
border-bottom-right-radius: 6px; |
|
} |
|
.chat-message.ai { |
|
background-color: #e9ecef; |
|
color: #383d41; |
|
align-self: flex-start; |
|
border-bottom-left-radius: 6px; |
|
} |
|
|
|
.copy-code-btn { |
|
position: absolute; |
|
top: 8px; |
|
right: 8px; |
|
background-color: rgba(80, 80, 80, 0.7); |
|
color: white; |
|
border: none; |
|
padding: 4px 8px; |
|
font-size: 0.75em; |
|
border-radius: 4px; |
|
cursor: pointer; |
|
font-family: sans-serif; |
|
z-index: 1; |
|
opacity: 0.7; |
|
visibility: visible; |
|
transition: |
|
opacity 0.2s, |
|
background-color 0.2s; |
|
} |
|
|
|
.copy-msg-btn { |
|
position: absolute; |
|
bottom: 6px; |
|
right: 8px; |
|
background-color: rgba(100, 100, 100, 0.6); |
|
color: white; |
|
border: none; |
|
border-radius: 50%; |
|
cursor: pointer; |
|
font-family: sans-serif; |
|
z-index: 1; |
|
opacity: 0.6; |
|
visibility: visible; |
|
transition: |
|
opacity 0.2s, |
|
background-color 0.2s; |
|
display: inline-flex; |
|
align-items: center; |
|
justify-content: center; |
|
width: 26px; |
|
height: 26px; |
|
padding: 0; |
|
} |
|
.copy-msg-btn svg { |
|
width: 14px; |
|
height: 14px; |
|
fill: currentColor; |
|
} |
|
|
|
.copy-code-btn:hover { |
|
background-color: rgba(100, 100, 100, 0.9); |
|
opacity: 1; |
|
} |
|
.copy-msg-btn:hover { |
|
background-color: rgba(80, 80, 80, 0.9); |
|
opacity: 1; |
|
} |
|
|
|
.copy-code-btn.copied, |
|
.copy-msg-btn.copied { |
|
background-color: #28a745; |
|
opacity: 1; |
|
} |
|
|
|
#gemini-typing-indicator { |
|
display: flex; |
|
align-items: center; |
|
padding: 8px 18px 4px 18px; |
|
background-color: #f8f9fa; |
|
height: 20px; |
|
flex-shrink: 0; |
|
} |
|
#gemini-typing-indicator span { |
|
height: 8px; |
|
width: 8px; |
|
background-color: #909090; |
|
border-radius: 50%; |
|
display: inline-block; |
|
margin: 0 2px; |
|
animation: geminiDotsBounce 1.3s infinite ease-in-out; |
|
} |
|
#gemini-typing-indicator span:nth-child(2) { |
|
animation-delay: -1.1s; |
|
} |
|
#gemini-typing-indicator span:nth-child(3) { |
|
animation-delay: -0.9s; |
|
} |
|
@keyframes geminiDotsBounce { |
|
0%, |
|
60%, |
|
100% { |
|
transform: scale(0.4); |
|
} |
|
30% { |
|
transform: scale(1); |
|
} |
|
} |
|
|
|
#gemini-chat-input-form { |
|
display: flex; |
|
padding: 12px 15px; |
|
border-top: 1px solid #e0e0e0; |
|
background-color: #ffffff; |
|
align-items: center; |
|
flex-shrink: 0; |
|
} |
|
#gemini-chat-input-form input[type='text'] { |
|
flex-grow: 1; |
|
padding: 12px 18px; |
|
border: 1px solid #dfe1e5; |
|
border-radius: 24px; |
|
margin-right: 10px; |
|
font-size: 0.95rem; |
|
outline: none; |
|
transition: |
|
border-color 0.2s, |
|
box-shadow 0.2s; |
|
} |
|
#gemini-chat-input-form input[type='text']:focus { |
|
border-color: #4285f4; |
|
box-shadow: 0 0 0 2px rgba(66, 133, 244, 0.2); |
|
} |
|
#gemini-chat-input-form input[type='text']:disabled { |
|
background-color: #e9ecef; |
|
cursor: not-allowed; |
|
} |
|
#gemini-chat-input-form button { |
|
background-color: #4285f4; |
|
color: white; |
|
border: none; |
|
border-radius: 50%; |
|
width: 44px; |
|
height: 44px; |
|
padding: 0; |
|
cursor: pointer; |
|
font-size: 1.3rem; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
transition: |
|
background-color 0.2s ease, |
|
width 0.2s ease, |
|
height 0.2s ease; |
|
flex-shrink: 0; |
|
} |
|
#gemini-chat-input-form button:hover { |
|
background-color: #3367d6; |
|
} |
|
#gemini-chat-input-form button:disabled { |
|
background-color: #a1c6ff; |
|
cursor: not-allowed; |
|
} |
|
#gemini-chat-input-form button svg { |
|
width: 22px; |
|
height: 22px; |
|
fill: white; |
|
transition: |
|
width 0.2s ease, |
|
height 0.2s ease; |
|
} |
|
|
|
@media (max-width: 991.98px) { |
|
#gemini-chat-container { |
|
width: 380px; |
|
max-height: 550px; |
|
} |
|
} |
|
@media (max-width: 767.98px) { |
|
#gemini-fab { |
|
width: 55px; |
|
height: 55px; |
|
bottom: 20px; |
|
right: 20px; |
|
} |
|
#gemini-fab img { |
|
width: 32px; |
|
height: 32px; |
|
} |
|
#gemini-chat-container { |
|
width: 360px; |
|
max-height: 75vh; |
|
bottom: 85px; |
|
right: 20px; |
|
} |
|
} |
|
@media (max-width: 575.98px) { |
|
#gemini-fab { |
|
width: 50px; |
|
height: 50px; |
|
bottom: 15px; |
|
right: 15px; |
|
} |
|
#gemini-fab img { |
|
width: 28px; |
|
height: 28px; |
|
} |
|
#gemini-chat-container { |
|
left: 10px; |
|
right: 10px; |
|
width: auto; |
|
bottom: 75px; |
|
max-height: calc(100vh - 95px); |
|
} |
|
.chat-message pre { |
|
font-size: 0.8em; |
|
padding: 0.6em 0.8em; |
|
} |
|
.copy-code-btn { |
|
font-size: 0.7em; |
|
padding: 3px 6px; |
|
top: 5px; |
|
right: 5px; |
|
} |
|
.copy-msg-btn { |
|
width: 24px; |
|
height: 24px; |
|
bottom: 5px; |
|
right: 5px; |
|
} |
|
.copy-msg-btn svg { |
|
width: 12px; |
|
height: 12px; |
|
} |
|
.gemini-chat-header { |
|
padding: 10px 12px; |
|
} |
|
.gemini-chat-header h3 { |
|
font-size: 0.98rem; |
|
} |
|
.gemini-chat-header .close-chat-btn { |
|
font-size: 1.3rem; |
|
} |
|
#gemini-chat-log { |
|
padding: 10px 12px; |
|
gap: 8px; |
|
} |
|
.chat-message { |
|
font-size: 0.88rem; |
|
padding: 8px 12px; |
|
padding-bottom: 30px; |
|
max-width: calc(100% - 10px); |
|
border-radius: 15px; |
|
} |
|
.chat-message.user { |
|
border-bottom-right-radius: 4px; |
|
} |
|
.chat-message.ai { |
|
border-bottom-left-radius: 4px; |
|
} |
|
#gemini-chat-input-form { |
|
padding: 8px 10px; |
|
} |
|
#gemini-chat-input-form input[type='text'] { |
|
padding: 10px 15px; |
|
font-size: 0.9rem; |
|
margin-right: 8px; |
|
} |
|
#gemini-chat-input-form button { |
|
width: 40px; |
|
height: 40px; |
|
} |
|
#gemini-chat-input-form button svg { |
|
width: 18px; |
|
height: 18px; |
|
} |
|
#gemini-typing-indicator { |
|
padding: 6px 12px 2px 12px; |
|
} |
|
} |
|
</style> |
|
|
|
<div id="gemini-fab" title="Chat with Gemini"> |
|
<img |
|
src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTYnHRL-tMaj6h2CYK5Yy4ixuXfuohG8g8J4g&s" |
|
alt="Gemini AI Logo" |
|
/> |
|
</div> |
|
|
|
<div id="gemini-chat-container"> |
|
<div class="gemini-chat-header"> |
|
<h3>Gemini Assistant</h3> |
|
<button class="close-chat-btn" aria-label="Close chat">×</button> |
|
</div> |
|
<div id="gemini-chat-log"> |
|
<div class="chat-message ai">Hello! How can I assist you today?</div> |
|
</div> |
|
<div id="gemini-typing-indicator" class="gemini-typing-indicator" style="display: none"> |
|
<span></span><span></span><span></span> |
|
</div> |
|
<form id="gemini-chat-input-form"> |
|
<input type="text" id="gemini-user-input" placeholder="Type a message..." autocomplete="off" /> |
|
<button type="submit" title="Send message"> |
|
<svg viewBox="0 0 24 24"> |
|
<path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"></path> |
|
</svg> |
|
</button> |
|
</form> |
|
</div> |
|
|
|
<script> |
|
(function () { |
|
const fab = document.getElementById('gemini-fab'); |
|
const chatContainer = document.getElementById('gemini-chat-container'); |
|
const chatLog = document.getElementById('gemini-chat-log'); |
|
const inputForm = document.getElementById('gemini-chat-input-form'); |
|
const userInput = document.getElementById('gemini-user-input'); |
|
const sendButton = inputForm.querySelector('button[type="submit"]'); |
|
const closeChatBtn = chatContainer.querySelector('.close-chat-btn'); |
|
const typingIndicator = document.getElementById('gemini-typing-indicator'); |
|
const TYPEWRITER_SPEED = 0.3; |
|
let currentConversationId = null; |
|
|
|
const SVG_COPY_ICON = ` |
|
<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor"> |
|
<path d="M0 0h24v24H0z" fill="none"/> |
|
<path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/> |
|
</svg>`; |
|
const SVG_CHECK_ICON = ` |
|
<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor"> |
|
<path d="M0 0h24v24H0z" fill="none"/> |
|
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/> |
|
</svg>`; |
|
|
|
fab.addEventListener('click', () => { |
|
chatContainer.classList.toggle('visible'); |
|
if (chatContainer.classList.contains('visible')) { |
|
userInput.focus(); |
|
} |
|
}); |
|
|
|
closeChatBtn.addEventListener('click', () => { |
|
chatContainer.classList.remove('visible'); |
|
}); |
|
|
|
inputForm.addEventListener('submit', async function (event) { |
|
event.preventDefault(); |
|
const messageText = userInput.value.trim(); |
|
if (messageText === '' || userInput.disabled) return; |
|
|
|
userInput.disabled = true; |
|
if (sendButton) sendButton.disabled = true; |
|
|
|
await addMessageToLog(messageText, 'user'); |
|
userInput.value = ''; |
|
showTypingIndicator(true); |
|
try { |
|
let apiUrl; |
|
const isFirstMessageInSession = currentConversationId === null; |
|
|
|
if (isFirstMessageInSession) { |
|
apiUrl = `https://api-improve-production.up.railway.app/gemini/chat?q=${encodeURIComponent(messageText)}`; |
|
} else { |
|
apiUrl = `https://api-improve-production.up.railway.app/gemini/chat?q=${encodeURIComponent(messageText)}&id=${currentConversationId}`; |
|
} |
|
|
|
const response = await fetch(apiUrl); |
|
const data = await response.json(); |
|
|
|
if (!response.ok) { |
|
const errorMessage = data?.error?.message || data?.message || `Error: ${response.status}`; |
|
await addMessageToLog(`Sorry, I couldn't get a response. ${errorMessage}`, 'ai'); |
|
return; |
|
} |
|
|
|
const reply = data.reply; |
|
|
|
if (isFirstMessageInSession && data.id) { |
|
currentConversationId = data.id; |
|
} else if (!isFirstMessageInSession && data.id && data.id !== currentConversationId) { |
|
currentConversationId = data.id; |
|
} |
|
|
|
if (reply !== undefined && reply !== null) { |
|
await addMessageToLog(reply, 'ai'); |
|
} else { |
|
await addMessageToLog('Sorry, I received an empty or malformed reply.', 'ai'); |
|
} |
|
} catch (error) { |
|
console.error('API/Network Error:', error); |
|
await addMessageToLog( |
|
'Oops! Something went wrong. Please check connection and try again.', |
|
'ai' |
|
); |
|
} finally { |
|
showTypingIndicator(false); |
|
userInput.disabled = false; |
|
if (sendButton) sendButton.disabled = false; |
|
userInput.focus(); |
|
} |
|
}); |
|
|
|
function showTypingIndicator(show) { |
|
typingIndicator.style.display = show ? 'flex' : 'none'; |
|
} |
|
|
|
function escapeHTML(str) { |
|
const p = document.createElement('p'); |
|
p.textContent = str; |
|
return p.innerHTML; |
|
} |
|
|
|
function isScrolledToBottom(element) { |
|
const threshold = 10; |
|
return element.scrollHeight - element.scrollTop - element.clientHeight < threshold; |
|
} |
|
|
|
async function typeSegment( |
|
targetElement, |
|
textToType, |
|
isCode, |
|
codeElementForHighlight, |
|
postTypeCallback |
|
) { |
|
return new Promise((resolve) => { |
|
let i = 0; |
|
targetElement.textContent = ''; |
|
function typeChar() { |
|
if (i < textToType.length) { |
|
targetElement.textContent += textToType.charAt(i); |
|
i++; |
|
if (isScrolledToBottom(chatLog)) { |
|
chatLog.scrollTop = chatLog.scrollHeight; |
|
} |
|
setTimeout(typeChar, TYPEWRITER_SPEED); |
|
} else { |
|
if (isCode && codeElementForHighlight && typeof hljs !== 'undefined') { |
|
try { |
|
hljs.highlightElement(codeElementForHighlight); |
|
} catch (e) { |
|
console.error('Highlight.js error:', e); |
|
} |
|
} |
|
if (postTypeCallback) postTypeCallback(); |
|
resolve(); |
|
} |
|
} |
|
if (textToType.length === 0) { |
|
if (postTypeCallback) postTypeCallback(); |
|
resolve(); |
|
return; |
|
} |
|
typeChar(); |
|
}); |
|
} |
|
|
|
async function addMessageToLog(text, sender) { |
|
const messageDiv = document.createElement('div'); |
|
messageDiv.classList.add('chat-message', sender); |
|
if (sender === 'ai') { |
|
messageDiv.style.opacity = 0; |
|
} |
|
|
|
const segmentsToProcess = []; |
|
const codeBlockRegex = /```(\w*)\n([\s\S]*?)```/g; |
|
let lastIndex = 0; |
|
let match; |
|
|
|
while ((match = codeBlockRegex.exec(text)) !== null) { |
|
const textBefore = text.substring(lastIndex, match.index); |
|
if (textBefore.trim()) { |
|
segmentsToProcess.push({ type: 'text', content: textBefore }); |
|
} |
|
const language = match[1].trim().toLowerCase(); |
|
const codeContent = match[2]; |
|
segmentsToProcess.push({ type: 'code', content: codeContent, lang: language }); |
|
lastIndex = codeBlockRegex.lastIndex; |
|
} |
|
const textAfter = text.substring(lastIndex); |
|
if (textAfter.trim()) { |
|
segmentsToProcess.push({ type: 'text', content: textAfter }); |
|
} |
|
|
|
if (segmentsToProcess.length === 0 && text.trim()) { |
|
segmentsToProcess.push({ type: 'text', content: text }); |
|
} |
|
|
|
chatLog.appendChild(messageDiv); |
|
|
|
if (sender === 'ai' && segmentsToProcess.length > 0) { |
|
messageDiv.style.opacity = 1; |
|
for (const segment of segmentsToProcess) { |
|
const contentToProcess = segment.content.trim(); |
|
if (segment.type === 'text') { |
|
const span = document.createElement('span'); |
|
messageDiv.appendChild(span); |
|
await typeSegment(span, contentToProcess, false, null, () => { |
|
span.innerHTML = escapeHTML(span.textContent); |
|
}); |
|
} else if (segment.type === 'code') { |
|
const preElement = document.createElement('pre'); |
|
const codeElement = document.createElement('code'); |
|
if (segment.lang) { |
|
codeElement.className = `language-${segment.lang}`; |
|
} |
|
preElement.appendChild(codeElement); |
|
const copyCodeBtn = document.createElement('button'); |
|
copyCodeBtn.className = 'copy-code-btn'; |
|
copyCodeBtn.textContent = 'Copy'; |
|
preElement.appendChild(copyCodeBtn); |
|
copyCodeBtn.addEventListener('click', () => { |
|
navigator.clipboard |
|
.writeText(codeElement.textContent) |
|
.then(() => { |
|
copyCodeBtn.textContent = 'Copied!'; |
|
copyCodeBtn.classList.add('copied'); |
|
setTimeout(() => { |
|
copyCodeBtn.textContent = 'Copy'; |
|
copyCodeBtn.classList.remove('copied'); |
|
}, 2000); |
|
}) |
|
.catch((err) => { |
|
console.error('Copy code failed', err); |
|
copyCodeBtn.textContent = 'Error'; |
|
setTimeout(() => { |
|
copyCodeBtn.textContent = 'Copy'; |
|
}, 2000); |
|
}); |
|
}); |
|
messageDiv.appendChild(preElement); |
|
await typeSegment(codeElement, contentToProcess, true, codeElement, null); |
|
} |
|
} |
|
} else { |
|
segmentsToProcess.forEach((segment) => { |
|
const contentToRender = segment.content.trim(); |
|
if (segment.type === 'text') { |
|
const span = document.createElement('span'); |
|
span.innerHTML = escapeHTML(contentToRender); |
|
messageDiv.appendChild(span); |
|
} else if (segment.type === 'code') { |
|
const preElement = document.createElement('pre'); |
|
const codeElement = document.createElement('code'); |
|
if (segment.lang) { |
|
codeElement.className = `language-${segment.lang}`; |
|
} |
|
codeElement.textContent = contentToRender; |
|
preElement.appendChild(codeElement); |
|
const copyCodeBtn = document.createElement('button'); |
|
copyCodeBtn.className = 'copy-code-btn'; |
|
copyCodeBtn.textContent = 'Copy'; |
|
preElement.appendChild(copyCodeBtn); |
|
copyCodeBtn.addEventListener('click', () => { |
|
navigator.clipboard |
|
.writeText(codeElement.textContent) |
|
.then(() => { |
|
copyCodeBtn.textContent = 'Copied!'; |
|
copyCodeBtn.classList.add('copied'); |
|
setTimeout(() => { |
|
copyCodeBtn.textContent = 'Copy'; |
|
copyCodeBtn.classList.remove('copied'); |
|
}, 2000); |
|
}) |
|
.catch((err) => { |
|
console.error('Copy code failed', err); |
|
copyCodeBtn.textContent = 'Error'; |
|
setTimeout(() => { |
|
copyCodeBtn.textContent = 'Copy'; |
|
}, 2000); |
|
}); |
|
}); |
|
messageDiv.appendChild(preElement); |
|
if (typeof hljs !== 'undefined') { |
|
try { |
|
hljs.highlightElement(codeElement); |
|
} catch (e) { |
|
console.error(e); |
|
} |
|
} |
|
} |
|
}); |
|
if (sender === 'ai') messageDiv.style.opacity = 1; |
|
} |
|
|
|
let canAddCopyMsgButton = true; |
|
if ( |
|
messageDiv.childNodes.length === 1 && |
|
messageDiv.firstChild && |
|
messageDiv.firstChild.nodeName === 'PRE' |
|
) { |
|
canAddCopyMsgButton = false; |
|
} |
|
if (text.trim() && canAddCopyMsgButton) { |
|
const copyMsgButton = document.createElement('button'); |
|
copyMsgButton.className = 'copy-msg-btn'; |
|
copyMsgButton.title = 'Copy message'; |
|
copyMsgButton.innerHTML = SVG_COPY_ICON; |
|
messageDiv.appendChild(copyMsgButton); |
|
copyMsgButton.addEventListener('click', (e) => { |
|
e.stopPropagation(); |
|
navigator.clipboard |
|
.writeText(text) |
|
.then(() => { |
|
copyMsgButton.innerHTML = SVG_CHECK_ICON; |
|
copyMsgButton.classList.add('copied'); |
|
setTimeout(() => { |
|
copyMsgButton.innerHTML = SVG_COPY_ICON; |
|
copyMsgButton.classList.remove('copied'); |
|
}, 2000); |
|
}) |
|
.catch((err) => { |
|
console.error('Copy msg failed', err); |
|
copyMsgButton.innerHTML = SVG_COPY_ICON; |
|
alert('Failed to copy message.'); |
|
}); |
|
}); |
|
} |
|
|
|
if (isScrolledToBottom(chatLog) || sender === 'user') { |
|
chatLog.scrollTop = chatLog.scrollHeight; |
|
} |
|
} |
|
})(); |
|
</script> |
|
|