Skip to content

Saving and copying an answer ​

An answer the user cannot take with them is a note nobody keeps. The panel offers two ways out of the chat, side by side: the clipboard and a PDF — of one answer, of the whole conversation, or of a page of your manual.

The two buttons in the header of the panel

Both are on by default for copying, and on by default for the PDF as well: it all happens on the user's machine, with the browser engine the panel already needs, and nothing leaves.

Where the buttons are ​

WhereCopySave as PDF
On an answer, appearing when the pointer is over itthat answerthat answer
In the header of the panelthe whole conversation, questions and answersthe same
In the bar of a documentation pagethat pagethat page

A reply still being written, and the error bubbles, are left out of the conversation in both cases: a document somebody files away does not carry half an answer.

Copy: both flavours at once ​

The clipboard gets the answer twice, as HTML and as plain text, the way a word processor and a text editor each expect it:

  • pasted into Word, Outlook or OneNote, the bold, the lists, the links and the tables are still there;
  • pasted into Notepad, a code editor or a plain-text mail, it is clean text — a table included, laid out as the panel lays it out.

Copying asks nothing of the host application and nothing of the engine: it is the browser's own clipboard. That is why it works with a remote engine too, where saving a file does not.

Save as PDF: what happens ​

  1. The engine builds the document — the answer as Markdown, the conversation as a series of turns, or the documentation page with its images already inlined.
  2. The component puts the print stylesheet on it, and asks the user where the file goes, with the save dialog of Windows.
  3. The WebView2 runtime lays it out and writes the file, on a window that is never shown.
  4. The user is asked whether to open it.
pascal
// Nothing to write: it is on unless you turn it off
HelpChat.Settings.Pdf.Enabled := True;

No second browser

The exporter borrows the WebView2 environment the panel already created, so no second browser process starts for a sheet of paper. Measured on an ordinary machine: about half a second for the first document and a quarter for the next.

The look of the document ​

A printed document is not a chat: no bubbles, no dark theme — a dark theme printed is a black rectangle — and no colours of the host application. The library ships resources/print.css, which lays out headings, lists, code, images and tables (with the header row repeated on every sheet), and your application can replace it entirely:

pascal
HelpChat.PdfStyleSheet := 'print.css';   // next to the executable

A relative path resolves against the executable, and a file that is named but not there raises EHelpChatConfigError — an application that asked for its own look and silently printed ours would be an application shipped with the wrong files.

Leave @page alone

The sheet, the margins and the orientation come from Settings.Pdf, not from the stylesheet. A margin written in CSS wins over the print settings: an @page { margin: 0 } in the stylesheet produced sheets with no margin at all, whatever the settings said. That is why the built-in stylesheet carries no @page rule, and yours should not either.

Settings.Pdf.HeaderFooter prints a band at the top and at the foot of every sheet:

┌──────────────────────────────────────────────┐
│ CRM Demo 1.4.2              Summary of leads │  ← print-header.html
│ ──────────────────────────────────────────── │
│                                              │
│ # Summary of leads                           │
│ …                                            │
│                                              │
│ ──────────────────────────────────────────── │
│ carlo · 02/11/2026 15:54              1 / 3  │  ← print-footer.html
└──────────────────────────────────────────────┘

Each band is a small HTML file of its own — the two built into the library, or the files your application names:

pascal
HelpChat.Settings.Pdf.HeaderFooter := True;
HelpChat.PdfHeaderFile := 'report-header.html';
HelpChat.PdfFooterFile := 'report-footer.html';

The placeholders in them are replaced before printing, HTML-escaped:

PlaceholderWhat it becomes
{app} {version}The product name and the file version of your executable
{user}The user Windows is logged on as
{title}The title of the document
{date} {time}When it was printed, in the format of the machine
{extra}Anything you put there — a customer, a department, "confidential"
{page} {pages}The page number and the number of pages

What the library found out by itself is rarely the whole truth, so it can be corrected before each document:

pascal
procedure TMainForm.HelpChatPdfInfo(Sender: TObject; var AInfo: THelpChatPdfInfo);
begin
  AInfo.AppName := 'Contoso ERP';
  AInfo.UserName := Session.UserFullName;   // not the user of the machine
  AInfo.Extra := 'Internal use only';
end;

The band does not eat the text

A band is laid out inside the margin of the sheet, so the margin grows by Settings.Pdf.BandMm (12 mm) when there is one: MarginMm stays the margin of the text. A template of two lines needs a larger BandMm, or what does not fit is cut.

A band is a little document of its own: the print stylesheet does not reach it, so its style lives inside the template, as a <style> block or inline. An image works too, as a data: URI — which is how a logo gets into the header.

Letting the assistant write a document ​

With Settings.Pdf.Action the assistant gets one more action, export_pdf:

User:      "write me a PDF with a summary of the open leads and what to do next"
Assistant: → export_pdf(title = "Open leads", markdown = "# Open leads\n\n…")
           "I have prepared the document."

and the save dialog opens with what it wrote, laid out by the same stylesheet. The model writes Markdown — headings, lists and tables are laid out as in any other document.

pascal
HelpChat.Settings.Pdf.Action := True;    // off by default

Off by default

It is one more tool in every request, and an answer that becomes a file without anybody asking for one. Turn it on when a document is something your users actually want.

The assistant knows what day it is — the system prompt carries the date — so a document it writes is dated properly instead of saying "today".

The settings ​

SettingDefaultWhat it does
Pdf.EnabledTrueThe buttons that save a PDF
Pdf.ActionFalseThe export_pdf action
Pdf.PaperhcppA4hcppA4 or hcppLetter
Pdf.OrientationhcpoPortraitUpright or on its side
Pdf.MarginMm10The margin of the text, on all four sides
Pdf.PrintBackgroundsTrueOff, the shaded header row of a table comes out white
Pdf.HeaderFooterFalseThe two bands
Pdf.BandMm12How much room a band gets

and on the components, next to them:

PropertyWhat it is
PdfStyleSheetThe stylesheet of the document
PdfHeaderFile PdfFooterFileThe two band templates
OnPdfInfoWhat the bands say

Copying has no setting of its own: the clipboard is the user's, and the text is already on their screen.

With a remote engine ​

With Settings.Remote the document would be built on the server, where there is no screen to save it from: the PDF buttons answer "Saving as PDF is not available in this application" (HTTP 501) rather than doing nothing. Copying works, because it never leaves the browser.

What is not there yet ​

  • The page number lives in the footer band, and nowhere else. Chromium implements no @page margin boxes, so a CSS counter cannot number the sheets: a counter(page) in the document prints 0 on every one of them. With HeaderFooter off, a document has no page numbers.
  • A band is drawn by the browser engine, through a DevTools call that Microsoft does not version with the rest of WebView2. If a future runtime refuses it, the PDF is written anyway and the component says the header and the footer are missing, rather than failing.
  • FireMonkey on macOS, iOS and Android has no WebView2: the PDF is Windows only, and THelpChatPdfExporter.IsAvailable answers False elsewhere. Copying is the page, so it works wherever the panel does.

Released under the Apache License 2.0