On paper this is a tiny feature. Put the prompt in a URL, open a new tab, done.
In practice each chatbot needed its own handling.
The app is Cover Styles (https://covers.yuriystasyuk.com), a free library of book cover styles where each reference cover comes with a prompt. You pick a cover, hit ChatGPT or Gemini or Grok or Meta AI, and the idea is that you land in that chatbot with the prompt ready and the cover image available as a style reference. Getting text across was easy. Getting the image across is where it got weird.
The easy half: prompts in the URL
Three of the four take a query parameter, and one of them names it differently:
var AIS = [
{ k: "chatgpt", n: "ChatGPT", u: q => "https://chatgpt.com/?q=" + q + "&hints=image" },
{ k: "gemini", n: "Gemini", u: q => "https://gemini.google.com/app?q=" + q },
{ k: "grok", n: "Grok", u: q => "https://grok.com/?q=" + q },
{ k: "meta", n: "Meta AI", u: q => "https://www.meta.ai/?prompt=" + q }
];
(Trimmed from the real code, which also has colors and icons.)
Two gotchas showed up fast.
URLs get long. A style prompt plus a list of reference covers can blow past what you want in a query string, so the text is capped at 1,800 characters. The reference list goes at the end and gets trimmed one cover at a time until it fits in about 1,000 characters or only three are left. If the prompt itself is still too long, it gets cut at a word boundary and ends with an ellipsis.
Desktop Gemini is its own story. More on that below.
Don't send a prompt with holes in it
The prompts are templates with {TITLE} and {AUTHOR} in them. Before I added a check, nothing stopped you from sending "Title {TITLE} in yellow-gold display serif" straight to a chatbot, and a model can take that literally.
So there's a gate. Every send goes through a function that checks both fields first. If one is empty, it opens a little sheet asking for title and author, remembers the send you tried, and runs it once both are filled in. The comment in the code says it plainly: never send a prompt with placeholders left in it.
The hard half: images and the clipboard
None of these chatbots take an image through the URL. What you can do is put the image on the clipboard so the user just pastes it. That's where I spent most of the time.
Lesson one: navigator.clipboard.write() has to happen inside the click. Browsers only let you write to the clipboard during a user gesture. If you await a fetch first and then write, the gesture is gone and the write fails.
The way around it is that ClipboardItem accepts a Promise of a Blob, not only a Blob. So you call write() right away, synchronously, and hand it a promise that resolves later:
function writeImage(src, text) {
// must be called synchronously inside the click handler
if (!canClipImg()) return Promise.reject(new Error("no clipboard images"));
var data = BLOBS[src] || encodeImg(src), item = {};
item["image/png"] = data; // a ready Blob if pre-encoded, else Promise<Blob>
if (text != null) item["text/plain"] = new Blob([text], { type: "text/plain" });
return navigator.clipboard.write([new ClipboardItem(item)]);
}
Lesson two: Chrome only accepts PNG here. The covers are stored as WebP to keep the site light. Chrome only accepts image/png in a ClipboardItem, so each image is fetched from the same origin (so the canvas isn't tainted by CORS), drawn onto a canvas, and exported with toBlob(..., "image/png"). If that fetch fails, there's a fallback that loads it as a CORS-enabled <img>, which works because the site sends Access-Control-Allow-Origin: * on its assets. Encoded PNGs are cached per image so the second click is instant.
Lesson three: feature-detect, then fall back to text. If navigator.clipboard.write, ClipboardItem or a secure context is missing, the app skips the image and copies the prompt plus a link to the cover instead. A link to a reference image is worse than the image, but it beats a broken button.
So each chatbot got its own mode
Once I accepted that, the code got a small lookup table:
var AI_MODE = { chatgpt: "image", gemini: "text", grok: "image-first", meta: "text" };
if (!IS_MOBILE) AI_MODE.gemini = "image-steps";
- ChatGPT: copy the image, open the tab with the prompt in the URL. You paste the image and go.
- Grok: copy the image first while the current tab still has focus, then open Grok. There's a 2.5 second timer so the tab still opens even if the copy is slow.
- Meta AI: text mode. The prompt goes in the URL and onto the clipboard, and the text includes the cover link.
-
Gemini on desktop: this one became a two-step sheet. The code comment says it ignores
?q=and won't open image links, so the app copies the cover image, opens Gemini, and then shows a "Step 2 of 2" with a Copy prompt button for when you come back. Not pretty. It works.
Android had one more surprise
On Android, a link to Gemini or Meta AI may open their app instead of the browser, and the app may drop the query parameter. For those two, the app builds an intent:// URL that asks for Chrome specifically, with the normal URL as the fallback:
var intent = "intent://" + url.replace(/^https?:\/\//, "") +
"#Intent;scheme=https;package=com.android.chrome;S.browser_fallback_url=" +
encodeURIComponent(url) + ";end";
If you're building something similar
Plan for the clipboard from the first commit, and test the real click path, because the gesture rule bites the moment you add an await. Expect each chatbot's URL handling to change under you, and keep the per-service behavior in one small table so it's a one-line fix when it does. And always keep a plain-text fallback.
The whole thing is live and free at https://covers.yuriystasyuk.com if you want to see the button in action. Fair warning, it might send you down a romantasy foil rabbit hole.
Top comments (0)