diff --git a/components/ping_ai_copilot/extension/content/content_scripts/aiSummarizer.js b/components/ping_ai_copilot/extension/content/content_scripts/aiSummarizer.js
index 306cbc4896a9..cf880fb1b49c 100644
--- a/components/ping_ai_copilot/extension/content/content_scripts/aiSummarizer.js
+++ b/components/ping_ai_copilot/extension/content/content_scripts/aiSummarizer.js
@@ -1,209 +1,221 @@
-let summarizer = null;
-let summaryBox = null;
-let isSummarizerVisible = false;
-let isTextSelected = false;
-
-const debounce = (func, delay) => {
- let timeoutId;
- return (...args) => {
- clearTimeout(timeoutId);
- timeoutId = setTimeout(() => func(...args), delay);
+const TextSummarizer = (() => {
+
+ let state = {
+ summarizer: null,
+ summaryBox: null,
+ isSummarizerVisible: false,
+ isTextSelected: false,
+ currentSelectedText: '',
};
-};
-
-const initializeExtension = () => {
- document.removeEventListener('mouseup', handleTextSelection);
- document.addEventListener('mouseup', debounce(handleTextSelection, 190));
- document.addEventListener('click', handleDocumentClick);
-}
-
-const handleTextSelection = (event) => {
- const selection = window.getSelection();
- const selectedText = selection.toString().trim();
- const wordCount = selectedText.split(/\s+/).length;
- if (wordCount >= 10 && wordCount <= 1000 && !(summaryBox && summaryBox.contains(selection.anchorNode))) {
- if (!isSummarizerVisible) {
- showSummarizeIcon(selectedText, event);
- isTextSelected = true;
+
+ const COPIED_MESSAGE_TIMEOUT = 2000;
+
+ const debounce = (func, delay) => {
+ let timeoutId;
+ return (...args) => {
+ clearTimeout(timeoutId);
+ timeoutId = setTimeout(() => func(...args), delay);
+ };
+ };
+
+ const copyToClipboard = (text) => {
+ navigator.clipboard.writeText(text).then(() => {
+ showCopiedMessage();
+ }).catch(err => {
+ console.error('Failed to copy:', err);
+ });
+ };
+
+ const showCopiedMessage = () => {
+ const copyButton = document.getElementById('copy-button');
+ if (!copyButton) return;
+
+ copyButton.style.display = 'none';
+
+ const copiedMessage = document.createElement('div');
+ copiedMessage.id = 'copiedMessage';
+ copiedMessage.textContent = 'Copied!';
+
+ copyButton.parentNode.insertBefore(copiedMessage, copyButton);
+
+ setTimeout(() => {
+ copiedMessage.remove();
+ copyButton.style.display = 'block';
+ }, COPIED_MESSAGE_TIMEOUT);
+ };
+
+ const handleTextSelection = (event) => {
+ const selection = window.getSelection();
+ const selectedText = selection.toString().trim();
+ const wordCount = selectedText.split(/\s+/).length;
+
+ state.currentSelectedText = selectedText;
+
+ if (wordCount >= 10 && wordCount <= 1000 && !(state.summaryBox && state.summaryBox.contains(selection.anchorNode))) {
+ if (!state.isSummarizerVisible) {
+ showSummarizeIcon(event);
+ state.isTextSelected = true;
+ }
+ } else {
+ hideSummarizeIcon();
+ state.isTextSelected = false;
}
- } else {
- hideSummarizeIcon();
- isTextSelected = false;
- }
-}
-
-const handleDocumentClick = (event) => {
- if(summaryBox && !summaryBox.contains(event.target)){
- hideSummaryBox();
- }
- if(isTextSelected && summarizer){
- if(summaryBox && !summaryBox.contains(event.target)){
- hideSummarizeIcon()
+ };
+
+ const handleDocumentClick = (event) => {
+ if (state.summaryBox && !state.summaryBox.contains(event.target)) {
hideSummaryBox();
}
- else if(!summaryBox){
- hideSummarizeIcon()
- hideSummaryBox();
- }
- }
-}
-
-const showSummarizeIcon = (selectedText, event) => {
- if (!summarizer) {
- summarizer = document.createElement('div');
- summarizer.id = 'summarizer-icon';
-
- const iconImage = document.createElement('img');
- iconImage.src = chrome.runtime.getURL('extension/assets/aiSummarizerIcon.svg');
- iconImage.alt = 'Summarize Icon';
- iconImage.id = 'iconImage';
-
- summarizer.appendChild(iconImage);
-
- summarizer.addEventListener('click', (e) => {
- e.preventDefault();
- e.stopPropagation();
- sendTextToSummarize(selectedText);
- });
- document.body.appendChild(summarizer);
- }
-
- const selection = window.getSelection();
- const range = selection.getRangeAt(0);
- const endRange = document.createRange();
- endRange.setStart(range.endContainer, range.endOffset);
- endRange.setEnd(range.endContainer, range.endOffset);
-
- let rect;
- if (range.endContainer.nodeType === Node.TEXT_NODE) {
- const text = range.endContainer.textContent;
- let wordStart = range.endOffset;
-
- while (wordStart > 0 && /\S/.test(text[wordStart - 1])) {
- wordStart--;
+ if (state.isTextSelected && state.summarizer) {
+ if (state.summaryBox && !state.summaryBox.contains(event.target)) {
+ hideSummarizeIcon();
+ hideSummaryBox();
+ } else if (!state.summaryBox) {
+ hideSummarizeIcon();
+ hideSummaryBox();
+ }
+ }
+ };
+
+ // UI Management
+ const showSummarizeIcon = (event) => {
+ if (!state.summarizer) {
+ state.summarizer = document.createElement('div');
+ state.summarizer.id = 'summarizer-icon';
+
+ const iconImage = document.createElement('img');
+ iconImage.src = chrome.runtime.getURL('extension/assets/aiSummarizerIcon.svg');
+ iconImage.alt = 'Summarize Icon';
+ iconImage.id = 'iconImage';
+
+ state.summarizer.appendChild(iconImage);
+
+ state.summarizer.addEventListener('click', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ sendTextToSummarize(state.currentSelectedText);
+ });
+ document.body.appendChild(state.summarizer);
+ }
+
+ const selection = window.getSelection();
+ const range = selection.getRangeAt(0);
+ const endRange = document.createRange();
+ endRange.setStart(range.endContainer, range.endOffset);
+ endRange.setEnd(range.endContainer, range.endOffset);
+
+ let rect;
+ if (range.endContainer.nodeType === Node.TEXT_NODE) {
+ const text = range.endContainer.textContent;
+ let wordStart = range.endOffset;
+
+ while (wordStart > 0 && /\S/.test(text[wordStart - 1])) {
+ wordStart--;
+ }
+ endRange.setStart(range.endContainer, wordStart);
+ rect = endRange.getBoundingClientRect();
+ } else {
+ rect = range.getBoundingClientRect();
}
- endRange.setStart(range.endContainer, wordStart);
- rect = endRange.getBoundingClientRect();
- } else {
- rect = range.getBoundingClientRect();
- }
-
- const scrollX = window.scrollX || document.documentElement.scrollLeft;
- const scrollY = window.scrollY || document.documentElement.scrollTop;
-
- let top; let left;
- if(event.detail === 3){
- top = rect.bottom + scrollY + 1;
- left = rect.right + scrollX + 1
- }
- top = rect.bottom + scrollY + 5;
- left = rect.right + scrollX + 5;
-
- summarizer.style.top = `${top}px`;
- summarizer.style.left = `${left}px`;
- summarizer.style.display = 'inline-block';
- isSummarizerVisible = true;
-};
-
-const hideSummarizeIcon = () => {
- if (summarizer) {
- summarizer.style.display = 'none';
- isSummarizerVisible = false;
- }
-}
-
-const sendTextToSummarize = (text) => {
- showSummaryBox(true);
- try {
- chrome.runtime.sendMessage({ action: 'summarize', text: text });
- } catch (error) {
- console.error('Failed to send message:', error);
- showSummaryBox(false, 'An error occurred. Please refresh the page and try again.');
- }
-}
-
-const showSummaryBox = (isLoading, summary = '', headerText = '') => {
- if (!summaryBox) {
- summaryBox = document.createElement('div');
- summaryBox.id = 'summary-box';
- document.body.appendChild(summaryBox);
- }
- summaryBox.innerHTML = `
-
- ${isLoading ? `
-
-
-
-
+
+ const scrollX = window.scrollX || document.documentElement.scrollLeft;
+ const scrollY = window.scrollY || document.documentElement.scrollTop;
+
+ let top, left;
+ if (event.detail === 3) {
+ top = rect.bottom + scrollY + 1;
+ left = rect.right + scrollX + 1;
+ } else {
+ top = rect.bottom + scrollY + 5;
+ left = rect.right + scrollX + 5;
+ }
+
+ state.summarizer.style.top = `${top}px`;
+ state.summarizer.style.left = `${left}px`;
+ state.summarizer.style.display = 'inline-block';
+ state.isSummarizerVisible = true;
+ };
+
+ const hideSummarizeIcon = () => {
+ if (state.summarizer) {
+ state.summarizer.style.display = 'none';
+ state.isSummarizerVisible = false;
+ }
+ };
+
+ const showSummaryBox = (isLoading, summary = '', headerText = '') => {
+ if (!state.summaryBox) {
+ state.summaryBox = document.createElement('div');
+ state.summaryBox.id = 'summary-box';
+ document.body.appendChild(state.summaryBox);
+ }
+
+ state.summaryBox.innerHTML = `
+
- ` : `
-
- ${summary.split('\n').map((point, index) => `
- - ${point}
- `).join('')}
-
- `}
- `;
-
- if (!isLoading) {
- document.getElementById('copy-button').addEventListener('click', (e) => {
- e.stopPropagation();
- copyToClipboard(summary);
- });
- }
-
- summaryBox.style.animation = 'slideUp 0.3s ease-out';
- summaryBox.style.display = 'block';
- setTimeout(() => {
- const height = summaryBox.scrollHeight;
- summaryBox.style.maxHeight = `${height}px`;
- summaryBox.style.opacity = '1';
- }, 10);
-}
-
-const hideSummaryBox = () => {
- if (summaryBox) {
- summaryBox.style.animation = 'slideDown 0.3s ease-out';
+ ${isLoading ? `
+
+ ` : `
+
+ ${summary.split('\n').map(point => `- ${point}
`).join('')}
+
+ `}
+ `;
+
+ if (!isLoading) {
+ document.getElementById('copy-button').addEventListener('click', (e) => {
+ e.stopPropagation();
+ copyToClipboard(summary);
+ });
+ }
+
+ state.summaryBox.style.animation = 'slideUp 0.3s ease-out';
+ state.summaryBox.style.display = 'block';
setTimeout(() => {
- summaryBox.style.display = 'none';
- }, 300);
- }
-}
-
-const copyToClipboard = (text) => {
- navigator.clipboard.writeText(text).then(() => {
- showCopiedMessage();
- }).catch(err => {
- console.error('Failed to copy:', err);
- });
-}
-
-const showCopiedMessage = () => {
- const copyButton = document.getElementById('copy-button');
- if (!copyButton) return;
-
- copyButton.style.display = 'none';
-
- const copiedMessage = document.createElement('div');
- copiedMessage.id = 'copiedMessage';
- copiedMessage.textContent = 'Copied!';
-
- copyButton.parentNode.insertBefore(copiedMessage, copyButton);
-
- setTimeout(() => {
- copiedMessage.remove();
- copyButton.style.display = 'block';
- }, 2000);
-}
-
-// Listen for messages from the background script
-chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
- if (request.action === 'displaySummary') {
- showSummaryBox(false, request.summary, request.headerText);
- }
-});
-
-initializeExtension();
+ const height = state.summaryBox.scrollHeight;
+ state.summaryBox.style.maxHeight = `${height}px`;
+ state.summaryBox.style.opacity = '1';
+ }, 10);
+ };
+
+ const hideSummaryBox = () => {
+ if (state.summaryBox) {
+ state.summaryBox.style.animation = 'slideDown 0.3s ease-out';
+ setTimeout(() => {
+ state.summaryBox.style.display = 'none';
+ }, 300);
+ }
+ };
+
+ const sendTextToSummarize = async(text) => {
+ showSummaryBox(true);
+ try {
+ const response = await chrome.runtime.sendMessage({ action: 'summarize', text: text });
+ if (response.success) {
+ showSummaryBox(false, response.summary, response.headerText);
+ } else
+ showSummaryBox(false, 'An error occurred. Please refresh the page and try again.');
+ } catch (error) {
+ console.error('Failed to send message:', error);
+ showSummaryBox(false, 'An error occurred. Please refresh the page and try again.');
+ }
+ };
+
+ const initialize = () => {
+ document.removeEventListener('mouseup', handleTextSelection);
+ document.addEventListener('mouseup', debounce(handleTextSelection, 190));
+ document.addEventListener('click', handleDocumentClick);
+ };
+
+ return {
+ initialize,
+ };
+})();
+
+TextSummarizer.initialize();
\ No newline at end of file
diff --git a/components/ping_ai_copilot/extension/content/content_scripts/rephraser.js b/components/ping_ai_copilot/extension/content/content_scripts/rephraser.js
index d95d8e2d61eb..3bcd8e94a01f 100644
--- a/components/ping_ai_copilot/extension/content/content_scripts/rephraser.js
+++ b/components/ping_ai_copilot/extension/content/content_scripts/rephraser.js
@@ -1,418 +1,480 @@
-let prevActiveElement = null;
-let originalText = '';
-let rephrasedText = '';
-let rephraseButton = null;
-let isFetching = false;
-let abortController = null;
-let pillContainer = null;
-let showRephraseButton = true;
-let isCanceled = false;
-let hasRephrasedBefore = false;
-
-const isDarkBackground = (color) => {
- const rgb = color.match(/\d+/g);
- if (rgb) {
- const [r, g, b] = rgb.map(Number);
- return (r * 0.299 + g * 0.587 + b * 0.114) < 128;
- }
- return false;
-};
-
-const getIconColor = (textBox) => {
- const bgColor = window.getComputedStyle(textBox).backgroundColor;
- return isDarkBackground(bgColor) ? 'dark' : 'light';
-};
-
-const applyGradientAnimation = (textBox) => {
- const gradient = 'linear-gradient(270deg, #F100C1, #00CED1, #F100C1)';
- const animation = 'gradientAnimation 5s ease infinite';
-
- textBox.style.backgroundImage = gradient;
- textBox.style.backgroundSize = '200% 200%';
- textBox.style.animation = animation;
- textBox.style.WebkitBackgroundClip = 'text';
- textBox.style.WebkitTextFillColor = 'transparent';
-}
-
-const removeGradientColor = (textBox) => {
- textBox.style.backgroundImage = 'none';
- textBox.style.animation = 'none';
- textBox.style.WebkitBackgroundClip = 'initial';
- textBox.style.WebkitTextFillColor = 'initial';
-}
-
-const createPillContainer = (textBox, img) => {
- if (pillContainer) {
- pillContainer.remove();
- }
-
- const iconColor = getIconColor(textBox);
- const pillBgColor = iconColor === 'light' ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)';
- const textColor = iconColor === 'light' ? 'black' : 'white';
-
- pillContainer = document.createElement('div');
- pillContainer.classList.add('pill-container');
- pillContainer.style.backgroundColor = pillBgColor;
- if (iconColor === 'light') {
- pillContainer.classList.remove('pill-dark-shadow')
- pillContainer.classList.add('pill-light-shadow')
- }
- else {
- pillContainer.classList.remove('pill-light-shadow');
- pillContainer.classList.add('pill-dark-shadow');
- }
-
- const leftImg = document.createElement('div');
- leftImg.classList.add('pill-img-container');
- const leftImgIcon = document.createElement('img');
- leftImgIcon.src = chrome.runtime.getURL(`extension/assets/back-${iconColor}.svg`);
- leftImgIcon.alt = 'Back';
- leftImgIcon.classList.add('icon');
- leftImg.appendChild(leftImgIcon);
-
- const leftTooltip = document.createElement('span');
- leftTooltip.textContent = 'Back';
- leftTooltip.classList.add('tooltip', 'left-tooltip', `tooltip-hover-${iconColor}`);
- leftImg.appendChild(leftTooltip);
- pillContainer.appendChild(leftImg);
-
- const separator = document.createElement('span');
- separator.textContent = '|';
- separator.style.color = iconColor === 'light' ? 'black' : 'white';
- separator.classList.add('separator');
- pillContainer.appendChild(separator);
-
- const rightImg = document.createElement('div');
- rightImg.classList.add('pill-img-container');
- const rightImgIcon = document.createElement('img');
- rightImgIcon.src = chrome.runtime.getURL(`extension/assets/rewrite-${iconColor}.svg`);
- rightImgIcon.alt = 'Retry';
- rightImgIcon.classList.add('icon');
- rightImg.appendChild(rightImgIcon);
-
- const rightTooltip = document.createElement('span');
- rightTooltip.textContent = 'Retry';
- rightTooltip.classList.add('tooltip', 'right-tooltip', `tooltip-hover-${iconColor}`);
- rightImg.appendChild(rightTooltip);
- pillContainer.appendChild(rightImg);
-
- document.body.appendChild(pillContainer);
-
- // Adjust position based on scroll offsets
- const rect = textBox.getBoundingClientRect();
- pillContainer.style.left = `${rect.right - pillContainer.offsetWidth - 5 + window.scrollX}px`;
- pillContainer.style.top = `${rect.bottom - pillContainer.offsetHeight + window.scrollY}px`;
-
- leftImgIcon.addEventListener('click', () => {
- if (prevActiveElement) {
- undoRephraseText(textBox);
- }
- });
+const TextRephraser = (() => {
+
+ const state = {
+ prevActiveElement: null,
+ originalText: '',
+ rephrasedText: '',
+ rephraseButton: null,
+ isFetching: false,
+ abortController: null,
+ pillContainer: null,
+ showRephraseButton: true,
+ isCanceled: false,
+ hasRephrasedBefore: false
+ };
- rightImgIcon.addEventListener('click', () => {
- if (prevActiveElement) {
- rephraseText(textBox);
+ // Constants
+ const MIN_WORDS = 10;
+ const MIN_WIDTH = 600;
+ const MIN_HEIGHT = 50;
+ const PILL_TIMEOUT = 1500;
+
+ const isDarkBackground = (color) => {
+ const rgb = color.match(/\d+/g);
+ if (rgb) {
+ const [r, g, b] = rgb.map(Number);
+ return (r * 0.299 + g * 0.587 + b * 0.114) < 128;
}
- });
-
- pillContainer.addEventListener('mouseleave', (event) => {
- // Check if the mouse is moving back to the rephrase button
- const buttonRect = rephraseButton ? rephraseButton.getBoundingClientRect() : null;
- if (!buttonRect ||
- event.clientX < buttonRect.left ||
- event.clientX > buttonRect.right ||
- event.clientY < buttonRect.top ||
- event.clientY > buttonRect.bottom) {
- pillContainer.remove();
- pillContainer = null;
- if (isFetching && rephraseButton) {
- img.src = chrome.runtime.getURL(`extension/assets/cross-light.svg`);
- rephraseButton.style.display = 'flex';
- rephraseButton.style.justifyContent = 'center';
- rephraseButton.style.alignItems = 'center';
- rephraseButton.onClick = () => {
- undoRephraseText(textBox, img);
- }
- }
- else if (rephraseButton) {
- rephraseButton.style.display = 'flex';
- rephraseButton.style.justifyContent = 'center';
- rephraseButton.style.alignItems = 'center';
+ return false;
+ };
+
+ const getIconColor = (textBox) => {
+ const bgColor = window.getComputedStyle(textBox).backgroundColor;
+ return isDarkBackground(bgColor) ? 'dark' : 'light';
+ };
+
+ const getAssetUrl = (name, color, hover = false) => {
+ const hoverSuffix = hover ? '-hover' : '';
+ return chrome.runtime.getURL(`extension/assets/${name}-${color}${hoverSuffix}.svg`);
+ };
+
+ // Text styling functions
+ const applyGradientAnimation = (textBox) => {
+ const gradient = 'linear-gradient(270deg, #F100C1, #00CED1, #F100C1)';
+ Object.assign(textBox.style, {
+ backgroundImage: gradient,
+ backgroundSize: '200% 200%',
+ animation: 'gradientAnimation 5s ease infinite',
+ WebkitBackgroundClip: 'text',
+ WebkitTextFillColor: 'transparent'
+ });
+ };
+
+ const removeGradientColor = (textBox) => {
+ Object.assign(textBox.style, {
+ backgroundImage: 'none',
+ animation: 'none',
+ WebkitBackgroundClip: 'initial',
+ WebkitTextFillColor: 'initial'
+ });
+ };
+
+ // Text manipulation functions
+ const typeText = async (textBox, text, delay = 16) => {
+ let index = 0;
+ while (index < text.length && !state.isCanceled) {
+ const char = text[index] === ' ' ? '\u00A0' : text[index];
+ if (textBox.tagName === 'INPUT' || textBox.tagName === 'TEXTAREA') {
+ textBox.value += char;
+ } else if (textBox.isContentEditable) {
+ textBox.innerText += char;
}
+ index++;
+ await new Promise(resolve => setTimeout(resolve, delay));
}
- });
-};
+ };
-const typeText = async (textBox, text, delay = 16) => {
- let index = 0;
- while (index < text.length) {
- if (isCanceled) return;
- if (textBox.tagName === 'INPUT' || textBox.tagName === 'TEXTAREA') {
- text[index] == ' ' ? textBox.value += '\u00A0' : textBox.value += text[index];
- } else if (textBox.isContentEditable) {
- text[index] == ' ' ? textBox.innerText += '\u00A0' : textBox.innerText += text[index];
+ const shouldShowRephraseButton = (text) => {
+ const words = text.trim().split(/\s+/);
+ return words.length >= MIN_WORDS;
+ };
+
+ // UI Component Creation
+ const createPillContainer = (textBox, img) => {
+ if (state.pillContainer) {
+ state.pillContainer.remove();
}
- index++;
- await new Promise(resolve => setTimeout(resolve, delay));
- }
-}
-
-const rephraseText = async (textBox, img) => {
- originalText = textBox.value || textBox.innerText;
- applyGradientAnimation(textBox);
- if (img) {
+
const iconColor = getIconColor(textBox);
- img.src = chrome.runtime.getURL(`extension/assets/cross-light.svg`);
- img.alt = "stop"
- img.style.width = '20px';
- img.style.height = '20px';
- const buttonContainer = img.parentElement;
+ const pillBgColor = iconColor === 'light' ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)';
- if (iconColor === 'light') {
- buttonContainer.classList.remove('dark-shadow')
- buttonContainer.classList.add('light-shadow')
- }
- else {
- buttonContainer.classList.remove('light-shadow');
- buttonContainer.classList.add('dark-shadow');
- }
- }
- isFetching = true;
- isCanceled = false;
+ const container = document.createElement('div');
+ container.classList.add('pill-container');
+ container.style.backgroundColor = pillBgColor;
+ container.classList.add(`pill-${iconColor}-shadow`);
- try {
- const response = await chrome.runtime.sendMessage({ action: 'rephrase', text: originalText });
+ const leftButton = createPillButton('back', iconColor, 'Back', () => {
+ if (state.prevActiveElement) {
+ undoRephraseText(textBox);
+ }
+ });
- if (isCanceled) {
- return;
+ const separator = document.createElement('span');
+ separator.textContent = '|';
+ separator.style.color = iconColor === 'light' ? 'black' : 'white';
+ separator.classList.add('separator');
+
+ const rightButton = createPillButton('rewrite', iconColor, 'Retry', () => {
+ if (state.prevActiveElement) {
+ rephraseText(textBox);
+ }
+ });
+
+ container.append(leftButton, separator, rightButton);
+ document.body.appendChild(container);
+
+ const rect = textBox.getBoundingClientRect();
+ Object.assign(container.style, {
+ left: `${rect.right - container.offsetWidth - 5 + window.scrollX}px`,
+ top: `${rect.bottom - container.offsetHeight + window.scrollY}px`
+ });
+
+ state.pillContainer = container;
+ setupPillContainerEvents(container, textBox, img);
+ };
+
+ const createPillButton = (iconName, iconColor, tooltipText, onClick) => {
+ const button = document.createElement('div');
+ button.classList.add('pill-img-container');
+
+ const icon = document.createElement('img');
+ icon.src = getAssetUrl(iconName, iconColor);
+ icon.alt = tooltipText;
+ icon.classList.add('icon');
+
+ const tooltip = document.createElement('span');
+ tooltip.textContent = tooltipText;
+ tooltip.classList.add('tooltip', `${tooltipText.toLowerCase()}-tooltip`, `tooltip-hover-${iconColor}`);
+
+ button.append(icon, tooltip);
+ button.addEventListener('click', onClick);
+
+ return button;
+ };
+
+ const setupPillContainerEvents = (container, textBox, img) => {
+ container.addEventListener('mouseleave', (event) => {
+ const buttonRect = state.rephraseButton?.getBoundingClientRect();
+ if (!buttonRect ||
+ event.clientX < buttonRect.left ||
+ event.clientX > buttonRect.right ||
+ event.clientY < buttonRect.top ||
+ event.clientY > buttonRect.bottom) {
+ container.remove();
+ state.pillContainer = null;
+
+ if (state.rephraseButton) {
+ state.rephraseButton.style.display = 'flex';
+ if (state.isFetching) {
+ img.src = getAssetUrl('cross', 'light');
+ state.rephraseButton.onClick = () => undoRephraseText(textBox, img);
+ }
+ }
+ }
+ });
+ };
+
+ const rephraseText = async (textBox, img) => {
+ state.originalText = textBox.value || textBox.innerText;
+ state.isFetching = true;
+ state.isCanceled = false;
+ applyGradientAnimation(textBox);
+
+ if (img) {
+ updateButtonForFetching(textBox, img);
}
- // Clear the textBox before typing the new text
- if (prevActiveElement.tagName === 'INPUT' || prevActiveElement.tagName === 'TEXTAREA') {
- prevActiveElement.value = '';
- } else if (prevActiveElement.isContentEditable) {
- prevActiveElement.innerText = '';
+ try {
+ const response = await chrome.runtime.sendMessage({
+ action: 'rephrase',
+ text: state.originalText
+ });
+ console.log('Rephrase response:', response);
+
+ if (!state.isCanceled) {
+ await handleSuccessfulRephrase(textBox, response.rephrase, img);
+ }
+ } catch (error) {
+ console.error('Rephrase failed:', error);
+ handleRephraseFailed(textBox, img);
+ } finally {
+ focusTextBox(textBox);
}
+ };
+ const handleSuccessfulRephrase = async (textBox, rephrasedText, img) => {
+ clearTextBox(state.prevActiveElement);
removeGradientColor(textBox);
- // Type the rephrased text with a smooth effect
- await typeText(prevActiveElement, response.rephrase);
+ await typeText(state.prevActiveElement, rephrasedText);
- isFetching = false;
- removeGradientColor(prevActiveElement);
+ state.isFetching = false;
+ removeGradientColor(state.prevActiveElement);
- if (rephraseButton) {
- rephraseButton.remove();
- rephraseButton = null;
+ if (state.rephraseButton) {
+ state.rephraseButton.remove();
+ state.rephraseButton = null;
}
- showRephraseButton = false;
- hasRephrasedBefore = true;
- // Show pill for 3 seconds
- createPillContainer(prevActiveElement, img);
+ state.showRephraseButton = false;
+ state.hasRephrasedBefore = true;
+
+ createPillContainer(state.prevActiveElement, img);
setTimeout(() => {
- if (pillContainer) {
- pillContainer.remove();
- pillContainer = null;
+ if (state.pillContainer) {
+ state.pillContainer.remove();
+ state.pillContainer = null;
}
- showRephraseButton = true;
- addButtonToTextBox(prevActiveElement);
- }, 2000);
- } catch (error) {
- console.error('Failed to send message:', error);
- isFetching = false;
+ state.showRephraseButton = true;
+ addButtonToTextBox(state.prevActiveElement);
+ }, PILL_TIMEOUT);
+ };
+
+ const handleRephraseFailed = (textBox, img) => {
+ state.isFetching = false;
if (img) {
- const iconColor = getIconColor(textBox);
- img.src = chrome.runtime.getURL(`extension/assets/rephrase-${iconColor}.svg`);
+ img.src = getAssetUrl('rephrase', getIconColor(textBox));
}
removeGradientColor(textBox);
- } finally {
- textBox.focus();
- if (textBox.tagName === 'INPUT' || textBox.tagName === 'TEXTAREA') {
- const length = textBox.value.length;
- textBox.setSelectionRange(length, length);
- } else if (textBox.isContentEditable) {
- const range = document.createRange();
- const sel = window.getSelection();
- range.selectNodeContents(textBox);
- range.collapse(false);
- sel.removeAllRanges();
- sel.addRange(range);
- }
- }
-}
-
-const undoRephraseText = (textBox, img) => {
- isFetching = false;
- isCanceled = true;
- removeGradientColor(textBox);
- if (img) {
+ };
+
+ const undoRephraseText = (textBox, img) => {
+ state.isFetching = false;
+ state.isCanceled = true;
+ removeGradientColor(textBox);
+
const iconColor = getIconColor(textBox);
- img.src = chrome.runtime.getURL(`extension/assets/rephrase-${iconColor}.svg`);
- }
- if (prevActiveElement) {
- if (prevActiveElement.tagName === 'INPUT' || prevActiveElement.tagName === 'TEXTAREA') {
- prevActiveElement.value = originalText;
- } else if (prevActiveElement.isContentEditable) {
- prevActiveElement.innerText = originalText;
+ img.src = getAssetUrl('rephrase', iconColor);
+ img.alt = 'Rephrase';
+ img.dataset.currentIcon = 'rephrase';
+ Object.assign(img.style, {
+ width: '17px',
+ height: '17px'
+ });
+
+ if (state.prevActiveElement) {
+ if (state.prevActiveElement.tagName === 'INPUT' || state.prevActiveElement.tagName === 'TEXTAREA') {
+ state.prevActiveElement.value = state.originalText;
+ } else if (state.prevActiveElement.isContentEditable) {
+ state.prevActiveElement.innerText = state.originalText;
+ }
}
- }
-}
-
-const shouldShowRephraseButton = (text) => {
- const words = text.trim().split(/\s+/);
- return words.length >= 10;
-};
-
-const addButtonToTextBox = (textBox) => {
-
- // Check if the input box is too small
- const rect = textBox.getBoundingClientRect();
- const minWidth = 600;
- const minHeight = 50;
-
- if (rect.width < minWidth && rect.height < minHeight) {
- return;
- }
-
- const text = textBox.value || textBox.innerText;
- if (!shouldShowRephraseButton(text)) {
- if (rephraseButton) {
- rephraseButton.remove();
- rephraseButton = null;
+ };
+
+ const addButtonToTextBox = (textBox) => {
+ const rect = textBox.getBoundingClientRect();
+ if (rect.width < MIN_WIDTH && rect.height < MIN_HEIGHT) {
+ return;
}
- return;
- }
- if (rephraseButton) {
- rephraseButton.remove();
- }
-
- const buttonContainer = document.createElement('div');
- buttonContainer.classList.add('rephrase-button-container');
- const iconColor = getIconColor(textBox);
- if (iconColor === 'light') {
- buttonContainer.classList.remove('dark-shadow')
- buttonContainer.classList.add('light-shadow')
- }
- else {
- buttonContainer.classList.remove('light-shadow');
- buttonContainer.classList.add('dark-shadow');
- }
- const img = document.createElement('img');
- img.src = chrome.runtime.getURL(`extension/assets/rephrase-${iconColor}.svg`);
- img.alt = 'Rephrase';
- img.style.width = '17px';
- img.style.height = '17px';
- img.style.objectFit = 'contain';
-
- buttonContainer.appendChild(img);
-
- const showPill = () => {
- if (hasRephrasedBefore) {
- buttonContainer.style.display = 'none';
- createPillContainer(textBox, img);
+
+ const text = textBox.value || textBox.innerText;
+ if (!shouldShowRephraseButton(text)) {
+ if (state.rephraseButton) {
+ state.rephraseButton.remove();
+ state.rephraseButton = null;
+ }
+ return;
}
+
+ createRephraseButton(textBox, rect);
};
- const hidePill = () => {
- if (pillContainer) {
- pillContainer.remove();
- pillContainer = null;
+ const createRephraseButton = (textBox, rect) => {
+ if (state.rephraseButton) {
+ state.rephraseButton.remove();
}
- buttonContainer.style.display = 'flex';
- buttonContainer.style.justifyContent = 'center';
- buttonContainer.style.alignItems = 'center';
+
+ const buttonContainer = document.createElement('div');
+ buttonContainer.classList.add('rephrase-button-container');
+
+ const iconColor = getIconColor(textBox);
+ buttonContainer.classList.add(`${iconColor}-shadow`);
+
+ const img = createButtonImage(iconColor);
+ buttonContainer.appendChild(img);
+
+ positionButton(buttonContainer, rect, img);
+ setupButtonEvents(buttonContainer, textBox, img, iconColor);
+
+ document.body.appendChild(buttonContainer);
+ state.rephraseButton = buttonContainer;
+ observeTextBox(textBox, buttonContainer);
};
- buttonContainer.addEventListener('mouseenter', () => {
- if (!isFetching) {
- if (hasRephrasedBefore) {
- showPill();
- } else {
- img.src = chrome.runtime.getURL(`extension/assets/rephrase-${iconColor}-hover.svg`);
+ const createButtonImage = (iconColor) => {
+ const img = document.createElement('img');
+ img.src = getAssetUrl('rephrase', iconColor);
+ img.alt = 'Rephrase';
+ img.dataset.currentIcon = 'rephrase';
+ Object.assign(img.style, {
+ width: '17px',
+ height: '17px',
+ objectFit: 'contain'
+ });
+ return img;
+ };
+
+ const positionButton = (buttonContainer, rect, img) => {
+ const containerHeight = img.style.height;
+ const containerWidth = img.style.width;
+ Object.assign(buttonContainer.style, {
+ left: `${rect.right - parseInt(containerWidth) - 30 + window.scrollX}px`,
+ top: `${rect.bottom - parseInt(containerHeight) - 10 + window.scrollY}px`,
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center'
+ });
+ };
+
+ const setupButtonEvents = (buttonContainer, textBox, img, iconColor) => {
+ buttonContainer.addEventListener('mouseenter', () => {
+ if (!state.isFetching) {
+ if (state.hasRephrasedBefore) {
+ showPill(buttonContainer, textBox, img);
+ } else {
+ if (img.dataset.currentIcon === 'rephrase') {
+ img.src = getAssetUrl('rephrase', iconColor, true);
+ }
+ }
}
- }
- });
-
- buttonContainer.addEventListener('mouseleave', (event) => {
- if (!isFetching) {
- if (hasRephrasedBefore) {
- // Check if the mouse is moving to the pill
- const pillRect = pillContainer ? pillContainer.getBoundingClientRect() : null;
- if (!pillRect ||
- event.clientX < pillRect.left ||
- event.clientX > pillRect.right ||
- event.clientY < pillRect.top ||
- event.clientY > pillRect.bottom) {
- hidePill();
+ });
+
+ buttonContainer.addEventListener('mouseleave', (event) => {
+ if (!state.isFetching) {
+ if (state.hasRephrasedBefore) {
+ handlePillMouseLeave(event, buttonContainer);
+ } else {
+ if (img.dataset.currentIcon === 'rephrase') {
+ img.src = getAssetUrl('rephrase', iconColor);
+ }
}
- } else {
- img.src = chrome.runtime.getURL(`extension/assets/rephrase-${iconColor}.svg`);
}
- }
- });
-
- const containerHeight = img.style.height;
- const containerWidth = img.style.width;
+ });
- buttonContainer.style.left = `${rect.right - parseInt(containerWidth) - 30 + window.scrollX}px`;
- buttonContainer.style.top = `${rect.bottom - parseInt(containerHeight) - 10 + window.scrollY}px`;
+ buttonContainer.addEventListener('click', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
- document.body.appendChild(buttonContainer);
+ if (state.isFetching) {
+ undoRephraseText(textBox, img);
+ } else {
+ rephraseText(textBox, img);
+ }
+ });
+ };
- rephraseButton = buttonContainer;
+ const showPill = (buttonContainer, textBox, img) => {
+ buttonContainer.style.display = 'none';
+ createPillContainer(textBox, img);
+ };
- buttonContainer.addEventListener('click', async (e) => {
- e.preventDefault();
- e.stopPropagation();
+ const handlePillMouseLeave = (event, buttonContainer) => {
+ const pillRect = state.pillContainer ? state.pillContainer.getBoundingClientRect() : null;
+ if (!pillRect ||
+ event.clientX < pillRect.left ||
+ event.clientX > pillRect.right ||
+ event.clientY < pillRect.top ||
+ event.clientY > pillRect.bottom) {
+ hidePill(buttonContainer);
+ }
+ };
- if (isFetching) {
- // If fetching, stop the process and revert to original text
- undoRephraseText(textBox, img);
- } else {
- // Start rephrasing process
- rephraseText(textBox, img);
+ const hidePill = (buttonContainer) => {
+ if (state.pillContainer) {
+ state.pillContainer.remove();
+ state.pillContainer = null;
}
- });
-
- // Observe changes to the DOM to detect when the text box is removed or hidden
- const observer = new MutationObserver((mutationsList) => {
- for (let mutation of mutationsList) {
- if (mutation.type === 'childList' || mutation.type === 'attributes') {
- if (!document.body.contains(textBox) || textBox.offsetParent === null) {
- buttonContainer.remove();
- observer.disconnect();
+ buttonContainer.style.display = 'flex';
+ };
+
+ const observeTextBox = (textBox, buttonContainer) => {
+ const observer = new MutationObserver((mutationsList) => {
+ for (let mutation of mutationsList) {
+ if (mutation.type === 'childList' || mutation.type === 'attributes') {
+ if (!document.body.contains(textBox) || textBox.offsetParent === null) {
+ buttonContainer.remove();
+ observer.disconnect();
+ }
}
}
+ });
+ observer.observe(document.body, { childList: true, subtree: true, attributes: true });
+ };
+
+ const handleTextBoxFocus = (activeElement) => {
+ if (state.showRephraseButton) {
+ if (state.pillContainer) {
+ state.pillContainer.remove();
+ }
+ addButtonToTextBox(activeElement);
+ } else {
+ createPillContainer(activeElement);
}
- });
- observer.observe(document.body, { childList: true, subtree: true, attributes: true });
-};
-document.addEventListener('focusin', (event) => {
- const activeElement = event.target;
+ state.showRephraseButton = true;
- if (activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA' || activeElement.isContentEditable) {
+ if (state.prevActiveElement) {
+ removeGradientColor(state.prevActiveElement);
+ }
+ state.prevActiveElement = activeElement;
+ };
- if (showRephraseButton) {
- if (pillContainer) pillContainer.remove();
- addButtonToTextBox(activeElement);
+ const updateButtonForFetching = (textBox, img) => {
+ img.src = getAssetUrl('cross', 'light');
+ img.alt = 'stop';
+ img.dataset.currentIcon = 'cross';
+ Object.assign(img.style, {
+ width: '20px',
+ height: '20px'
+ });
+
+ const buttonContainer = img.parentElement;
+ const iconColor = getIconColor(textBox);
+ buttonContainer.classList.remove('dark-shadow', 'light-shadow');
+ buttonContainer.classList.add(`${iconColor}-shadow`);
+ };
+
+ const clearTextBox = (element) => {
+ if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') {
+ element.value = '';
+ } else if (element.isContentEditable) {
+ element.innerText = '';
}
- else
- createPillContainer(activeElement);
+ };
- showRephraseButton = true;
+ const focusTextBox = (textBox) => {
+ textBox.focus();
+ if (textBox.tagName === 'INPUT' || textBox.tagName === 'TEXTAREA') {
+ const length = textBox.value.length;
+ textBox.setSelectionRange(length, length);
+ } else if (textBox.isContentEditable) {
+ const range = document.createRange();
+ const sel = window.getSelection();
+ range.selectNodeContents(textBox);
+ range.collapse(false);
+ sel.removeAllRanges();
+ sel.addRange(range);
+ }
+ };
- if (prevActiveElement) {
- removeGradientColor(prevActiveElement);
+ // Event handlers
+ const focusinHandler = (event) => {
+ const activeElement = event.target;
+ if (isValidTextBox(activeElement)) {
+ handleTextBoxFocus(activeElement);
+ }
+ };
+
+ const inputHandler = (event) => {
+ const activeElement = event.target;
+ if (isValidTextBox(activeElement)) {
+ addButtonToTextBox(activeElement);
}
- prevActiveElement = activeElement;
- }
-});
-
-document.addEventListener('input', (event) => {
- const activeElement = event.target;
- if (activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA' || activeElement.isContentEditable) {
- addButtonToTextBox(activeElement);
- }
-});
\ No newline at end of file
+ };
+
+ const isValidTextBox = (element) => {
+ return element.tagName === 'INPUT' ||
+ element.tagName === 'TEXTAREA' ||
+ element.isContentEditable;
+ };
+
+ const initialize = () => {
+ document.addEventListener('focusin', focusinHandler);
+ document.addEventListener('input', inputHandler);
+ };
+
+ return {
+ initialize,
+ };
+})();
+
+TextRephraser.initialize();
\ No newline at end of file
diff --git a/components/ping_ai_copilot/extension/content/ui/style.css b/components/ping_ai_copilot/extension/content/ui/style.css
index df082315d3d7..22a9882cef4c 100644
--- a/components/ping_ai_copilot/extension/content/ui/style.css
+++ b/components/ping_ai_copilot/extension/content/ui/style.css
@@ -56,13 +56,13 @@
z-index: 10000001;
}
-.left-tooltip {
+.back-tooltip {
bottom: 106%;
left: 30%;
transform: translateX(-50%);
}
-.right-tooltip {
+.retry-tooltip {
bottom: 106%;
left: 70%;
transform: translateX(-50%);
diff --git a/components/ping_ai_copilot/extension/manifest.json b/components/ping_ai_copilot/extension/manifest.json
index b8aeb621c64a..fcd231c9f3aa 100644
--- a/components/ping_ai_copilot/extension/manifest.json
+++ b/components/ping_ai_copilot/extension/manifest.json
@@ -4,6 +4,8 @@
"version": "1.0",
"description": "Ai copilot - by Ping.",
"permissions": [
+ "storage",
+ "tabs",
"activeTab",
"scripting",
"declarativeNetRequest",
@@ -31,5 +33,5 @@
"type": "module"
},
- "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArfOx1MW/cb3YPNlmT37CuISYgRbtR1SIdgnx/cfTyXO/PuD1VVsQWLDmrZGDmYVCzZvP36t75uhpJH4IoXL58U16yhdXZeSlb0LKcgMZB6cMNyjznV4NTEeY+tLnwGaB1TVdkJgSlY09psyfvcdzQd8xz9CNE6CXDzEq8+uMSaoAyEJ3nP78yV33nBrMj3jbjTi1fr2QsrpoISql/pJ9Zr5V0QbK4wIqln20ly96KuAO5c1DM9z9VnoYFdirEZBfkT/4gB7pBfyd4ScoMhXuaa9w53N8Espu1bC0RGmaKB679rGQdaBTrEUGF+PNfsucjnyrsnup6GMVhc91CXTDjQIDBQAA"
+ "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArfOx1MW/cb3YPNlmT37CuISYgRbtR1SIdgnx/cfTyXO/PuD1VVsQWLDmrZGDmYVCzZvP36t75uhpJH4IoXL58U16yhdXZeSlb0LKcgMZB6cMNyjznV4NTEeY+tLnwGaB1TVdkJgSlY09psyfvcdzQd8xz9CNE6CXDzEq8+uMSaoAyEJ3nP78yV33nBrMj3jbjTi1fr2QsrpoISql/pJ9Zr5V0QbK4wIqln20ly96KuAO5c1DM9z9VnoYFdirEZBfkT/4gB7pBfyd4ScoMhXuaa9w53N8Espu1bC0RGmaKB679rGQdaBTrEUGF+PNfsucjnyrsnup6GMVhc91CXTDjQIDAQAA"
}
diff --git a/components/ping_ai_copilot/extension/service_worker/background.js b/components/ping_ai_copilot/extension/service_worker/background.js
index f64a51f142eb..6857f1024c86 100644
--- a/components/ping_ai_copilot/extension/service_worker/background.js
+++ b/components/ping_ai_copilot/extension/service_worker/background.js
@@ -1,56 +1,84 @@
import { translations } from "../constants/constants.js";
+const API_ENDPOINT = "https://openai-text-summarizer.azurewebsites.net";
+
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
- if (request.action === 'summarize') {
- (async () => {
- try {
- let ln = chrome.i18n.getUILanguage();
- let headerText = 'Text summary';
- const translation = translations.find(t => t.code === ln);
- if (translation) {
- headerText = translation.translation;
- }
- const response = await fetch('https://openai-text-summarizer.azurewebsites.net/summarize', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({ text: request.text, lang: ln }),
- });
-
- const data = await response.json();
- chrome.tabs.sendMessage(sender.tab.id, { action: 'displaySummary', summary: data.summary, headerText: headerText }, response => {
- if (chrome.runtime.lastError) {
- console.error(chrome.runtime.lastError);
- }
- });
- } catch (error) {
- chrome.tabs.sendMessage(sender.tab.id, { action: 'displaySummary', summary: 'An error occurred while summarizing the text' }, response => {
- if (chrome.runtime.lastError) {
- console.error(chrome.runtime.lastError);
- }
- });
- }
- })();
- return true; // Indicates that the response will be sent asynchronously
+ switch (request.action) {
+ case 'reload':
+ handleReloadMessage(sendResponse);
+ break;
+ case 'summarize':
+ handleSummarizeMessage(request, sendResponse);
+ break;
+ case 'rephrase':
+ handleRephraseMessage(request, sendResponse);
+ break;
+ default:
+ console.warn('Unknown action:', request.action);
+ sendResponse({ error: 'Unknown action' });
+ }
+
+ return true;
+});
+
+const handleSummarizeMessage = async (request, sendResponse) => {
+ try {
+ const ln = chrome.i18n.getUILanguage();
+ let headerText = 'Text summary';
+
+ const translation = translations.find(t => t.code === ln);
+ if (translation) {
+ headerText = translation.translation;
+ }
+
+ const response = await fetch(`${API_ENDPOINT}/summarize`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ text: request.text, lang: ln }),
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const data = await response.json();
+
+ sendResponse({ success: true, summary: data.summary, headerText: headerText });
+ } catch (error) {
+ console.error('Summarize error:', error);
+ sendResponse({
+ success: false,
+ error: error.message,
+ summary: "An error occurred while summarizing the text"
+ });
}
- if (request.action === 'rephrase') {
- (async () => {
- try {
- let ln = chrome.i18n.getUILanguage();
- const response = await fetch('https://openai-text-summarizer.azurewebsites.net/rephrase', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({ text: request.text, lang: ln }),
- });
-
- const data = await response.json();
- sendResponse({ rephrase: data.rText })
- } catch (error) {
- sendResponse({ rephrase: "An error occurred while rephrasing the text" })
- }
- })();
- return true;
+}
+
+const handleRephraseMessage = async (request, sendResponse) => {
+ try {
+ const ln = chrome.i18n.getUILanguage();
+ const response = await fetch(`${API_ENDPOINT}/rephrase`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ text: request.text, lang: ln }),
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const data = await response.json();
+
+ if (!data.rText) {
+ throw new Error('Response missing rText property');
+ }
+
+ sendResponse({ success: true, rephrase: data.rText });
+ } catch (error) {
+ console.error('Rephrase error:', error);
+ sendResponse({
+ success: false,
+ error: error.message,
+ rephrase: "An error occurred while rephrasing the text"
+ });
}
-});
\ No newline at end of file
+}
\ No newline at end of file