跳到正文
GitHub·· 8 天前AI 评分60

ai-slot-component 发布:为既有网站加入 AI 内容插槽

iannil/ai-slot-component

AI 导读

iannil/ai-slot-component 发布,通过 <ai-slot> 包裹既有 HTML,让 AI 生成经校验的组件树 JSON 并渲染为开发者注册的真实组件,而非 HTML。

正文

ai-slot logo

The AI content delivery layer for the pages you already have.

English | 简体中文

Node pnpm runtime deps React Vue npm

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.

Three delivery modes in one recording: AI content streams in on load, a data-source change updates the title in place without a redeploy, then an ops-console prompt change rewrites the slot

AI-enhanced hero rendered from a validated component tree After the ops prompt publish: the same slot re-rendered with the new angle

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.mjs bakes developer prompts into ai-cache.json at build time, so production serves with no runtime LLM calls on cache hits.
  • Content updates without a redeploy — live subscribes to invalidation pushes; a data-source change re-renders the slot on every open tab.
  • Skeleton-first streaming — stream fetches 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 — editable adds 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:

  1. 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.
  2. Delivery runtime — <ai-slot>, client-side re-validation, and fallback recovery for handled failures.
  3. 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/a2ui for A2UI endpoints); server — npm install @ai-slot/proxy @ai-slot/registry. To hack on the monorepo instead, clone and pnpm 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.mjs is a safe smoke test for an agent to verify the repo.
  • OutputValidator is a pure function with adversarial test cases in packages/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