Skip to content

Architecture ​

You do not need this page to use HelpChatAI. Read it if you want to extend it, host the engine yourself, or understand why a chat panel talks to its own application over HTTP.

Three ideas ​

1. The engine knows nothing about the UI or the web. Providers, index, scoring, the conversation store and the Markdown conversion are plain Pascal with no Vcl.*, FMX.*, Indy or WebView unit in sight. That is why the same engine serves a VCL application, an FMX one, a console program and a server.

2. Nothing waits on the model. Send records the question, creates a pending assistant message and returns immediately; a worker pool fills it in. A remote model call lasts seconds and never runs on the UI thread. Shutdown waits with a timeout and detaches a stuck worker rather than freezing the application — a detached worker can never touch a freed store.

3. Streaming needs no push channel. Tokens are appended to the pending message in the store; whoever reads the store — the polling page, a UI event — sees the partial grow. No WebSocket, no server-sent events towards the page, nothing to keep alive.

The units ​

UnitResponsibility
HelpChatAI.TypesMessage and context records, cancellation token, retriever contract
HelpChatAI.MessagesLocalized strings (English, Italian), chosen at runtime
HelpChatAI.SettingsSettings, defaults in one place, JSON load/save
HelpChatAI.StoreThread-safe conversation store, pruning, persistence
HelpChatAI.RunnerWorker pool, cancellation, bounded and memory-safe shutdown
HelpChatAI.ProviderIHelpChatProvider, registry by name, stub
HelpChatAI.Provider.DocSearchDeterministic answers from the index
HelpChatAI.Provider.ClaudeAnthropic Messages API, SSE streaming, grounding, tool use
HelpChatAI.DocIndexThe index and the keyword retriever (stemming, idf, excerpts)
HelpChatAI.IndexBuilderMarkdown folder → index JSON
HelpChatAI.ActionsRegistry of actions, schema for the model, the queue to the host thread
HelpChatAI.HtmlSafe Markdown → HTML, URL sanitizing, autolink, bubble fragments
HelpChatAI.EngineThe facade: send, read, cancel, clear, render, persist
HelpChatAI.TransportIHelpChatTransport: what the UI asks for the panel URL
HelpChatAI.PanelThe panel page and the API contract
HelpChatAI.HelpViewerThe chat as a System.HelpIntfs viewer (shared by VCL and FMX)
HelpChatAI.ServerThe HTTP layer: sessions, routing, endpoints; the local server
HelpChatAI.Server.RemoteThe multi-session server
HelpChatAI.Server.HostChannelThe loopback channel a remote page reaches its host through
HelpChatAI.Transport.RemoteThe client of a HelpChatAI server
Vcl.HelpChatAI*, FMX.HelpChatAI*The components, the chat control, the VCL style theming

How the panel talks to the engine ​

EndpointWhat it is for
GET /api/historyThe conversation as HTML fragments
POST /api/sendUser bubble + pending assistant bubble
GET /api/poll/{id}That bubble, still pending or complete
POST /api/cancel/{id}Stop a reply
POST /api/clearStart again
POST /api/contextThe screen the user is on (from the native side)
POST /api/themeThe colours of the host
POST /api/assistantThe name of the assistant and its icon, for the header
POST /api/ui/{command}What the page asks of its host (close)

Every /api/* call carries X-HelpChatAI-Token: <secret>, generated per process; only the panel page knows it, because it receives it in its URL. The server answers HTML fragments, never JSON: htmx swaps them into the page, which is why the page has almost no JavaScript.

The screen context never travels through the page: the component computes it natively and hands it to the transport. A page cannot forge it and cannot lose it.

Why the page calls its host over HTTP ​

The page must sometimes ask the application for something — today "close me". WebView2 has a message bridge, but FireMonkey exposes none, and a page served by a remote server has no native side at all. One mechanism works everywhere: an HTTP call to somebody who is listening.

  • Local engine: that is the same server, and /api/ui/close reaches the component.
  • Remote engine: the client opens a small listener on 127.0.0.1 — the host channel — and the server tells the page where it is. CORS is limited to the panel origin, the channel has its own secret, and the browser's private-network preflight is answered.

The same channel is where file exchange between page and application will live.

Threads ​

Runs onWhat
Main threadThe components, the context, the theme, the action handlers
Worker poolProviders: the network call, the SSE parsing, the grounding
Indy threadsThe HTTP requests of the page

The store is thread-safe and is the meeting point. OnMessageChanged fires on the thread that changed the message — a UI layer must marshal it; the polling page does not care.

An action crosses the boundary in the other direction: a provider on a worker asks for something that must run on the main thread, and waits with a timeout while honouring cancellation.

Security boundaries ​

  • Model output is untrusted content: rendered through safe-mode Markdown with active HTML escaped, URL schemes restricted to http(s), mailto, ftp and relative paths, and a Content-Security-Policy on the page.
  • The browser is confined to the panel origin: any other navigation is cancelled and opened in the system browser.
  • The local server binds 127.0.0.1 only and requires the per-process secret.

More in Security and privacy.

Released under the Apache License 2.0