Over the last few weeks, I’ve been building an photo product.
At first, I thought the hard part would be the image model.
Which model gives the best quality?
How much does each generation cost?
How do I handle edits versus generation?
How do I keep latency acceptable?
Those questions matter.
But after building more of the product, I realized something else:
the image model is only half the product.
The other half is everything around it.
My first instinct was pretty standard.
User enters a prompt.
The backend sends it to an image model.
The UI shows a loading state.
The result comes back.
Technically, it worked.
But it did not feel like an “AI agent.”
It felt like a form connected to an API.
That difference is bigger than I expected.
Users now have a strong mental model for conversational AI products.
They expect:
Once you notice these expectations, a lot of small UX problems suddenly become obvious.
One thing I underestimated was how damaging bad loading behavior can be.
If the user sends a message and the interface briefly shows:
“Agent is thinking…”
then removes it,
then shows something else,
then disappears again,
then finally shows the result,
the product feels unstable.
Even if the final image is good.
The user has no idea what is happening.
I ended up caring much more about continuity.
The ideal experience is closer to:
This sounds trivial compared with model selection.
It is not.
For slow AI operations, UX becomes part of perceived intelligence.
I ran into a similar problem with image uploads.
Originally, uploading felt like a separate system action.
You selected a file, then a small status message appeared somewhere else in the UI.
That broke the conversation.
The better mental model was:
the uploaded image is a message.
Once I started thinking about it that way, the UI decisions became easier.
The upload should appear directly in the conversation.
The user should see which image is currently active.
The system should make it obvious what the next edit applies to.
That is especially important when users start generating multiple versions.
A simple AI image tool can get away with showing only the latest result.
A conversational one cannot.
Imagine this sequence:
“Make the background white.”
“Try a darker background.”
“Actually, go back to the first version.”
Now the application needs to understand what “first version” means.
Or:
“Keep the product from this version but use the lighting from the previous one.”
That is where the problem becomes less about generation and more about state.
You need some representation of:
That made me rethink the database earlier than I expected.
I had assumed most of the complexity would live in prompt engineering.
A lot of it ended up living in application state.
Another thing I noticed while building is how loosely the word “agent” gets used.
A normal prompt box with an AI API behind it can easily be labeled an agent.
But from a product perspective, I think users only really feel the difference when the system maintains context and helps carry work forward.
For an image product, that means things like:
Without those things, the “agent” label does not add much.
One unexpected lesson was that a strong workflow can make an imperfect model much more useful.
A single generation may not be perfect.
But if the user can easily say:
“Keep everything else, but fix the lighting.”
or:
“Try another version.”
or:
“Make the background simpler.”
then the product does not need every first result to be perfect.
Iteration becomes part of the value.
That changed how I thought about model quality.
The important question is not just:
“How often does the model get it right on the first try?”
It is also:
“How quickly can the user recover when it does not?”
That is a much more product-oriented metric.
Image generation is much more expensive than chat.
That creates another product challenge.
If every action costs real money, the pricing system affects user behavior immediately.
I found it useful to separate cheap conversational interactions from expensive generation actions.
The user should be able to clarify what they want without feeling like every sentence consumes paid credits.
Otherwise, people become hesitant to interact.
That is bad for a conversational product.
If the product wants users to iterate, the pricing model should not punish iteration unnecessarily.
I spent a lot of time thinking about landing page design.
Hero layout.
Colors.
CTA buttons.
Showcase images.
All of that matters.
But I increasingly think the real activation event is much simpler:
the first time a user uploads or generates an image, asks for a change, and gets a result that feels useful.
That moment communicates the product better than almost any marketing copy.
So I’ve started thinking more about reducing friction between signup and that first useful edit.
The homepage should probably do less explaining and more moving people toward that moment.
The obvious retention strategy for an AI image tool is “make the outputs better.”
But there may be another layer.
If users build up:
then the product starts accumulating value for them.
That can matter more than a slightly better model elsewhere.
A competitor can copy features.
It is harder to copy a user’s accumulated working context.
That feels like a more interesting long-term product moat.
The project I’m building is called PhotoAgent.
The current direction is centered around conversational image generation and editing, but I’m still figuring out which parts users actually care about most.
The biggest lesson so far is that building an AI product is not mainly about connecting a model.
It is about designing the layer between the model and the user.
That layer includes:
The model gets most of the attention.
But the surrounding product experience is where a lot of the real differentiation seems to happen.
Curious if others building AI products have found the same thing.
What part ended up being harder than you expected: the model layer, or everything around it?
The pending-task state seems like a good place for an explicit job ID plus an optimistic UI state machine (queued → generating → succeeded/failed/canceled). If a user starts a second edit before the first finishes, attaching the result to the job’s parent-image ID—and ignoring late results for a non-current branch—can prevent an older generation from silently replacing the image they’re viewing. Retries also become safer if the generation request is idempotent.
The loading-state point generalizes further than you might think. For anything slow and generative, perceived speed is mostly about when the first byte of output appears, not total time. Streaming the first chunk while the rest is still generating turns a multi-second wait into something that feels immediate — same total latency, completely different product. The corollary to "UX becomes part of perceived intelligence" is that a stable, honest progress state beats a fast-but-jumpy one. Did you try streaming partial/low-res previews, or is the image model all-or-nothing on output?
Thanks for sharing the numbers, that makes it much easier to follow.
Great breakdown. What feedback have you had from early users?
Clear and practical, thanks. Did anything surprise you along the way?
This is useful. How are you finding your first users so far?
Nice work shipping it. What has been the biggest challenge since launch?
The cheap chat / expensive generation split feels important. If every clarification burns a credit, users will start writing one giant prompt and stop using the conversation the way you designed it. I’d watch whether people who can ask a few cheap questions reach that first useful edit more often, even if their first image is not perfect.
Solid lesson. Which channel has worked best for you so far?
Great breakdown. What feedback have you had from early users?
Solid lesson. Which channel has worked best for you so far?
"The uploaded image is a message" is exactly the right model, and the loading-state point matches what we saw.
Disclosure: I build Mythex, an AI app builder with a chat interface, so we've made most of these mistakes ourselves.
Two you're likely to hit next:
Keep the stream alive when the tab is hidden. Browsers throttle background tabs, and a chat that quietly stalls while the user is elsewhere reads as broken when they come back. We ended up handling visibility changes explicitly instead of hoping the connection survived.
Image size in context. Once users iterate ("go back to the first version"), every model call can end up re-sending every image in the conversation at full size. One turn with six 2.4 MB scans pushed our agent past 950 MB of memory. Converting anything over ~400 KB to a JPEG of at most 1568 px, once per file, took it from ~20 MB to ~4.4 MB per call.
Your version-history point is the one that decides whether the product feels smart. How are you representing "the first version": by position, or by an explicit id the model can reference?
The version-history point is where “AI feature” turns into a real workflow. I’d model every output as a node with explicit parent and applied change, then make the current selection visible in the conversation itself; that avoids asking the model to infer state from a long transcript. It also gives support a concrete trail when a user says the result changed unexpectedly.
Clear and practical, thanks. Did anything surprise you along the way?
Curious how long it took before you saw the first real results?
Nice progress. What is the next thing you are focusing on?
Interesting take. Would you still recommend this approach to someone starting today?
Good point. Did you test that with users before committing to it?
Thanks for writing this up. Bookmarking it for later.
'It felt like a form connected to an API' is a great way to put it. The experience around the model is where users decide whether it feels like a real product.
Ran almost this experiment yesterday. Same photo, same model, three prompts: one with no description, one with a single typed line from the user, one with a rule telling the model to be more observational. The typed line won easily and it wasn't close. With nothing typed I got "high-contrast black and white portrait of an older man in a flat cap". With one line from the user I got "half a second of eye contact before he looked away". No prompt engineering closed that gap, because the missing thing was something only the person holding the camera knew.
Clear and practical, thanks. Did anything surprise you along the way?
Clear and practical, thanks. Did anything surprise you along the way?
Clear and practical, thanks. Did anything surprise you along the way?
As you test PhotoAgent, what user behavior would confirm that the first useful edit is the real activation point rather than just a better model or smoother workflow?
This resonates a lot — how long did it take before you saw any real signal on it?