Software Engineering :: Web :: Browser :: Extension :: Development :: Conversation Title for ChatGPT
⪼ Made with 💜 by Polyglot.
- Conversation Titles for ChatGPT - Chrome Web Store (Chromewebstore.google.com)
- Users (Chrome.google.com)
-
chrome://extensions
document.querySelector('div.min-h-4 > div').textContent = document.title
document.querySelector('main [aria-haspopup="menu"]').parentElement.previousSibling.textContent = "Crixus: Rebel Gladiator Leader";
document.querySelector('title').textContent.trim()
document.querySelector('h3[class~=text-token-text-tertiary]')
h3.parentElement.parentElement.querySelector('li a div').textContent
document.querySelector('h3[class~=text-token-text-tertiary]').parentElement.parentElement.querySelector('li a div').textContent.trim()
- When the conversation is new, the
<Title />tag's content will contain the text"ChatGPT". - When the conversation is new, the title should be extracted from the sidebar.
- Otherwise, it should come from the
<Title />tag.
effectiveTitle
const effectiveTitle = () => {
const documentTitle = document.querySelector('title').textContent.trim();
const sidebarTitle = document.querySelector('nav[aria-label="Chat history"] ol li:first-child a div').textContent.trim()
return documentTitle === "ChatGPT" ? sidebarTitle : documentTitle;
}
document.querySelector('main [aria-haspopup="menu"]').parentElement.previousSibling.textContent = effectiveTitle();
HTML
<div class="flex items-center text-lg font-medium">Crixus: Rebel Gladiator Leader</div>
document.querySelector('#title > h1 > yt-formatted-string').addEventListener('click', (event) => navigator.clipboard.writeText(event.target.textContent).then(alert('title copied')).catch('unable to copy title'));
document.querySelector('#title > h1 > yt-formatted-string').addEventListener('click', e => { const r = document.createRange(), s = window.getSelection(); s.removeAllRanges(); r.selectNodeContents(e.target); s.addRange(r); });
// Function to be called when the text content of either selector changes
function handleMutation(mutationsList, observer) {
const updateTitle = () => {
const documentTitle = document.querySelector('title').textContent.trim();
const sidebarTitle = document.querySelector('nav[aria-label="Chat history"] ol li:first-child a div').textContent.trim();
document.querySelector('main [aria-haspopup="menu"]').parentElement.previousSibling.textContent = "ChatGPT" ? sidebarTitle : documentTitle;
}
for (const mutation of mutationsList) {
if (mutation.type === 'characterData' || mutation.type === 'childList') {
console.log(`Mutation detected: ${mutation.target.textContent}`);
updateTitle();
}
}
}
// Create a MutationObserver instance
const observer = new MutationObserver(handleMutation);
// Configuration options for the observer (observe changes to text content and child nodes)
const config = { characterData: true, subtree: true, childList: true };
// Function to observe a target element
function observeTarget(selector) {
const target = document.querySelector(selector);
if (target) {
observer.observe(target, config);
} else {
console.warn(`No element found for selector: ${selector}`);
}
}
// Selectors to observe
const selectors = [
'title',
'nav[aria-label="Chat history"] ol li:first-child a div'
];
// Observe each selector
selectors.forEach(observeTarget);
const updateTitle = () => {
let effectiveTitle = "";
const documentTitle = document.querySelector('title').textContent.trim();
const sidebarTitle = document.querySelector('nav[aria-label="Chat history"] ol li:first-child a div').textContent.trim();
if (documentTitle === "ChatGPT") {
effectiveTitle = "(Untitled)";
} else {
effectiveTitle = documentTitle;
}
console.log("Title Updated", effectiveTitle);
document.querySelector('main [aria-haspopup="menu"]').parentElement.previousSibling.textContent = effectiveTitle;
}
// Debounce function to delay the callback execution
function debounce(callback, delay) {
let timeoutId;
return (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
callback(...args);
}, delay);
};
}
// Function to be called when the text content of either selector changes
function handleMutation(mutationsList, observer) {
for (const mutation of mutationsList) {
if (mutation.type === 'characterData' || mutation.type === 'childList') {
console.log(`Mutation detected: ${mutation.target.textContent}`);
updateTitle();
}
}
}
// Debounced version of the handleMutation function
const debouncedHandleMutation = debounce(handleMutation, 300); // 300ms delay
// Create a MutationObserver instance with the debounced callback
const observer = new MutationObserver(debouncedHandleMutation);
// Configuration options for the observer (observe changes to text content and child nodes)
const config = { characterData: true, subtree: true, childList: true };
// Function to observe a target element
function observeTarget(selector) {
const target = document.querySelector(selector);
if (target) {
observer.observe(target, config);
} else {
console.warn(`No element found for selector: ${selector}`);
}
}
// Selectors to observe
const selectors = [
'title',
'nav[aria-label="Chat history"] ol li:first-child a div'
];
// Observe each selector
selectors.forEach(observeTarget);
const getSelectedSidebarItem = () => {
const SELECTOR = 'nav[aria-label="Chat history"] ol li a div[class~="from-token-sidebar-surface-secondary"]';
return document.querySelector(SELECTOR)
? document.querySelector(SELECTOR).parentElement
: null;
}
const isSidebarItemSelected = () => !!getSelectedSidebarItem();
const getDocumentTitle = () => document.querySelector('title').textContent.trim();
const getSidebarTitle = () => isSidebarItemSelected() ? getSelectedSidebarItem().textContent : "";
const hasConversationId = () => /(?<ConversationId>[a-fA-F0-9]{32})/.test(window.location.pathname.replace(/[-]/g, ""));
const conversationIdIsEmpty = () => !hasConversationId();
const documentTitleIsEmpty = () => getDocumentTitle() === "ChatGPT";
const hasDocumentTitle = () => !documentTitleIsEmpty();
const getTitleElement = () => document.querySelector('main [aria-haspopup="menu"]').parentElement.previousSibling;
const injectConversationTitle = () => {
if (conversationIdIsEmpty() && documentTitleIsEmpty()) {
getTitleElement().textContent = "Untitled Conversation (ChatGPT)";
}
if (hasConversationId() && hasDocumentTitle()) {
getTitleElement().textContent = getDocumentTitle();
}
if (hasConversationId() && isSidebarItemSelected()) {
getTitleElement().textContent = getSidebarTitle();
}
}
function executeWithExponentialBackoff(fn, minDelay, maxDelay, attempts = 1) {
const delay = Math.min(maxDelay, minDelay * Math.pow(2, attempts - 1));
setTimeout(() => {
fn();
if (delay < maxDelay) {
executeWithExponentialBackoff(fn, minDelay, maxDelay, attempts + 1);
} else {
executeWithExponentialBackoff(fn, minDelay, maxDelay);
}
}, delay * 1000);
}
injectConversationTitle();
// Start the execution with exponential backoff
setInterval(injectConversationTitle, 3000);
const getSelectedSidebarItem = () => {
const SELECTOR = 'nav[aria-label="Chat history"] ol li a div[class~="from-token-sidebar-surface-secondary"]';
const selectedElementAnchor = document.querySelector(SELECTOR);
const selectedElement = selectedElementAnchor && selectedElementAnchor.parentElement;
return selectedElement
? selectedElement
: null;
}
const isSidebarItemSelected = () => !!getSelectedSidebarItem();
const getDocumentTitle = () => document.querySelector('title').textContent.trim();
const getSidebarTitle = () => isSidebarItemSelected() ? getSelectedSidebarItem().textContent : "";
const hasConversationId = () => /(?<ConversationId>[a-fA-F0-9]{32})/.test(window.location.pathname.replace(/[-]/g, ""));
const conversationIdIsEmpty = () => !hasConversationId();
const documentTitleIsEmpty = () => getDocumentTitle() === "ChatGPT";
const hasDocumentTitle = () => !documentTitleIsEmpty();
const getTitleElement = () => document.querySelector('main [aria-haspopup="menu"]').parentElement.previousSibling;
const injectConversationTitle = () => {
if (conversationIdIsEmpty() && documentTitleIsEmpty() && getTitleElement()) {
getTitleElement().textContent = "Untitled Conversation (ChatGPT)";
}
if (hasConversationId() && hasDocumentTitle() && getTitleElement()) {
getTitleElement().textContent = getDocumentTitle();
}
if (hasConversationId() && isSidebarItemSelected() && getTitleElement()) {
getTitleElement().textContent = getSidebarTitle();
}
getTitleElement() && getTitleElement().addEventListener('click', (event) => {
const range = document.createRange();
const selection = window.getSelection();
selection.removeAllRanges();
range.selectNodeContents(event.target);
selection.addRange(range);
});
}
const monitorSidebarForChanges = () => {
// Select the node that will be observed for mutations
const targetNode = document.querySelector('nav[aria-label="Chat history"] ol');
// Options for the observer (which mutations to observe)
const config = { characterData: true, subtree: true, childList: true, attributes: true };
// Callback function to execute when mutations are observed
const callback = (mutationList, observer) => {
for (const mutation of mutationList) setTimeout(injectConversationTitle, 600);
};
// Create an observer instance linked to the callback function
const observer = new MutationObserver(callback);
// Start observing the target node for configured mutations
observer.observe(targetNode, config);
}
monitorSidebarForChanges();
injectConversationTitle();
setInterval(injectConversationTitle, 2000);
(function() {
// Function to log the current document title
function logTitleChange() {
console.log('Title changed:', document.title);
}
// MutationObserver to detect changes in the document title
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
logTitleChange();
}
});
});
// Configuration of the observer
const config = { childList: true };
// Observe changes to the document title element
const titleElement = document.querySelector('title');
if (titleElement) {
observer.observe(titleElement, config);
} else {
console.warn('No title element found');
}
// Initial log of the current document title
logTitleChange();
})();
(function() {
let idempotency = '';
// Function to log the current document title
function logTitleChange() {
const conversationId = window.location.pathname.split(/[/]/).pop()
if (conversationId === idempotency) return
idempotency = conversationId
console.log('Title changed:', conversationId);
}
// MutationObserver to detect changes in the document title
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
logTitleChange();
}
});
});
// Configuration of the observer
const config = { childList: true };
// Observe changes to the document title element
const titleElement = document.querySelector('title');
if (titleElement) {
observer.observe(titleElement, config);
} else {
console.warn('No title element found');
}
// Initial log of the current document title
logTitleChange();
})();