Build Journal
Wynner AIC++ · Qt · llama.cpp

Wynner AI: from a console chatbot to a clean desktop app

Wynner runs the Gemma 3 12B model on my own PC, so conversations never leave it. This entry covers how the desktop app talks to the model, the bug that made it show placeholder text instead of answers, and what I learned from redesigning it from a mockup.

A real answer from the local model, captured on 8 November 2026.

Where it started: a console engine

The first working version of Wynner was a C++ console program built with CMake on top of llama.cpp. It loads Gemma 3 12B Instruct (a 7.3 GB Q4_K_M file) with a 4,096-token context and 22 layers offloaded to the GPU. A system prompt tells the model that it is Wynner, a local assistant. When it’s loaded, the program prints Wynner is ready! and waits for you to type after a You: prompt.

It worked, but only in a terminal window. I wanted something that felt like a real product.

Putting a desktop app on top

Rather than rewrite a working engine, I kept it and built a desktop app around it. The interface is Qt Quick (QML). A small C++ class, WynnerBackend, starts the engine as a child process with QProcess, writes each message to its standard input and reads the answer from its standard output.

The trade-off is that the app has to understand text that was written for a human in a terminal. That turned out to be exactly where things broke.

Bug 1: answers that never appeared

In the first version of the app, answers only showed up in the debug console. The send button seemed to do nothing. The fix was to add a proper chat model in QML, so each message and answer is added to a list the chat view draws.

Bug 2: “[thinking...]” instead of an answer

Next, the chat showed [thinking...] in place of real answers. The cause was in how the engine draws its output. It first prints Wynner: [thinking...]. When the answer is ready, it goes back to the start of the line with a carriage return (\r) and prints the answer over it.

In a terminal, you only ever see the final line. A program reading the output sees every byte, so it gets both versions. The app was taking the first Wynner: it found, which was the placeholder.

The fix is to wait for the next You: prompt and take the last Wynner: before it:

int nextPrompt = m_buffer.indexOf("\nYou:", firstReply);
int replyStart = m_buffer.lastIndexOf("Wynner:", nextPrompt) + 7;
QString reply  = m_buffer.mid(replyStart, nextPrompt - replyStart);
reply.remove("[thinking...]");
reply.remove('\r');

Lesson: a terminal shows you the final state, but a pipe gives you everything that was written. When one program reads another’s output, parse it for what is actually sent, not for what you see on screen.

Redesigning from a mockup

The app started out black and white. I made a mockup of what I wanted: deep navy, bronze gold and a gear-and-bridge emblem. The first pass got the colours but still didn’t look like my picture. The second pass added the details that made the difference:

  • the emblem, drawn by a script as a high-resolution image so it stays sharp;
  • window buttons in the sidebar;
  • a deeper navy;
  • a soft glow behind the logo.

Lesson: a concrete picture makes feedback specific. “It doesn’t look like the mockup” is easy to act on when the mockup exists.

Testing it with real messages

On 8 November 2026 I tested the app with real questions:

  • Asked who it was, it answered: “I’m Wynner, a local AI assistant created by Wynn.”
  • Asked to explain a load-bearing wall in two sentences, it gave the answer in the screenshot above. That first answer after launch took between 75 and 135 seconds, because the model has to load into memory first.

One small Windows lesson came from rebuilding: if the app is still running, the linker can’t replace the .exe and fails with error LNK1168. You have to close the app before you rebuild.

What’s next

  • The sidebar pages: AI Assistant, AI Models, Documents, Data Insights, Tools and Settings. They are visual only today.
  • Attachments and code tools in the chat box.
  • A packaged installer.
  • A screen-recorded video walkthrough for the case study.

How this was built: Wynner is built with AI coding assistants. I set the direction, designed the look and checked the results. The redesign and the parsing fix described here were made with Claude Code.