helban.dev

An AI chat as a blog post: 133 pictures, each under its own paragraph

A long chat with a model is hundreds of messages and hundreds of pictures. Paste it into a blog editor and the formatting goes, while the pictures stay linked to somebody else's server and quietly stop working months later. This extension turns that chat into a finished post in one click: every picture is uploaded to the author's own blog as a permanent link and dropped under the paragraph it belongs to. Below: where the tool gets the file names from, how it picks the paragraph, how often it lands, and what it writes down when it does not.

Stack: Chrome MV3 · JavaScript · Blogger API Scope: extension, two contracts Delivered: August 2026
133/133
pictures uploaded to the blog as permanent links
132/133
file names recovered from the conversation
46/56
placed within one paragraph of the target
The brief

Pasting the chat destroys the part worth keeping

A conversation with a model has structure: bold, links, headings, nested lists, embedded video. The clipboard carries some of that across and flattens the rest into one gray block. Pictures come off worse than the text. They paste as addresses pointing back at the server they came from, so the post looks right on publication day and rots afterward, on somebody else's schedule.

The work came in two contracts. The first one was the faithful export: keep the formatting, and put every picture into the author's own blog storage so that the post owns its images. The second was the harder half. Getting the pictures into the post is not the same as getting them into the right place in it.

The core of it

The file name is not on the page

Somebody who drops twenty pictures into a chat names them first: a number and a few words. The number says which order they belong in, the words say what each one is about. That is a complete instruction for where every picture should sit in the finished post, written by the only person who knows the answer.

Symptom

Once the message is sent, that instruction is gone. The page shows the picture and nothing else: no name, no number. An extension that reads what is on the screen has nothing to work with, so it can only stack the pictures at the top of the message and leave the arranging to a human. That is what the first version did, and on a message with twenty pictures it is an afternoon of dragging.

What is really going on

The names do exist, just not where anyone would look for them. They arrive once, in the response that loads the conversation, paired with the identifier of each upload. They are not in the markup, they are not in the message, and they are not there at the moment a picture is sent.

The way around it

The extension listens for that response and binds each name to the upload identifier rather than to where the picture sits on screen. Position moves when the page re-renders. The identifier does not. It is the rule that saves every integration built against somebody else's service: pin yourself to the record, not to the place.

One consequence of that cannot be engineered away, so the tool warns about it instead. A picture uploaded after the tab was last loaded is not in that data yet and has no name to read, so it stays at the top of its message. The popup says so in plain words after every export, and tells you to reload the tab and export again.

The method

The words choose the paragraph, the number sets the order

Those two halves of a file name answer two different questions, so the tool uses them for two different things. For every pairing of a picture with a paragraph it counts how much of the name shows up in that paragraph, which gives a score. The leading number gives an order.

Then it places all the pictures of a message at once, maximizing the total score while keeping them in the order the numbers ask for. Choosing the best paragraph for each picture separately would be simpler and wrong: two pictures would swap past each other and the reader would meet the third one before the second. A picture is never allowed to land above the one before it, and that single constraint is what makes the result readable rather than merely well-matched.

A message with a single paragraph has nothing to choose between, so there the number decides alone. A message with pictures and no writing at all is still a message, which the first version did not notice.

The measurement

How often it lands

The acceptance set is 56 pictures out of a real conversation, each one with a target paragraph marked by hand. Exact hits: 31 of 56. Within one paragraph of the target: 46 of 56.

Nobody can sell that as it works. What it says is how much work is left: instead of arranging a hundred and thirty pictures, you nudge a dozen of them by one paragraph. I would rather publish that ratio than a rounder one, because the person reading this is deciding whether to hand me something similar.

On the finished post the export ran over 78 messages carrying 133 pictures. All 133 went into the blog's own storage as permanent links, and names were recovered for 132 of them. Two YouTube links and one Vimeo link became players. I measured the Vimeo wrapper on a real Blogger post before shipping it, because a bare paste of one renders at a quarter of the size.

The reporting

The tool explains every decision it made

A counter tells you that something went wrong. It never tells you why, and on somebody else's data, which I cannot see, why is the only thing worth having. So the last version writes a file: one row per message saying how it recognized the speaker, and one row per picture saying which name it read, which number it took from it, which paragraph it chose and what made it choose that one.

It paid for itself on the first report that came back. One picture kept landing in the wrong place despite a name that looked perfect, and the report settled it without a single guess. The words in that name matched the target paragraph completely, a full score, so the naming had done its job. What moved the picture was the order: two files in that message began with the same number, and equal numbers carry no ordering information, so the tie fell to whichever was uploaded first. Since a picture may never land above the one before it, once the other file took the paragraph above, this one could no longer reach the paragraph its words had won.

The answer was one paragraph of writing, not a new build: give every picture its own number, running in the order they should be read. Sixteen of the placements in that post had the words pointing one way and the order rule holding the picture elsewhere, four of them on a full-score match, and every one of those is now explainable in a sentence instead of argued about by feel.

The limit

What this extension does not do

There is no public repository and there will not be one. The commit history names the person who commissioned it, and the tool itself walks through undocumented internals of services that never promised to keep them still. Publishing it would be an invitation to break it. There is public code of mine in the same class linked below. You can read one of my Manifest V3 extensions before deciding anything.

One thing here is unexplained and stays unexplained. On a single export, five messages came out attributed to the wrong speaker. It has not happened again, in any run since, and I cannot tell you what caused it, so the word fixed does not appear anywhere on this page. What the last version does is record the evidence it used to identify the speaker of every message, so the next occurrence will name its own cause instead of hiding.

Worth knowing before you name your files

Asking the model to rename a file changes nothing the tool reads. Verified on live data: after such a request the extension still reads the original name, because it is still the same upload record. The name is set on your own disk, before the picture is sent, and never afterward.

Something to move out of a chat?

Tell me what it is now and what it has to become, and whether the pictures have to end up on your side. If an export that already exists will do the job, I will say so instead of quoting you for an extension.

Ask about your case