feat: add copy buttons and user message delete, remove confirmation dialogs

This commit is contained in:
Russell Ballestrini 2025-07-02 13:05:09 -04:00
parent f55e7e3ac4
commit 7063e382a9

318
src/ui.js
View file

@ -909,6 +909,41 @@ async function openUncloseaiEmbeddedModalNew() {
const actions = [
{ text: "🔄 Refresh Models", action: loadModels },
{ text: "📋 Full Raw", action: async () => {
try {
const history = getChatHistory();
let rawContent = '';
history.forEach(msg => {
if (msg.role === 'user') {
rawContent += `**You:** ${msg.content}\n\n`;
} else if (msg.role === 'assistant') {
rawContent += `**AI:** ${msg.content}\n\n`;
}
});
await navigator.clipboard.writeText(rawContent);
alert("Full chat copied as raw markdown!");
} catch (error) {
alert("Failed to copy: " + error.message);
}
}},
{ text: "📄 Full HTML", action: async () => {
try {
const history = getChatHistory();
let htmlContent = '<div style="font-family: system-ui, -apple-system, sans-serif;">';
history.forEach(msg => {
if (msg.role === 'user') {
htmlContent += `<div style="margin-bottom: 16px;"><strong>You:</strong><br>${marked.parse(msg.content)}</div>`;
} else if (msg.role === 'assistant') {
htmlContent += `<div style="margin-bottom: 16px;"><strong>AI:</strong><br>${marked.parse(msg.content)}</div>`;
}
});
htmlContent += '</div>';
await navigator.clipboard.writeText(htmlContent);
alert("Full chat copied as HTML!");
} catch (error) {
alert("Failed to copy: " + error.message);
}
}},
{ text: "🗑️ Clear Chat", action: async () => {
if (confirm("Clear all conversation history?")) {
chatBox.innerHTML = '';
@ -1099,8 +1134,48 @@ async function openUncloseaiEmbeddedModalNew() {
border-radius: 18px 18px 4px 18px;
max-width: 80%;
word-wrap: break-word;
position: relative;
`;
userMsg.textContent = message;
// Add delete button to user message
const userDeleteBtn = document.createElement("button");
userDeleteBtn.textContent = "×";
userDeleteBtn.style.cssText = `
position: absolute;
top: -8px;
right: -8px;
width: 20px;
height: 20px;
border-radius: 50%;
background: rgba(255, 0, 0, 0.8);
color: white;
border: none;
cursor: pointer;
font-size: 12px;
display: none;
transition: all 0.2s;
`;
userDeleteBtn.onclick = (e) => {
e.stopPropagation();
// Remove from chat history
const currentHistory = getChatHistory();
const updatedHistory = currentHistory.filter(msg => !(msg.role === 'user' && msg.content === message));
updateChatHistory(updatedHistory);
saveConversationHistory(updatedHistory);
userMsg.remove();
};
// Show delete button on hover
userMsg.onmouseenter = () => {
userDeleteBtn.style.display = 'block';
};
userMsg.onmouseleave = () => {
userDeleteBtn.style.display = 'none';
};
userMsg.appendChild(userDeleteBtn);
chatBox.appendChild(userMsg);
// Add AI response placeholder
@ -1241,13 +1316,66 @@ async function openUncloseaiEmbeddedModalNew() {
transition: all 0.2s;
`;
deleteBtn.onclick = () => {
if (confirm("Delete this message?")) {
userMsg.remove();
aiMsg.remove();
userMsg.remove();
aiMsg.remove();
};
// Copy Raw (Markdown) button
const copyRawBtn = document.createElement("button");
copyRawBtn.textContent = "📋";
copyRawBtn.title = "Copy raw (Markdown)";
copyRawBtn.style.cssText = `
background: none;
border: 1px solid #dee2e6;
border-radius: 4px;
padding: 4px 8px;
cursor: pointer;
font-size: 12px;
transition: all 0.2s;
`;
copyRawBtn.onclick = async () => {
try {
await navigator.clipboard.writeText(response);
const originalText = copyRawBtn.textContent;
copyRawBtn.textContent = "✓";
setTimeout(() => {
copyRawBtn.textContent = originalText;
}, 1000);
} catch (error) {
alert("Failed to copy: " + error.message);
}
};
// Copy HTML button
const copyHtmlBtn = document.createElement("button");
copyHtmlBtn.textContent = "📄";
copyHtmlBtn.title = "Copy HTML";
copyHtmlBtn.style.cssText = `
background: none;
border: 1px solid #dee2e6;
border-radius: 4px;
padding: 4px 8px;
cursor: pointer;
font-size: 12px;
transition: all 0.2s;
`;
copyHtmlBtn.onclick = async () => {
try {
const htmlContent = marked.parse(response);
await navigator.clipboard.writeText(htmlContent);
const originalText = copyHtmlBtn.textContent;
copyHtmlBtn.textContent = "✓";
setTimeout(() => {
copyHtmlBtn.textContent = originalText;
}, 1000);
} catch (error) {
alert("Failed to copy: " + error.message);
}
};
messageActions.appendChild(ttsBtn);
messageActions.appendChild(copyRawBtn);
messageActions.appendChild(copyHtmlBtn);
messageActions.appendChild(deleteBtn);
aiMsg.appendChild(messageActions);
@ -1337,8 +1465,57 @@ You have complete knowledge of this page content and can reference any details,
border-radius: 18px 18px 4px 18px;
max-width: 80%;
word-wrap: break-word;
position: relative;
`;
userMsg.textContent = msg.content;
// Add delete button to historical user message
const userDeleteBtn = document.createElement("button");
userDeleteBtn.textContent = "×";
userDeleteBtn.style.cssText = `
position: absolute;
top: -8px;
right: -8px;
width: 20px;
height: 20px;
border-radius: 50%;
background: rgba(255, 0, 0, 0.8);
color: white;
border: none;
cursor: pointer;
font-size: 12px;
display: none;
transition: all 0.2s;
`;
userDeleteBtn.onclick = (e) => {
e.stopPropagation();
// Remove from chat history by index
const currentHistory = loadConversationHistory();
if (index >= 0 && index < currentHistory.length) {
currentHistory.splice(index, 1);
saveConversationHistory(currentHistory);
// Update the chat.js module history
const systemMsg = currentHistory.find(msg => msg.role === 'system');
const newHistory = [
{ role: 'system', content: getSystemMessage() },
...currentHistory
];
updateChatHistory(newHistory);
}
userMsg.remove();
};
// Show delete button on hover
userMsg.onmouseenter = () => {
userDeleteBtn.style.display = 'block';
};
userMsg.onmouseleave = () => {
userDeleteBtn.style.display = 'none';
};
userMsg.appendChild(userDeleteBtn);
chatBox.appendChild(userMsg);
console.log("Added user message to chatBox");
} else if (msg.role === 'assistant') {
@ -1455,7 +1632,62 @@ You have complete knowledge of this page content and can reference any details,
// TODO: Remove from chat history
};
// Copy Raw (Markdown) button
const copyRawBtn = document.createElement("button");
copyRawBtn.textContent = "📋";
copyRawBtn.title = "Copy raw (Markdown)";
copyRawBtn.style.cssText = `
background: none;
border: 1px solid #dee2e6;
border-radius: 4px;
padding: 4px 8px;
cursor: pointer;
font-size: 12px;
transition: all 0.2s;
`;
copyRawBtn.onclick = async () => {
try {
await navigator.clipboard.writeText(msg.content);
const originalText = copyRawBtn.textContent;
copyRawBtn.textContent = "✓";
setTimeout(() => {
copyRawBtn.textContent = originalText;
}, 1000);
} catch (error) {
alert("Failed to copy: " + error.message);
}
};
// Copy HTML button
const copyHtmlBtn = document.createElement("button");
copyHtmlBtn.textContent = "📄";
copyHtmlBtn.title = "Copy HTML";
copyHtmlBtn.style.cssText = `
background: none;
border: 1px solid #dee2e6;
border-radius: 4px;
padding: 4px 8px;
cursor: pointer;
font-size: 12px;
transition: all 0.2s;
`;
copyHtmlBtn.onclick = async () => {
try {
const htmlContent = marked.parse(msg.content);
await navigator.clipboard.writeText(htmlContent);
const originalText = copyHtmlBtn.textContent;
copyHtmlBtn.textContent = "✓";
setTimeout(() => {
copyHtmlBtn.textContent = originalText;
}, 1000);
} catch (error) {
alert("Failed to copy: " + error.message);
}
};
messageActions.appendChild(ttsBtn);
messageActions.appendChild(copyRawBtn);
messageActions.appendChild(copyHtmlBtn);
messageActions.appendChild(deleteBtn);
aiMsg.appendChild(messageActions);
@ -1657,7 +1889,62 @@ You have complete knowledge of this page content and can reference any details,
introMsg.remove();
};
// Copy Raw (Markdown) button
const copyRawBtn = document.createElement("button");
copyRawBtn.textContent = "📋";
copyRawBtn.title = "Copy raw (Markdown)";
copyRawBtn.style.cssText = `
background: none;
border: 1px solid #dee2e6;
border-radius: 4px;
padding: 4px 8px;
cursor: pointer;
font-size: 12px;
transition: all 0.2s;
`;
copyRawBtn.onclick = async () => {
try {
await navigator.clipboard.writeText(response);
const originalText = copyRawBtn.textContent;
copyRawBtn.textContent = "✓";
setTimeout(() => {
copyRawBtn.textContent = originalText;
}, 1000);
} catch (error) {
alert("Failed to copy: " + error.message);
}
};
// Copy HTML button
const copyHtmlBtn = document.createElement("button");
copyHtmlBtn.textContent = "📄";
copyHtmlBtn.title = "Copy HTML";
copyHtmlBtn.style.cssText = `
background: none;
border: 1px solid #dee2e6;
border-radius: 4px;
padding: 4px 8px;
cursor: pointer;
font-size: 12px;
transition: all 0.2s;
`;
copyHtmlBtn.onclick = async () => {
try {
const htmlContent = marked.parse(response);
await navigator.clipboard.writeText(htmlContent);
const originalText = copyHtmlBtn.textContent;
copyHtmlBtn.textContent = "✓";
setTimeout(() => {
copyHtmlBtn.textContent = originalText;
}, 1000);
} catch (error) {
alert("Failed to copy: " + error.message);
}
};
messageActions.appendChild(ttsBtn);
messageActions.appendChild(copyRawBtn);
messageActions.appendChild(copyHtmlBtn);
messageActions.appendChild(deleteBtn);
introMsg.appendChild(messageActions);
@ -2165,15 +2452,14 @@ export async function openUncloseaiEmbeddedModal() {
"position: absolute; top: 2px; right: 2px; width: 20px; height: 20px; font-size: 12px; background: rgba(255,0,0,0.7); color: white; border: none; border-radius: 50%; cursor: pointer;";
deleteBtn.onclick = ((messageIndex) => {
return () => {
if (confirm("Delete this message?")) {
const savedHistory = loadConversationHistory();
if (messageIndex >= 0 && messageIndex < savedHistory.length) {
savedHistory.splice(messageIndex, 1);
localStorage.setItem(
getPageSpecificKey("hermes-conversation-history"),
JSON.stringify(savedHistory),
);
// Update global chat history
const savedHistory = loadConversationHistory();
if (messageIndex >= 0 && messageIndex < savedHistory.length) {
savedHistory.splice(messageIndex, 1);
localStorage.setItem(
getPageSpecificKey("hermes-conversation-history"),
JSON.stringify(savedHistory),
);
// Update global chat history
chatHistory = [
{
role: "system",
@ -2513,11 +2799,9 @@ Format: Start with "Greetings! I'm Hermes..." and make it sound natural and enga
deleteBtn.style.cssText =
"position: absolute; top: 2px; right: 2px; width: 20px; height: 20px; font-size: 12px; background: rgba(255,0,0,0.7); color: white; border: none; border-radius: 50%; cursor: pointer;";
deleteBtn.onclick = () => {
if (confirm("Delete this message?")) {
div.remove();
// Update stored history
restoreConversationHistory();
}
div.remove();
// Update stored history
restoreConversationHistory();
};
div.appendChild(deleteBtn);