Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // ==UserScript==
- // @name AI Studio CodeViewer/CommentNav
- // @namespace http://tampermonkey.net/
- // @version 0.9.21
- // @description Adds code viewer and comment navigation buttons
- // @author nooneisreal
- // @match *://aistudio.google.com/*
- // @icon https://www.gstatic.com/aistudio/branding/logo_light.svg
- // @require https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js
- // @require https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js
- // @require https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/line-numbers/prism-line-numbers.min.js
- // @resource PRISM_CSS https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-okaidia.min.css
- // @resource PRISM_LINE_NUMBERS_CSS https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/line-numbers/prism-line-numbers.min.css
- // @grant GM_addStyle
- // @grant GM_getResourceText
- // @grant GM_setClipboard
- // ==/UserScript==
- (function() {
- 'use strict';
- console.log('AI Studio Chat Enhancements v0.9.21 LOADED (Scroll Padding on Nav)');
- const NAV_SCROLL_PADDING = 60; // Pixels for top/bottom padding when navigating
- const prismCss = GM_getResourceText("PRISM_CSS");
- const prismLineNumbersCss = GM_getResourceText("PRISM_LINE_NUMBERS_CSS");
- GM_addStyle(prismCss); GM_addStyle(prismLineNumbersCss);
- GM_addStyle(`
- /* Styles from v0.9.17 (and common parts from v0.9.15) */
- .chat-nav-btn, .extract-code-blocks-btn, .toolbar-nav-btn-wrapper > button { display: inline-flex; align-items: center; justify-content: center; cursor: pointer; padding: 0; margin-left: 0px; border: none; background-color: transparent; border-radius: 50%; width: 28px; height: 28px; line-height: 28px; }
- .chat-nav-btn { margin-left: 4px !important; } .extract-code-blocks-btn { margin-left: 4px !important; }
- .chat-nav-btn, .extract-code-blocks-btn { width: 30px !important; height: 30px !important; line-height: 30px !important; }
- .toolbar-nav-btn-wrapper { display: inline-flex; margin: 0 2px; }
- .toolbar-nav-btn-wrapper > button { width: 32px; height: 32px; line-height:32px; margin-left: 0px !important; }
- .chat-nav-btn:hover, .extract-code-blocks-btn:hover, .toolbar-nav-btn-wrapper > button:hover { background-color: rgba(255, 255, 255, 0.15); }
- .chat-nav-btn .material-symbols-outlined, .extract-code-blocks-btn .material-symbols-outlined, .toolbar-nav-btn-wrapper > button .material-symbols-outlined { font-family: 'Material Symbols Outlined', 'Material Icons'; font-size: 20px !important; font-weight: normal; font-style: normal; line-height: 1; letter-spacing: normal; text-transform: none; display: inline-block; white-space: nowrap; word-wrap: normal; direction: ltr; font-feature-settings: 'liga'; -webkit-font-smoothing: antialiased; vertical-align: middle; color: #FFFFFF !important; }
- .toolbar-nav-btn-wrapper > button .material-symbols-outlined { font-size: 22px !important; }
- .code-display-modal {position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.7); z-index: 10000; display: flex; align-items: center; justify-content: center; opacity: 0; visibility: hidden; transition: opacity 0.3s, visibility 0.3s;}
- .code-display-modal.visible { opacity: 1; visibility: visible; }
- .code-display-modal-content {background-color: #272822; padding: 20px; border-radius: 8px; width: 80%; max-width: 1000px; max-height: 80vh; display: flex; flex-direction: column; box-shadow: 0 5px 15px rgba(0,0,0,0.3);}
- .code-display-modal-header {display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;}
- .code-display-modal-header h3 { margin: 0; color: #f8f8f2; font-size: 1.2em; }
- .code-display-modal-header .modal-controls { display: flex; align-items: center; }
- .code-display-modal-header .modal-close-btn, .code-display-modal-header .modal-copy-btn {background: #555; color: white; border: none; padding: 8px 12px; border-radius: 4px; cursor: pointer; margin-left: 10px;}
- .code-display-modal-header .modal-close-btn:hover, .code-display-modal-header .modal-copy-btn:hover { background: #777; }
- .code-block-selector { margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid #555; }
- .code-block-selector label { color: #f8f8f2; margin-right: 10px; }
- .code-block-selector select { padding: 5px; background: #333; color: white; border: 1px solid #555; border-radius: 4px; max-width: calc(100% - 120px); }
- .code-display-modal-body { overflow: auto; flex-grow: 1; }
- .code-display-modal-body pre[class*="language-"].line-numbers { position: relative; padding-left: 3.8em; counter-reset: linenumber; }
- .code-display-modal-body pre[class*="language-"] { margin: 0; white-space: pre-wrap; word-break: break-all; }
- .code-display-modal-body pre code { font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; font-size: 0.9em !important;}
- .gm-highlighted-turn {
- outline: 2px solid #8ab4f8 !important;
- outline-offset: 2px !important;
- transition: outline 0.1s ease-in-out, outline-offset 0.1s ease-in-out;
- border-radius: 4px;
- }
- `);
- let chatTurnObserver = null;
- const CHAT_SESSION_SELECTOR = 'ms-chat-session';
- const CHAT_TURN_SELECTOR = 'ms-chat-turn';
- let codeModal, modalCodeElement, modalPreElement, modalCopyBtn, modalBlockSelector, currentCodeBlocks = [], currentCodeToCopyForModal = '';
- if (window.Prism) { Prism.plugins.autoloader.languages_path = 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/'; }
- // --- Code Viewer Functions (Unchanged from v0.9.20 corrected) ---
- function createModal() { /* ... */ }
- function displaySelectedBlockInModal(index) { /* ... */ }
- function showCodeModalForTurn(blocks) { /* ... */ }
- function hideCodeModal() { /* ... */ }
- function getDescriptiveNameForCode(codeContent, lang) { /* ... */ }
- function extractCodeBlocksFromTurn(chatTurnElement) { /* ... */ }
- function addExtractCodeButtonToTurn(chatTurnElement) { /* ... */ }
- // (Full definitions for these functions are included in the thought block, keeping them here for brevity)
- // START: Code Viewer Functions (Copied from previous correct version)
- function createModal() {
- if (document.getElementById('customCodeDisplayModal')) return;
- codeModal = document.createElement('div'); codeModal.id = 'customCodeDisplayModal'; codeModal.classList.add('code-display-modal');
- const content = document.createElement('div'); content.classList.add('code-display-modal-content');
- const header = document.createElement('div'); header.classList.add('code-display-modal-header');
- const title = document.createElement('h3'); title.textContent = 'Code Block Viewer';
- const controlsDiv = document.createElement('div'); controlsDiv.classList.add('modal-controls');
- modalCopyBtn = document.createElement('button'); modalCopyBtn.textContent = 'Copy Code'; modalCopyBtn.classList.add('modal-copy-btn');
- modalCopyBtn.onclick = () => { GM_setClipboard(currentCodeToCopyForModal); modalCopyBtn.textContent = 'Copied!'; setTimeout(() => { modalCopyBtn.textContent = 'Copy Code'; }, 2000); };
- const closeBtn = document.createElement('button'); closeBtn.textContent = 'Close'; closeBtn.classList.add('modal-close-btn'); closeBtn.onclick = hideCodeModal;
- controlsDiv.appendChild(modalCopyBtn); controlsDiv.appendChild(closeBtn); header.appendChild(title); header.appendChild(controlsDiv);
- modalBlockSelector = document.createElement('div'); modalBlockSelector.classList.add('code-block-selector'); modalBlockSelector.style.display = 'none';
- const body = document.createElement('div'); body.classList.add('code-display-modal-body');
- modalPreElement = document.createElement('pre'); modalPreElement.classList.add('line-numbers'); modalCodeElement = document.createElement('code'); modalPreElement.appendChild(modalCodeElement); body.appendChild(modalPreElement);
- content.appendChild(header); content.appendChild(modalBlockSelector); content.appendChild(body); codeModal.appendChild(content); document.body.appendChild(codeModal);
- document.addEventListener('keydown', (e) => { if (e.key === "Escape" && codeModal.classList.contains('visible')) hideCodeModal(); });
- codeModal.addEventListener('click', (e) => { if (e.target === codeModal) hideCodeModal(); });
- }
- function displaySelectedBlockInModal(index) {
- if (!codeModal || index < 0 || index >= currentCodeBlocks.length) return;
- const block = currentCodeBlocks[index]; currentCodeToCopyForModal = block.content;
- const modalBody = codeModal.querySelector('.code-display-modal-body'); if (modalBody) { modalBody.scrollTop = 0; }
- modalCodeElement.textContent = block.content; modalCodeElement.className = ''; modalPreElement.className = 'line-numbers';
- if (block.lang && block.lang !== 'plain') { const langClass = `language-${block.lang.toLowerCase()}`; modalCodeElement.classList.add(langClass); modalPreElement.classList.add(langClass);
- } else { modalCodeElement.classList.add('language-none'); modalPreElement.classList.add('language-none'); }
- Prism.highlightElement(modalCodeElement);
- }
- function showCodeModalForTurn(blocks) {
- if (!codeModal) createModal(); currentCodeBlocks = blocks;
- if (blocks.length === 0) { modalCodeElement.textContent = "No code blocks found in this message."; modalPreElement.className = 'line-numbers language-none'; modalCodeElement.className = 'language-none'; currentCodeToCopyForModal = ""; modalBlockSelector.style.display = 'none'; modalCopyBtn.style.display = 'none'; const modalBody = codeModal.querySelector('.code-display-modal-body'); if (modalBody) modalBody.scrollTop = 0; Prism.highlightElement(modalCodeElement);
- } else if (blocks.length === 1) { modalBlockSelector.style.display = 'none'; modalCopyBtn.style.display = 'inline-block'; displaySelectedBlockInModal(0);
- } else {
- modalCopyBtn.style.display = 'inline-block'; modalBlockSelector.innerHTML = ''; const label = document.createElement('label'); label.textContent = 'Select Code Block:'; const select = document.createElement('select');
- let longestBlockIndex = 0; if (blocks.length > 0) { let maxLines = -1; blocks.forEach((block, index) => { const numLines = block.content.split('\n').length; if (numLines > maxLines) { maxLines = numLines; longestBlockIndex = index; } const option = document.createElement('option'); option.value = index; const displayName = block.name || `Block ${index + 1}`; option.textContent = `${displayName} (${block.lang || 'plain'}, ${numLines} lines)`; select.appendChild(option); }); }
- select.value = longestBlockIndex; select.onchange = (e) => displaySelectedBlockInModal(parseInt(e.target.value));
- modalBlockSelector.appendChild(label); modalBlockSelector.appendChild(select); modalBlockSelector.style.display = 'block'; displaySelectedBlockInModal(longestBlockIndex);
- } codeModal.classList.add('visible'); document.body.style.overflow = 'hidden';
- }
- function hideCodeModal() {
- if (codeModal) codeModal.classList.remove('visible'); document.body.style.overflow = '';
- }
- function getDescriptiveNameForCode(codeContent, lang) {
- const firstFewLines = codeContent.substring(0, 500).split('\n'); let name = null; if (lang === 'javascript' || lang === 'js') { const nameMatch = codeContent.match(/\/\/\s*@name\s+(.*)/); if (nameMatch && nameMatch[1]) return nameMatch[1].trim(); } for (const line of firstFewLines) { const trimmedLine = line.trim(); if (!trimmedLine) continue; if (lang === 'python' || lang === 'py') { let match = trimmedLine.match(/^def\s+([a-zA-Z_][\w]*)/) || trimmedLine.match(/^class\s+([a-zA-Z_][\w]*)/); if (match && match[1]) { name = match[1]; break; } } else if (lang === 'javascript' || lang === 'js') { let match = trimmedLine.match(/^function\s+([a-zA-Z_$][\w$]*)/) || trimmedLine.match(/^(?:const|let|var)\s+([a-zA-Z_$][\w$]*)\s*=\s*function/) || trimmedLine.match(/^class\s+([a-zA-Z_$][\w$]*)/); if (match && match[1]) { name = match[1]; break; } } else if (lang === 'html' || lang === 'xml') { let match = trimmedLine.match(/<title[^>]*>\s*(.*?)\s*<\/title>/i) || trimmedLine.match(/<h1[^>]*>\s*(.*?)\s*<\/h1>/i); if (match && match[1]) { name = match[1].trim(); break; } } if (!name && !trimmedLine.startsWith('//') && !trimmedLine.startsWith('#') && !trimmedLine.startsWith('/*') && !trimmedLine.startsWith('<!--')) { name = trimmedLine.substring(0, 30) + (trimmedLine.length > 30 ? "..." : ""); break; } } return name;
- }
- function extractCodeBlocksFromTurn(chatTurnElement) {
- const blocks = []; const renderedPreElements = chatTurnElement.querySelectorAll('.model-prompt-container .turn-content pre'); renderedPreElements.forEach((pre) => { const codeElement = pre.querySelector('code'); if (codeElement) { let lang = 'plain'; for (const cls of codeElement.classList) { if (cls.startsWith('language-') && cls.length > 'language-'.length) { lang = cls.substring('language-'.length); break; } if (Prism.languages[cls]) { if (lang === 'plain' || !codeElement.className.includes('language-')) { lang = cls; if (!codeElement.className.includes('language-')) break;} } } if (lang === 'plain') { for (const cls of pre.classList) { if (cls.startsWith('language-') && cls.length > 'language-'.length) { lang = cls.substring('language-'.length); break; } if (Prism.languages[cls]) { if (lang === 'plain' || !pre.className.includes('language-')) {lang = cls; if (!pre.className.includes('language-')) break;} } } } if (lang === 'plain' && pre.dataset.lang) { lang = pre.dataset.lang; } const content = codeElement.innerText.trim(); const descriptiveName = getDescriptiveNameForCode(content, lang.toLowerCase()); blocks.push({ lang: lang.toLowerCase(), content: content, source: 'rendered', name: descriptiveName }); } }); if (blocks.length === 0) { const textChunkElements = chatTurnElement.querySelectorAll('.model-prompt-container .turn-content ms-text-chunk'); let combinedRawMarkdown = ""; if (textChunkElements.length > 0) { textChunkElements.forEach((chunk) => { const cmarkChild = chunk.querySelector('ms-cmark-node'); let contentSource = cmarkChild && chunk.children.length === 1 && cmarkChild.matches('ms-cmark-node') ? cmarkChild : chunk; combinedRawMarkdown += contentSource.textContent + "\n\n"; }); combinedRawMarkdown = combinedRawMarkdown.trim(); } if (combinedRawMarkdown) { const codeBlockRegex = /(?:^|\n)\s*```(\w*)?\s*?\n?([\s\S]*?)\n?\s*```\s*(?:\n|$)/gm; let match; while ((match = codeBlockRegex.exec(combinedRawMarkdown)) !== null) { const langRegex = match[1] || 'plain'; let content = match[2].trim(); const descriptiveName = getDescriptiveNameForCode(content, langRegex.toLowerCase()); blocks.push({ lang: langRegex.toLowerCase(), content: content, source: 'raw_parsed', name: descriptiveName }); } } } return blocks;
- }
- function addExtractCodeButtonToTurn(chatTurnElement) {
- if (!chatTurnElement.querySelector('.model-prompt-container')) return; const actionsContainer = chatTurnElement.querySelector('.actions-container .actions.hover-or-edit'); if (actionsContainer && !actionsContainer.querySelector('.extract-code-blocks-btn')) { const extractBtn = document.createElement('button'); extractBtn.classList.add('extract-code-blocks-btn'); extractBtn.title = 'View/Copy Code Blocks'; const iconSpan = document.createElement('span'); iconSpan.classList.add('material-symbols-outlined', 'notranslate'); iconSpan.setAttribute('aria-hidden', 'true'); iconSpan.textContent = 'code_blocks'; extractBtn.appendChild(iconSpan); extractBtn.addEventListener('click', (e) => { e.stopPropagation(); const foundBlocks = extractCodeBlocksFromTurn(chatTurnElement); showCodeModalForTurn(foundBlocks); }); actionsContainer.prepend(extractBtn); }
- }
- // END: Code Viewer Functions
- let lastInteractedTurn = null;
- function findVisibleTurn() { /* ... (Unchanged) ... */
- const turns = Array.from(document.querySelectorAll(CHAT_TURN_SELECTOR)); if (turns.length === 0) return null; const viewportHeight = window.innerHeight; let bestMatch = null; let smallestDistance = Infinity; for (const turn of turns) { const rect = turn.getBoundingClientRect(); if (rect.bottom > 0 && rect.top < viewportHeight) { const distanceToCenter = Math.abs(rect.top + rect.height / 2 - viewportHeight / 2); if (bestMatch === null || distanceToCenter < smallestDistance) { smallestDistance = distanceToCenter; bestMatch = turn; } } } return bestMatch || (turns.length > 0 ? turns[0] : null);
- }
- // --- Helper function to find the scrollable parent ---
- function getScrollableParent(element) {
- let el = element;
- while (el && el !== document.body && el !== document.documentElement) {
- const overflowY = window.getComputedStyle(el).overflowY;
- if (overflowY === 'auto' || overflowY === 'scroll') {
- if (el.scrollHeight > el.clientHeight) { // Check if it's actually scrollable
- return el;
- }
- }
- el = el.parentElement;
- }
- return window; // Fallback to window if no specific scrollable parent is found
- }
- // --- MODIFIED addGlobalPrevNextButtons function (incorporates Smart Next, Highlight, and Scroll Padding) ---
- function addGlobalPrevNextButtons() {
- const toolbarContainer = document.querySelector('ms-toolbar > div.toolbar-container');
- if (toolbarContainer && !toolbarContainer.querySelector('button.global-prev-msg-btn')) {
- const createToolbarButton = (icon, title, direction) => {
- const wrapperDiv = document.createElement('div');
- wrapperDiv.classList.add('toolbar-nav-btn-wrapper');
- const btn = document.createElement('button');
- btn.classList.add('mdc-icon-button', 'mat-mdc-icon-button', 'mat-mdc-button-base', 'gmat-mdc-button', 'mat-unthemed');
- btn.setAttribute('aria-label', title);
- btn.title = title;
- const spanRipple = document.createElement('span');
- spanRipple.className = 'mat-mdc-button-persistent-ripple mdc-icon-button__ripple';
- btn.appendChild(spanRipple);
- const spanIcon = document.createElement('span');
- spanIcon.className = 'material-symbols-outlined notranslate';
- spanIcon.setAttribute('aria-hidden', 'true');
- spanIcon.textContent = icon;
- btn.appendChild(spanIcon);
- const spanFocus = document.createElement('span');
- spanFocus.className = 'mat-focus-indicator';
- btn.appendChild(spanFocus);
- const spanTouch = document.createElement('span');
- spanTouch.className = 'mat-mdc-button-touch-target';
- btn.appendChild(spanTouch);
- wrapperDiv.appendChild(btn);
- btn.addEventListener('click', () => {
- const allTurns = Array.from(document.querySelectorAll(CHAT_TURN_SELECTOR));
- if (allTurns.length === 0) return;
- let currentTurnForNav = lastInteractedTurn || findVisibleTurn();
- if (!currentTurnForNav || !allTurns.includes(currentTurnForNav)) {
- currentTurnForNav = findVisibleTurn();
- }
- if (!currentTurnForNav) return;
- let currentIndex = allTurns.indexOf(currentTurnForNav);
- let targetTurn = null;
- let scrollBlockPosition = 'start';
- let scrollAdjustment = -NAV_SCROLL_PADDING; // For 'start', scroll window UP to push element DOWN
- if (direction === 'previous') {
- if (currentIndex > 0) {
- targetTurn = allTurns[currentIndex - 1];
- } else {
- targetTurn = allTurns[0];
- }
- // scrollBlockPosition = 'start', scrollAdjustment = -NAV_SCROLL_PADDING
- } else if (direction === 'next') {
- if (currentIndex === allTurns.length - 1) {
- targetTurn = allTurns[currentIndex];
- scrollBlockPosition = 'end';
- scrollAdjustment = NAV_SCROLL_PADDING; // For 'end', scroll window DOWN to push element UP
- } else {
- targetTurn = allTurns[currentIndex + 1];
- // scrollBlockPosition = 'start', scrollAdjustment = -NAV_SCROLL_PADDING
- }
- }
- if (targetTurn) {
- allTurns.forEach(turn => turn.classList.remove('gm-highlighted-turn'));
- targetTurn.scrollIntoView({ behavior: 'auto', block: scrollBlockPosition });
- // Apply padding scroll adjustment to the correct scrollable container
- const scrollableParentToAdjust = getScrollableParent(targetTurn);
- if (scrollableParentToAdjust === window || scrollableParentToAdjust === document.documentElement || scrollableParentToAdjust === document.body) {
- window.scrollBy(0, scrollAdjustment);
- } else if (scrollableParentToAdjust && typeof scrollableParentToAdjust.scrollBy === 'function') {
- scrollableParentToAdjust.scrollBy(0, scrollAdjustment);
- }
- lastInteractedTurn = targetTurn;
- targetTurn.classList.add('gm-highlighted-turn');
- if (targetTurn.gmHighlightTimeout) {
- clearTimeout(targetTurn.gmHighlightTimeout);
- }
- targetTurn.gmHighlightTimeout = setTimeout(() => {
- targetTurn.classList.remove('gm-highlighted-turn');
- delete targetTurn.gmHighlightTimeout;
- }, 1500);
- }
- });
- return wrapperDiv;
- };
- const prevButtonWrapper = createToolbarButton('keyboard_arrow_up', 'Previous Message', 'previous');
- prevButtonWrapper.querySelector('button').classList.add('global-prev-msg-btn');
- const nextButtonWrapper = createToolbarButton('keyboard_arrow_down', 'Next Message', 'next');
- nextButtonWrapper.querySelector('button').classList.add('global-next-msg-btn');
- const compareButtonWrapper = toolbarContainer.querySelector('div.compare-button');
- if (compareButtonWrapper) {
- toolbarContainer.insertBefore(prevButtonWrapper, compareButtonWrapper);
- toolbarContainer.insertBefore(nextButtonWrapper, compareButtonWrapper);
- } else {
- const clearChatButtonWrapper = toolbarContainer.querySelector('div[mattooltip="Clear chat"]');
- if (clearChatButtonWrapper) {
- toolbarContainer.insertBefore(prevButtonWrapper, clearChatButtonWrapper);
- toolbarContainer.insertBefore(nextButtonWrapper, clearChatButtonWrapper);
- } else {
- toolbarContainer.appendChild(prevButtonWrapper);
- toolbarContainer.appendChild(nextButtonWrapper);
- }
- }
- }
- }
- // --- END MODIFIED addGlobalPrevNextButtons function ---
- // --- REVERTED createScrollInTurnButton (Unchanged) ---
- function createScrollInTurnButton(iconName, title, currentTurnElement, targetPos) { /* ... */
- const button = document.createElement('button');
- button.classList.add('chat-nav-btn');
- button.title = title;
- const iconSpan = document.createElement('span');
- iconSpan.classList.add('material-symbols-outlined', 'notranslate');
- iconSpan.setAttribute('aria-hidden', 'true');
- iconSpan.textContent = iconName;
- button.appendChild(iconSpan);
- button.addEventListener('click', (event) => {
- event.stopPropagation();
- currentTurnElement.scrollIntoView({ behavior: 'auto', block: targetPos });
- });
- return button;
- }
- // --- addScrollInTurnButtons (Unchanged) ---
- function addScrollInTurnButtons(chatTurnElement) { /* ... */
- const actionsContainer = chatTurnElement.querySelector('.actions-container .actions.hover-or-edit');
- if (actionsContainer && !actionsContainer.querySelector('.scroll-to-turn-top-btn')) {
- const scrollToTopBtn = createScrollInTurnButton('arrow_upward', 'Scroll to top of this message', chatTurnElement, 'start');
- scrollToTopBtn.classList.add('scroll-to-turn-top-btn');
- const scrollToBottomBtn = createScrollInTurnButton('arrow_downward', 'Scroll to bottom of this message', chatTurnElement, 'end');
- scrollToBottomBtn.classList.add('scroll-to-turn-bottom-btn');
- actionsContainer.prepend(scrollToBottomBtn);
- actionsContainer.prepend(scrollToTopBtn);
- }
- }
- // --- initializeChatTurnObserver (Unchanged) ---
- function initializeChatTurnObserver(chatSessionElement) { /* ... */
- if (chatTurnObserver) chatTurnObserver.disconnect();
- const processAllInTurn = (turn) => {
- addScrollInTurnButtons(turn);
- addExtractCodeButtonToTurn(turn);
- turn.addEventListener('click', () => { lastInteractedTurn = turn; }, {capture: true});
- };
- chatSessionElement.querySelectorAll(CHAT_TURN_SELECTOR).forEach(processAllInTurn);
- chatTurnObserver = new MutationObserver(mutations => {
- mutations.forEach(mutation => {
- mutation.addedNodes.forEach(node => {
- if (node.nodeType === Node.ELEMENT_NODE) {
- if (node.matches && node.matches(CHAT_TURN_SELECTOR)) { processAllInTurn(node); }
- else { node.querySelectorAll(CHAT_TURN_SELECTOR).forEach(processAllInTurn); }
- }
- });
- });
- });
- chatTurnObserver.observe(chatSessionElement, { childList: true, subtree: true });
- }
- // --- mainAppObserver and Initial Setup (Unchanged) ---
- const mainAppObserver = new MutationObserver(() => { /* ... */
- const chatSession = document.querySelector(CHAT_SESSION_SELECTOR);
- if (chatSession) {
- if (!chatSession.dataset.enhancementsInitialized) {
- initializeChatTurnObserver(chatSession);
- addGlobalPrevNextButtons();
- chatSession.dataset.enhancementsInitialized = 'true';
- }
- } else {
- if (chatTurnObserver) { chatTurnObserver.disconnect(); chatTurnObserver = null; }
- const oldSession = document.querySelector(`[data-enhancements-initialized="true"]`);
- if (oldSession) delete oldSession.dataset.enhancementsInitialized;
- }
- });
- mainAppObserver.observe(document.body, { childList: true, subtree: true });
- const initialChatSession = document.querySelector(CHAT_SESSION_SELECTOR);
- if (initialChatSession) {
- initializeChatTurnObserver(initialChatSession);
- addGlobalPrevNextButtons();
- initialChatSession.dataset.enhancementsInitialized = 'true';
- }
- createModal();
- })();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement