A generative AI engine for live web pages
I started building this engine in 2024 because I wanted AI to work with Microthemer, not sit beside it as a general-purpose chat box. The model can inspect the selected part of a live WordPress page, ask the browser for more information and propose changes in the editor's own format. The result is saved as normal Microthemer and Amender settings that the user can inspect and edit. I have kept developing the same system as the available models have changed.
The model needs the page, not just the request
A request such as “make this heading line up with the card below” is incomplete on its own. The model needs to know which element is selected, what surrounds it, which CSS rules already affect it, the current breakpoint and which selectors Microthemer can safely use. Sending the entire page and every stylesheet would add a lot of cost and delay while still asking the model to find the relevant part.
The browser builds a smaller snapshot around the selection. It includes the element's HTML and XPath, useful attributes, computed styles, verified selectors, existing Microthemer rules, page path, responsive state and the folder rules that determine when the settings load. If an existing action refers to a snippet, the client includes the HTML or JavaScript behind that snippet instead of leaving the model with an unexplained ID.
Each type of context has a size limit. The initial HTML is truncated with a visible note, requested fragments are allowed more space, and there is still an absolute ceiling for unusually large pages. The client removes classes that exist only for the editor and avoids resending a child subtree when a complete ancestor has already supplied the same markup.
Letting the model ask for evidence
I keep the first snapshot fairly small. If the request refers to something outside the selection or depends on an overriding rule, the model can return `requestContext` instead of attempting the change. It can ask for relative or document-level XPath results, particular computed properties, applicable stylesheet rules, wider editor settings or earlier sketchpad code.
The browser runs that request against the live page and sends the task again with the requested information. The model can do this up to three times. Each round records what has already been requested, so the model can distinguish the new evidence from the default context. The collected context remains available while the user refines the same task and is cleared when the routing step detects a new element or objective.
This follows the way I normally debug a page. I inspect the selected element first, then look at a parent or a competing rule if the cause is not clear. The model is told not to invent selectors for content it has not found; it has to request and inspect that part of the page. Some tasks take an extra round trip, but the model is less likely to make a confident change to the wrong element.
A structured change protocol
The model does not return free-form code that runs immediately. It returns JSON with a plain-language reply and either another context request or a `changeResponse`. A change response has separate CSS, HTML and JavaScript sketchpads. Structured comments connect the snippets to selectors and to Amender's action, aspect, event and snippet-ID fields.
For example, an animation can define one selector rule that says to run a named function when the element enters view, then place the function body in the JavaScript sketchpad. A text replacement uses the same protocol with a content snippet. The server supplies category-specific instructions for CSS, layout, images, video, movement, GSAP or Three.js only when the task needs them, which keeps unrelated rules out of the main prompt.
When the response returns, the client parses the JSON, repairs a limited set of formatting errors, fills the visible sketchpad and shows the explanation in the conversation. From that point the code goes through the normal editor. The user can adjust it visually or by hand, compare it with the page and undo it through the same revision system as any other edit.
Constructing the prompt on the server
The WordPress plugin collects the page state, while a Firebase function builds the actual model request. A routing call classifies the task, decides whether the conversation has moved to something new and checks that the request belongs to the product's web-development scope. The prompt builder then assembles the common instructions, relevant category modules, reference material, examples and page context in a fixed order.
Keeping that construction server-side means the product can improve its protocol without shipping its complete prompt library inside every WordPress installation. It also gives the backend one place to validate model choice, request shape and screenshot ownership before a provider call. Development telemetry records how long routing, payload construction, the main request and safety checking take, so a slow response can be attributed to a stage rather than described as “the AI was slow.”
The routing step uses a smaller model call with thinking disabled and a constrained JSON response. It also decides whether existing screenshots still apply and which specialist instructions to include. The main model then receives guidance for the current task instead of instructions for every feature the editor supports.
Regional execution and smaller payloads
Delay is particularly noticeable in a live editor because the user is waiting with the target element in front of them. A Web Worker obtains an approximate location, compares it with the supported Google Cloud regions and connects the Firebase client to the nearest deployment. That function calls Vertex AI in the same region, with `us-central1` as the fallback when location lookup or a regional request fails.
I deploy the callable function in twelve European and US regions. Keeping the Firebase and Vertex calls in the same region removes an avoidable network leg from every request before generation starts.
The browser also compresses production requests with Pako. Firebase serialises the `Uint8Array` as an indexed object, so the function reconstructs the bytes, inflates them and parses the JSON before validation. Selective context and deduplicated HTML reduce the number of model tokens; compression reduces the transfer size of whatever context remains.
Provider access without a model key in WordPress
The plugin contains the public Firebase project configuration needed to call Firebase, but it does not store a Gemini or other model-provider API key in the browser or WordPress database. Before a request, the client signs in anonymously with Firebase Auth. The callable endpoint rejects requests without an authenticated Firebase context.
In production, the function runs as a dedicated least-privilege service account. Google's application-default credentials provide a short-lived access token for Vertex AI, so there is no service-account key file to deploy or refresh token to expose. The runtime identity has the specific Firestore, logging and Vertex permissions it needs rather than the broad Editor role of a default compute account.
For screenshots, the API accepts at most four supported images and verifies that each URL belongs to the expected Firebase Storage location and caller. It refuses redirects and checks the URL again immediately before fetching it. This prevents the screenshot field from being used as a general server-side URL fetcher.
Using both model review and deterministic checks
The routing model reviews every prompt, including follow-up context requests, for relevance and malicious intent. That is useful for understanding language and intent, but I did not want a second probabilistic answer to be the final safety check on generated code.
After generation, a static analyser checks the complete model response. It rejects `javascript:` and `vbscript:` URLs, non-image data URIs, cookie or browser-storage access, `eval`, `Function` constructors, string-based timers and external script tags. Every absolute URL is compared with a normalised allowlist containing the current site and explicitly approved services or user domains.
If a static check fails, the response is quarantined rather than copied into the sketchpad. The user sees the reason and can explicitly allow an unfamiliar domain if it is one they trust. The model handles the broad question of intent; the analyser handles a smaller set of code rules in the same way every time.
Applying AI changes through the product
Once a response passes the server checks, it still does not bypass Microthemer or Amender. The code appears in the editor's sketchpad, is attached to the current revision and is converted into the same selectors, snippets and action records used for manual work. The user can inspect the target, test the page at different widths, refine the code, retry with deeper page analysis or revert to an earlier revision.
Generated work keeps the behaviour of the rest of the product. A CSS rule follows the existing responsive and conditional-loading system. An HTML change can run on the server for search and accessibility or in the browser for an interaction. A GSAP function can use the package manager and page-level dependency loading. After the conversation, the result is still an ordinary part of the project rather than an opaque script pasted into the page.
The conversation and sketchpad can also be resumed from the stored editor state. When the user moves to a new task, the router marks that boundary so old page context is not silently reused. This matters on a long editing session where a selector, screenshot or computed style from the previous element could otherwise make a plausible but incorrect answer look well informed.
What has held up
The model choice has changed several times since I began this work. The parts that have lasted are the context system, the request for missing evidence and the structured route back into the editor. They solve product problems that remain the same when a newer model arrives.
Response time comes from several smaller decisions rather than one dramatic optimisation. Regional functions reduce network distance, compression reduces transfer size, lazy server imports avoid loading the AI code for requests that fail early validation, and selective context reduces the model input. Authentication, a dedicated runtime identity, screenshot validation and static analysis add protection without asking WordPress users to manage provider credentials.
I have kept the editor in charge of targeting, persistence, dependencies, publication and undo. The model examines the live page and proposes work, but the resulting code is stored as normal project data. It can still be inspected and maintained after the conversation that produced it has been forgotten.