Skip to content

Repository files navigation

Stellar Canvas

Generative UI builder. Users describe the interface they want; the AI composes a JSON spec from a fixed component catalog and binds it to live data from NASA, SpaceX, and other registered sources. Specs are persisted in Supabase for later edits.

Built on:

  • @json-render/core + @json-render/react — catalog-constrained schema-driven rendering.
  • Vercel AI SDK + AI Gateway — streaming text + tool calling. One AI_GATEWAY_API_KEY routes to Anthropic, OpenAI, xAI, Google, Groq, and anyone else behind the gateway; swap models by string id (anthropic/claude-opus-4-7openai/gpt-5.1) without touching code.
  • Next.js 15 (App Router, Node runtime for the streaming route).
  • Supabase — Postgres + Auth + RLS for canvas storage.
  • Upstash Redis — optional fetch-through cache for public API responses.

Status

M0–M5 of the roadmap in design.md are scaffolded:

  • ✅ Catalog (lib/catalog.ts) + React registry (lib/registry.tsx).
  • ✅ Data sources: SpaceX (upcoming, past), NASA (APOD, Mars rover, NeoWs).
  • ✅ Streaming route (app/api/canvas/run) with tool-call hydration over custom SSE.
  • ✅ Client Canvas with chat, live Renderer, and hydration store.
  • ✅ Supabase schema + save/load/list API.
  • ⏭ Inspector (click-to-edit), auth pages, version history UI, public share page — to come.

Setup

# 1. Install
cd stellar-canvas
npm install

# 2. Configure
cp .env.example .env.local
#   - AI_GATEWAY_API_KEY is the only credential you need for the model (one key, every provider).
#     Get one at https://vercel.com/ai-gateway. When deployed to Vercel it's auto-provided.
#   - STELLAR_DEFAULT_MODEL picks the default model (e.g. "anthropic/claude-opus-4-7").
#   - NASA_API_KEY=DEMO_KEY works for light use; free key at https://api.nasa.gov.
#   - Supabase vars are optional until you wire auth — the canvas generator runs without them.
#   - Want to bypass the gateway? Set STELLAR_USE_DIRECT_PROVIDERS=true and provide
#     ANTHROPIC_API_KEY / OPENAI_API_KEY.

# 3. (Optional) Supabase
#   Create a project at supabase.com, run the migration:
#     supabase db push
#   or paste supabase/migrations/0001_init.sql into the SQL editor.

# 4. Dev
npm run dev
# → http://localhost:3000

How it works

prompt ──► /api/canvas/run (streamText)
              │
              ├─ tool calls ──► data sources ──► cache ──► hydration SSE events
              │
              └─ text stream ──► createSpecStreamCompiler ──► spec SSE events
                                          │
                              client ◄────┤
                                          ▼
                           <Renderer spec registry state/>

The model never inlines API data into props; it returns a spec with $state references (e.g. {"$state": "/sources/spacex_upcoming_3/0/name"}) that the Renderer resolves against the hydrated state object. This keeps specs tiny, portable, and safely re-hydratable.

Adding a new data source

Create lib/sources/mysource.ts:

import { z } from "zod";
import type { DataSource } from "./types";

export const myThing: DataSource = {
  id: "my.thing",
  label: "My Thing",
  description: "Plain-English sentence the model will see as a tool description.",
  ttlSeconds: 300,
  input:  z.object({ limit: z.number().int().default(5) }),
  output: z.array(z.object({ name: z.string(), count: z.number() })),
  statePath: ({ limit }) => `/sources/my_thing_${limit}`,
  fetch: async ({ limit }) => { /* … */ },
};

Then register it in lib/sources/registry.ts. The tool, proxy route, cache entry, and picker chip are all derived automatically.

Adding a new component

  1. Add a Zod entry in lib/catalog.ts under components: — props and a short description (the description ships to the model).
  2. Add a React impl in lib/registry.tsx under defineRegistry(catalog, { components: { … } }).

Never pass user/AI strings to dangerouslySetInnerHTML or as src without URL validation. The Zod schema on Image.src + Next.js remotePatterns in next.config.ts are the two guards.

Directory layout

app/
  api/canvas/run/       streaming generation (SSE)
  api/canvas/save/      create or version a canvas
  api/canvas/[id]/      load / delete
  api/canvas/list/      list user canvases
  api/sources/[id]/     read-through proxy for a single source
  page.tsx              Canvas editor
components/canvas/      ChatPanel, SourcePanel, Canvas
lib/
  catalog.ts            json-render catalog (Zod-typed components)
  registry.tsx          React impls for catalog components
  state.ts              hydration store (zustand)
  useCanvasStream.ts    SSE consumer + spec stream compiler
  ai/                   model resolver + system prompt
  sources/              DataSource type + plugins + cache + registry
  supabase/             browser/server/admin clients
supabase/migrations/    SQL schema + RLS

Security notes

  • Catalog is the only allow-list. The model cannot render arbitrary HTML.
  • $state path resolution is sealed: unresolved paths render as empty strings, not errors.
  • Supabase RLS gates every read/write on owner_id = auth.uid() (or is_public).
  • Service-role key lives in SUPABASE_SERVICE_ROLE_KEY, server-only.
  • Rate limiting belongs on /api/canvas/run once Upstash is wired (hook is in lib/sources/cache.ts).

About

Generative UI builder

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages