Building with AILesson 47 of 80

Contents

Building with AI

Lesson 47 of 80. Sign in to keep your place.

Module 5 · Conversation, memory and the interface

The chat interface: the controls that are load-bearing

9 min read

The box is the easy part

A text area, a send button, a list of bubbles. Any framework gives you that in an afternoon, and the free path is no framework at all: a textarea, a fetch that reads a stream, and a markdown renderer. What separates a chat interface people trust from one they abandon is a set of controls, each of which corresponds to something true about the model underneath. This lesson is that list.

Stop, and what it must actually do

Streaming, from the first module, means the user sees tokens as they arrive. A stop button must do two things. In the browser it aborts the fetch, so the display freezes where it is and the partial text is kept: the user stopped because they had read enough, not because they wanted it discarded. On the server it must cancel the upstream request to the provider. If it does not, generation continues to completion and you pay for every token nobody will read; with long outputs that is most of the cost of the request. Propagate the abort signal all the way through.

Regenerate and edit are branches

Regenerate sends the same prompt again for a new sample. Editing an earlier user message means everything after it is discarded and the conversation continues from the edit. Both imply that the history is a tree, not a list, and that the previous branch should remain reachable. The parent column in the messages table from the first lesson of this block is what makes that possible; add it at the start, because converting a list to a tree with live data is an unpleasant migration.

Show the sources

For anything built on retrieval, render citations as links, each with a preview of the chunk it points at, and verify every id against the retrieved set before rendering, as the permissions lesson required. Few users click sources (a small fraction on most products) but the presence of a link that could be clicked changes how the answer is read. An answer with no source is a claim; an answer with a source is a claim you can check.

Show what the model sees

When a file is attached, show that it is in context, and when it has dropped out, show that. When history has been compacted, mark the point. When a tool runs, say so in the transcript (“Searched orders for #48213”) with the arguments visible. Hidden tool calls are the reason a wrong answer becomes an inexplicable one: the user cannot see that the search was for the wrong order number, so the product seems wrong. Visibility is also the first line of defence against the tool being used in a way the user did not intend.

Render model output as untrusted

Model output is text you did not write, and it can contain anything a prompt injection or a bad sample puts there. Render markdown through a sanitiser: marked or react-markdown with raw HTML disabled, and DOMPurify or an equivalent on the result. Do not auto-load remote images from model output: an image tag pointing at an attacker’s server with the conversation in the query string is a data-exfiltration channel the safety module will return to. Show links with their real domain visible, and open them in a new tab. This is standard web hygiene, applied to a source that is less trustworthy than a user.

Latency states

Time to first token should be under a second; the streaming lesson explained why that is the number that matters. While waiting, show a state that says what is happening, and when a tool call takes long, say which tool. On a timeout, offer a retry, and never lose the text the user typed: a cleared input after an error is a small thing that makes people leave.

Errors as messages

A rate limit, an overloaded provider or a failed tool should appear in the transcript as a plain message with a retry, not as a toast that vanishes. The conversation is the place the user is looking.

Say it is AI, on the surface

Every chat surface should say that the assistant is an AI, near the input, in words rather than an icon. This site’s own rule for its tutors is the standard: the disclosure is on the surface the person is using, not in a policy page. Add the one honest limitation (that it can be wrong, and that important details should be checked) where it will be read. Several jurisdictions are moving towards requiring this disclosure; the safety module covers what is known and what is unsettled.

Keyboard, screen readers and phones

Enter sends and Shift+Enter inserts a newline, and the reverse on a phone, where Enter is the only way to make a line break. Streamed text should be announced to a screen reader without reading every token as it arrives: an aria-live="polite" region updated at sentence boundaries, or an announcement when the message completes. On a phone the keyboard covers half the screen; the input and the newest message must both stay visible, and every control must be a comfortable tap. Test on a real phone, because most of this readership is on one.

Every lesson here is open. Your progress is recorded once you pass the Python, from Zero, for AI exam.

Check your understanding

A team’s stop button aborts the browser’s fetch but does nothing server-side. What is the consequence?

Pick the one you would defend. Your answer stays private.

Next lesson · 9 minVoice in and out: transcription, speech and the latency budget

Stuck on something?Ask Sayali, your AI tutor

How to cite: Addaly (2026) ‘The chat interface: the controls that are load-bearing’, in Building with AI. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/building-with-ai/the-chat-interface-honestly (Accessed: 13 October 2026).