Skip to content

Web chat

Read by the page every time it opens, from the same folder as index.html, bypassing the browser cache. In the container the path is /usr/share/nginx/html/config.json.

Field Type Default Meaning
backendUrl string required Backend address as the browser sees it. Absolute http:// or https:// URL with no user name, password, query or fragment. A path prefix is allowed.
language "it" or "en" the first supported language of the browser, otherwise "en" Language of the interface.

Any other field, an unsupported language or an invalid backendUrl makes the file invalid. From backendUrl the page derives api/v1/conversations for the list and history, and api/session for the conversation session, with ws:// for http:// and wss:// for https://.

When the file is missing or invalid, the page shows “Invalid web client configuration” in the browser’s language (see Interface texts), followed by a technical reason, and contacts nothing else:

Reason Meaning
config.json: HTTP <status> The file could not be served, for example 404 when it is not mounted.
config.json: cannot be read The request failed.
config.json: invalid JSON The file is not valid JSON.
config.json: expected a JSON object The file is valid JSON but not an object.
backendUrl: required string backendUrl is missing or not a string.
backendUrl: absolute http(s) URL without credentials, query or fragment backendUrl is not an acceptable address.
language: unsupported value "<value>" language is neither it nor en.
unknown field "<name>" The file has a field other than backendUrl and language.

The page’s origin must be listed in the backend’s AMBROGIO_ALLOWED_ORIGINS, otherwise the backend refuses the conversation session with 403 and sends no CORS headers for the list and history. See Backend configuration.

Item Value
Image ghcr.io/ambrogio-app/ambrogio-web:<tag>, for linux/amd64 and linux/arm64, without a latest tag. See Versions and compatibility.
Port 8080, plain HTTP
User Unprivileged (nginx-unprivileged)
Configuration config.json, mounted read-only at /usr/share/nginx/html/config.json. Not included in the image.

Every response carries these headers:

Header Value
X-Content-Type-Options nosniff
Referrer-Policy no-referrer
Content-Security-Policy default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; connect-src 'self' http: https: ws: wss:; frame-ancestors 'none'; base-uri 'none'; form-action 'none'; object-src 'none'
Cache-Control no-store for index.html, config.json and everything outside assets/; public, max-age=31536000, immutable under assets/

The Server header does not show the nginx version. HTTPS and HSTS are up to your reverse proxy. The page uses relative paths, so a proxy can serve it under a path prefix.

Key Where Action
Enter Message field Send the message.
Shift+Enter Message field New line.
Tab, Shift+Tab Anywhere Move between commands, chats and the message field.
Enter, Space A button Activate it.
Esc Conversations panel on narrow screens Close the panel and return to the button that opened it.
Situation What the page does
Opening Selects a local New chat as the first entry. Nothing is created on the backend until you send the first message.
First message sent Opens the WebSocket session. One session serves every chat. The entry takes the first message as its name.
Message empty, only spaces, or larger than 64 KiB Not sent; the page shows “Write a non-empty message.” or “Message too large (64 KiB at most).”
No session.ready within 10 seconds, or the session cannot open The message ends with “Cannot connect to the backend.”
Answer arriving Shows the text as it streams in. When the turn ends, the page reloads the conversation from the backend.
turn.completed The turn is done and saved.
turn.failed or turn.rejected Replaces the partial text with the backend’s message.
turn.interrupted, or the session drops before an outcome Shows “Response interrupted.”
session.error for the message Shows “Protocol error.”
A turn still being answered when you open a chat Shows “In progress. Refresh later to see the outcome.” Press Refresh later.
While a turn is being answered You can open other chats, start a new one, refresh and send in other chats. Only the chat that is answering cannot send until the turn ends. Coming back to it shows the answer received so far.
Text typed and not sent Stays in its own chat: each chat has its own message field.
Refresh Reloads the first page of the list and the open chat.
More conversations Loads the next page. Shown only when there is one.
Chat list Each chat is named after its first message, up to 60 characters with “…”. If its history cannot be read, it shows the first 8 characters of its ID.
Screen narrower than 720 pixels The list becomes a panel opened by the Conversations button; choosing a chat closes it.
Colours Follow the light or dark setting of the device.

The page ignores expectsReply: the message field is available again when a turn ends, whether or not the answer asks a question.

Ambrogio’s answers are shown as Markdown, including tables and task lists. HTML inside an answer is shown as text. Only http:, https: and mailto: links are clickable, and they open in a new tab. Images are never loaded: their alternative text is shown instead, as a link when the image address is http: or https:. Your own messages and error messages are shown as plain text.

The open conversation appears in the address as #/c/<conversationId>; New chat has no fragment. You can reload the page, bookmark a conversation or use the browser’s back button. A fragment of another form is ignored. If the conversation does not exist, the page shows “Conversation not found.” and New chat stays available.

At first use the browser creates the identifier web-<random UUID> and keeps it in local storage under ambrogio.web.clientId. The name sent to the backend is Web Firefox, Web Chrome, Web Edge, Web Safari or Web browser. Clearing the site data, or a browser that blocks local storage, gives a new identifier. The identifier only labels the connection: everyone sees the same conversations.

The chat list is a navigation region and the open chat the main region. Every command has an accessible name and works from the keyboard, the focused element is outlined, and each message is labelled “You” or “Ambrogio”. The current chat is marked as current. When a turn ends, screen readers announce the answer, the error or “Response interrupted.”, but not each fragment.

Key it en
New chat Nuova chat New chat
Refresh Aggiorna Refresh
More conversations Altre conversazioni More conversations
Conversations panel Conversazioni Conversations
Send Invia Send
Message field Scrivi un messaggio Write a message
Your messages Tu You
Answers Ambrogio Ambrogio
Turn in progress In elaborazione. Aggiorna più tardi per vedere l’esito. In progress. Refresh later to see the outcome.
Interrupted Risposta interrotta. Response interrupted.
Empty message Scrivi un messaggio non vuoto. Write a non-empty message.
Message too large Messaggio troppo grande (massimo 64 KiB). Message too large (64 KiB at most).
Backend unreachable Impossibile collegarsi al backend. Cannot connect to the backend.
Protocol error Errore del protocollo. Protocol error.
Conversation not found Conversazione non trovata. Conversation not found.
Backend HTTP error Il backend ha risposto con un errore (HTTP <n>). The backend answered with an error (HTTP <n>).
Invalid configuration Configurazione del client web non valida Invalid web client configuration

Messages produced by the backend, such as refusals and turn errors, appear as the backend sends them, in the language set by its AMBROGIO_LANGUAGE. An error of the list or of a history read is shown with the texts above, “Conversation not found.” when the conversation does not exist, the HTTP error text for any other error.

The client uses the ambrogio.v1 protocol and HTTP API v1 of the backend, and works with every backend release that offers them. See Versions and compatibility.