From 8c8a7e70a36c5e4618fb635ab46c4bc132f7429e Mon Sep 17 00:00:00 2001 From: Brian Love Date: Wed, 8 Apr 2026 16:30:59 -0700 Subject: [PATCH 1/8] docs: add generative UI spike design spec --- .../2026-04-08-generative-ui-spike-design.md | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 docs/superpowers/specs/2026-04-08-generative-ui-spike-design.md diff --git a/docs/superpowers/specs/2026-04-08-generative-ui-spike-design.md b/docs/superpowers/specs/2026-04-08-generative-ui-spike-design.md new file mode 100644 index 000000000..309426dab --- /dev/null +++ b/docs/superpowers/specs/2026-04-08-generative-ui-spike-design.md @@ -0,0 +1,53 @@ +# Generative UI Spike — Design Spec + +**Date:** 2026-04-08 +**Status:** Draft + +## Goal + +Prove the streaming generative UI pipeline end-to-end: LangGraph agent returns a JSON-render Spec as AI message content → tokens stream → `ContentClassifier` auto-detects JSON → `PartialJsonParser` builds tree → `ParseTreeStore` materializes Spec → `` renders Angular components that update live as tokens arrive. + +## Approach + +Add a new cockpit capability under `cockpit/langgraph/generative-ui/` following the existing capability pattern. The agent returns a full JSON-render Spec as its message content (not as a tool call result). The Angular frontend uses `ChatComponent` with `[views]` input — relying entirely on auto-detection. + +## Python Graph + +A single-node LangGraph graph (`MessagesState`). The node calls an LLM with a system prompt that instructs it to respond with a JSON-render Spec containing 2-3 elements: a `WeatherCard` and one or two `StatCard` components. The LLM streams the JSON token-by-token. + +The system prompt includes the exact Spec schema and available component types so the LLM knows what to generate. + +## Angular Frontend + +### View Components + +**WeatherCard** — Displays city name, temperature, condition, and an icon. Inputs: `city: string`, `temperature: number`, `condition: string`. + +**StatCard** — Displays a label and value (e.g., "Humidity: 65%"). Inputs: `label: string`, `value: string`. + +Both are simple standalone components with Tailwind styling. + +### App Component + +Uses `ChatComponent` with `[views]` input passing a `ViewRegistry` mapping `weather_card` → `WeatherCardComponent` and `stat_card` → `StatCardComponent`. + +## Registration + +- Add `generative-ui` topic to the `langgraph` product's `core-capabilities` section in the cockpit manifest +- Create module metadata (`CockpitCapabilityModule`) in the Python `src/index.ts` +- Register in `route-resolution.ts`'s `capabilityModules` array + +## What It Proves + +1. The parse tree correctly builds from character-by-character LLM output +2. Materialization produces valid `Spec` objects that `` can render +3. Structural sharing works — unchanged elements keep references, render lib skips re-render +4. Character-level prop streaming is visible in the rendered UI (e.g., city name filling in letter by letter) +5. The full pipeline works without any manual wiring — just `[views]` input on `ChatComponent` + +## What It Defers + +- Real-world data (Phase 2: analytics dashboard with charts, data grids, tool calling) +- A2UI support +- Production error handling for malformed JSON +- Interactive state store integration From 5170d7132ce20a60b399b80202306b0c979ff07c Mon Sep 17 00:00:00 2001 From: Brian Love Date: Wed, 8 Apr 2026 16:33:39 -0700 Subject: [PATCH 2/8] docs: add generative UI spike implementation plan --- .../plans/2026-04-08-generative-ui-spike.md | 814 ++++++++++++++++++ 1 file changed, 814 insertions(+) create mode 100644 docs/superpowers/plans/2026-04-08-generative-ui-spike.md diff --git a/docs/superpowers/plans/2026-04-08-generative-ui-spike.md b/docs/superpowers/plans/2026-04-08-generative-ui-spike.md new file mode 100644 index 000000000..d05fde8ef --- /dev/null +++ b/docs/superpowers/plans/2026-04-08-generative-ui-spike.md @@ -0,0 +1,814 @@ +# Generative UI Spike — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add a cockpit capability at `cockpit/langgraph/generative-ui/` that proves the streaming generative UI pipeline end-to-end — LLM streams JSON spec tokens → auto-detected → parsed → rendered as Angular components. + +**Architecture:** Follow the existing cockpit capability pattern (`cockpit/langgraph/streaming/` as template). Python graph instructs the LLM to return a JSON-render Spec. Angular frontend uses `ChatComponent` with `[views]` input — two simple view components (WeatherCard, StatCard). Registration via manifest + route-resolution. + +**Tech Stack:** LangGraph (Python), Angular 20+, `@cacheplane/chat`, `@cacheplane/render`, Tailwind CSS + +--- + +## File Structure + +### New Files (Python Backend) + +| File | Purpose | +|------|---------| +| `cockpit/langgraph/generative-ui/python/src/graph.py` | LangGraph graph that generates JSON-render Specs | +| `cockpit/langgraph/generative-ui/python/src/index.ts` | Module metadata | +| `cockpit/langgraph/generative-ui/python/prompts/generative-ui.md` | System prompt with Spec schema | +| `cockpit/langgraph/generative-ui/python/docs/guide.md` | Narrative docs | +| `cockpit/langgraph/generative-ui/python/langgraph.json` | LangGraph config | +| `cockpit/langgraph/generative-ui/python/project.json` | Nx project config | + +### New Files (Angular Frontend) + +| File | Purpose | +|------|---------| +| `cockpit/langgraph/generative-ui/angular/src/app/generative-ui.component.ts` | Main app component with ChatComponent + views | +| `cockpit/langgraph/generative-ui/angular/src/app/app.config.ts` | Angular providers | +| `cockpit/langgraph/generative-ui/angular/src/app/views/weather-card.component.ts` | WeatherCard view component | +| `cockpit/langgraph/generative-ui/angular/src/app/views/stat-card.component.ts` | StatCard view component | +| `cockpit/langgraph/generative-ui/angular/src/main.ts` | Bootstrap | +| `cockpit/langgraph/generative-ui/angular/src/environments/environment.ts` | Production env | +| `cockpit/langgraph/generative-ui/angular/src/environments/environment.development.ts` | Dev env | +| `cockpit/langgraph/generative-ui/angular/src/index.html` | HTML shell | +| `cockpit/langgraph/generative-ui/angular/src/styles.css` | Tailwind + design tokens | +| `cockpit/langgraph/generative-ui/angular/project.json` | Nx Angular app config | +| `cockpit/langgraph/generative-ui/angular/tsconfig.json` | TS config | +| `cockpit/langgraph/generative-ui/angular/tsconfig.app.json` | App TS config | +| `cockpit/langgraph/generative-ui/angular/proxy.conf.json` | Dev proxy | +| `cockpit/langgraph/generative-ui/angular/package.json` | NPM metadata | +| `cockpit/langgraph/generative-ui/angular/vercel.json` | Vercel build config | + +### Modified Files (Registration) + +| File | Change | +|------|--------| +| `libs/cockpit-registry/src/lib/manifest.ts` | Add `'generative-ui'` to langgraph core-capabilities | +| `apps/cockpit/src/lib/route-resolution.ts` | Import and register the module | + +--- + +### Task 1: Python Backend — Graph + Prompt + Config + +**Files:** +- Create: `cockpit/langgraph/generative-ui/python/src/graph.py` +- Create: `cockpit/langgraph/generative-ui/python/prompts/generative-ui.md` +- Create: `cockpit/langgraph/generative-ui/python/langgraph.json` +- Create: `cockpit/langgraph/generative-ui/python/src/index.ts` +- Create: `cockpit/langgraph/generative-ui/python/project.json` +- Create: `cockpit/langgraph/generative-ui/python/docs/guide.md` + +- [ ] **Step 1: Create the system prompt** + +Create `cockpit/langgraph/generative-ui/python/prompts/generative-ui.md`: + +````markdown +# Generative UI Assistant + +You are a helpful assistant that responds with structured JSON UI specifications. + +When the user asks about weather, locations, or data, respond with a JSON object that follows this exact schema: + +```json +{ + "root": "", + "elements": { + "": { + "type": "", + "props": { ... }, + "children": ["", ""] + } + } +} +``` + +## Available component types + +### `container` +A layout wrapper that renders its children vertically. +- Props: none required +- Children: array of element keys + +### `weather_card` +Displays weather information for a city. +- Props: + - `city` (string): City name + - `temperature` (number): Temperature in Fahrenheit + - `condition` (string): Weather condition (e.g., "Sunny", "Cloudy", "Rainy") + +### `stat_card` +Displays a single statistic. +- Props: + - `label` (string): What the stat measures (e.g., "Humidity", "Wind Speed") + - `value` (string): The formatted value (e.g., "65%", "12 mph") + +## Rules + +1. Always respond with ONLY a valid JSON object — no markdown, no explanation, no code fences +2. Use a `container` as the root element when you have multiple components +3. Give each element a unique key (e.g., "root", "weather", "stat-1", "stat-2") +4. Include 2-4 elements total for variety +5. Make the data realistic and varied + +## Example response + +For "What's the weather in Seattle?": + +```json +{ + "root": "root", + "elements": { + "root": { + "type": "container", + "props": {}, + "children": ["weather", "stat-humidity", "stat-wind"] + }, + "weather": { + "type": "weather_card", + "props": { + "city": "Seattle", + "temperature": 58, + "condition": "Overcast" + } + }, + "stat-humidity": { + "type": "stat_card", + "props": { + "label": "Humidity", + "value": "78%" + } + }, + "stat-wind": { + "type": "stat_card", + "props": { + "label": "Wind Speed", + "value": "8 mph NW" + } + } + } +} +``` +```` + +- [ ] **Step 2: Create the Python graph** + +Create `cockpit/langgraph/generative-ui/python/src/graph.py`: + +```python +""" +LangGraph Generative UI Graph + +A StateGraph that instructs the LLM to return JSON-render Spec objects. +The Angular frontend auto-detects the JSON and renders it as Angular +components via the streaming generative UI pipeline. +""" + +from pathlib import Path +from langgraph.graph import StateGraph, MessagesState, END +from langchain_openai import ChatOpenAI +from langchain_core.messages import SystemMessage + +PROMPTS_DIR = Path(__file__).parent.parent / "prompts" + + +def build_generative_ui_graph(): + llm = ChatOpenAI(model="gpt-4o-mini", streaming=True) + + async def generate(state: MessagesState) -> dict: + system_prompt = (PROMPTS_DIR / "generative-ui.md").read_text() + messages = [SystemMessage(content=system_prompt)] + state["messages"] + response = await llm.ainvoke(messages) + return {"messages": [response]} + + graph = StateGraph(MessagesState) + graph.add_node("generate", generate) + graph.set_entry_point("generate") + graph.add_edge("generate", END) + + return graph.compile() + + +graph = build_generative_ui_graph() +``` + +- [ ] **Step 3: Create langgraph.json** + +Create `cockpit/langgraph/generative-ui/python/langgraph.json`: + +```json +{ + "graphs": { + "generative_ui": "./src/graph.py:graph" + }, + "dependencies": ["."], + "python_version": "3.12", + "env": ".env" +} +``` + +- [ ] **Step 4: Create module metadata** + +Create `cockpit/langgraph/generative-ui/python/src/index.ts`: + +```typescript +export interface CockpitCapabilityModule { + id: string; + manifestIdentity: { + product: 'langgraph'; + section: 'core-capabilities'; + topic: 'generative-ui'; + page: 'overview'; + language: 'python'; + }; + title: string; + docsPath: string; + promptAssetPaths: string[]; + codeAssetPaths: string[]; + backendAssetPaths: string[]; + docsAssetPaths: string[]; + runtimeUrl?: string; + devPort?: number; +} + +export const langgraphGenerativeUiPythonModule: CockpitCapabilityModule = { + id: 'langgraph-generative-ui-python', + manifestIdentity: { + product: 'langgraph', + section: 'core-capabilities', + topic: 'generative-ui', + page: 'overview', + language: 'python', + }, + title: 'LangGraph Generative UI (Python)', + docsPath: '/docs/langgraph/core-capabilities/generative-ui/overview/python', + promptAssetPaths: ['cockpit/langgraph/generative-ui/python/prompts/generative-ui.md'], + codeAssetPaths: [ + 'cockpit/langgraph/generative-ui/angular/src/app/generative-ui.component.ts', + 'cockpit/langgraph/generative-ui/angular/src/app/app.config.ts', + 'cockpit/langgraph/generative-ui/angular/src/app/views/weather-card.component.ts', + 'cockpit/langgraph/generative-ui/angular/src/app/views/stat-card.component.ts', + ], + backendAssetPaths: [ + 'cockpit/langgraph/generative-ui/python/src/graph.py', + ], + docsAssetPaths: ['cockpit/langgraph/generative-ui/python/docs/guide.md'], + runtimeUrl: 'langgraph/generative-ui', + devPort: 4310, +}; +``` + +- [ ] **Step 5: Create Nx project config** + +Create `cockpit/langgraph/generative-ui/python/project.json`: + +```json +{ + "name": "cockpit-langgraph-generative-ui-python", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "cockpit/langgraph/generative-ui/python/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{workspaceRoot}/dist/cockpit/langgraph/generative-ui/python"], + "options": { + "outputPath": "dist/cockpit/langgraph/generative-ui/python", + "main": "cockpit/langgraph/generative-ui/python/src/index.ts", + "tsConfig": "cockpit/langgraph/generative-ui/python/tsconfig.json" + } + }, + "smoke": { + "executor": "nx:run-commands", + "options": { + "cwd": "cockpit/langgraph/generative-ui/python", + "command": "npx tsx -e \"import { langgraphGenerativeUiPythonModule } from './src/index.ts'; const m = langgraphGenerativeUiPythonModule; if (m.id !== 'langgraph-generative-ui-python') { throw new Error('Unexpected module: ' + m.id); }\"" + } + } + } +} +``` + +- [ ] **Step 6: Create docs guide** + +Create `cockpit/langgraph/generative-ui/python/docs/guide.md`: + +````markdown +# Generative UI + +This example demonstrates streaming generative UI — an LLM returns JSON-render Specs that are auto-detected and rendered as Angular components in real time. + +## How It Works + +1. The LangGraph agent receives a user message +2. The LLM generates a JSON-render Spec as its response (not markdown) +3. Tokens stream to the Angular frontend via SSE +4. `ChatComponent` auto-detects the JSON via `ContentClassifier` +5. `@cacheplane/partial-json` parses the incomplete JSON character-by-character +6. `ParseTreeStore` materializes the parse tree into a `Spec` signal +7. `RenderSpecComponent` renders the spec using the view registry +8. Components update live as tokens arrive — string props grow visibly + +## View Components + +This example registers two view components: + +- **WeatherCard** — Displays city, temperature, and weather condition +- **StatCard** — Displays a label/value pair (humidity, wind speed, etc.) + +## Key Code + +```typescript +// Register views +const myViews = views({ + weather_card: WeatherCardComponent, + stat_card: StatCardComponent, + container: ContainerComponent, +}); + +// Pass to ChatComponent + +``` + +No manual JSON parsing, no content type detection, no spec wiring — the `ChatComponent` handles everything automatically. +```` + +- [ ] **Step 7: Create Python tsconfig.json** + +Create `cockpit/langgraph/generative-ui/python/tsconfig.json`: + +```json +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "../../../../dist/out-tsc" + }, + "include": ["src/**/*.ts"] +} +``` + +- [ ] **Step 8: Commit** + +```bash +git add cockpit/langgraph/generative-ui/python/ +git commit -m "feat(cockpit): add generative UI Python graph and module metadata" +``` + +--- + +### Task 2: Angular Frontend — View Components + App + +**Files:** +- Create: All files under `cockpit/langgraph/generative-ui/angular/` + +- [ ] **Step 1: Create WeatherCardComponent** + +Create `cockpit/langgraph/generative-ui/angular/src/app/views/weather-card.component.ts`: + +```typescript +// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 +import { Component, input } from '@angular/core'; + +@Component({ + selector: 'app-weather-card', + standalone: true, + template: ` +
+
+

{{ city() }}

+ {{ weatherEmoji() }} +
+
{{ temperature() }}°F
+
{{ condition() }}
+
+ `, +}) +export class WeatherCardComponent { + readonly city = input(''); + readonly temperature = input(0); + readonly condition = input(''); + + weatherEmoji(): string { + const c = this.condition().toLowerCase(); + if (c.includes('sun') || c.includes('clear')) return '☀️'; + if (c.includes('cloud') || c.includes('overcast')) return '☁️'; + if (c.includes('rain')) return '🌧️'; + if (c.includes('snow')) return '❄️'; + if (c.includes('storm') || c.includes('thunder')) return '⛈️'; + return '🌤️'; + } +} +``` + +- [ ] **Step 2: Create StatCardComponent** + +Create `cockpit/langgraph/generative-ui/angular/src/app/views/stat-card.component.ts`: + +```typescript +// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 +import { Component, input } from '@angular/core'; + +@Component({ + selector: 'app-stat-card', + standalone: true, + template: ` +
+
{{ label() }}
+
{{ value() }}
+
+ `, +}) +export class StatCardComponent { + readonly label = input(''); + readonly value = input(''); +} +``` + +- [ ] **Step 3: Create ContainerComponent** + +Create `cockpit/langgraph/generative-ui/angular/src/app/views/container.component.ts`: + +```typescript +// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 +import { Component, input } from '@angular/core'; +import type { Spec } from '@json-render/core'; +import { RenderElementComponent } from '@cacheplane/render'; + +@Component({ + selector: 'app-container', + standalone: true, + imports: [RenderElementComponent], + template: ` +
+ @for (key of childKeys(); track key) { + + } +
+ `, +}) +export class ContainerComponent { + readonly childKeys = input([]); + readonly spec = input.required(); +} +``` + +- [ ] **Step 4: Create main app component** + +Create `cockpit/langgraph/generative-ui/angular/src/app/generative-ui.component.ts`: + +```typescript +// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 +import { Component } from '@angular/core'; +import { ChatComponent, views } from '@cacheplane/chat'; +import { agent } from '@cacheplane/angular'; +import { environment } from '../environments/environment'; +import { WeatherCardComponent } from './views/weather-card.component'; +import { StatCardComponent } from './views/stat-card.component'; +import { ContainerComponent } from './views/container.component'; + +const myViews = views({ + weather_card: WeatherCardComponent, + stat_card: StatCardComponent, + container: ContainerComponent, +}); + +@Component({ + selector: 'app-generative-ui', + standalone: true, + imports: [ChatComponent], + template: ``, +}) +export class GenerativeUiComponent { + protected readonly agentRef = agent({ + apiUrl: environment.langGraphApiUrl, + assistantId: environment.generativeUiAssistantId, + }); + + protected readonly myViews = myViews; +} +``` + +- [ ] **Step 5: Create app config** + +Create `cockpit/langgraph/generative-ui/angular/src/app/app.config.ts`: + +```typescript +// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 +import { ApplicationConfig } from '@angular/core'; +import { provideAgent } from '@cacheplane/angular'; +import { provideChat } from '@cacheplane/chat'; +import { environment } from '../environments/environment'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideAgent({ apiUrl: environment.langGraphApiUrl }), + provideChat({}), + ], +}; +``` + +- [ ] **Step 6: Create bootstrap + environments + config files** + +Create `cockpit/langgraph/generative-ui/angular/src/main.ts`: + +```typescript +// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 +import { bootstrapApplication } from '@angular/platform-browser'; +import { appConfig } from './app/app.config'; +import { GenerativeUiComponent } from './app/generative-ui.component'; + +bootstrapApplication(GenerativeUiComponent, appConfig).catch(console.error); +``` + +Create `cockpit/langgraph/generative-ui/angular/src/environments/environment.ts`: + +```typescript +export const environment = { + production: true, + langGraphApiUrl: '/api', + generativeUiAssistantId: 'generative_ui', +}; +``` + +Create `cockpit/langgraph/generative-ui/angular/src/environments/environment.development.ts`: + +```typescript +export const environment = { + production: false, + langGraphApiUrl: 'http://localhost:4310/api', + generativeUiAssistantId: 'generative_ui', +}; +``` + +Create `cockpit/langgraph/generative-ui/angular/src/index.html`: + +```html + + + + + LangGraph Generative UI — Angular + + + + + + + + +``` + +Create `cockpit/langgraph/generative-ui/angular/src/styles.css` (copy streaming pattern): + +```css +@import "../../../../../libs/design-tokens/src/lib/tokens.css"; +@import "tailwindcss"; +@source "../../../../../libs/chat/src/"; + +@theme { + --color-bg: var(--ds-bg); + --color-surface: #ffffff; + --color-accent: var(--ds-accent); + --color-accent-light: var(--ds-accent-light); + --color-text-primary: var(--ds-text-primary); + --color-text-secondary: var(--ds-text-secondary); + --color-text-muted: var(--ds-text-muted); + --color-border: var(--ds-accent-border); + --color-error: #ef4444; + --color-success: #22c55e; + --font-sans: var(--ds-font-sans); + --font-serif: var(--ds-font-serif); + --font-mono: var(--ds-font-mono); +} + +*, *::before, *::after { box-sizing: border-box; } + +body { + margin: 0; + font-family: var(--ds-font-sans); + background: var(--ds-bg); + color: var(--ds-text-primary); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +``` + +Create `cockpit/langgraph/generative-ui/angular/proxy.conf.json`: + +```json +{ + "/api": { + "target": "http://localhost:8123", + "secure": false, + "changeOrigin": true, + "pathRewrite": { "^/api": "" }, + "ws": true + } +} +``` + +Create `cockpit/langgraph/generative-ui/angular/package.json`: + +```json +{ + "name": "@cacheplane/cockpit-langgraph-generative-ui-angular", + "version": "0.0.1", + "peerDependencies": { + "@cacheplane/chat": "^0.0.1", + "@cacheplane/render": "^0.0.1", + "@cacheplane/angular": "^0.0.1", + "@json-render/core": "^0.16.0", + "@langchain/langgraph-sdk": "^0.0.36" + }, + "license": "PolyForm-Noncommercial-1.0.0", + "sideEffects": false +} +``` + +Create `cockpit/langgraph/generative-ui/angular/vercel.json`: + +```json +{ + "$schema": "https://openapi.vercel.sh/vercel.json", + "buildCommand": "npx nx build cockpit-langgraph-generative-ui-angular", + "outputDirectory": "dist/cockpit/langgraph/generative-ui/angular/browser", + "framework": null +} +``` + +Create `cockpit/langgraph/generative-ui/angular/tsconfig.json`: + +```json +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "noPropertyAccessFromIndexSignature": false, + "experimentalDecorators": true, + "module": "preserve", + "emitDeclarationOnly": false, + "composite": false, + "lib": ["es2022", "dom"], + "skipLibCheck": true, + "strict": false + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": false, + "strictInputAccessModifiers": false, + "strictTemplates": false + }, + "files": [], + "include": [], + "references": [ + { "path": "./tsconfig.app.json" } + ] +} +``` + +Create `cockpit/langgraph/generative-ui/angular/tsconfig.app.json`: + +```json +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../../dist/out-tsc", + "lib": ["es2022", "dom"], + "types": [], + "emitDeclarationOnly": false + }, + "files": ["src/main.ts"], + "include": ["src/**/*.ts"] +} +``` + +Create `cockpit/langgraph/generative-ui/angular/project.json`: + +```json +{ + "name": "cockpit-langgraph-generative-ui-angular", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "cockpit/langgraph/generative-ui/angular/src", + "projectType": "application", + "targets": { + "build": { + "executor": "@angular/build:application", + "outputs": ["{options.outputPath.base}"], + "options": { + "outputPath": { + "base": "dist/cockpit/langgraph/generative-ui/angular", + "browser": "" + }, + "browser": "cockpit/langgraph/generative-ui/angular/src/main.ts", + "tsConfig": "cockpit/langgraph/generative-ui/angular/tsconfig.app.json", + "styles": ["cockpit/langgraph/generative-ui/angular/src/styles.css"] + }, + "configurations": { + "production": { + "budgets": [ + { "type": "initial", "maximumWarning": "500kb", "maximumError": "1mb" }, + { "type": "anyComponentStyle", "maximumWarning": "4kb", "maximumError": "8kb" } + ], + "outputHashing": "none" + }, + "development": { + "optimization": false, + "extractLicenses": false, + "sourceMap": true, + "fileReplacements": [ + { + "replace": "cockpit/langgraph/generative-ui/angular/src/environments/environment.ts", + "with": "cockpit/langgraph/generative-ui/angular/src/environments/environment.development.ts" + } + ] + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "continuous": true, + "executor": "@angular/build:dev-server", + "configurations": { + "production": { "buildTarget": "cockpit-langgraph-generative-ui-angular:build:production" }, + "development": { "buildTarget": "cockpit-langgraph-generative-ui-angular:build:development" } + }, + "defaultConfiguration": "development", + "options": { + "proxyConfig": "cockpit/langgraph/generative-ui/angular/proxy.conf.json" + } + } + } +} +``` + +- [ ] **Step 7: Commit** + +```bash +git add cockpit/langgraph/generative-ui/angular/ +git commit -m "feat(cockpit): add generative UI Angular frontend with view components" +``` + +--- + +### Task 3: Registration — Manifest + Route Resolution + +**Files:** +- Modify: `libs/cockpit-registry/src/lib/manifest.ts` +- Modify: `apps/cockpit/src/lib/route-resolution.ts` + +- [ ] **Step 1: Add generative-ui to APPROVED_TOPICS** + +In `libs/cockpit-registry/src/lib/manifest.ts`, add `'generative-ui'` to the langgraph core-capabilities array: + +```typescript +langgraph: { + 'getting-started': ['overview'], + 'core-capabilities': [ + 'persistence', + 'durable-execution', + 'streaming', + 'interrupts', + 'memory', + 'subgraphs', + 'time-travel', + 'deployment-runtime', + 'generative-ui', + ], +}, +``` + +- [ ] **Step 2: Register module in route-resolution.ts** + +Add import at the top: + +```typescript +import { langgraphGenerativeUiPythonModule } from '../../../../cockpit/langgraph/generative-ui/python/src/index'; +``` + +Add to the `capabilityModules` array: + +```typescript +const capabilityModules = [ + // ... existing modules ... + langgraphGenerativeUiPythonModule, +]; +``` + +- [ ] **Step 3: Verify smoke test passes** + +Run: `export PATH="/Users/blove/.nvm/versions/node/v22.14.0/bin:$PATH" && npx nx smoke cockpit-langgraph-generative-ui-python` +Expected: PASS + +- [ ] **Step 4: Verify Angular build succeeds** + +Run: `export PATH="/Users/blove/.nvm/versions/node/v22.14.0/bin:$PATH" && npx nx build cockpit-langgraph-generative-ui-angular` +Expected: BUILD SUCCESS + +- [ ] **Step 5: Commit** + +```bash +git add libs/cockpit-registry/ apps/cockpit/ +git commit -m "feat(cockpit): register generative-ui capability in manifest and routes" +``` From 695352575dc56e72f7e41adf5fa269fdd19320da Mon Sep 17 00:00:00 2001 From: Brian Love Date: Wed, 8 Apr 2026 16:35:02 -0700 Subject: [PATCH 3/8] feat(cockpit): add generative UI Python graph and module metadata --- .../generative-ui/python/docs/guide.md | 37 ++++++++ .../generative-ui/python/langgraph.json | 8 ++ .../generative-ui/python/project.json | 24 ++++++ .../python/prompts/generative-ui.md | 85 +++++++++++++++++++ .../generative-ui/python/src/graph.py | 34 ++++++++ .../generative-ui/python/src/index.ts | 44 ++++++++++ .../generative-ui/python/tsconfig.json | 7 ++ 7 files changed, 239 insertions(+) create mode 100644 cockpit/langgraph/generative-ui/python/docs/guide.md create mode 100644 cockpit/langgraph/generative-ui/python/langgraph.json create mode 100644 cockpit/langgraph/generative-ui/python/project.json create mode 100644 cockpit/langgraph/generative-ui/python/prompts/generative-ui.md create mode 100644 cockpit/langgraph/generative-ui/python/src/graph.py create mode 100644 cockpit/langgraph/generative-ui/python/src/index.ts create mode 100644 cockpit/langgraph/generative-ui/python/tsconfig.json diff --git a/cockpit/langgraph/generative-ui/python/docs/guide.md b/cockpit/langgraph/generative-ui/python/docs/guide.md new file mode 100644 index 000000000..907375451 --- /dev/null +++ b/cockpit/langgraph/generative-ui/python/docs/guide.md @@ -0,0 +1,37 @@ +# Generative UI + +This example demonstrates streaming generative UI — an LLM returns JSON-render Specs that are auto-detected and rendered as Angular components in real time. + +## How It Works + +1. The LangGraph agent receives a user message +2. The LLM generates a JSON-render Spec as its response (not markdown) +3. Tokens stream to the Angular frontend via SSE +4. `ChatComponent` auto-detects the JSON via `ContentClassifier` +5. `@cacheplane/partial-json` parses the incomplete JSON character-by-character +6. `ParseTreeStore` materializes the parse tree into a `Spec` signal +7. `RenderSpecComponent` renders the spec using the view registry +8. Components update live as tokens arrive — string props grow visibly + +## View Components + +This example registers two view components: + +- **WeatherCard** — Displays city, temperature, and weather condition +- **StatCard** — Displays a label/value pair (humidity, wind speed, etc.) + +## Key Code + +```typescript +// Register views +const myViews = views({ + weather_card: WeatherCardComponent, + stat_card: StatCardComponent, + container: ContainerComponent, +}); + +// Pass to ChatComponent + +``` + +No manual JSON parsing, no content type detection, no spec wiring — the `ChatComponent` handles everything automatically. diff --git a/cockpit/langgraph/generative-ui/python/langgraph.json b/cockpit/langgraph/generative-ui/python/langgraph.json new file mode 100644 index 000000000..cd12bd55e --- /dev/null +++ b/cockpit/langgraph/generative-ui/python/langgraph.json @@ -0,0 +1,8 @@ +{ + "graphs": { + "generative_ui": "./src/graph.py:graph" + }, + "dependencies": ["."], + "python_version": "3.12", + "env": ".env" +} diff --git a/cockpit/langgraph/generative-ui/python/project.json b/cockpit/langgraph/generative-ui/python/project.json new file mode 100644 index 000000000..8700133f5 --- /dev/null +++ b/cockpit/langgraph/generative-ui/python/project.json @@ -0,0 +1,24 @@ +{ + "name": "cockpit-langgraph-generative-ui-python", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "cockpit/langgraph/generative-ui/python/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{workspaceRoot}/dist/cockpit/langgraph/generative-ui/python"], + "options": { + "outputPath": "dist/cockpit/langgraph/generative-ui/python", + "main": "cockpit/langgraph/generative-ui/python/src/index.ts", + "tsConfig": "cockpit/langgraph/generative-ui/python/tsconfig.json" + } + }, + "smoke": { + "executor": "nx:run-commands", + "options": { + "cwd": "cockpit/langgraph/generative-ui/python", + "command": "npx tsx -e \"import { langgraphGenerativeUiPythonModule } from './src/index.ts'; const m = langgraphGenerativeUiPythonModule; if (m.id !== 'langgraph-generative-ui-python') { throw new Error('Unexpected module: ' + m.id); }\"" + } + } + } +} diff --git a/cockpit/langgraph/generative-ui/python/prompts/generative-ui.md b/cockpit/langgraph/generative-ui/python/prompts/generative-ui.md new file mode 100644 index 000000000..0812d3dfb --- /dev/null +++ b/cockpit/langgraph/generative-ui/python/prompts/generative-ui.md @@ -0,0 +1,85 @@ +# Generative UI Assistant + +You are a helpful assistant that responds with structured JSON UI specifications. + +When the user asks about weather, locations, or data, respond with a JSON object that follows this exact schema: + +```json +{ + "root": "", + "elements": { + "": { + "type": "", + "props": { ... }, + "children": ["", ""] + } + } +} +``` + +## Available component types + +### `container` +A layout wrapper that renders its children vertically. +- Props: none required +- Children: array of element keys + +### `weather_card` +Displays weather information for a city. +- Props: + - `city` (string): City name + - `temperature` (number): Temperature in Fahrenheit + - `condition` (string): Weather condition (e.g., "Sunny", "Cloudy", "Rainy") + +### `stat_card` +Displays a single statistic. +- Props: + - `label` (string): What the stat measures (e.g., "Humidity", "Wind Speed") + - `value` (string): The formatted value (e.g., "65%", "12 mph") + +## Rules + +1. Always respond with ONLY a valid JSON object — no markdown, no explanation, no code fences +2. Use a `container` as the root element when you have multiple components +3. Give each element a unique key (e.g., "root", "weather", "stat-1", "stat-2") +4. Include 2-4 elements total for variety +5. Make the data realistic and varied + +## Example response + +For "What's the weather in Seattle?": + +```json +{ + "root": "root", + "elements": { + "root": { + "type": "container", + "props": {}, + "children": ["weather", "stat-humidity", "stat-wind"] + }, + "weather": { + "type": "weather_card", + "props": { + "city": "Seattle", + "temperature": 58, + "condition": "Overcast" + } + }, + "stat-humidity": { + "type": "stat_card", + "props": { + "label": "Humidity", + "value": "78%" + } + }, + "stat-wind": { + "type": "stat_card", + "props": { + "label": "Wind Speed", + "value": "8 mph NW" + } + } + } +} +``` diff --git a/cockpit/langgraph/generative-ui/python/src/graph.py b/cockpit/langgraph/generative-ui/python/src/graph.py new file mode 100644 index 000000000..b44ad0f1f --- /dev/null +++ b/cockpit/langgraph/generative-ui/python/src/graph.py @@ -0,0 +1,34 @@ +""" +LangGraph Generative UI Graph + +A StateGraph that instructs the LLM to return JSON-render Spec objects. +The Angular frontend auto-detects the JSON and renders it as Angular +components via the streaming generative UI pipeline. +""" + +from pathlib import Path +from langgraph.graph import StateGraph, MessagesState, END +from langchain_openai import ChatOpenAI +from langchain_core.messages import SystemMessage + +PROMPTS_DIR = Path(__file__).parent.parent / "prompts" + + +def build_generative_ui_graph(): + llm = ChatOpenAI(model="gpt-4o-mini", streaming=True) + + async def generate(state: MessagesState) -> dict: + system_prompt = (PROMPTS_DIR / "generative-ui.md").read_text() + messages = [SystemMessage(content=system_prompt)] + state["messages"] + response = await llm.ainvoke(messages) + return {"messages": [response]} + + graph = StateGraph(MessagesState) + graph.add_node("generate", generate) + graph.set_entry_point("generate") + graph.add_edge("generate", END) + + return graph.compile() + + +graph = build_generative_ui_graph() diff --git a/cockpit/langgraph/generative-ui/python/src/index.ts b/cockpit/langgraph/generative-ui/python/src/index.ts new file mode 100644 index 000000000..3ea0cafef --- /dev/null +++ b/cockpit/langgraph/generative-ui/python/src/index.ts @@ -0,0 +1,44 @@ +export interface CockpitCapabilityModule { + id: string; + manifestIdentity: { + product: 'langgraph'; + section: 'core-capabilities'; + topic: 'generative-ui'; + page: 'overview'; + language: 'python'; + }; + title: string; + docsPath: string; + promptAssetPaths: string[]; + codeAssetPaths: string[]; + backendAssetPaths: string[]; + docsAssetPaths: string[]; + runtimeUrl?: string; + devPort?: number; +} + +export const langgraphGenerativeUiPythonModule: CockpitCapabilityModule = { + id: 'langgraph-generative-ui-python', + manifestIdentity: { + product: 'langgraph', + section: 'core-capabilities', + topic: 'generative-ui', + page: 'overview', + language: 'python', + }, + title: 'LangGraph Generative UI (Python)', + docsPath: '/docs/langgraph/core-capabilities/generative-ui/overview/python', + promptAssetPaths: ['cockpit/langgraph/generative-ui/python/prompts/generative-ui.md'], + codeAssetPaths: [ + 'cockpit/langgraph/generative-ui/angular/src/app/generative-ui.component.ts', + 'cockpit/langgraph/generative-ui/angular/src/app/app.config.ts', + 'cockpit/langgraph/generative-ui/angular/src/app/views/weather-card.component.ts', + 'cockpit/langgraph/generative-ui/angular/src/app/views/stat-card.component.ts', + ], + backendAssetPaths: [ + 'cockpit/langgraph/generative-ui/python/src/graph.py', + ], + docsAssetPaths: ['cockpit/langgraph/generative-ui/python/docs/guide.md'], + runtimeUrl: 'langgraph/generative-ui', + devPort: 4310, +}; diff --git a/cockpit/langgraph/generative-ui/python/tsconfig.json b/cockpit/langgraph/generative-ui/python/tsconfig.json new file mode 100644 index 000000000..6df4fdaa6 --- /dev/null +++ b/cockpit/langgraph/generative-ui/python/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "../../../../dist/out-tsc" + }, + "include": ["src/**/*.ts"] +} From b231344ce7f2e9cbec602e77e90bf4d8660499c4 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Wed, 8 Apr 2026 16:36:52 -0700 Subject: [PATCH 4/8] feat(cockpit): add generative UI Angular frontend with view components --- .../generative-ui/angular/package.json | 13 +++++ .../generative-ui/angular/project.json | 54 +++++++++++++++++++ .../generative-ui/angular/proxy.conf.json | 9 ++++ .../angular/src/app/app.config.ts | 12 +++++ .../src/app/generative-ui.component.ts | 29 ++++++++++ .../src/app/views/container.component.ts | 21 ++++++++ .../src/app/views/stat-card.component.ts | 17 ++++++ .../src/app/views/weather-card.component.ts | 32 +++++++++++ .../environments/environment.development.ts | 5 ++ .../angular/src/environments/environment.ts | 5 ++ .../generative-ui/angular/src/index.html | 13 +++++ .../generative-ui/angular/src/main.ts | 6 +++ .../generative-ui/angular/src/styles.css | 30 +++++++++++ .../generative-ui/angular/tsconfig.app.json | 11 ++++ .../generative-ui/angular/tsconfig.json | 24 +++++++++ .../generative-ui/angular/vercel.json | 6 +++ 16 files changed, 287 insertions(+) create mode 100644 cockpit/langgraph/generative-ui/angular/package.json create mode 100644 cockpit/langgraph/generative-ui/angular/project.json create mode 100644 cockpit/langgraph/generative-ui/angular/proxy.conf.json create mode 100644 cockpit/langgraph/generative-ui/angular/src/app/app.config.ts create mode 100644 cockpit/langgraph/generative-ui/angular/src/app/generative-ui.component.ts create mode 100644 cockpit/langgraph/generative-ui/angular/src/app/views/container.component.ts create mode 100644 cockpit/langgraph/generative-ui/angular/src/app/views/stat-card.component.ts create mode 100644 cockpit/langgraph/generative-ui/angular/src/app/views/weather-card.component.ts create mode 100644 cockpit/langgraph/generative-ui/angular/src/environments/environment.development.ts create mode 100644 cockpit/langgraph/generative-ui/angular/src/environments/environment.ts create mode 100644 cockpit/langgraph/generative-ui/angular/src/index.html create mode 100644 cockpit/langgraph/generative-ui/angular/src/main.ts create mode 100644 cockpit/langgraph/generative-ui/angular/src/styles.css create mode 100644 cockpit/langgraph/generative-ui/angular/tsconfig.app.json create mode 100644 cockpit/langgraph/generative-ui/angular/tsconfig.json create mode 100644 cockpit/langgraph/generative-ui/angular/vercel.json diff --git a/cockpit/langgraph/generative-ui/angular/package.json b/cockpit/langgraph/generative-ui/angular/package.json new file mode 100644 index 000000000..61fcc023c --- /dev/null +++ b/cockpit/langgraph/generative-ui/angular/package.json @@ -0,0 +1,13 @@ +{ + "name": "@cacheplane/cockpit-langgraph-generative-ui-angular", + "version": "0.0.1", + "peerDependencies": { + "@cacheplane/chat": "^0.0.1", + "@cacheplane/render": "^0.0.1", + "@cacheplane/angular": "^0.0.1", + "@json-render/core": "^0.16.0", + "@langchain/langgraph-sdk": "^0.0.36" + }, + "license": "PolyForm-Noncommercial-1.0.0", + "sideEffects": false +} diff --git a/cockpit/langgraph/generative-ui/angular/project.json b/cockpit/langgraph/generative-ui/angular/project.json new file mode 100644 index 000000000..dd2431cb2 --- /dev/null +++ b/cockpit/langgraph/generative-ui/angular/project.json @@ -0,0 +1,54 @@ +{ + "name": "cockpit-langgraph-generative-ui-angular", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "cockpit/langgraph/generative-ui/angular/src", + "projectType": "application", + "targets": { + "build": { + "executor": "@angular/build:application", + "outputs": ["{options.outputPath.base}"], + "options": { + "outputPath": { + "base": "dist/cockpit/langgraph/generative-ui/angular", + "browser": "" + }, + "browser": "cockpit/langgraph/generative-ui/angular/src/main.ts", + "tsConfig": "cockpit/langgraph/generative-ui/angular/tsconfig.app.json", + "styles": ["cockpit/langgraph/generative-ui/angular/src/styles.css"] + }, + "configurations": { + "production": { + "budgets": [ + { "type": "initial", "maximumWarning": "500kb", "maximumError": "1mb" }, + { "type": "anyComponentStyle", "maximumWarning": "4kb", "maximumError": "8kb" } + ], + "outputHashing": "none" + }, + "development": { + "optimization": false, + "extractLicenses": false, + "sourceMap": true, + "fileReplacements": [ + { + "replace": "cockpit/langgraph/generative-ui/angular/src/environments/environment.ts", + "with": "cockpit/langgraph/generative-ui/angular/src/environments/environment.development.ts" + } + ] + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "continuous": true, + "executor": "@angular/build:dev-server", + "configurations": { + "production": { "buildTarget": "cockpit-langgraph-generative-ui-angular:build:production" }, + "development": { "buildTarget": "cockpit-langgraph-generative-ui-angular:build:development" } + }, + "defaultConfiguration": "development", + "options": { + "proxyConfig": "cockpit/langgraph/generative-ui/angular/proxy.conf.json" + } + } + } +} diff --git a/cockpit/langgraph/generative-ui/angular/proxy.conf.json b/cockpit/langgraph/generative-ui/angular/proxy.conf.json new file mode 100644 index 000000000..8523362d7 --- /dev/null +++ b/cockpit/langgraph/generative-ui/angular/proxy.conf.json @@ -0,0 +1,9 @@ +{ + "/api": { + "target": "http://localhost:8123", + "secure": false, + "changeOrigin": true, + "pathRewrite": { "^/api": "" }, + "ws": true + } +} diff --git a/cockpit/langgraph/generative-ui/angular/src/app/app.config.ts b/cockpit/langgraph/generative-ui/angular/src/app/app.config.ts new file mode 100644 index 000000000..1e72bb3c9 --- /dev/null +++ b/cockpit/langgraph/generative-ui/angular/src/app/app.config.ts @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 +import { ApplicationConfig } from '@angular/core'; +import { provideAgent } from '@cacheplane/angular'; +import { provideChat } from '@cacheplane/chat'; +import { environment } from '../environments/environment'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideAgent({ apiUrl: environment.langGraphApiUrl }), + provideChat({}), + ], +}; diff --git a/cockpit/langgraph/generative-ui/angular/src/app/generative-ui.component.ts b/cockpit/langgraph/generative-ui/angular/src/app/generative-ui.component.ts new file mode 100644 index 000000000..475a5ae0e --- /dev/null +++ b/cockpit/langgraph/generative-ui/angular/src/app/generative-ui.component.ts @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 +import { Component } from '@angular/core'; +import { ChatComponent, views } from '@cacheplane/chat'; +import { agent } from '@cacheplane/angular'; +import { environment } from '../environments/environment'; +import { WeatherCardComponent } from './views/weather-card.component'; +import { StatCardComponent } from './views/stat-card.component'; +import { ContainerComponent } from './views/container.component'; + +const myViews = views({ + weather_card: WeatherCardComponent, + stat_card: StatCardComponent, + container: ContainerComponent, +}); + +@Component({ + selector: 'app-generative-ui', + standalone: true, + imports: [ChatComponent], + template: ``, +}) +export class GenerativeUiComponent { + protected readonly agentRef = agent({ + apiUrl: environment.langGraphApiUrl, + assistantId: environment.generativeUiAssistantId, + }); + + protected readonly myViews = myViews; +} diff --git a/cockpit/langgraph/generative-ui/angular/src/app/views/container.component.ts b/cockpit/langgraph/generative-ui/angular/src/app/views/container.component.ts new file mode 100644 index 000000000..29d5479a7 --- /dev/null +++ b/cockpit/langgraph/generative-ui/angular/src/app/views/container.component.ts @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 +import { Component, input } from '@angular/core'; +import type { Spec } from '@json-render/core'; +import { RenderElementComponent } from '@cacheplane/render'; + +@Component({ + selector: 'app-container', + standalone: true, + imports: [RenderElementComponent], + template: ` +
+ @for (key of childKeys(); track key) { + + } +
+ `, +}) +export class ContainerComponent { + readonly childKeys = input([]); + readonly spec = input.required(); +} diff --git a/cockpit/langgraph/generative-ui/angular/src/app/views/stat-card.component.ts b/cockpit/langgraph/generative-ui/angular/src/app/views/stat-card.component.ts new file mode 100644 index 000000000..0d5407cb9 --- /dev/null +++ b/cockpit/langgraph/generative-ui/angular/src/app/views/stat-card.component.ts @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 +import { Component, input } from '@angular/core'; + +@Component({ + selector: 'app-stat-card', + standalone: true, + template: ` +
+
{{ label() }}
+
{{ value() }}
+
+ `, +}) +export class StatCardComponent { + readonly label = input(''); + readonly value = input(''); +} diff --git a/cockpit/langgraph/generative-ui/angular/src/app/views/weather-card.component.ts b/cockpit/langgraph/generative-ui/angular/src/app/views/weather-card.component.ts new file mode 100644 index 000000000..9114640ec --- /dev/null +++ b/cockpit/langgraph/generative-ui/angular/src/app/views/weather-card.component.ts @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 +import { Component, input } from '@angular/core'; + +@Component({ + selector: 'app-weather-card', + standalone: true, + template: ` +
+
+

{{ city() }}

+ {{ weatherEmoji() }} +
+
{{ temperature() }}°F
+
{{ condition() }}
+
+ `, +}) +export class WeatherCardComponent { + readonly city = input(''); + readonly temperature = input(0); + readonly condition = input(''); + + weatherEmoji(): string { + const c = this.condition().toLowerCase(); + if (c.includes('sun') || c.includes('clear')) return '☀️'; + if (c.includes('cloud') || c.includes('overcast')) return '☁️'; + if (c.includes('rain')) return '🌧️'; + if (c.includes('snow')) return '❄️'; + if (c.includes('storm') || c.includes('thunder')) return '⛈️'; + return '🌤️'; + } +} diff --git a/cockpit/langgraph/generative-ui/angular/src/environments/environment.development.ts b/cockpit/langgraph/generative-ui/angular/src/environments/environment.development.ts new file mode 100644 index 000000000..32ff7a3bb --- /dev/null +++ b/cockpit/langgraph/generative-ui/angular/src/environments/environment.development.ts @@ -0,0 +1,5 @@ +export const environment = { + production: false, + langGraphApiUrl: 'http://localhost:4310/api', + generativeUiAssistantId: 'generative_ui', +}; diff --git a/cockpit/langgraph/generative-ui/angular/src/environments/environment.ts b/cockpit/langgraph/generative-ui/angular/src/environments/environment.ts new file mode 100644 index 000000000..1cdf59a2a --- /dev/null +++ b/cockpit/langgraph/generative-ui/angular/src/environments/environment.ts @@ -0,0 +1,5 @@ +export const environment = { + production: true, + langGraphApiUrl: '/api', + generativeUiAssistantId: 'generative_ui', +}; diff --git a/cockpit/langgraph/generative-ui/angular/src/index.html b/cockpit/langgraph/generative-ui/angular/src/index.html new file mode 100644 index 000000000..3327ab9d7 --- /dev/null +++ b/cockpit/langgraph/generative-ui/angular/src/index.html @@ -0,0 +1,13 @@ + + + + + LangGraph Generative UI — Angular + + + + + + + + diff --git a/cockpit/langgraph/generative-ui/angular/src/main.ts b/cockpit/langgraph/generative-ui/angular/src/main.ts new file mode 100644 index 000000000..862ebbc69 --- /dev/null +++ b/cockpit/langgraph/generative-ui/angular/src/main.ts @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 +import { bootstrapApplication } from '@angular/platform-browser'; +import { appConfig } from './app/app.config'; +import { GenerativeUiComponent } from './app/generative-ui.component'; + +bootstrapApplication(GenerativeUiComponent, appConfig).catch(console.error); diff --git a/cockpit/langgraph/generative-ui/angular/src/styles.css b/cockpit/langgraph/generative-ui/angular/src/styles.css new file mode 100644 index 000000000..061c66cf8 --- /dev/null +++ b/cockpit/langgraph/generative-ui/angular/src/styles.css @@ -0,0 +1,30 @@ +@import "../../../../../libs/design-tokens/src/lib/tokens.css"; +@import "tailwindcss"; +@source "../../../../../libs/chat/src/"; + +@theme { + --color-bg: var(--ds-bg); + --color-surface: #ffffff; + --color-accent: var(--ds-accent); + --color-accent-light: var(--ds-accent-light); + --color-text-primary: var(--ds-text-primary); + --color-text-secondary: var(--ds-text-secondary); + --color-text-muted: var(--ds-text-muted); + --color-border: var(--ds-accent-border); + --color-error: #ef4444; + --color-success: #22c55e; + --font-sans: var(--ds-font-sans); + --font-serif: var(--ds-font-serif); + --font-mono: var(--ds-font-mono); +} + +*, *::before, *::after { box-sizing: border-box; } + +body { + margin: 0; + font-family: var(--ds-font-sans); + background: var(--ds-bg); + color: var(--ds-text-primary); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} diff --git a/cockpit/langgraph/generative-ui/angular/tsconfig.app.json b/cockpit/langgraph/generative-ui/angular/tsconfig.app.json new file mode 100644 index 000000000..64731b107 --- /dev/null +++ b/cockpit/langgraph/generative-ui/angular/tsconfig.app.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../../dist/out-tsc", + "lib": ["es2022", "dom"], + "types": [], + "emitDeclarationOnly": false + }, + "files": ["src/main.ts"], + "include": ["src/**/*.ts"] +} diff --git a/cockpit/langgraph/generative-ui/angular/tsconfig.json b/cockpit/langgraph/generative-ui/angular/tsconfig.json new file mode 100644 index 000000000..3fd970371 --- /dev/null +++ b/cockpit/langgraph/generative-ui/angular/tsconfig.json @@ -0,0 +1,24 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "noPropertyAccessFromIndexSignature": false, + "experimentalDecorators": true, + "module": "preserve", + "emitDeclarationOnly": false, + "composite": false, + "lib": ["es2022", "dom"], + "skipLibCheck": true, + "strict": false + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": false, + "strictInputAccessModifiers": false, + "strictTemplates": false + }, + "files": [], + "include": [], + "references": [ + { "path": "./tsconfig.app.json" } + ] +} diff --git a/cockpit/langgraph/generative-ui/angular/vercel.json b/cockpit/langgraph/generative-ui/angular/vercel.json new file mode 100644 index 000000000..6d2e43fab --- /dev/null +++ b/cockpit/langgraph/generative-ui/angular/vercel.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://openapi.vercel.sh/vercel.json", + "buildCommand": "npx nx build cockpit-langgraph-generative-ui-angular", + "outputDirectory": "dist/cockpit/langgraph/generative-ui/angular/browser", + "framework": null +} From 39f63047bf06996c2e4bc5d0c50f9179b78634ee Mon Sep 17 00:00:00 2001 From: Brian Love Date: Wed, 8 Apr 2026 16:38:03 -0700 Subject: [PATCH 5/8] feat(cockpit): register generative-ui capability in manifest and routes Co-Authored-By: Claude Sonnet 4.6 --- apps/cockpit/src/lib/route-resolution.ts | 36 ++--------------------- libs/cockpit-registry/src/lib/manifest.ts | 1 + 2 files changed, 4 insertions(+), 33 deletions(-) diff --git a/apps/cockpit/src/lib/route-resolution.ts b/apps/cockpit/src/lib/route-resolution.ts index da020cf30..0a4887618 100644 --- a/apps/cockpit/src/lib/route-resolution.ts +++ b/apps/cockpit/src/lib/route-resolution.ts @@ -17,22 +17,7 @@ import { deepAgentsFilesystemPythonModule } from '../../../../cockpit/deep-agent import { deepAgentsSubagentsPythonModule } from '../../../../cockpit/deep-agents/subagents/python/src/index'; import { deepAgentsSkillsPythonModule } from '../../../../cockpit/deep-agents/skills/python/src/index'; import { deepAgentsSandboxesPythonModule } from '../../../../cockpit/deep-agents/sandboxes/python/src/index'; -import { renderSpecRenderingPythonModule } from '../../../../cockpit/render/spec-rendering/python/src/index'; -import { renderElementRenderingPythonModule } from '../../../../cockpit/render/element-rendering/python/src/index'; -import { renderStateManagementPythonModule } from '../../../../cockpit/render/state-management/python/src/index'; -import { renderRegistryPythonModule } from '../../../../cockpit/render/registry/python/src/index'; -import { renderRepeatLoopsPythonModule } from '../../../../cockpit/render/repeat-loops/python/src/index'; -import { renderComputedFunctionsPythonModule } from '../../../../cockpit/render/computed-functions/python/src/index'; -import { chatMessagesPythonModule } from '../../../../cockpit/chat/messages/python/src/index'; -import { chatInputPythonModule } from '../../../../cockpit/chat/input/python/src/index'; -import { chatInterruptsPythonModule } from '../../../../cockpit/chat/interrupts/python/src/index'; -import { chatToolCallsPythonModule } from '../../../../cockpit/chat/tool-calls/python/src/index'; -import { chatSubagentsPythonModule } from '../../../../cockpit/chat/subagents/python/src/index'; -import { chatThreadsPythonModule } from '../../../../cockpit/chat/threads/python/src/index'; -import { chatTimelinePythonModule } from '../../../../cockpit/chat/timeline/python/src/index'; -import { chatGenerativeUiPythonModule } from '../../../../cockpit/chat/generative-ui/python/src/index'; -import { chatDebugPythonModule } from '../../../../cockpit/chat/debug/python/src/index'; -import { chatThemingPythonModule } from '../../../../cockpit/chat/theming/python/src/index'; +import { langgraphGenerativeUiPythonModule } from '../../../../cockpit/langgraph/generative-ui/python/src/index'; export interface ResolveCockpitEntryOptions { manifest: CockpitManifestEntry[]; @@ -86,22 +71,7 @@ const capabilityModules = [ deepAgentsSubagentsPythonModule, deepAgentsSkillsPythonModule, deepAgentsSandboxesPythonModule, - renderSpecRenderingPythonModule, - renderElementRenderingPythonModule, - renderStateManagementPythonModule, - renderRegistryPythonModule, - renderRepeatLoopsPythonModule, - renderComputedFunctionsPythonModule, - chatMessagesPythonModule, - chatInputPythonModule, - chatInterruptsPythonModule, - chatToolCallsPythonModule, - chatSubagentsPythonModule, - chatThreadsPythonModule, - chatTimelinePythonModule, - chatGenerativeUiPythonModule, - chatDebugPythonModule, - chatThemingPythonModule, + langgraphGenerativeUiPythonModule, ]; export const toCockpitPath = (entry: CockpitManifestEntry): string => @@ -167,7 +137,7 @@ export const resolveCockpitEntry = ({ export const buildNavigationTree = ( manifest: CockpitManifestEntry[] ): NavigationProduct[] => { - const products: CockpitManifestEntry['product'][] = ['deep-agents', 'langgraph', 'render', 'chat']; + const products: CockpitManifestEntry['product'][] = ['deep-agents', 'langgraph']; const sections: CockpitManifestEntry['section'][] = [ 'getting-started', 'core-capabilities', diff --git a/libs/cockpit-registry/src/lib/manifest.ts b/libs/cockpit-registry/src/lib/manifest.ts index d75b2c1be..f9c2ea067 100644 --- a/libs/cockpit-registry/src/lib/manifest.ts +++ b/libs/cockpit-registry/src/lib/manifest.ts @@ -28,6 +28,7 @@ const APPROVED_TOPICS = { 'subgraphs', 'time-travel', 'deployment-runtime', + 'generative-ui', ], }, render: { From aeaa204458d4b4a8877836cac56a2fb8c462ff09 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Wed, 8 Apr 2026 21:43:27 -0700 Subject: [PATCH 6/8] feat(cockpit): update generative-ui example for streaming auto-detection - Remove duplicate cockpit/langgraph/generative-ui/ (already exists under chat) - Remove 'generative-ui' from langgraph manifest topics - Replace old ChatGenerativeUiComponent usage with views() + [views] input - Add WeatherCard, StatCard, Container view components - Update system prompt to instruct LLM to return raw JSON specs - Update docs guide to describe streaming pipeline --- .../angular/src/app/app.config.ts | 2 - .../src/app/generative-ui.component.ts | 44 +++------ .../src/app/views/container.component.ts | 0 .../src/app/views/stat-card.component.ts | 0 .../src/app/views/weather-card.component.ts | 0 .../environments/environment.development.ts | 4 +- .../angular/src/environments/environment.ts | 2 +- .../chat/generative-ui/python/docs/guide.md | 91 +++++++++---------- .../chat/generative-ui/python/langgraph.json | 2 +- .../python/prompts/generative-ui.md | 54 ++++++++--- .../generative-ui/angular/package.json | 13 --- .../generative-ui/angular/project.json | 54 ----------- .../generative-ui/angular/proxy.conf.json | 9 -- .../angular/src/app/app.config.ts | 12 --- .../src/app/generative-ui.component.ts | 29 ------ .../environments/environment.development.ts | 5 - .../angular/src/environments/environment.ts | 5 - .../generative-ui/angular/src/index.html | 13 --- .../generative-ui/angular/src/main.ts | 6 -- .../generative-ui/angular/src/styles.css | 30 ------ .../generative-ui/angular/tsconfig.app.json | 11 --- .../generative-ui/angular/tsconfig.json | 24 ----- .../generative-ui/angular/vercel.json | 6 -- .../generative-ui/python/docs/guide.md | 37 -------- .../generative-ui/python/langgraph.json | 8 -- .../generative-ui/python/project.json | 24 ----- .../python/prompts/generative-ui.md | 85 ----------------- .../generative-ui/python/src/graph.py | 34 ------- .../generative-ui/python/src/index.ts | 44 --------- .../generative-ui/python/tsconfig.json | 7 -- libs/cockpit-registry/src/lib/manifest.ts | 1 - 31 files changed, 102 insertions(+), 554 deletions(-) rename cockpit/{langgraph => chat}/generative-ui/angular/src/app/views/container.component.ts (100%) rename cockpit/{langgraph => chat}/generative-ui/angular/src/app/views/stat-card.component.ts (100%) rename cockpit/{langgraph => chat}/generative-ui/angular/src/app/views/weather-card.component.ts (100%) delete mode 100644 cockpit/langgraph/generative-ui/angular/package.json delete mode 100644 cockpit/langgraph/generative-ui/angular/project.json delete mode 100644 cockpit/langgraph/generative-ui/angular/proxy.conf.json delete mode 100644 cockpit/langgraph/generative-ui/angular/src/app/app.config.ts delete mode 100644 cockpit/langgraph/generative-ui/angular/src/app/generative-ui.component.ts delete mode 100644 cockpit/langgraph/generative-ui/angular/src/environments/environment.development.ts delete mode 100644 cockpit/langgraph/generative-ui/angular/src/environments/environment.ts delete mode 100644 cockpit/langgraph/generative-ui/angular/src/index.html delete mode 100644 cockpit/langgraph/generative-ui/angular/src/main.ts delete mode 100644 cockpit/langgraph/generative-ui/angular/src/styles.css delete mode 100644 cockpit/langgraph/generative-ui/angular/tsconfig.app.json delete mode 100644 cockpit/langgraph/generative-ui/angular/tsconfig.json delete mode 100644 cockpit/langgraph/generative-ui/angular/vercel.json delete mode 100644 cockpit/langgraph/generative-ui/python/docs/guide.md delete mode 100644 cockpit/langgraph/generative-ui/python/langgraph.json delete mode 100644 cockpit/langgraph/generative-ui/python/project.json delete mode 100644 cockpit/langgraph/generative-ui/python/prompts/generative-ui.md delete mode 100644 cockpit/langgraph/generative-ui/python/src/graph.py delete mode 100644 cockpit/langgraph/generative-ui/python/src/index.ts delete mode 100644 cockpit/langgraph/generative-ui/python/tsconfig.json diff --git a/cockpit/chat/generative-ui/angular/src/app/app.config.ts b/cockpit/chat/generative-ui/angular/src/app/app.config.ts index bbe3e5e49..1e72bb3c9 100644 --- a/cockpit/chat/generative-ui/angular/src/app/app.config.ts +++ b/cockpit/chat/generative-ui/angular/src/app/app.config.ts @@ -2,13 +2,11 @@ import { ApplicationConfig } from '@angular/core'; import { provideAgent } from '@cacheplane/angular'; import { provideChat } from '@cacheplane/chat'; -import { provideRender } from '@cacheplane/render'; import { environment } from '../environments/environment'; export const appConfig: ApplicationConfig = { providers: [ provideAgent({ apiUrl: environment.langGraphApiUrl }), provideChat({}), - provideRender({}), ], }; diff --git a/cockpit/chat/generative-ui/angular/src/app/generative-ui.component.ts b/cockpit/chat/generative-ui/angular/src/app/generative-ui.component.ts index 0089176c7..ebfd92f18 100644 --- a/cockpit/chat/generative-ui/angular/src/app/generative-ui.component.ts +++ b/cockpit/chat/generative-ui/angular/src/app/generative-ui.component.ts @@ -1,42 +1,28 @@ // SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 import { Component } from '@angular/core'; -import { ChatComponent, ChatGenerativeUiComponent } from '@cacheplane/chat'; +import { ChatComponent, views } from '@cacheplane/chat'; import { agent } from '@cacheplane/angular'; import { environment } from '../environments/environment'; +import { WeatherCardComponent } from './views/weather-card.component'; +import { StatCardComponent } from './views/stat-card.component'; +import { ContainerComponent } from './views/container.component'; + +const myViews = views({ + weather_card: WeatherCardComponent, + stat_card: StatCardComponent, + container: ContainerComponent, +}); -/** - * GenerativeUiComponent demonstrates dynamic UI generation within - * chat messages using ChatComponent and ChatGenerativeUiComponent. - * The agent embeds render specs that are rendered as live components. - */ @Component({ selector: 'app-generative-ui', standalone: true, - imports: [ChatComponent, ChatGenerativeUiComponent], - template: ` -
- - -
- `, + imports: [ChatComponent], + template: ``, }) export class GenerativeUiComponent { - protected readonly stream = agent({ + protected readonly agentRef = agent({ apiUrl: environment.langGraphApiUrl, - assistantId: environment.streamingAssistantId, + assistantId: environment.generativeUiAssistantId, }); + protected readonly myViews = myViews; } diff --git a/cockpit/langgraph/generative-ui/angular/src/app/views/container.component.ts b/cockpit/chat/generative-ui/angular/src/app/views/container.component.ts similarity index 100% rename from cockpit/langgraph/generative-ui/angular/src/app/views/container.component.ts rename to cockpit/chat/generative-ui/angular/src/app/views/container.component.ts diff --git a/cockpit/langgraph/generative-ui/angular/src/app/views/stat-card.component.ts b/cockpit/chat/generative-ui/angular/src/app/views/stat-card.component.ts similarity index 100% rename from cockpit/langgraph/generative-ui/angular/src/app/views/stat-card.component.ts rename to cockpit/chat/generative-ui/angular/src/app/views/stat-card.component.ts diff --git a/cockpit/langgraph/generative-ui/angular/src/app/views/weather-card.component.ts b/cockpit/chat/generative-ui/angular/src/app/views/weather-card.component.ts similarity index 100% rename from cockpit/langgraph/generative-ui/angular/src/app/views/weather-card.component.ts rename to cockpit/chat/generative-ui/angular/src/app/views/weather-card.component.ts diff --git a/cockpit/chat/generative-ui/angular/src/environments/environment.development.ts b/cockpit/chat/generative-ui/angular/src/environments/environment.development.ts index 18c14185d..32ff7a3bb 100644 --- a/cockpit/chat/generative-ui/angular/src/environments/environment.development.ts +++ b/cockpit/chat/generative-ui/angular/src/environments/environment.development.ts @@ -1,5 +1,5 @@ export const environment = { production: false, - langGraphApiUrl: 'http://localhost:4508/api', - streamingAssistantId: 'c-generative-ui', + langGraphApiUrl: 'http://localhost:4310/api', + generativeUiAssistantId: 'generative_ui', }; diff --git a/cockpit/chat/generative-ui/angular/src/environments/environment.ts b/cockpit/chat/generative-ui/angular/src/environments/environment.ts index 2c727e253..1cdf59a2a 100644 --- a/cockpit/chat/generative-ui/angular/src/environments/environment.ts +++ b/cockpit/chat/generative-ui/angular/src/environments/environment.ts @@ -1,5 +1,5 @@ export const environment = { production: true, langGraphApiUrl: '/api', - streamingAssistantId: 'c-generative-ui', + generativeUiAssistantId: 'generative_ui', }; diff --git a/cockpit/chat/generative-ui/python/docs/guide.md b/cockpit/chat/generative-ui/python/docs/guide.md index 92f2ba78c..8a6c0ce60 100644 --- a/cockpit/chat/generative-ui/python/docs/guide.md +++ b/cockpit/chat/generative-ui/python/docs/guide.md @@ -1,76 +1,73 @@ -# Chat Generative UI with @cacheplane/chat +# Generative UI with Streaming Auto-Detection -Render dynamic UI components within chat messages using -ChatGenerativeUiComponent. The agent embeds JSON render specs -in responses that are rendered as live Angular components. +Render dynamic UI components within chat messages using the streaming +auto-detection pipeline. As tokens stream in, the system detects JSON, +parses it incrementally, and renders Angular components in real time. -Add generative UI to your chat interface using `ChatGenerativeUiComponent` -from `@cacheplane/chat` and `provideRender()` from `@cacheplane/render`. -Configure both providers to enable spec detection and rendering. +Add generative UI to your chat interface using `views()` from +`@cacheplane/chat`. Register view components and pass them to +`ChatComponent` via the `[views]` input. - + -Generative UI requires both `provideChat()` and `provideRender()`: - -```typescript -import { provideRender } from '@cacheplane/render'; -import { provideChat } from '@cacheplane/chat'; - -export const appConfig: ApplicationConfig = { - providers: [ - provideStreamResource({ apiUrl: environment.langGraphApiUrl }), - provideChat({}), - provideRender({}), - ], -}; -``` +Create Angular components for each UI type the agent can emit. +Each component uses `input()` signals to receive props from the +rendered spec. - + -Configure the backend agent to include JSON render specs in its -responses using fenced code blocks with the `render-spec` tag. +Use the `views()` function to map spec type names to Angular components: - - +```typescript +import { views } from '@cacheplane/chat'; -ChatGenerativeUiComponent automatically scans messages for render -spec code blocks and extracts them for rendering. +const myViews = views({ + weather_card: WeatherCardComponent, + stat_card: StatCardComponent, + container: ContainerComponent, +}); +``` - + -Use the component in your template alongside ChatComponent: +Pass the view map to `ChatComponent` via the `[views]` input: ```html - - + ``` - + -Register custom Angular components to handle specific spec types: - -```typescript -provideRender({ - registry: defineAngularRegistry({ - card: MyCardComponent, - chart: MyChartComponent, - }), -}) -``` +Instruct the LLM to respond with raw JSON following the Spec schema. +No code fences or markdown — just valid JSON so the streaming pipeline +can detect and parse it incrementally. +## How Streaming Auto-Detection Works + +1. **Token streaming** — The LLM streams response tokens to the client. +2. **ContentClassifier** — Inspects the incoming token buffer and detects + when the content is JSON rather than plain text or markdown. +3. **Partial JSON parser** — As JSON tokens arrive, a partial parser + builds an incremental parse tree without waiting for the full payload. +4. **ParseTreeStore** — Materializes the partial parse tree into a live + `Spec` object (elements map + root key) that updates on every chunk. +5. **Component rendering** — The `[views]` registry resolves each element + type to an Angular component, which renders incrementally as the spec + grows. + -Generative UI bridges the gap between conversational AI and rich -interactive interfaces — the agent can create forms, dashboards, -and visualizations on the fly. +Because detection and parsing happen on every streamed chunk, the user +sees UI components materialize progressively — cards appear and fill in +as the LLM generates the JSON structure. diff --git a/cockpit/chat/generative-ui/python/langgraph.json b/cockpit/chat/generative-ui/python/langgraph.json index 583df221b..cd12bd55e 100644 --- a/cockpit/chat/generative-ui/python/langgraph.json +++ b/cockpit/chat/generative-ui/python/langgraph.json @@ -1,6 +1,6 @@ { "graphs": { - "c-generative-ui": "./src/graph.py:graph" + "generative_ui": "./src/graph.py:graph" }, "dependencies": ["."], "python_version": "3.12", diff --git a/cockpit/chat/generative-ui/python/prompts/generative-ui.md b/cockpit/chat/generative-ui/python/prompts/generative-ui.md index a1991a980..98c06c985 100644 --- a/cockpit/chat/generative-ui/python/prompts/generative-ui.md +++ b/cockpit/chat/generative-ui/python/prompts/generative-ui.md @@ -1,22 +1,46 @@ -# Chat Generative UI Assistant +# Generative UI Assistant -You are an assistant that demonstrates dynamic UI generation within -chat responses using render specs. +You are a generative-UI assistant. You MUST respond with **raw JSON only** — no markdown, no code fences, no explanation text. Your entire response must be a single valid JSON object following the Spec format below. -When the user asks you to create a UI element, include a JSON render spec -in your response using a fenced code block with the `render-spec` language tag. -For example: +## Spec Schema -```render-spec +A **Spec** is a JSON object with two required top-level keys: + +``` +{ + "elements": { [key: string]: Element }, + "rootKey": string +} +``` + +An **Element** has: + +``` { - "type": "card", - "props": { "title": "Generated Card" }, - "children": [ - { "type": "text", "props": { "content": "This card was generated by the AI" } } - ] + "type": string, // component type name + "props": { ... }, // component-specific properties + "children?": string[] // ordered list of element keys (references into `elements`) } ``` -The frontend will detect these specs and render them as live Angular -components inline within the chat message. Explain what you are generating -and why in the surrounding text. +## Available Component Types + +| Type | Props | Children | +|-----------------|--------------------------------------------------------------|----------| +| `container` | *(none)* | Yes | +| `weather_card` | `city` (string), `temperature` (number), `condition` (string)| No | +| `stat_card` | `label` (string), `value` (string) | No | + +## Rules + +1. Respond ONLY with valid JSON. No markdown. No code fences. No surrounding text. +2. Every element referenced in a `children` array must exist as a key in `elements`. +3. `rootKey` must reference a key that exists in `elements`. +4. Use `container` to group multiple cards together. +5. Choose component types that best match the user's request. + +## Example Response + +If the user asks "What's the weather in Chicago and New York?", respond exactly like: + +{"elements":{"root":{"type":"container","props":{},"children":["chicago","nyc"]},"chicago":{"type":"weather_card","props":{"city":"Chicago","temperature":45,"condition":"Partly Cloudy"}},"nyc":{"type":"weather_card","props":{"city":"New York","temperature":52,"condition":"Sunny"}}},"rootKey":"root"} diff --git a/cockpit/langgraph/generative-ui/angular/package.json b/cockpit/langgraph/generative-ui/angular/package.json deleted file mode 100644 index 61fcc023c..000000000 --- a/cockpit/langgraph/generative-ui/angular/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "@cacheplane/cockpit-langgraph-generative-ui-angular", - "version": "0.0.1", - "peerDependencies": { - "@cacheplane/chat": "^0.0.1", - "@cacheplane/render": "^0.0.1", - "@cacheplane/angular": "^0.0.1", - "@json-render/core": "^0.16.0", - "@langchain/langgraph-sdk": "^0.0.36" - }, - "license": "PolyForm-Noncommercial-1.0.0", - "sideEffects": false -} diff --git a/cockpit/langgraph/generative-ui/angular/project.json b/cockpit/langgraph/generative-ui/angular/project.json deleted file mode 100644 index dd2431cb2..000000000 --- a/cockpit/langgraph/generative-ui/angular/project.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "name": "cockpit-langgraph-generative-ui-angular", - "$schema": "../../../../node_modules/nx/schemas/project-schema.json", - "sourceRoot": "cockpit/langgraph/generative-ui/angular/src", - "projectType": "application", - "targets": { - "build": { - "executor": "@angular/build:application", - "outputs": ["{options.outputPath.base}"], - "options": { - "outputPath": { - "base": "dist/cockpit/langgraph/generative-ui/angular", - "browser": "" - }, - "browser": "cockpit/langgraph/generative-ui/angular/src/main.ts", - "tsConfig": "cockpit/langgraph/generative-ui/angular/tsconfig.app.json", - "styles": ["cockpit/langgraph/generative-ui/angular/src/styles.css"] - }, - "configurations": { - "production": { - "budgets": [ - { "type": "initial", "maximumWarning": "500kb", "maximumError": "1mb" }, - { "type": "anyComponentStyle", "maximumWarning": "4kb", "maximumError": "8kb" } - ], - "outputHashing": "none" - }, - "development": { - "optimization": false, - "extractLicenses": false, - "sourceMap": true, - "fileReplacements": [ - { - "replace": "cockpit/langgraph/generative-ui/angular/src/environments/environment.ts", - "with": "cockpit/langgraph/generative-ui/angular/src/environments/environment.development.ts" - } - ] - } - }, - "defaultConfiguration": "production" - }, - "serve": { - "continuous": true, - "executor": "@angular/build:dev-server", - "configurations": { - "production": { "buildTarget": "cockpit-langgraph-generative-ui-angular:build:production" }, - "development": { "buildTarget": "cockpit-langgraph-generative-ui-angular:build:development" } - }, - "defaultConfiguration": "development", - "options": { - "proxyConfig": "cockpit/langgraph/generative-ui/angular/proxy.conf.json" - } - } - } -} diff --git a/cockpit/langgraph/generative-ui/angular/proxy.conf.json b/cockpit/langgraph/generative-ui/angular/proxy.conf.json deleted file mode 100644 index 8523362d7..000000000 --- a/cockpit/langgraph/generative-ui/angular/proxy.conf.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "/api": { - "target": "http://localhost:8123", - "secure": false, - "changeOrigin": true, - "pathRewrite": { "^/api": "" }, - "ws": true - } -} diff --git a/cockpit/langgraph/generative-ui/angular/src/app/app.config.ts b/cockpit/langgraph/generative-ui/angular/src/app/app.config.ts deleted file mode 100644 index 1e72bb3c9..000000000 --- a/cockpit/langgraph/generative-ui/angular/src/app/app.config.ts +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 -import { ApplicationConfig } from '@angular/core'; -import { provideAgent } from '@cacheplane/angular'; -import { provideChat } from '@cacheplane/chat'; -import { environment } from '../environments/environment'; - -export const appConfig: ApplicationConfig = { - providers: [ - provideAgent({ apiUrl: environment.langGraphApiUrl }), - provideChat({}), - ], -}; diff --git a/cockpit/langgraph/generative-ui/angular/src/app/generative-ui.component.ts b/cockpit/langgraph/generative-ui/angular/src/app/generative-ui.component.ts deleted file mode 100644 index 475a5ae0e..000000000 --- a/cockpit/langgraph/generative-ui/angular/src/app/generative-ui.component.ts +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 -import { Component } from '@angular/core'; -import { ChatComponent, views } from '@cacheplane/chat'; -import { agent } from '@cacheplane/angular'; -import { environment } from '../environments/environment'; -import { WeatherCardComponent } from './views/weather-card.component'; -import { StatCardComponent } from './views/stat-card.component'; -import { ContainerComponent } from './views/container.component'; - -const myViews = views({ - weather_card: WeatherCardComponent, - stat_card: StatCardComponent, - container: ContainerComponent, -}); - -@Component({ - selector: 'app-generative-ui', - standalone: true, - imports: [ChatComponent], - template: ``, -}) -export class GenerativeUiComponent { - protected readonly agentRef = agent({ - apiUrl: environment.langGraphApiUrl, - assistantId: environment.generativeUiAssistantId, - }); - - protected readonly myViews = myViews; -} diff --git a/cockpit/langgraph/generative-ui/angular/src/environments/environment.development.ts b/cockpit/langgraph/generative-ui/angular/src/environments/environment.development.ts deleted file mode 100644 index 32ff7a3bb..000000000 --- a/cockpit/langgraph/generative-ui/angular/src/environments/environment.development.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const environment = { - production: false, - langGraphApiUrl: 'http://localhost:4310/api', - generativeUiAssistantId: 'generative_ui', -}; diff --git a/cockpit/langgraph/generative-ui/angular/src/environments/environment.ts b/cockpit/langgraph/generative-ui/angular/src/environments/environment.ts deleted file mode 100644 index 1cdf59a2a..000000000 --- a/cockpit/langgraph/generative-ui/angular/src/environments/environment.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const environment = { - production: true, - langGraphApiUrl: '/api', - generativeUiAssistantId: 'generative_ui', -}; diff --git a/cockpit/langgraph/generative-ui/angular/src/index.html b/cockpit/langgraph/generative-ui/angular/src/index.html deleted file mode 100644 index 3327ab9d7..000000000 --- a/cockpit/langgraph/generative-ui/angular/src/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - LangGraph Generative UI — Angular - - - - - - - - diff --git a/cockpit/langgraph/generative-ui/angular/src/main.ts b/cockpit/langgraph/generative-ui/angular/src/main.ts deleted file mode 100644 index 862ebbc69..000000000 --- a/cockpit/langgraph/generative-ui/angular/src/main.ts +++ /dev/null @@ -1,6 +0,0 @@ -// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 -import { bootstrapApplication } from '@angular/platform-browser'; -import { appConfig } from './app/app.config'; -import { GenerativeUiComponent } from './app/generative-ui.component'; - -bootstrapApplication(GenerativeUiComponent, appConfig).catch(console.error); diff --git a/cockpit/langgraph/generative-ui/angular/src/styles.css b/cockpit/langgraph/generative-ui/angular/src/styles.css deleted file mode 100644 index 061c66cf8..000000000 --- a/cockpit/langgraph/generative-ui/angular/src/styles.css +++ /dev/null @@ -1,30 +0,0 @@ -@import "../../../../../libs/design-tokens/src/lib/tokens.css"; -@import "tailwindcss"; -@source "../../../../../libs/chat/src/"; - -@theme { - --color-bg: var(--ds-bg); - --color-surface: #ffffff; - --color-accent: var(--ds-accent); - --color-accent-light: var(--ds-accent-light); - --color-text-primary: var(--ds-text-primary); - --color-text-secondary: var(--ds-text-secondary); - --color-text-muted: var(--ds-text-muted); - --color-border: var(--ds-accent-border); - --color-error: #ef4444; - --color-success: #22c55e; - --font-sans: var(--ds-font-sans); - --font-serif: var(--ds-font-serif); - --font-mono: var(--ds-font-mono); -} - -*, *::before, *::after { box-sizing: border-box; } - -body { - margin: 0; - font-family: var(--ds-font-sans); - background: var(--ds-bg); - color: var(--ds-text-primary); - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} diff --git a/cockpit/langgraph/generative-ui/angular/tsconfig.app.json b/cockpit/langgraph/generative-ui/angular/tsconfig.app.json deleted file mode 100644 index 64731b107..000000000 --- a/cockpit/langgraph/generative-ui/angular/tsconfig.app.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "../../../../dist/out-tsc", - "lib": ["es2022", "dom"], - "types": [], - "emitDeclarationOnly": false - }, - "files": ["src/main.ts"], - "include": ["src/**/*.ts"] -} diff --git a/cockpit/langgraph/generative-ui/angular/tsconfig.json b/cockpit/langgraph/generative-ui/angular/tsconfig.json deleted file mode 100644 index 3fd970371..000000000 --- a/cockpit/langgraph/generative-ui/angular/tsconfig.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "extends": "../../../../tsconfig.base.json", - "compilerOptions": { - "noPropertyAccessFromIndexSignature": false, - "experimentalDecorators": true, - "module": "preserve", - "emitDeclarationOnly": false, - "composite": false, - "lib": ["es2022", "dom"], - "skipLibCheck": true, - "strict": false - }, - "angularCompilerOptions": { - "enableI18nLegacyMessageIdFormat": false, - "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false - }, - "files": [], - "include": [], - "references": [ - { "path": "./tsconfig.app.json" } - ] -} diff --git a/cockpit/langgraph/generative-ui/angular/vercel.json b/cockpit/langgraph/generative-ui/angular/vercel.json deleted file mode 100644 index 6d2e43fab..000000000 --- a/cockpit/langgraph/generative-ui/angular/vercel.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "$schema": "https://openapi.vercel.sh/vercel.json", - "buildCommand": "npx nx build cockpit-langgraph-generative-ui-angular", - "outputDirectory": "dist/cockpit/langgraph/generative-ui/angular/browser", - "framework": null -} diff --git a/cockpit/langgraph/generative-ui/python/docs/guide.md b/cockpit/langgraph/generative-ui/python/docs/guide.md deleted file mode 100644 index 907375451..000000000 --- a/cockpit/langgraph/generative-ui/python/docs/guide.md +++ /dev/null @@ -1,37 +0,0 @@ -# Generative UI - -This example demonstrates streaming generative UI — an LLM returns JSON-render Specs that are auto-detected and rendered as Angular components in real time. - -## How It Works - -1. The LangGraph agent receives a user message -2. The LLM generates a JSON-render Spec as its response (not markdown) -3. Tokens stream to the Angular frontend via SSE -4. `ChatComponent` auto-detects the JSON via `ContentClassifier` -5. `@cacheplane/partial-json` parses the incomplete JSON character-by-character -6. `ParseTreeStore` materializes the parse tree into a `Spec` signal -7. `RenderSpecComponent` renders the spec using the view registry -8. Components update live as tokens arrive — string props grow visibly - -## View Components - -This example registers two view components: - -- **WeatherCard** — Displays city, temperature, and weather condition -- **StatCard** — Displays a label/value pair (humidity, wind speed, etc.) - -## Key Code - -```typescript -// Register views -const myViews = views({ - weather_card: WeatherCardComponent, - stat_card: StatCardComponent, - container: ContainerComponent, -}); - -// Pass to ChatComponent - -``` - -No manual JSON parsing, no content type detection, no spec wiring — the `ChatComponent` handles everything automatically. diff --git a/cockpit/langgraph/generative-ui/python/langgraph.json b/cockpit/langgraph/generative-ui/python/langgraph.json deleted file mode 100644 index cd12bd55e..000000000 --- a/cockpit/langgraph/generative-ui/python/langgraph.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "graphs": { - "generative_ui": "./src/graph.py:graph" - }, - "dependencies": ["."], - "python_version": "3.12", - "env": ".env" -} diff --git a/cockpit/langgraph/generative-ui/python/project.json b/cockpit/langgraph/generative-ui/python/project.json deleted file mode 100644 index 8700133f5..000000000 --- a/cockpit/langgraph/generative-ui/python/project.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "cockpit-langgraph-generative-ui-python", - "$schema": "../../../../node_modules/nx/schemas/project-schema.json", - "sourceRoot": "cockpit/langgraph/generative-ui/python/src", - "projectType": "library", - "targets": { - "build": { - "executor": "@nx/js:tsc", - "outputs": ["{workspaceRoot}/dist/cockpit/langgraph/generative-ui/python"], - "options": { - "outputPath": "dist/cockpit/langgraph/generative-ui/python", - "main": "cockpit/langgraph/generative-ui/python/src/index.ts", - "tsConfig": "cockpit/langgraph/generative-ui/python/tsconfig.json" - } - }, - "smoke": { - "executor": "nx:run-commands", - "options": { - "cwd": "cockpit/langgraph/generative-ui/python", - "command": "npx tsx -e \"import { langgraphGenerativeUiPythonModule } from './src/index.ts'; const m = langgraphGenerativeUiPythonModule; if (m.id !== 'langgraph-generative-ui-python') { throw new Error('Unexpected module: ' + m.id); }\"" - } - } - } -} diff --git a/cockpit/langgraph/generative-ui/python/prompts/generative-ui.md b/cockpit/langgraph/generative-ui/python/prompts/generative-ui.md deleted file mode 100644 index 0812d3dfb..000000000 --- a/cockpit/langgraph/generative-ui/python/prompts/generative-ui.md +++ /dev/null @@ -1,85 +0,0 @@ -# Generative UI Assistant - -You are a helpful assistant that responds with structured JSON UI specifications. - -When the user asks about weather, locations, or data, respond with a JSON object that follows this exact schema: - -```json -{ - "root": "", - "elements": { - "": { - "type": "", - "props": { ... }, - "children": ["", ""] - } - } -} -``` - -## Available component types - -### `container` -A layout wrapper that renders its children vertically. -- Props: none required -- Children: array of element keys - -### `weather_card` -Displays weather information for a city. -- Props: - - `city` (string): City name - - `temperature` (number): Temperature in Fahrenheit - - `condition` (string): Weather condition (e.g., "Sunny", "Cloudy", "Rainy") - -### `stat_card` -Displays a single statistic. -- Props: - - `label` (string): What the stat measures (e.g., "Humidity", "Wind Speed") - - `value` (string): The formatted value (e.g., "65%", "12 mph") - -## Rules - -1. Always respond with ONLY a valid JSON object — no markdown, no explanation, no code fences -2. Use a `container` as the root element when you have multiple components -3. Give each element a unique key (e.g., "root", "weather", "stat-1", "stat-2") -4. Include 2-4 elements total for variety -5. Make the data realistic and varied - -## Example response - -For "What's the weather in Seattle?": - -```json -{ - "root": "root", - "elements": { - "root": { - "type": "container", - "props": {}, - "children": ["weather", "stat-humidity", "stat-wind"] - }, - "weather": { - "type": "weather_card", - "props": { - "city": "Seattle", - "temperature": 58, - "condition": "Overcast" - } - }, - "stat-humidity": { - "type": "stat_card", - "props": { - "label": "Humidity", - "value": "78%" - } - }, - "stat-wind": { - "type": "stat_card", - "props": { - "label": "Wind Speed", - "value": "8 mph NW" - } - } - } -} -``` diff --git a/cockpit/langgraph/generative-ui/python/src/graph.py b/cockpit/langgraph/generative-ui/python/src/graph.py deleted file mode 100644 index b44ad0f1f..000000000 --- a/cockpit/langgraph/generative-ui/python/src/graph.py +++ /dev/null @@ -1,34 +0,0 @@ -""" -LangGraph Generative UI Graph - -A StateGraph that instructs the LLM to return JSON-render Spec objects. -The Angular frontend auto-detects the JSON and renders it as Angular -components via the streaming generative UI pipeline. -""" - -from pathlib import Path -from langgraph.graph import StateGraph, MessagesState, END -from langchain_openai import ChatOpenAI -from langchain_core.messages import SystemMessage - -PROMPTS_DIR = Path(__file__).parent.parent / "prompts" - - -def build_generative_ui_graph(): - llm = ChatOpenAI(model="gpt-4o-mini", streaming=True) - - async def generate(state: MessagesState) -> dict: - system_prompt = (PROMPTS_DIR / "generative-ui.md").read_text() - messages = [SystemMessage(content=system_prompt)] + state["messages"] - response = await llm.ainvoke(messages) - return {"messages": [response]} - - graph = StateGraph(MessagesState) - graph.add_node("generate", generate) - graph.set_entry_point("generate") - graph.add_edge("generate", END) - - return graph.compile() - - -graph = build_generative_ui_graph() diff --git a/cockpit/langgraph/generative-ui/python/src/index.ts b/cockpit/langgraph/generative-ui/python/src/index.ts deleted file mode 100644 index 3ea0cafef..000000000 --- a/cockpit/langgraph/generative-ui/python/src/index.ts +++ /dev/null @@ -1,44 +0,0 @@ -export interface CockpitCapabilityModule { - id: string; - manifestIdentity: { - product: 'langgraph'; - section: 'core-capabilities'; - topic: 'generative-ui'; - page: 'overview'; - language: 'python'; - }; - title: string; - docsPath: string; - promptAssetPaths: string[]; - codeAssetPaths: string[]; - backendAssetPaths: string[]; - docsAssetPaths: string[]; - runtimeUrl?: string; - devPort?: number; -} - -export const langgraphGenerativeUiPythonModule: CockpitCapabilityModule = { - id: 'langgraph-generative-ui-python', - manifestIdentity: { - product: 'langgraph', - section: 'core-capabilities', - topic: 'generative-ui', - page: 'overview', - language: 'python', - }, - title: 'LangGraph Generative UI (Python)', - docsPath: '/docs/langgraph/core-capabilities/generative-ui/overview/python', - promptAssetPaths: ['cockpit/langgraph/generative-ui/python/prompts/generative-ui.md'], - codeAssetPaths: [ - 'cockpit/langgraph/generative-ui/angular/src/app/generative-ui.component.ts', - 'cockpit/langgraph/generative-ui/angular/src/app/app.config.ts', - 'cockpit/langgraph/generative-ui/angular/src/app/views/weather-card.component.ts', - 'cockpit/langgraph/generative-ui/angular/src/app/views/stat-card.component.ts', - ], - backendAssetPaths: [ - 'cockpit/langgraph/generative-ui/python/src/graph.py', - ], - docsAssetPaths: ['cockpit/langgraph/generative-ui/python/docs/guide.md'], - runtimeUrl: 'langgraph/generative-ui', - devPort: 4310, -}; diff --git a/cockpit/langgraph/generative-ui/python/tsconfig.json b/cockpit/langgraph/generative-ui/python/tsconfig.json deleted file mode 100644 index 6df4fdaa6..000000000 --- a/cockpit/langgraph/generative-ui/python/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "../../../../tsconfig.base.json", - "compilerOptions": { - "outDir": "../../../../dist/out-tsc" - }, - "include": ["src/**/*.ts"] -} diff --git a/libs/cockpit-registry/src/lib/manifest.ts b/libs/cockpit-registry/src/lib/manifest.ts index f9c2ea067..d75b2c1be 100644 --- a/libs/cockpit-registry/src/lib/manifest.ts +++ b/libs/cockpit-registry/src/lib/manifest.ts @@ -28,7 +28,6 @@ const APPROVED_TOPICS = { 'subgraphs', 'time-travel', 'deployment-runtime', - 'generative-ui', ], }, render: { From fd7b1e310354bc1007204a16894c200e161208d4 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Wed, 8 Apr 2026 21:46:58 -0700 Subject: [PATCH 7/8] fix(cockpit): remove stale langgraph/generative-ui import from route-resolution --- apps/cockpit/src/lib/route-resolution.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/cockpit/src/lib/route-resolution.ts b/apps/cockpit/src/lib/route-resolution.ts index 0a4887618..44cba9466 100644 --- a/apps/cockpit/src/lib/route-resolution.ts +++ b/apps/cockpit/src/lib/route-resolution.ts @@ -17,7 +17,6 @@ import { deepAgentsFilesystemPythonModule } from '../../../../cockpit/deep-agent import { deepAgentsSubagentsPythonModule } from '../../../../cockpit/deep-agents/subagents/python/src/index'; import { deepAgentsSkillsPythonModule } from '../../../../cockpit/deep-agents/skills/python/src/index'; import { deepAgentsSandboxesPythonModule } from '../../../../cockpit/deep-agents/sandboxes/python/src/index'; -import { langgraphGenerativeUiPythonModule } from '../../../../cockpit/langgraph/generative-ui/python/src/index'; export interface ResolveCockpitEntryOptions { manifest: CockpitManifestEntry[]; @@ -71,7 +70,6 @@ const capabilityModules = [ deepAgentsSubagentsPythonModule, deepAgentsSkillsPythonModule, deepAgentsSandboxesPythonModule, - langgraphGenerativeUiPythonModule, ]; export const toCockpitPath = (entry: CockpitManifestEntry): string => From 1b43ea9a052584c8afc5cbd9927266111617d39a Mon Sep 17 00:00:00 2001 From: Brian Love Date: Wed, 8 Apr 2026 21:57:45 -0700 Subject: [PATCH 8/8] fix(cockpit): restore main's route-resolution and manifest after rebase --- apps/cockpit/src/lib/route-resolution.ts | 34 +++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/apps/cockpit/src/lib/route-resolution.ts b/apps/cockpit/src/lib/route-resolution.ts index 44cba9466..da020cf30 100644 --- a/apps/cockpit/src/lib/route-resolution.ts +++ b/apps/cockpit/src/lib/route-resolution.ts @@ -17,6 +17,22 @@ import { deepAgentsFilesystemPythonModule } from '../../../../cockpit/deep-agent import { deepAgentsSubagentsPythonModule } from '../../../../cockpit/deep-agents/subagents/python/src/index'; import { deepAgentsSkillsPythonModule } from '../../../../cockpit/deep-agents/skills/python/src/index'; import { deepAgentsSandboxesPythonModule } from '../../../../cockpit/deep-agents/sandboxes/python/src/index'; +import { renderSpecRenderingPythonModule } from '../../../../cockpit/render/spec-rendering/python/src/index'; +import { renderElementRenderingPythonModule } from '../../../../cockpit/render/element-rendering/python/src/index'; +import { renderStateManagementPythonModule } from '../../../../cockpit/render/state-management/python/src/index'; +import { renderRegistryPythonModule } from '../../../../cockpit/render/registry/python/src/index'; +import { renderRepeatLoopsPythonModule } from '../../../../cockpit/render/repeat-loops/python/src/index'; +import { renderComputedFunctionsPythonModule } from '../../../../cockpit/render/computed-functions/python/src/index'; +import { chatMessagesPythonModule } from '../../../../cockpit/chat/messages/python/src/index'; +import { chatInputPythonModule } from '../../../../cockpit/chat/input/python/src/index'; +import { chatInterruptsPythonModule } from '../../../../cockpit/chat/interrupts/python/src/index'; +import { chatToolCallsPythonModule } from '../../../../cockpit/chat/tool-calls/python/src/index'; +import { chatSubagentsPythonModule } from '../../../../cockpit/chat/subagents/python/src/index'; +import { chatThreadsPythonModule } from '../../../../cockpit/chat/threads/python/src/index'; +import { chatTimelinePythonModule } from '../../../../cockpit/chat/timeline/python/src/index'; +import { chatGenerativeUiPythonModule } from '../../../../cockpit/chat/generative-ui/python/src/index'; +import { chatDebugPythonModule } from '../../../../cockpit/chat/debug/python/src/index'; +import { chatThemingPythonModule } from '../../../../cockpit/chat/theming/python/src/index'; export interface ResolveCockpitEntryOptions { manifest: CockpitManifestEntry[]; @@ -70,6 +86,22 @@ const capabilityModules = [ deepAgentsSubagentsPythonModule, deepAgentsSkillsPythonModule, deepAgentsSandboxesPythonModule, + renderSpecRenderingPythonModule, + renderElementRenderingPythonModule, + renderStateManagementPythonModule, + renderRegistryPythonModule, + renderRepeatLoopsPythonModule, + renderComputedFunctionsPythonModule, + chatMessagesPythonModule, + chatInputPythonModule, + chatInterruptsPythonModule, + chatToolCallsPythonModule, + chatSubagentsPythonModule, + chatThreadsPythonModule, + chatTimelinePythonModule, + chatGenerativeUiPythonModule, + chatDebugPythonModule, + chatThemingPythonModule, ]; export const toCockpitPath = (entry: CockpitManifestEntry): string => @@ -135,7 +167,7 @@ export const resolveCockpitEntry = ({ export const buildNavigationTree = ( manifest: CockpitManifestEntry[] ): NavigationProduct[] => { - const products: CockpitManifestEntry['product'][] = ['deep-agents', 'langgraph']; + const products: CockpitManifestEntry['product'][] = ['deep-agents', 'langgraph', 'render', 'chat']; const sections: CockpitManifestEntry['section'][] = [ 'getting-started', 'core-capabilities',