We recently worked on building a Chrome extension to make copying ChatGPT responses to Google Docs super simple. Our goal was to allow users to copy everything—text, tables, images, and formatting—with a single click. The basic copying of text and tables worked fine using navigator.clipboard.write
. However, we ran into a problem when it came to handling images.
The Problem: Copying Images from ChatGPT to Google Docs
When copying content from ChatGPT, the images are included as external links. However, Google Docs doesn’t allow images to be embedded through these external links. To fix this, we needed to convert the image links into a base64 format, which embeds the image directly in the document.
We wrote a function to convert the image URLs to base64. Here's the code that helped us do this:
export function getBase64FromImageUrl(url: string) {
return new Promise((resolve, reject) => {
const img = new Image();
img.crossOrigin = "Anonymous";
img.onload = () => {
const canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext("2d");
if (!ctx) {
reject("Failed to get canvas context");
return;
}
ctx.drawImage(img, 0, 0);
const dataURL = canvas.toDataURL("image/png");
resolve(dataURL);
};
img.onerror = reject;
img.src = url;
});
}
const images = response.querySelectorAll("img");
await Promise.all(
Array.from(images).map(async (img: HTMLImageElement) => {
const base64 = (await getBase64FromImageUrl(img.src)) as string;
img.src = base64;
})
);
Using this code, we were able to convert the images to base64 before copying. However, we discovered a new problem: the image URLs expire after about 5 minutes.
The Problem with Expired Image URLs
The image links in ChatGPT responses expire after a short time (around 5 minutes). If the user tries to copy the image after the link has expired, the image won’t load, and we get an error.
Here’s an example of a typical image URL from ChatGPT:
https://files.oaiusercontent.com/file-4pGWvO8jQmZ19JcomJIxGhj8?se=2024-09-30T17%3A46%3A54Z&sp=r&sv=2024-08-04&sr=b&rscc=max-age%3D604800%2C%20immutable%2C%20private&rscd=attachment%3B%20filename%3D3981d032-167c-44e6-a334-122046a8ebd7.webp&sig=v7cWDoj0SaWK3DMxp%2BV37lw%2BtV2UdoyQmW4ZsZNe0W0%3D
As you can see, the URL contains a signature (sig
) and an expiry time (se
). Once this URL expires, we can’t access the image anymore, which causes an issue when trying to copy it to Google Docs.
The Solution: Caching Images Locally
To solve this, we decided to cache the images locally as soon as they load. We convert them to base64 immediately and store them in an IndexedDB database so that we don’t have to rely on the external URL when the user copies the content.
Here’s how we approached it:
- Cache Images on Load: When the images first load in ChatGPT, we immediately convert them to base64 and store them in IndexedDB.
- Periodic Caching: We set up a timer to periodically check for new images and cache them automatically.
- Retrieve Images on Copy: When the user clicks "copy," we retrieve the base64-encoded images from the cache and insert them into the document.
Here’s the key code for this:
export async function initImageCaching() {
await initImageDB();
startImageCachingTimer();
}
function initImageDB() {
return new Promise<void>((resolve, reject) => {
const request = indexedDB.open(DB_NAME, 1);
request.onerror = reject;
request.onsuccess = (event) => {
db = (event.target as IDBRequest).result;
resolve();
};
request.onupgradeneeded = (event) => {
const db = (event.target as IDBRequest).result;
db.createObjectStore(STORE_NAME, { keyPath: "url" });
};
});
}
function startImageCachingTimer() {
function checkForNewImages() {
const newImages = document.querySelectorAll(
agentTurnSelector + " img:not([data-cached])"
);
newImages.forEach((img) => {
cacheImageIfNeeded(img as HTMLImageElement);
});
}
setInterval(checkForNewImages, 1000); // Check every second
document
.querySelectorAll(agentTurnSelector + " img")
.forEach((img: Element) => cacheImageIfNeeded(img as HTMLImageElement));
}
async function cacheImageIfNeeded(img: HTMLImageElement) {
if (!img.dataset.caching && !img.dataset.cached) {
img.dataset.caching = "true";
try {
const cachedBase64 = await getCachedImage(img.src);
if (!cachedBase64) {
await cacheImage(img.src);
}
img.dataset.cached = "true";
} catch (error) {
console.error("Failed to cache image:", error);
} finally {
delete img.dataset.caching;
}
}
}
The Result: Smooth Copying Without Expired URLs
With this solution, we no longer have to worry about image URLs expiring. By caching the images in base64 as soon as they load, users can copy the content at any time, even if the original URL has expired. This makes the process of exporting ChatGPT responses to Google Docs smooth and hassle-free.
Conclusion
Handling image copying from ChatGPT to Google Docs was tricky due to expiring URLs. But by converting images to base64 and caching them in IndexedDB, we created a solution that works even after the URLs expire. Now, users can copy text, tables, and images from ChatGPT into Google Docs with ease, ensuring that the content is fully portable and reliable.
Try it for yourself with our extension, Convert ChatGPT to Google Doc.
About Boopesh Mahendran
Boopesh is one of the Co-Founders of CyberMind Works and the Head of Engineering. An alum of Madras Institute of Technology with a rich professional background, he has previously worked at Adobe and Amazon. His expertise drives the innovative solutions at CyberMind Works.