I am trying to make an extension for FireFox/Gecko-based browsers that add an upload-file button to chat.openai.com. When I add my extension, it'll only add the button when I refresh on 1 chat page. If I go to a past chat, it won't put in the button. (BTW, I wrote this code with the help of ChatGPT lol).
manifest.json:
{
"manifest_version": 3,
"name": "ChatGPT File Upload",
"version": "1.0",
"description": "Adds a button to upload files into ChatGPT. (NOT for images, videos, Word Documents, or other non-raw-text files. Please use .txt, .js, .py, .html, .css, .json, and .csv.",
"permissions": [
"scripting",
"https://chat.openai.com/*"
],
"action": {
"default_icon": {
"128": "icon128.png",
"256": "icon128.png"
}
},
"icons": {
"128": "icon128.png",
"256": "icon256.png"
},
"content_scripts": [
{
"matches": ["https://chat.openai.com/*"],
"js": ["content.js"]
}
],
"background": {
"scripts": ["background.js"],
"service_worker": "background.js"
}
}
background.js:
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
if (changeInfo.url && changeInfo.url.startsWith('https://chat.openai.com/')) {
chrome.scripting.executeScript({
target: { tabId: tabId },
files: ['content.js']
});
}
});
content.js:
console.log("Content script loaded.");
// This script will be injected into chat.openai.com pages
// You can add your desired functionality here
// Create the button
const button = document.createElement('button');
button.innerText = '📂 Submit File';
button.style.backgroundColor = '#35393d';
button.style.color = 'white';
button.style.padding = '5px';
button.style.border = '1px solid #6b6458';
button.style.borderRadius = '5px';
button.style.margin = '5px';
button.style.width = '180px';
// Create a container div for centering
const containerDiv = document.createElement('div');
containerDiv.style.display = 'flex';
containerDiv.style.justifyContent = 'center';
// Append the button to the container div
containerDiv.appendChild(button);
// Find the target element
const targetElement = document.querySelector("div.relative.flex.h-full.max-w-full.flex-1.overflow-hidden > div > main > div.absolute.bottom-0 > form > div > div:nth-child(1)");
// Insert the container div before the target element
targetElement.parentNode.insertBefore(containerDiv, targetElement);
// Add click event listener to the button
button.addEventListener('click', async () => {
// Create the file input element
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = '.txt, .js, .py, .html, .css, .json, .csv';
// Handle file selection
fileInput.addEventListener('change', async (event) => {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = async (e) => {
const fileContent = e.target.result;
const chunkSize = 15000;
const chunks = [];
// Split file content into chunks
for (let i = 0; i < fileContent.length; i += chunkSize) {
const chunk = fileContent.slice(i, i + chunkSize);
chunks.push(chunk);
}
// Submit each chunk to the conversation
for (let i = 0; i < chunks.length; i++) {
const chunk = chunks[i];
const part = i + 1;
const filename = file.name;
await submitConversation(chunk, part, filename);
}
};
reader.readAsText(file);
}
});
// Trigger file input click event
fileInput.click();
});
// Submit conversation function
async function submitConversation(text, part, filename) {
const textarea = document.querySelector("textarea[tabindex='0']");
const enterKeyEvent = new KeyboardEvent('keydown', {
bubbles: true,
cancelable: true,
keyCode: 13,
});
textarea.value = `Part ${part} of ${filename}:\n\n${text}`;
textarea.dispatchEvent(enterKeyEvent);
}
I gone through different background.js's that I found online, but none seemed to solve my issue. I'm very new to development, so I'm mostly lost on this sort of thing.
Expanding my comment here.
There can be two types of navigations
In the first case the extension will do its job and load the button but in the second case if the web app is loading the whole page with new elements then your "edited" element will get replaced with a new element.
You can solve this in two ways (AFAIK)
Listening to navigation Event
If the web app is changing the address in address bar then you can listen to that event and add the button if not already present. https://developer.mozilla.org/en-US/docs/Web/API/Navigation/navigate_event
navigation.addEventListener("navigate", (event) => {
checkAndInjectButton()
})
Mutation Observer
If for some reason you are not able to detect navigation change in web app then you can listen to the changes in DOM and react based on the event.
Mutation oberver keeps track of attributes, child node and sub tree so if any changes are made to the target element, your script will get a callback.
// Select the node that will be observed for mutations
const targetElement = document.querySelector("div.relative.flex.h-full.max-w-full.flex-1.overflow-hidden > div > main > div.absolute.bottom-0 > form > div > div:nth-child(1)");
// Options for the observer (which mutations to observe)
const config = { attributes: true, childList: true, subtree: true };
// Callback function to execute when mutations are observed
const callback = (mutationList, observer) => {
checkAndInjectButton();
};
// Create an observer instance linked to the callback function
const observer = new MutationObserver(callback);
// Start observing the target node for configured mutations
observer.observe(targetElement, config);
// If you need to stop observing
observer.disconnect();
Both the solutions will go to your content.js