ai-slot-component 发布:为既有网站加入 AI 内容插槽
iannil/ai-slot-component
iannil/ai-slot-component 发布,通过 <ai-slot> 包裹既有 HTML,让 AI 生成经校验的组件树 JSON 并渲染为开发者注册的真实组件,而非 HTML。
The AI content delivery layer for the pages you already have.
English | 简体中文
AI-SLOT adds local AI content and component composition to existing sites, CMSs and site builders. AI produces validated component-tree JSON using developer-registered components. Endpoints can also supply the same protocol without calling AI for every update. After initial integration, supported content updates do not require a host redeploy. Versioned publishing and management MCP are planned; the current website MCP reads documentation only.
Wrap a region of your page and provide the initial HTML as fallback. The runtime keeps a copy for recovery when it replaces the visible content:
<ai-slot name="hero" src="/ai-render/hero"> <section class="hero"> <h1>Our product</h1> <p>A normal product intro.</p> </section> </ai-slot>
Behind that element sits a delivery pipeline: a stateless server proxy turns model output into a validated component tree — never HTML — rendered as your real components, served from a two-tier cache, streamed skeleton-first, and re-pushed when the underlying data changes. If the model times out, invents a component name, or violates a prop schema, the original markup above silently stays. The initial HTML supplies fallback content independently of the model; indexing, accessibility and uptime still depend on the host application.
Content reaches a slot in three ways — visitor personalization is only one of them:
| Delivery mode | Trigger | Path |
|---|---|---|
| Pregenerated | your build (prewarm.mjs) |
baked into ai-cache.json, long-TTL cache, no runtime LLM calls on cache hits |
| Live | a data-source change | invalidation push re-renders the slot on every open tab — no redeploy |
| Personalized | a visitor prompt (editable) |
sanitized, rate-limited, short TTL — scoped to that one slot |
Quick Start
Prerequisites: Node ≥ 18, pnpm (corepack enable). No API key needed — the demo ships with a deterministic mock LLM.
git clone https://github.com/iannil/ai-slot-component cd ai-slot-component pnpm install && pnpm build node examples/plain-html/server.mjs # → open http://localhost:4173
The demo app opens with an industry picker — e-commerce, hotel, news and banking pages, each a realistic legacy page. The hero slot carries stream + live, and a recommendation slot whose AI always fails shows the silent fallback. There is no input box on the page — the operator is the one who adjusts: the admin.html ops console (or POST /admin/prompt?slot=<slotId>&prompt=<new prompt>) changes the selling-point angle, POST /admin/publish?slot=<slotId> simulates a data-source change, and both push invalidation so open pages update in place. node examples/plain-html/prewarm.mjs bakes ai-cache.json for the pregenerated path. The project website embeds the same pages as an interactive, step-guided demo running the real runtime in your browser, and the demo app itself is also deployed live on Cloudflare Workers.
To use a real model instead of the mock:
OPENAI_API_KEY=sk-... node examples/plain-html/server.mjs # optional: AI_BASE_URL (any OpenAI-compatible endpoint) # optional: AI_MODEL_DEVELOPER (default gpt-4o) / AI_MODEL_USER (default gpt-4o-mini)
Got it running? A GitHub star helps others find the project.
Features
- Drop-in for the site you already have — wrap existing markup in
<ai-slot>; plain HTML, React and Vue all work, no rewrite required. - Ship AI content like a static asset —
prewarm.mjsbakes developer prompts intoai-cache.jsonat build time, so production serves with no runtime LLM calls on cache hits. - Content updates without a redeploy —
livesubscribes to invalidation pushes; a data-source change re-renders the slot on every open tab. - Skeleton-first streaming —
streamfetches over SSE: a skeleton frame renders immediately, the final tree replaces it. - Protocol-neutral — native JSON today, A2UI tomorrow via
@ai-slot/a2ui; the endpoint changes, the page does not. - Visitor personalization is opt-in, per slot —
editableadds a one-line prompt box scoped to that slot: sanitized, rate-limited, short TTL. - Runs offline out of the box — deterministic mock LLM by default; unit tests replay fixtures and never hit a real API.
Why it is safe
- The model never writes markup. It returns component-tree JSON whose component names must exist in the registry you declared, validated against prop schemas, slot rules, depth and node-count limits before anything renders. This constrains model output; application components must still handle untrusted props safely.
- Fallback for handled delivery failures. A network failure, timeout, rate limit or invalid output falls back silently to the content inside
<ai-slot>. No blank screen, no error shown to visitors. - Secrets stay on the server. LLM keys live only in the proxy. Visitor prompts are sanitized, length-capped and injected as bounded context, with token budgets, an 8s timeout, per-minute rate limiting and usage logs.
- Initial HTML contains the fallback you provide. After a successful render, the runtime replaces the slot’s visible children and retains the fallback for recovery. Crawlers that render JavaScript may see the enhanced content; indexing is not guaranteed.
Protocol-neutral by design
ai-slot separates three layers:
- Slot protocol — the wire format between the AI endpoint and the page. Native JSON today; A2UI (Google's open protocol for agent-driven UI) works too via
@ai-slot/a2ui. - Delivery runtime —
<ai-slot>, client-side re-validation, and fallback recovery for handled failures. - Lifecycle infrastructure — two-tier caching, build-time pregeneration, invalidation push, rate limiting.
An agent endpoint using the A2UI versions and message subset supported by @ai-slot/a2ui can feed an existing page:
import { defineRegistry } from "@ai-slot/registry"; import { createDomRenderer } from "@ai-slot/adapter-dom"; import { configureAiSlot, registerRenderer, registerWireFormat } from "@ai-slot/runtime"; import { a2uiWireFormat, basicCatalogComponentDefs, basicCatalogDomDefs } from "@ai-slot/a2ui"; const registry = defineRegistry({ components: { ...basicCatalogComponentDefs /* , your brand components */ }, }); registerWireFormat("a2ui", a2uiWireFormat); registerRenderer("dom", createDomRenderer(basicCatalogDomDefs)); configureAiSlot({ registry, onFailure: (f) => console.debug(f) });
| A2UI / AG-UI | ai-slot | |
|---|---|---|
| Layer | Wire format for agent ↔ in-app UI (Google / CopilotKit) | Delivery layer for public web content slots — speaks A2UI via @ai-slot/a2ui |
| Existing pages | Integration depends on the host application | Wraps existing HTML with an explicit fallback |
| SEO & crawlers | Depends on the host rendering strategy, access and crawler capabilities | Author-provided fallback in initial HTML; generated enhancements render on the client |
| Lifecycle | A protocol does not determine the host application’s cache or delivery lifecycle | Two-tier TTL, build-time prewarm, invalidation push, rate limiting |
Rendering and indexing boundaries
prewarm.mjs generates ai-cache.json, not HTML. It avoids runtime model calls for cache hits; a cache miss or expiry can still invoke the model. Generated component trees are fetched and rendered by the browser. To put generated content in the initial HTML, the host must add its own static or server rendering integration; this example does not provide one.
Google can render JavaScript, while other crawlers may have different capabilities. Keep useful fallback content in the initial response and verify the deployed page. See Google’s JavaScript SEO guide and the Chinese delivery-boundary guide.
Usage
All packages are on npm as
@ai-slot/*: client —npm install @ai-slot/runtime @ai-slot/adapter-dom(+@ai-slot/a2uifor A2UI endpoints); server —npm install @ai-slot/proxy @ai-slot/registry. To hack on the monorepo instead, clone andpnpm install && pnpm build.
1. Declare what the AI may build with (shared by proxy, runtime and build tooling):
import { defineRegistry } from "@ai-slot/registry"; export const registry = defineRegistry({ components: { "hero-banner": { description: "Page hero section", props: { title: { type: "string", maxLength: 60 }, subtitle: { type: "string", maxLength: 120 }, }, required: ["title"], }, }, });
2. Wire the proxy — a Web-standard (Request) => Promise<Response>, deployable to Node, Edge or any serverless runtime:
import { createAiRenderHandler, createOpenAIClient } from "@ai-slot/proxy"; import { registry } from "./registry.js"; export const handler = createAiRenderHandler({ registry, llm: createOpenAIClient({ apiKey: process.env.OPENAI_API_KEY! }), // baseUrl: any OpenAI-compatible endpoint resolveSlot: (slotId) => slots[slotId] ?? null, }); // Exposes GET/POST /ai-render/:slotId — GET for CI pregeneration, POST for real-time delivery (visitor prompts included).
3. Render it — pick your stack:
<!-- Plain HTML --> <script type="module"> import { configureAiSlot, registerRenderer } from "@ai-slot/runtime"; import { createDomRenderer } from "@ai-slot/adapter-dom"; import { registry } from "./registry.js"; import { domComponents } from "./components.js"; registerRenderer("dom", createDomRenderer(domComponents)); configureAiSlot({ registry }); </script>
// React ≥ 18 — any failure keeps `fallback`, same semantics as the Web Component <AiSlot src="/ai-render/hero" components={{ "hero-banner": HeroBanner }} fallback={<HeroStatic />} editable />
<!-- Vue ≥ 3.4 — fallback is the default slot --> <AiSlot src="/ai-render/hero" :components="vueComponents"> <HeroStatic /> </AiSlot>
<ai-slot> attributes
| Attribute | Effect |
|---|---|
src |
Proxy endpoint for this slot (/ai-render/:slotId) |
name |
Slot id sent to the proxy |
renderer |
Registered renderer to use (default dom) |
stream |
Fetch over SSE: skeleton frame first, final tree second |
live |
Subscribe to invalidation pushes and re-render on change (live-src overrides the endpoint) |
refresh-interval |
Re-fetch every N seconds |
editable |
Adds a one-line prompt box that re-renders this slot |
The delivery pipeline
graph TD
A["ai-slot element in your HTML"] -->|"GET / POST /ai-render/:slotId"| B["Stateless proxy (Edge / Serverless)"]
B --> C["PromptCompiler"]
C --> D["LLM (OpenAI-compatible)"]
D --> E["OutputValidator"]
E -->|"valid component tree"| F["Cache: L1 developer / L2 visitor"]
F -->|"JSON, or SSE skeleton + tree"| G["Adapter renders your real components"]
E -->|"invalid / timeout / rate-limited"| H["Silent fallback: original content stays"]
Loading
Packages
| Package | What it does |
|---|---|
@ai-slot/registry |
The contract: defineRegistry, component-tree types, and the OutputValidator shared by proxy, runtime and tooling |
@ai-slot/proxy |
Stateless render proxy: prompt compilation, LLM calls, validation, two-tier cache, rate limiting, SSE, invalidation push, pregeneration |
@ai-slot/runtime |
Zero-dependency <ai-slot> Web Component + registerRenderer registry |
@ai-slot/adapter-dom |
Maps component trees to plain DOM (tag / class / applyProps) — no framework needed |
@ai-slot/adapter-react |
treeToReact + <AiSlot> component |
@ai-slot/adapter-vue |
treeToVue + AiSlot component |
Development
pnpm build # build all packages (ESM → dist/) pnpm test # all unit tests (Vitest) pnpm typecheck # needs dist type artifacts: run pnpm build first on a fresh checkout pnpm --filter @ai-slot/runtime test # single package cd packages/proxy && npx vitest run src/cache.test.ts # single test file # End-to-end (Playwright, 7 specs) pnpm --filter example-plain-html exec playwright install chromium pnpm --filter example-plain-html test:e2e # CI pregeneration (bakes ai-cache.json, hydrated on server start) node examples/plain-html/prewarm.mjs
Type checking is the only static gate — there is no ESLint/Prettier config.
For AI coding agents
- Repo guides: CLAUDE.md and AGENTS.md. The design spec in
docs/superpowers/specs/is the source of truth for implementation work. - The example runs on a deterministic mock LLM with no network and no key, so
pnpm build && node examples/plain-html/server.mjsis a safe smoke test for an agent to verify the repo. OutputValidatoris a pure function with adversarial test cases inpackages/registry— the security boundary lives there, not in prompts.
Project status
v1 and v1.1 are implemented and merged to master: global prop guardrails, cache serialization + CI pregeneration, SSE streaming, invalidation push, React/Vue adapters.
Deliberately deferred: token-level LLM streaming, live auto-reconnect, multi-instance rate-limit/cache stores. Explicitly out of scope: arbitrary HTML/CSS generation, client-side DOM patch protocols, in-browser inference, multi-tenant billing.
Contributing
Issues and PRs are welcome — see AGENTS.md for architecture, commands and conventions (Conventional Commits, Chinese commit messages).
Bugs and feature requests: GitHub Issues.
Links
License
Released under the MIT License.
来源:GitHub · github.com


