Purpose of this document: explain what
PythiaSampleVCLis, what it lets you discover, and how to read its source units when you want to understand what each part of the Pythia-Webview2 UI actually does. This demo is not a vendor integration sample and not a plugin. It is a guided exploration of the component, its panels, its capabilities, and the scriptable surface ofIPythiaBrowser.
Important
PythiaSampleVCL does not call any real LLM. It ships with a fake vendor (TVendorTest) that intercepts prompt submissions and renders the raw TInputPromptState as a preformatted block in the chat.
This is deliberate: the goal of the demo is to let you observe how the WebView side composes a prompt, what the Delphi side receives, and how the visual surface reacts to capability/panel/button changes — without requiring an API key or a network call.
A new integrator usually has the same first questions:
- what does the chat surface actually look like at runtime?
- which panels can be toggled, hidden, or replaced?
- which buttons live in the input bar, and how are they enabled?
- what does a
TInputPromptStateactually contain when the user clicks Send? - how does the component render generated images, audios, videos, files?
- how is a chat session built programmatically?
- where do the capabilities, model lists, and card files live?
- what does a typical error UI look like (e.g.
DialogService not assigned)?
PythiaSampleVCL answers each of those questions in a single runnable application. The form exposes around 35 checkboxes and a dozen buttons, each wired to a small piece of behavior that you can toggle, observe, then turn off again.
| Family | Demo | What you observe |
|---|---|---|
| Discovery | PythiaSampleVCL (this document) |
The component itself: panels, buttons, capabilities, content rendering, sessions, configuration files |
| Starter | NewVCLproject, NewFMXproject |
The minimal correct wiring to host the component |
| Vendor | VCL_Anthropic |
A real LLM connected through a Delphi vendor SDK |
| Plugin | PythiaSnippetDemo, PythiaGitDemo, PythiaGrepDemo |
Extension through slash commands and the JS↔Delphi bridge |
PythiaSampleVCL is the one you should run first. It does not pretend to be a real product; it is the application equivalent of an inspector window.
When you open Main.dfm you see four scrollable areas:
| Area | Purpose |
|---|---|
| Custom panels (top-left scroll box) | Toggles for the three replaceable panels: Cards, Models, Settings, plus button-visibility toggles for the input bar (microphone, function, parameters, models). |
| Capabilities (middle-left scroll box) | One checkbox per ICapabilities boolean: endpoint, web research, reasoning, attach file, knowledge search, vision, deep research, integration, media, custom. |
| Capability sub-menus (right of the capabilities, paged) | Sub-toggles that only make sense when the parent capability is on: endpoint variants, thinking tiers, integration kinds (function / MCP / skill / agent), media kinds. The Next/Previous buttons cycle through the sub-pages. |
| Discovery pages (bottom, paged) | Action buttons that populate the chat with curated scenarios: generated media, attached files, long prompts, LaTeX, code blocks, programmatic sessions, JSON-file edition, error helpers, How to start. The Next/Previous buttons cycle through the pages. |
The host form is intentionally crowded. Treat it as a control panel, not as a product mockup.
The demo's logic is split across three units. Reading them in this order will give you the clearest mental model.
This unit defines TVendorTest, the fake vendor that replaces a real LLM call.
It does three things, all small:
- holds a reference to the
IPythiaBrowserso it can write back into the chat; - registers itself as the auto-rename handler (
OnChatSessionAutoRename) — the first line of the prompt becomes the session title; - exposes
Validation, the entry point reached when the user clicks Send.
Validation is the interesting part:
procedure TVendorTest.Validation(const AState: TInputPromptState;
const AOnFinalize: TManagedItemFinalizeProc);
begin
var Reader := TJsonReader.Parse(AState.Source);
var S := TEscapeHelper.ToPreformattedHTML(Reader.Format);
...
FBrowser.Display(S);
...
AOnFinalize(ResponseFlow);
end;It parses the raw JSON of TInputPromptState, formats it as a preformatted HTML block, and renders that block as the assistant turn. There is no network, no token, no streaming.
Why this matters: every time you click Send in this demo, you can read exactly what the WebView side packed into the prompt state — selected models, selected cards, attached files, capabilities snapshot, request parameters, custom event payload, everything. This is the fastest way to learn the schema you would need to handle in a real vendor service.
TContentComposer is the unit that exposes one method per discovery scenario. It does not react to events; it is called explicitly by the form's buttons.
The methods fall into five groups.
| Group | Methods | What they demonstrate |
|---|---|---|
| Generated media | DisplayImageGenerated, DisplayAudioGenerated, DisplayVideoGenerated, DisplayFilesGenerated |
How the component renders an image, audio, video, or set of files returned by an LLM. Uses IPythiaBrowser.DisplayMedia with dkImages, dkAudio, dkVideo, dkFile. |
| Attached prompts | ImageAttachedToThePrompt, FilesAttachedToThePrompt, ImagesAndFilesAttached, PromptsVeryLong |
How the user-side bubble renders attachments and how a very long prompt is summarized. Uses PromptMedia and Prompt. |
| Rich content | LaTeXUsing, CodeAndArrayUsing |
How the chat renders LaTeX (KaTeX) and code/tables (Markdown + highlight.js) via Display(Response, Reasoning). |
| Programmatic sessions | CreateSessionByCode, CreateSessionAboutReadme |
How to build a multi-turn chat session purely from Delphi (PersistentChat.AddChat, AddPrompt, SaveToFile, ChatSessionAdd) without a real LLM call. |
| Configuration files | ModelListJsonEdition, ModelGetReplaceVersion, CapabilitiesEdition, CustomTemplateEdition, FunctionCardEdition, McpCardEdition, SkillsCardEdition, AgentsCardEdition, CustomCardEdition |
How to open the JSON configuration files of the application support folder, with explanatory text and the file content rendered inline. The actual files used are returned by Browser.GetCapabilitiesFileName, GetModelListFileName, GetFunctionCardsFileName, etc. |
| Helper screens | DialogServiceError, DefaultModelError, HowToStart |
The didactic walkthroughs shown when an integrator first runs into the typical setup mistakes. |
The reusable building block of this unit is DoEdition:
procedure TContentComposer.DoEdition(const Filenames: TArray<string>;
const Prompt: string);It clears the chat, prints the explanatory Prompt, displays the JSON content as a preformatted block, attaches the JSON file as an inline document, and scrolls back to the top. This is the same pattern any host application can reuse to render its own "open this configuration file in the chat" experience.
TCheckComponent is the glue:
- it owns the
TContentComposer; - it remembers which sub-menu page and which discovery page is currently visible;
- it implements one handler per checkbox / button on the form;
- each handler either flips a capability, toggles a panel, toggles an input-bar button, or calls a
TContentComposermethod.
TDidacticCustomProc.MyChatContentBuilder is the second piece — it is plugged into Pythia.OnRenderChatContent and produces the welcome screen displayed when the chat is empty.
You will rarely write code that looks like this unit in a real application. It exists here only to hook a large amount of UI affordance to a small amount of behavior.
Despite the size of the form, the actual Pythia setup is the same four lines you find in NewVCLproject:
Pythia := TVCLPythia.Create(Panel2);
Pythia.ServiceAdapter := TVCLChatManagedItemDialogService.Create;
Pythia.OnThemeChanged := UpdateTheme;
Pythia.OnRenderChatContent := TDidacticCustomProc.MyChatContentBuilder;
Pythia.OnInitialized := DoOnInitialized;
Pythia.Update;The diagnostic vendor and the content composer are created in DoOnInitialized — that is, after the WebView2 chat runtime has finished booting. This is the correct order if any of those services need to read paths produced by the runtime (e.g. GetMediaFolder, GetCapabilitiesFileName).
UpdateTheme is also worth noting: it shows the recommended pattern for synchronizing the host application's chrome (panel colors here) with the chat's selected look-and-feel through Pythia.OnThemeChanged.
The demo deliberately exposes everything at once. Here is a path that builds knowledge step by step.
- Run the demo.
- Type any prompt and click Send.
- Observe the assistant turn: it shows the complete JSON of the
TInputPromptStatethat was sent. - Note the fields you recognize and the ones you do not — they are exactly what a real vendor service would have to read.
- Tick / untick a capability checkbox (e.g.
Reasoning,Vision). - Watch the chat input bar update accordingly.
- Re-send a prompt. The new JSON snapshot reflects the toggled capability.
This is where the relationship between ICapabilities, the JSON capability file, and the visible UI becomes concrete.
- Tick Custom Cards / Custom Models / Custom Parameters.
- Try to open the Cards / Models / Settings panels: the host now owns rendering — the framework does not show its default panel.
- Untick to restore the default panel.
This shows the contract behind Pythia.CustomPanels.
Click the discovery buttons in order and observe the chat:
| Button | What you see |
|---|---|
Display image / audio / video / files |
How DisplayMedia renders generated content. |
Image attached / Files attached / Both attached |
How the user bubble renders attachments. |
Prompts very long |
How the input rendering handles a long user prompt summary. |
LaTeX using |
KaTeX rendering of presheaves + Yoneda lemma. |
Code and array using |
Markdown code blocks + tables. |
Create session by code / about README |
A programmatic multi-turn session appears in the session drawer, fully persisted. |
Click the edition buttons (Model list, Model categories, Capabilities, Custom template, Function cards, MCP cards, Skills cards, Agents cards, Custom cards):
- The chat clears.
- A short prompt explains the role of the file.
- The current content is displayed inline as a preformatted block.
- The actual file is attached as a downloadable item.
This is the fastest way to understand which JSON files exist in the support folder and what each of them controls.
Click Dialog service error, Default model error, How to start. These produce the same in-chat walkthroughs that ship as didactic content; they are what a new integrator hits when something is missing in the setup.
The first thing the diagnostic vendor displays is a JSON snapshot of TInputPromptState. Browse it with these landmarks in mind:
| JSON field | Where it comes from |
|---|---|
models.categories[*].model |
The model selector. The integrator's vendor service typically reads models.categories[<index>].model to pick the model to call. |
endpoint, thinking, vision, ... |
A snapshot of the active capabilities. |
attachments.images, attachments.files, attachments.knowledge |
The files the user dropped or selected. |
cards.function, cards.mcp, cards.skills, cards.agents, cards.custom |
The cards selected in the Cards panel. Only the identity (id, name) is here — the full content payload of each card lives in the matching JSON file. |
prompt, language |
The prompt text and the active locale. |
requestParams |
The values currently set in the Settings panel. |
Demo.Browser.Services.Validation shows how to read one such field directly:
var Model := Reader.AsString('models.categories[1].model');That single line is the entire boilerplate a real vendor service would need to extract the active model.
- Real LLM calls. Use
VCL_Anthropicfor a vendor-native integration sample. - Slash command plugins. Use
PythiaSnippetDemo,PythiaGitDemo, orPythiaGrepDemo. - Custom JS templates beyond
OnRenderChatContent. The/grepplugin demo is the canonical example of a JS picker injected at runtime. - Production styling. The form is a control panel, not a polished UI.
If you need any of those, run the matching demo afterwards.
Once you have explored PythiaSampleVCL, the next steps are:
- Open
docs/integrator/index.md— the how-to guide for building a first real application. - Run
VCL_Anthropicto see a real vendor service replaceTVendorTest. - Read
docs/reference/index.mdfor the exact interfaces, events, JSON schemas, and template list referenced throughout this walkthrough. - Optionally explore one of the plugin demos (
/snippet,/git,/grep) to see how the chat surface can be extended with command-layer logic.
PythiaSampleVCL does not replace any of those. It is the warm-up that makes them readable.
PythiaSampleVCL is the application equivalent of opening every drawer of the component. It demonstrates:
- the visible surface — chat area, input bar, sessions drawer, panels;
- the toggleable surface — capabilities, panels, buttons;
- the rendering surface — text, Markdown, code, LaTeX, generated media, attachments;
- the configuration surface — capabilities JSON, model list, model categories, card files, custom-template manifest;
- the diagnostic surface —
TInputPromptStateprinted verbatim on every submit.
It does this without a real LLM, without an API key, and without a plugin. After running it once, you should have a clear mental map of what IPythiaBrowser exposes, where its data lives, and which extension points you will want to plug into in a real application.