diff --git a/src/chat.js b/src/chat.js index 6f539c9..9c7603b 100644 --- a/src/chat.js +++ b/src/chat.js @@ -6,6 +6,65 @@ import { getSelectedModel, getSelectedModelEndpoint } from "./models.js"; import { initializeChatHistory, saveConversationHistory } from "./storage.js"; import { generateTitleForTTS, speakText } from "./tts.js"; +// Helper function to add copy buttons to code blocks +function addCodeBlockCopyButtons(element) { + const codeBlocks = element.querySelectorAll("pre code"); + codeBlocks.forEach((codeBlock) => { + const pre = codeBlock.parentElement; + if (pre.tagName.toLowerCase() === "pre") { + // Check if copy button already exists to avoid duplicates + if (pre.querySelector('.code-copy-btn')) { + return; + } + + // Make the pre element relative for positioning + pre.style.position = "relative"; + + // Create copy button + const copyBtn = document.createElement("button"); + copyBtn.className = "code-copy-btn"; + copyBtn.textContent = "📋"; + copyBtn.title = "Copy code"; + copyBtn.style.cssText = ` + position: absolute; + top: 8px; + right: 8px; + background: rgba(0, 0, 0, 0.7); + color: white; + border: none; + border-radius: 4px; + padding: 4px 8px; + cursor: pointer; + font-size: 12px; + opacity: 0.8; + transition: opacity 0.2s; + z-index: 10; + `; + + copyBtn.onmouseenter = () => { + copyBtn.style.opacity = "1"; + }; + copyBtn.onmouseleave = () => { + copyBtn.style.opacity = "0.8"; + }; + + copyBtn.onclick = async () => { + try { + await navigator.clipboard.writeText(codeBlock.textContent); + copyBtn.textContent = "✓"; + setTimeout(() => { + copyBtn.textContent = "📋"; + }, 2000); + } catch (error) { + console.error("Failed to copy code:", error); + } + }; + + pre.appendChild(copyBtn); + } + }); +} + // Initialize chat history export let chatHistory = initializeChatHistory(); @@ -91,6 +150,9 @@ export async function handleUserInput() { }); } + // Streaming is complete - add copy buttons to code blocks + addCodeBlockCopyButtons(responseContent); + chatBox.scrollTop = chatBox.scrollHeight; // Add the response to chat history diff --git a/src/uncloseai-embed-modal.js b/src/uncloseai-embed-modal.js index 28b37e6..b96b12a 100644 --- a/src/uncloseai-embed-modal.js +++ b/src/uncloseai-embed-modal.js @@ -85,11 +85,17 @@ function addCodeBlockCopyButtons(element) { codeBlocks.forEach((codeBlock) => { const pre = codeBlock.parentElement; if (pre.tagName.toLowerCase() === "pre") { + // Check if copy button already exists to avoid duplicates + if (pre.querySelector('.code-copy-btn')) { + return; + } + // Make the pre element relative for positioning pre.style.position = "relative"; // Create copy button const copyBtn = document.createElement("button"); + copyBtn.className = "code-copy-btn"; copyBtn.textContent = "📋"; copyBtn.title = "Copy code"; copyBtn.style.cssText = ` @@ -1404,9 +1410,19 @@ async function openUncloseaiEmbeddedModalNew() { for await (const chunk of sendMessage(message)) { response += chunk; aiMsg.innerHTML = marked.parse(response); - addCodeBlockCopyButtons(aiMsg); } + // Streaming is complete - now add final processing + aiMsg.innerHTML = marked.parse(response); + + // Add syntax highlighting to all code blocks + aiMsg.querySelectorAll("pre code").forEach((block) => { + hljs.highlightElement(block); + }); + + // Add copy buttons after highlighting + addCodeBlockCopyButtons(aiMsg); + // Add the AI response to chat history manually since sendMessage generator doesn't do it const { getChatHistory, updateChatHistory } = await import("./chat.js"); const currentHistory = await getChatHistory(); @@ -2346,9 +2362,19 @@ ${getUIText("systemPromptInstructions")} ${languageName}.`; for await (const chunk of sendMessageWithCustomHistory(introHistory)) { response += chunk; introMsg.innerHTML = marked.parse(response); - addCodeBlockCopyButtons(introMsg); } + // Streaming is complete - now add final processing + introMsg.innerHTML = marked.parse(response); + + // Add syntax highlighting to all code blocks + introMsg.querySelectorAll("pre code").forEach((block) => { + hljs.highlightElement(block); + }); + + // Add copy buttons after highlighting + addCodeBlockCopyButtons(introMsg); + // SECOND: Set up conversation system prompt for follow-up messages const conversationContextAppend = `