import { openDB } from "idb"; // Ensure you have `idb` library installed or included import { copy_download } from "../../components/settings/copy_download.js"; const copy_buttons = `
`; // const button_util_container = ` //
// ` export function debounce(func, wait) { let timeout; return function (...args) { const context = this; clearTimeout(timeout); timeout = setTimeout(() => func.apply(context, args), wait); }; } export function extractQuestion(text) { // Remove leading/trailing whitespace text = text.trim(); // Remove "Use this question:" and variants text = text.replace(/^(Use this question:?\s*)/i, ''); // Case 1: Text between * and ? if (text.startsWith('*')) { const match = text.match(/\*(.*?)\?/); if (match) return match[1].trim() + '?'; } // Case 2: Text up to first ? const questionMatch = text.match(/(.*?)\?/); if (questionMatch) return questionMatch[0].trim(); // Case 3: Return full text if no ? found return text; } export function generateUniqueId() { // Get current timestamp in milliseconds const timestamp = new Date().getTime(); // Generate a random number and convert to base 36 (includes letters) const randomPart = Math.random().toString(36).substring(2, 10); // Get performance timing if available for more precision const performance = window.performance && window.performance.now ? window.performance.now().toString(36).replace(".", "") : ""; // Combine all parts return `id-${timestamp}-${performance}-${randomPart}`; } // We can also add a simpler version if needed export function generateSimpleId() { return `${new Date().getTime()}-${Math.random() .toString(36) .substring(2, 15)}`; } export function truncateToLastChars(text, maxChars) { if (text.length <= maxChars) return text; return text.substr(text.length - maxChars); } export function alert(shadowElem, text, type) { const token = "none"; try { let noticeElement; if (type === "success") { noticeElement = shadowElem.querySelector("#success-notice"); if (!noticeElement) { // Create the success notice element noticeElement = document.createElement("div"); noticeElement.id = "success-notice"; noticeElement.style.display = "none"; noticeElement.style.position = "fixed"; noticeElement.style.bottom = "5px"; noticeElement.style.left = "5px"; noticeElement.style.background = "lightgreen"; noticeElement.style.padding = "5px"; noticeElement.style.borderRadius = "5px"; noticeElement.style.zIndex = "9999"; shadowElem.appendChild(noticeElement); } noticeElement.textContent = text; noticeElement.style.display = "block"; // Hide the success notice after 3 seconds setTimeout(() => { noticeElement.style.display = "none"; }, 2000); return token; } else { noticeElement = shadowElem.querySelector("#error-notice"); if (!noticeElement) { // Create the error notice element noticeElement = document.createElement("div"); noticeElement.id = "error-notice"; noticeElement.style.display = "none"; noticeElement.style.position = "fixed"; noticeElement.style.bottom = "5px"; noticeElement.style.left = "5px"; noticeElement.style.background = "lightcoral"; noticeElement.style.padding = "5px"; noticeElement.style.borderRadius = "5px"; noticeElement.style.zIndex = "9999"; shadowElem.appendChild(noticeElement); } noticeElement.textContent = text; noticeElement.style.display = "block"; // Hide the error notice after 3 seconds setTimeout(() => { noticeElement.style.display = "none"; }, 2000); } } catch (error) { console.error("Error in alert function:", error); } } export async function saveToken(token) { let db; try { db = await openDB("tinyML_token", 1, { upgrade(db) { if (!db.objectStoreNames.contains("tokens")) { db.createObjectStore("tokens", { keyPath: "id" }); } }, }); const tx = db.transaction("tokens", "readwrite"); const store = tx.objectStore("tokens"); await store.put({ id: "token_avaya", value: token }); await tx.done; // Ensures the transaction completes successfully } catch (error) { console.error("Failed to save token:", error); // Handle the error (e.g., by retrying, logging, or notifying the user) } finally { if (db) db.close(); // Close the database connection to prevent leaks } } export async function getToken() { const db = await openDB("tinyML_token", 1); const tx = db.transaction("tokens", "readonly"); const store = tx.objectStore("tokens"); const tokenObject = await store.get("token_avaya"); await tx.done; return tokenObject ? tokenObject.value : null; } export function scrollToBottom(shadowRoot) { const iframeContainer = shadowRoot.querySelector("#message-container"); if (iframeContainer) { // Wait for the next 'paint' after the browser has had a chance to layout content requestAnimationFrame(() => { iframeContainer.scrollTop = iframeContainer.scrollHeight; }); } } export function scrollToBottomSmooth(shadowRoot) { const iframeContainer = shadowRoot.querySelector("#message-container"); let scrollTarget = shadowRoot.querySelector("#scroll-target"); if (iframeContainer && scrollTarget) { scrollTarget.scrollIntoView({ behavior: "smooth" }); } else if (iframeContainer && !scrollTarget) { // Create a new element with the ID 'scroll-target' scrollTarget = document.createElement("div"); scrollTarget.id = "scroll-target"; // Append the new element to the iframeContainer iframeContainer.appendChild(scrollTarget); // Now scroll into view scrollTarget.scrollIntoView({ behavior: "smooth" }); } } export function scrollToTopSmooth(shadowRoot) { const iframeContainer = shadowRoot.querySelector("#message-container"); console.log("we gonna SMOOTH scroll down"); // Get ALL difficulty-dropdown elements and select the last one const difficultyDropdowns = iframeContainer.querySelectorAll(".difficulty-dropdown"); const lastDifficultyDropdown = difficultyDropdowns[difficultyDropdowns.length - 1]; // Scroll the last difficultyDropdown into view if it exists if (lastDifficultyDropdown) { lastDifficultyDropdown.scrollIntoView({ behavior: "smooth", block: 'start' }); console.log("Scrolling to last difficulty dropdown:", lastDifficultyDropdown); } else { console.log("No difficulty dropdowns found"); } } export function cloneElementById( shadowEle, originalElementId, newId = "", targetContainerId = "" ) { const originalElement = shadowEle.querySelector(`#${originalElementId}`); if (!originalElement) { console.error("Element with ID " + originalElementId + " not found."); return null; } // Clone the original element const clone = originalElement.cloneNode(true); // Assign a new ID to the clone if provided if (newId !== "") { clone.id = newId; } else { clone.removeAttribute("id"); // Remove ID attribute if no new ID provided } // If a target container ID is provided, append the clone to this container if (targetContainerId !== "") { const targetContainer = shadowEle.querySelector(`#${targetContainerId}`); if (targetContainer) { if (targetContainerId === "message-container") { const scrollTarget = shadowEle.querySelector("#scroll-target"); // Insert the new message just before the scroll-target targetContainer.insertBefore(clone, scrollTarget); } else { // if (targetContainer) { targetContainer.appendChild(clone); } } else { console.error( "Target container with ID " + targetContainerId + " not found." ); } } // Return the cloned element return clone; } export function cloneElementById_OLD_DELETE( shadowEle, originalElementId, newId = "", targetContainerId = "" ) { // Assuming shadowEle is a shadow root or document const originalElement = shadowEle.querySelector(`#${originalElementId}`); if (!originalElement) { console.error("Element with ID " + originalElementId + " not found."); return null; } // Clone the original element const clone = originalElement.cloneNode(true); // Assign a new ID to the clone if provided if (newId !== "") { clone.id = newId; } else { clone.removeAttribute("id"); // Remove ID attribute if no new ID provided } // If a target container ID is provided, append the clone to this container if (targetContainerId !== "") { const targetContainer = shadowEle.querySelector(`#${targetContainerId}`); if (targetContainer) { targetContainer.appendChild(clone); } else { console.error( "Target container with ID " + targetContainerId + " not found." ); } } // Return the cloned element return clone; } export function generateHighPrecisionUniqueId() { const perfTime = performance.now().toString().replace(".", ""); const randomPortion = Math.random().toString(36).substring(2, 15); return `id-${perfTime}-${randomPortion}`; } export function assignUniqueIdsToElementAndChildren(element) { // Assign a unique ID to the root element element.id = generateHighPrecisionUniqueId(); // Iterate over all child elements and recursively assign unique IDs element.querySelectorAll("*").forEach(assignUniqueIdsToElementAndChildren); } export function assignAndTrackUniqueIds(element, idsToTrack = []) { // Initialize array to store new IDs in same order as idsToTrack const trackedNewIds = new Array(idsToTrack.length).fill(null); // Assign unique ID to root element const rootId = generateHighPrecisionUniqueId(); element.id = rootId; // Check if root element's original ID was in tracking list const rootIndex = idsToTrack.indexOf(element.id); if (rootIndex !== -1) { trackedNewIds[rootIndex] = rootId; } // Process all child elements element.querySelectorAll("*").forEach(child => { const originalId = child.id; const newId = generateHighPrecisionUniqueId(); child.id = newId; // If this child's original ID was in our tracking list, store the new ID const trackIndex = idsToTrack.indexOf(originalId); if (trackIndex !== -1) { trackedNewIds[trackIndex] = newId; } }); return trackedNewIds; } // Add this new function to encode messages for URL export function encodeMessageForURL(text) { try { // If text is an HTML element, get its outerHTML const htmlContent = typeof text === "string" ? text : text.outerHTML; return encodeURIComponent(htmlContent).replace(/%20/g, "+"); } catch (error) { console.error("Error encoding message for URL:", error); return ""; } } // Add this new function to decode messages from URL export function decodeMessageFromURL(text) { try { // First decode the URI component const decodedText = decodeURIComponent(text.replace(/\+/g, " ")); // Parse the HTML string into actual DOM elements const parser = new DOMParser(); const doc = parser.parseFromString(decodedText, "text/html"); // Return the first element from the body // This will preserve the entire HTML structure return doc.body.firstElementChild; } catch (error) { console.error("Error decoding message from URL:", error); return null; } } // Add new popover function export function showPopover(shadowEle, message, type = "info", duration = 3000) { const popover = document.createElement('div'); popover.style.cssText = ` position: fixed; bottom: 20px; left: 20px; padding: 12px 24px; border-radius: 8px; opacity: 0; transform: translateY(20px); transition: all 0.3s ease; z-index: 10000; font-size: 14px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); `; popover.classList.add('popover-socratiq'); // Set style based on type switch(type) { case "info": popover.style.backgroundColor = 'rgba(59, 130, 246, 0.9)'; // Blue popover.style.color = 'white'; break; case "error": popover.style.backgroundColor = 'rgba(239, 68, 68, 0.9)'; // Red popover.style.color = 'white'; break; case "success": popover.style.backgroundColor = 'rgba(34, 197, 94, 0.9)'; // Green popover.style.color = 'white'; break; default: popover.style.backgroundColor = 'rgba(59, 130, 246, 0.9)'; // Default blue popover.style.color = 'white'; } popover.textContent = message; shadowEle.appendChild(popover); // Trigger animation setTimeout(() => { popover.style.opacity = "1"; popover.style.transform = "translateY(0)"; }, 10); // Remove after duration setTimeout(() => { popover.style.opacity = "0"; popover.style.transform = "translateY(-20px)"; setTimeout(() => popover.remove(), 300); }, duration); } // Update share button code export function add_copy_paste_share_buttons( shadowEle, clone, ref_buttons_element, accumulatedResponse = "" ) { // Store the markdown content as a data attribute if (accumulatedResponse) { clone.setAttribute("data-markdown", accumulatedResponse); } const container = document.createElement("div"); container.setAttribute("class", "flex justify-between"); container.classList.add("copy-paste-share-btn-container"); const refButtonsContainer = document.createElement("div"); refButtonsContainer.setAttribute("class", "flex items-center"); if (ref_buttons_element instanceof Element) { refButtonsContainer.appendChild(ref_buttons_element); } const copyButtonsContainer = document.createElement("div"); copyButtonsContainer.innerHTML = copy_buttons; container.appendChild(refButtonsContainer); container.appendChild(copyButtonsContainer); clone.appendChild(container); copy_download(shadowEle, clone); return clone; } /** * Sets the target attribute of all tags inside elements to '_blank' and adds rel='noopener noreferrer'. * * @param {HTMLElement} clone - The element to clone and search for tags */ export function make_links_load_new_page(clone) { // Query all tags inside elements const links = clone.querySelectorAll("a"); // Set the target attribute of each link to '_blank' links.forEach((link) => { link.setAttribute("target", "_blank"); // Optional: Add rel="noopener noreferrer" for security reasons link.setAttribute("rel", "noopener noreferrer"); }); } export function updateCloneAttributes(clone, prompt, type, title='') { // Ensure the clone is a valid DOM element if (!(clone instanceof HTMLElement)) { throw new Error("Invalid clone element provided."); } clone.setAttribute('data-prompt', prompt); clone.setAttribute('data-type', type); clone.setAttribute('data-title', title); } export function removeSkeletonLoaders(clone) { // Remove all skeleton loader elements const skeletonLoaders = clone.querySelectorAll('.skeleton-loader'); skeletonLoaders.forEach(loader => loader.remove()); // Also remove any elements with loader class const loaderElements = clone.querySelectorAll('.loader'); loaderElements.forEach(loader => loader.remove()); return clone; } export function insertDiagramElements(messageContent, clone) { // Find all strong elements const strongElements = clone.querySelectorAll('strong'); // Find the one containing "Related" text const relatedElement = Array.from(strongElements).find(el => el.textContent.toLowerCase().includes('related') ); if (relatedElement) { // Create new div for diagram const diagramDiv = document.createElement('div'); diagramDiv.className = 'generated-diagram'; diagramDiv.style.cssText = ` margin: 1.5rem 0; padding: 1rem; border-radius: 8px; background-color: #f8f9fa; border: 1px solid #e9ecef; `; diagramDiv.innerHTML = `

Generated Diagram

${messageContent} `; // Insert before the parent element containing "related" relatedElement.parentElement.parentElement.insertBefore( diagramDiv, relatedElement.parentElement ); } else { // If no "Related" section found, append to the end of clone clone.appendChild(diagramDiv); } } export function findClosestAIMessage(element) { if (!element) { console.warn('No element provided to findClosestAIMessage'); return null; } // Try to find the closest parent with any of these selectors const selectors = [ '.ai-message-chat', '.message-container[data-type="ai"]', '[id^="id-"][class*="text-sm"][class*="markdown-preview-container"]', '.markdown-preview-container' ]; for (const selector of selectors) { const match = element.closest(selector); if (match) { // For debugging console.log(`Found AI message container with selector: ${selector}`); return match; } } // If no match is found, log the element for debugging console.warn('Could not find parent AI message element. Element:', element); return null; } export function insertAtProportionalPoints(parentElement, firstComponent, secondComponent) { try { // Log initial state console.log('Inserting charts - Initial container state:', { parentElement, childCount: parentElement.children.length, firstComponent, secondComponent }); // Get all child nodes that aren't charts (to avoid counting previously inserted charts) const children = Array.from(parentElement.children).filter(child => !child.classList.contains('chart-wrapper') ); const totalChildren = children.length; console.log('Filtered children count:', totalChildren); // Calculate insertion points (rounded to nearest integer) const oneThirdIndex = Math.round(totalChildren / 3); const twoThirdsIndex = Math.round((totalChildren * 2) / 3); console.log('Calculated insertion points:', { oneThirdIndex, twoThirdsIndex }); // Create wrapper divs for the charts const firstWrapper = document.createElement('div'); firstWrapper.className = 'chart-wrapper xy-chart-container'; firstWrapper.style.cssText = ` margin: 1.5rem 0; padding: 1rem; border-radius: 8px; background-color: #f8f9fa; border: 1px solid #e9ecef; `; firstWrapper.appendChild(firstComponent); const secondWrapper = document.createElement('div'); secondWrapper.className = 'chart-wrapper quadrant-chart-container'; secondWrapper.style.cssText = ` margin: 1.5rem 0; padding: 1rem; border-radius: 8px; background-color: #f8f9fa; border: 1px solid #e9ecef; `; secondWrapper.appendChild(secondComponent); // Store references to elements at insertion points const firstInsertionPoint = children[oneThirdIndex]; const secondInsertionPoint = children[twoThirdsIndex]; console.log('Insertion points found:', { firstInsertionPoint, secondInsertionPoint }); // Insert components at calculated positions if (firstInsertionPoint) { firstInsertionPoint.parentNode.insertBefore(firstWrapper, firstInsertionPoint); console.log('Inserted first chart before:', firstInsertionPoint); } else { parentElement.appendChild(firstWrapper); console.log('Appended first chart to end (no insertion point found)'); } // Recalculate children after first insertion to maintain proper spacing const updatedChildren = Array.from(parentElement.children).filter(child => !child.classList.contains('chart-wrapper') ); const newSecondInsertionPoint = updatedChildren[twoThirdsIndex]; if (newSecondInsertionPoint) { newSecondInsertionPoint.parentNode.insertBefore(secondWrapper, newSecondInsertionPoint); console.log('Inserted second chart before:', newSecondInsertionPoint); } else { parentElement.appendChild(secondWrapper); console.log('Appended second chart to end (no insertion point found)'); } console.log('Final container state:', { childCount: parentElement.children.length, chartWrappers: parentElement.querySelectorAll('.chart-wrapper').length }); return true; } catch (error) { console.error('Error inserting components at proportional points:', error); return false; } } export function injectOfflineWarning(clone) { const markdownContainer = clone.querySelector('.markdown-preview-container'); if (!markdownContainer) return; // Create warning element const warningDiv = document.createElement('div'); warningDiv.className = 'offline-warning'; warningDiv.style.cssText = ` background-color: #fff3cd; border: 1px solid #ffeeba; border-left: 4px solid #ffc107; border-radius: 4px; padding: 1rem; margin-bottom: 1.5rem; font-size: 0.95rem; color: #856404; `; // Create warning content warningDiv.innerHTML = `
⚠️ Offline Mode
This quiz was generated offline. Some features may be limited.
`; // Insert at the beginning of the markdown container markdownContainer.insertBefore(warningDiv, markdownContainer.firstChild); } // Add this new function export function cleanupPopovers(shadowEle) { const popovers = shadowEle.querySelectorAll('.popover-socratiq'); popovers.forEach(popover => popover.remove()); }